From 0e44239daf58a0c328cc7846a93b29c6c99fba66 Mon Sep 17 00:00:00 2001 From: Daniel Norman Date: Tue, 30 Jul 2024 19:26:52 +0000 Subject: [PATCH 1/8] Never restricts Accessibility Activities based on ECM/admin. Keeps accessibility activity preference as inheriting from RestrictedPreference since it shares lots of behavior with the accessibility service perference (same base class), but always calls setEnabled(true) for activities. Fix: 331990900 Flag: com.android.settings.accessibility.never_restrict_accessibility_activity Test: atest RestrictedPreferenceHelperTest Change-Id: I39971bc3f65aa630fa62a0e31132f177fd21b635 --- .../accessibility/accessibility_flags.aconfig | 10 +++++ .../RestrictedPreferenceHelper.java | 8 +++- .../RestrictedPreferenceHelperTest.java | 45 ++++++++++++++++--- 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/aconfig/accessibility/accessibility_flags.aconfig b/aconfig/accessibility/accessibility_flags.aconfig index 3092b8fe72b..88cbfceb236 100644 --- a/aconfig/accessibility/accessibility_flags.aconfig +++ b/aconfig/accessibility/accessibility_flags.aconfig @@ -61,6 +61,16 @@ flag { } } +flag { + name: "never_restrict_accessibility_activity" + namespace: "accessibility" + description: "Stops possibly restricting AccessibilityActivityPreferences" + bug: "331990900" + metadata { + purpose: PURPOSE_BUGFIX + } +} + flag { name: "remove_qs_tooltip_in_suw" namespace: "accessibility" diff --git a/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java b/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java index 2cabc76eab0..c324130bb3a 100644 --- a/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java +++ b/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java @@ -128,7 +128,13 @@ public class RestrictedPreferenceHelper { AccessibilityActivityPreference preference = new AccessibilityActivityPreference( mContext, componentName.getPackageName(), activityInfo.applicationInfo.uid, info); - setRestrictedPreferenceEnabled(preference, permittedServices, serviceEnabled); + if (Flags.neverRestrictAccessibilityActivity()) { + // Accessibility Activities do not have elevated privileges so restricting + // them based on ECM or device admin does not give any value. + preference.setEnabled(true); + } else { + setRestrictedPreferenceEnabled(preference, permittedServices, serviceEnabled); + } preferenceList.add(preference); } return preferenceList; diff --git a/tests/robotests/src/com/android/settings/accessibility/RestrictedPreferenceHelperTest.java b/tests/robotests/src/com/android/settings/accessibility/RestrictedPreferenceHelperTest.java index b4f4dc1127e..2a41e62e7ae 100644 --- a/tests/robotests/src/com/android/settings/accessibility/RestrictedPreferenceHelperTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/RestrictedPreferenceHelperTest.java @@ -31,9 +31,8 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.core.app.ApplicationProvider; @@ -83,8 +82,6 @@ public class RestrictedPreferenceHelperTest { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); - @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @Test public void createAccessibilityServicePreferenceList_hasOneInfo_containsSameKey() { @@ -100,7 +97,7 @@ public class RestrictedPreferenceHelperTest { } @Test - @RequiresFlagsEnabled(value = {android.security.Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS, + @EnableFlags(value = {android.security.Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS, android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED}) public void createAccessibilityServicePreferenceList_ecmRestricted_prefIsEcmRestricted() { ShadowRestrictedLockUtilsInternal.setEcmRestrictedPkgs( @@ -116,7 +113,7 @@ public class RestrictedPreferenceHelperTest { } @Test - @RequiresFlagsEnabled(value = {android.security.Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS, + @EnableFlags(value = {android.security.Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS, android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED}) public void createAccessibilityServicePreferenceList_ecmNotRestricted_prefIsNotEcmRestricted() { ShadowRestrictedLockUtilsInternal.setEcmRestrictedPkgs(); @@ -144,6 +141,40 @@ public class RestrictedPreferenceHelperTest { assertThat(preference.getKey()).isEqualTo(key); } + @Test + @EnableFlags(value = {android.security.Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS, + android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED}) + @DisableFlags(Flags.FLAG_NEVER_RESTRICT_ACCESSIBILITY_ACTIVITY) + public void createAccessibilityActivityPreference_ecmRestricted_prefIsEcmRestricted() { + setMockAccessibilityShortcutInfo(mShortcutInfo); + ShadowRestrictedLockUtilsInternal.setEcmRestrictedPkgs(PACKAGE_NAME); + + final List preferenceList = + mHelper.createAccessibilityActivityPreferenceList(List.of(mShortcutInfo)); + assertThat(preferenceList).hasSize(1); + final RestrictedPreference preference = preferenceList.get(0); + + assertThat(preference.isDisabledByEcm()).isTrue(); + } + + @Test + @EnableFlags(value = { + android.security.Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS, + android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED, + Flags.FLAG_NEVER_RESTRICT_ACCESSIBILITY_ACTIVITY, + }) + public void createAccessibilityActivityPreference_ecmRestricted_prefIsNotEcmRestricted() { + setMockAccessibilityShortcutInfo(mShortcutInfo); + ShadowRestrictedLockUtilsInternal.setEcmRestrictedPkgs(PACKAGE_NAME); + + final List preferenceList = + mHelper.createAccessibilityActivityPreferenceList(List.of(mShortcutInfo)); + assertThat(preferenceList).hasSize(1); + final RestrictedPreference preference = preferenceList.get(0); + + assertThat(preference.isDisabledByEcm()).isFalse(); + } + private AccessibilityServiceInfo getMockAccessibilityServiceInfo(String packageName, String className) { final ApplicationInfo applicationInfo = new ApplicationInfo(); From 5460a244bef7bf84cc053e6a609997d0b9b4eee7 Mon Sep 17 00:00:00 2001 From: Chun-Ku Lin Date: Thu, 1 Aug 2024 22:33:11 +0000 Subject: [PATCH 2/8] Don't truncate the title in Color Correction options By default, the SelectorWithWidgetPreference hard coded the maxlines of the title to be 2 lines. Updating the SelectorWithWidgetPreference to accept changes on maxlines on title. Set titleMaxLines as 2147483647 (Integer.MAX_VALUE) as there is no alternative to clear the maxLines that are already set by the Preference Bug: 356726764 Test: manually tested the color correction screen in Deutsch Test: verify the Wifi Hotspot sceurity screen is not affected by this change (still have 2 as the max lines of the title) Flag: EXEMPT resource only Change-Id: I1414b6212b75d3f28bef6aad50a31ea7861d5811 --- res/values/integers.xml | 2 ++ res/xml/accessibility_daltonizer_settings.xml | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/res/values/integers.xml b/res/values/integers.xml index 5427cdd0d35..5631e401ba1 100644 --- a/res/values/integers.xml +++ b/res/values/integers.xml @@ -40,4 +40,6 @@ 0 3 + + 2147483647 diff --git a/res/xml/accessibility_daltonizer_settings.xml b/res/xml/accessibility_daltonizer_settings.xml index 8dc5f3b5ab4..db961675c29 100644 --- a/res/xml/accessibility_daltonizer_settings.xml +++ b/res/xml/accessibility_daltonizer_settings.xml @@ -33,6 +33,7 @@ android:persistent="false" android:summary="@string/daltonizer_mode_deuteranomaly_summary" android:title="@string/daltonizer_mode_deuteranomaly_title" + settings:titleMaxLines="@integer/max_integer" settings:controller="com.android.settings.accessibility.DaltonizerRadioButtonPreferenceController" /> Date: Fri, 2 Aug 2024 13:35:42 +0800 Subject: [PATCH 3/8] Speed up BasePreferenceController.updateNonIndexableKeys By avoid unnecessary calls to getAvailabilityStatus(), which also avoid unnecessary crash potential. - When key already marked searchable="false" in xml, no need to check getAvailabilityStatus() again. - When getAvailabilityStatus() return AVAILABLE, no need to call it again to check it not equals to AVAILABLE_UNSEARCHABLE. Bug: 352455031 Flag: EXEMPT refactor Test: redo search index - by changing locale Change-Id: Ic0c43b9bcd5974907b3a7d4aba73d4c7203f8af4 --- .../core/BasePreferenceController.java | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/com/android/settings/core/BasePreferenceController.java b/src/com/android/settings/core/BasePreferenceController.java index 5763d3b93ef..3f91fb70ab4 100644 --- a/src/com/android/settings/core/BasePreferenceController.java +++ b/src/com/android/settings/core/BasePreferenceController.java @@ -263,6 +263,16 @@ public abstract class BasePreferenceController extends AbstractPreferenceControl || availabilityStatus == DISABLED_DEPENDENT_SETTING); } + private boolean isAvailableForSearch() { + if (mIsForWork && mWorkProfileUser == null) { + return false; + } + + final int availabilityStatus = getAvailabilityStatus(); + return (availabilityStatus == AVAILABLE + || availabilityStatus == DISABLED_DEPENDENT_SETTING); + } + /** * @return {@code false} if the setting is not applicable to the device. This covers both * settings which were only introduced in future versions of android, or settings that have @@ -303,18 +313,12 @@ public abstract class BasePreferenceController extends AbstractPreferenceControl * Called by SearchIndexProvider#getNonIndexableKeys */ public void updateNonIndexableKeys(List keys) { - final boolean shouldSuppressFromSearch = !isAvailable() - || getAvailabilityStatus() == AVAILABLE_UNSEARCHABLE; - if (shouldSuppressFromSearch) { - final String key = getPreferenceKey(); - if (TextUtils.isEmpty(key)) { - Log.w(TAG, "Skipping updateNonIndexableKeys due to empty key " + toString()); - return; - } - if (keys.contains(key)) { - Log.w(TAG, "Skipping updateNonIndexableKeys, key already in list. " + toString()); - return; - } + final String key = getPreferenceKey(); + if (TextUtils.isEmpty(key)) { + Log.w(TAG, "Skipping updateNonIndexableKeys due to empty key " + this); + return; + } + if (!keys.contains(key) && !isAvailableForSearch()) { keys.add(key); } } From a8297a313bba98599c4d23655e82a5c9bde3bd48 Mon Sep 17 00:00:00 2001 From: Angela Wang Date: Fri, 2 Aug 2024 03:52:03 +0000 Subject: [PATCH 4/8] Fix LE Audio toggle missing issue for dual mode hearing device After the code change of ag/28283226, they hide the LE Audio toggle for LE Audio only device since turning off the profile may cause this device no functioning. But the dual mode hearing devices(LE Audio + Asha) are wrongly recognized as LE Audio only devices since HearingAidProfile.accessProfileEnabled() return false. This make the users lost the ability to switch between profiles they preferred. Make HearingAidProfile.accessProfileEnabled() return true and hide the Hearing Aid toggle by default since it's also meaningless to turn off the Asha profile for Asha-only devices. Flag: com.android.settingslib.flags.asha_profile_access_profile_enabled_true Bug: 356530795 Test: manual checking the UI Test: atest BluetoothDetailsProfilesControllerTest Change-Id: Ica350b4c16c1b07945399bfee1038f7b0824baed --- .../BluetoothDetailsProfilesController.java | 9 ++-- ...luetoothDetailsProfilesControllerTest.java | 50 ++++++++++++++++--- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java index 015d427f8cd..47687d4cd24 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java @@ -309,7 +309,7 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll * Helper to get the list of connectable and special profiles. */ private List getProfiles() { - List result = new ArrayList(); + List result = new ArrayList<>(); mProfileDeviceMap.clear(); if (mAllOfCachedDevices == null || mAllOfCachedDevices.isEmpty()) { return result; @@ -320,8 +320,7 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll if (mProfileDeviceMap.containsKey(profile.toString())) { mProfileDeviceMap.get(profile.toString()).add(cachedItem); } else { - List tmpCachedDeviceList = - new ArrayList(); + List tmpCachedDeviceList = new ArrayList<>(); tmpCachedDeviceList.add(cachedItem); mProfileDeviceMap.put(profile.toString(), tmpCachedDeviceList); result.add(profile); @@ -357,6 +356,10 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll } boolean hearingAidSupported = result.contains( mManager.getProfileManager().getHearingAidProfile()); + // Remove hearing aids toggle anyway since showing the toggle will confuse users + if (hearingAidSupported) { + result.remove(mManager.getProfileManager().getHearingAidProfile()); + } if (leAudioSupported && !classicAudioSupported && !hearingAidSupported) { mIsLeAudioOnlyDevice = true; } diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java index 2d1f4c0e907..219c37b6c08 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java @@ -44,6 +44,7 @@ import com.android.settings.testutils.shadow.ShadowBluetoothDevice; import com.android.settingslib.R; import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; +import com.android.settingslib.bluetooth.HearingAidProfile; import com.android.settingslib.bluetooth.LeAudioProfile; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfile; @@ -90,8 +91,12 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont @Mock private CachedBluetoothDeviceManager mCachedBluetoothDeviceManager; - private @Mock A2dpProfile mA2dpProfile; - private @Mock LeAudioProfile mLeAudioProfile; + @Mock + private A2dpProfile mA2dpProfile; + @Mock + private LeAudioProfile mLeAudioProfile; + @Mock + private HearingAidProfile mHearingAidProfile; @Override public void setUp() { @@ -399,18 +404,23 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont when(mProfileManager.getProfileByName(eq(mA2dpProfile.toString()))) .thenReturn(mA2dpProfile); when(mA2dpProfile.getNameResource(any())) - .thenReturn(com.android.settingslib.R.string.bluetooth_profile_a2dp); + .thenReturn(R.string.bluetooth_profile_a2dp); when(mA2dpProfile.getHighQualityAudioOptionLabel(any())).thenReturn( - mContext.getString(com.android.settingslib.R - .string.bluetooth_profile_a2dp_high_quality_unknown_codec)); + mContext.getString(R.string.bluetooth_profile_a2dp_high_quality_unknown_codec)); when(mA2dpProfile.isProfileReady()).thenReturn(true); when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); when(mLeAudioProfile.toString()).thenReturn("LE_AUDIO"); when(mLeAudioProfile.getNameResource(any())) - .thenReturn(com.android.settingslib.R.string.bluetooth_profile_le_audio); + .thenReturn(R.string.bluetooth_profile_le_audio); when(mLeAudioProfile.isProfileReady()).thenReturn(true); when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile); + + when(mHearingAidProfile.toString()).thenReturn("HearingAid"); + when(mHearingAidProfile.getNameResource(any())) + .thenReturn(R.string.bluetooth_profile_hearing_aid); + when(mHearingAidProfile.isProfileReady()).thenReturn(true); + when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile); } private void addA2dpProfileToDevice(boolean preferred, boolean supportsHighQualityAudio, @@ -426,6 +436,11 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont mConnectableProfiles.add(mLeAudioProfile); } + private void addHearingAidProfileToDevice(boolean enabled) { + when(mHearingAidProfile.isEnabled(any())).thenReturn(enabled); + mConnectableProfiles.add(mHearingAidProfile); + } + private SwitchPreferenceCompat getHighQualityAudioPref() { return (SwitchPreferenceCompat) mScreen.findPreference( BluetoothDetailsProfilesController.HIGH_QUALITY_AUDIO_PREF_TAG); @@ -591,4 +606,27 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont List switches = getProfileSwitches(false); assertThat(switches.get(0).isVisible()).isFalse(); } + + @Test + public void ashaHearingAid_hideAshaToggle() { + setupDevice(makeDefaultDeviceConfig()); + addHearingAidProfileToDevice(true); + + showScreen(mController); + + List switches = getProfileSwitches(false); + assertThat(switches.isEmpty()).isTrue(); + } + @Test + public void ashaHearingAidWithLeAudio_showLeAudioToggle() { + setupDevice(makeDefaultDeviceConfig()); + addHearingAidProfileToDevice(false); + addLeAudioProfileToDevice(true); + + showScreen(mController); + + List switches = getProfileSwitches(false); + assertThat(switches.getFirst().getTitle()).isEqualTo( + mContext.getString(mLeAudioProfile.getNameResource(mDevice))); + } } From 103e90636fe819b78d12e89a4534dde653009fc2 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Mon, 5 Aug 2024 17:33:48 +0800 Subject: [PATCH 5/8] Settings search for W-Fi calling Fix: 299641035 Flag: EXEMPT bug fix Test: manual - search wifi calling Test: atest WifiCallingPreferenceControllerTest Change-Id: I230531000d7fe01f3295cf2dd7ced698e5346a0b --- res/xml/mobile_network_settings.xml | 2 + .../MobileNetworkSettingsSearchIndex.kt | 4 +- .../WifiCallingPreferenceController.kt | 44 ++++++++++++------- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/res/xml/mobile_network_settings.xml b/res/xml/mobile_network_settings.xml index 63d8a3d7b50..539c145e4e2 100644 --- a/res/xml/mobile_network_settings.xml +++ b/res/xml/mobile_network_settings.xml @@ -202,10 +202,12 @@ android:title="@string/call_category" settings:controller="com.android.settings.network.telephony.CallingPreferenceCategoryController"> + = listOf( - RoamingSearchItem(context), MmsMessageSearchItem(context), NrAdvancedCallingSearchItem(context), + RoamingSearchItem(context), + WifiCallingSearchItem(context), ) } } diff --git a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt index 3bb267940d2..7e8e58cceb5 100644 --- a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt +++ b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt @@ -22,14 +22,17 @@ import android.telecom.TelecomManager import android.telephony.SubscriptionManager import android.telephony.TelephonyManager import android.telephony.ims.ImsMmTelManager -import android.util.Log import androidx.lifecycle.LifecycleOwner import androidx.preference.Preference import androidx.preference.PreferenceScreen import com.android.settings.R +import com.android.settings.core.BasePreferenceController +import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchItem import com.android.settings.network.telephony.wificalling.WifiCallingRepository import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext /** @@ -44,20 +47,21 @@ open class WifiCallingPreferenceController @JvmOverloads constructor( private val wifiCallingRepositoryFactory: (subId: Int) -> WifiCallingRepository = { subId -> WifiCallingRepository(context, subId) }, -) : TelephonyBasePreferenceController(context, key) { +) : BasePreferenceController(context, key) { + private var subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID private lateinit var preference: Preference private lateinit var callingPreferenceCategoryController: CallingPreferenceCategoryController private val resourcesForSub by lazy { - SubscriptionManager.getResourcesForSubId(mContext, mSubId) + SubscriptionManager.getResourcesForSubId(mContext, subId) } fun init( subId: Int, callingPreferenceCategoryController: CallingPreferenceCategoryController, ): WifiCallingPreferenceController { - mSubId = subId + this.subId = subId this.callingPreferenceCategoryController = callingPreferenceCategoryController return this } @@ -65,39 +69,32 @@ open class WifiCallingPreferenceController @JvmOverloads constructor( /** * Note: Visibility also controlled by [onViewCreated]. */ - override fun getAvailabilityStatus(subId: Int) = + override fun getAvailabilityStatus() = if (SubscriptionManager.isValidSubscriptionId(subId)) AVAILABLE else CONDITIONALLY_UNAVAILABLE override fun displayPreference(screen: PreferenceScreen) { // Not call super here, to avoid preference.isVisible changed unexpectedly preference = screen.findPreference(preferenceKey)!! - preference.intent?.putExtra(Settings.EXTRA_SUB_ID, mSubId) + preference.intent?.putExtra(Settings.EXTRA_SUB_ID, subId) } override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) { - if(mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID){ - Log.e( - this.javaClass.simpleName, - "mSubId is INVALID_SUBSCRIPTION_ID" - ) - return - } - wifiCallingRepositoryFactory(mSubId).wifiCallingReadyFlow() + wifiCallingRepositoryFactory(subId).wifiCallingReadyFlow() .collectLatestWithLifecycle(viewLifecycleOwner) { isReady -> preference.isVisible = isReady callingPreferenceCategoryController.updateChildVisible(preferenceKey, isReady) if (isReady) update() } - callStateRepository.callStateFlow(mSubId).collectLatestWithLifecycle(viewLifecycleOwner) { + callStateRepository.callStateFlow(subId).collectLatestWithLifecycle(viewLifecycleOwner) { preference.isEnabled = (it == TelephonyManager.CALL_STATE_IDLE) } } private suspend fun update() { val simCallManager = mContext.getSystemService(TelecomManager::class.java) - ?.getSimCallManagerForSubscription(mSubId) + ?.getSimCallManagerForSubscription(subId) if (simCallManager != null) { val intent = withContext(Dispatchers.Default) { MobileNetworkUtils.buildPhoneAccountConfigureIntent(mContext, simCallManager) @@ -116,7 +113,7 @@ open class WifiCallingPreferenceController @JvmOverloads constructor( } private fun getSummaryForWfcMode(): String { - val resId = when (wifiCallingRepositoryFactory(mSubId).getWiFiCallingMode()) { + val resId = when (wifiCallingRepositoryFactory(subId).getWiFiCallingMode()) { ImsMmTelManager.WIFI_MODE_WIFI_ONLY -> com.android.internal.R.string.wfc_mode_wifi_only_summary @@ -130,4 +127,17 @@ open class WifiCallingPreferenceController @JvmOverloads constructor( } return resourcesForSub.getString(resId) } + + companion object { + class WifiCallingSearchItem( + private val context: Context, + ) : MobileNetworkSettingsSearchItem { + override val key: String = "wifi_calling" + override val title: String = context.getString(R.string.wifi_calling_settings_title) + + override fun isAvailable(subId: Int): Boolean = runBlocking { + WifiCallingRepository(context, subId).wifiCallingReadyFlow().first() + } + } + } } From bc79a1e074f57d528eda24db19abf06e487f748b Mon Sep 17 00:00:00 2001 From: Ming-Shin Lu Date: Mon, 5 Aug 2024 14:02:05 +0000 Subject: [PATCH 6/8] Delete keyboard_category_enabled flag (2/2) As keyboard_category_enabled feature has launched, remove the feature flag since it's unnessary. And, this CL is the preparation of removing CATEGORY_KEYBOARD vibration attribute and logic, remove the flag usage ing settings since no longer need this flag. Flag: EXEMPT flag removal Bug: 332661766 Test: build Test: atest KeyboardVibrationTogglePreferenceControllerTest Change-Id: I19cecb977d52a74b26eea1f494052e0e852359c4 --- ...KeyboardVibrationTogglePreferenceController.java | 4 +--- ...oardVibrationTogglePreferenceControllerTest.java | 13 ------------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceController.java b/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceController.java index 3ba0f0d0841..833638bb2e0 100644 --- a/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceController.java +++ b/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceController.java @@ -29,7 +29,6 @@ import android.net.Uri; import android.os.Handler; import android.os.VibrationAttributes; import android.os.Vibrator; -import android.os.vibrator.Flags; import android.provider.Settings; import android.util.Log; @@ -110,8 +109,7 @@ public class KeyboardVibrationTogglePreferenceController extends TogglePreferenc @Override public int getAvailabilityStatus() { - if (Flags.keyboardCategoryEnabled() - && mContext.getResources().getBoolean( + if (mContext.getResources().getBoolean( com.android.internal.R.bool.config_keyboardVibrationSettingsSupported)) { return AVAILABLE; } diff --git a/tests/robotests/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceControllerTest.java index 569109c5cd4..78f49a66906 100644 --- a/tests/robotests/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceControllerTest.java @@ -33,7 +33,6 @@ import android.app.settings.SettingsEnums; import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; -import android.os.vibrator.Flags; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; @@ -41,7 +40,6 @@ import androidx.preference.PreferenceScreen; import androidx.preference.SwitchPreference; import androidx.test.core.app.ApplicationProvider; -import com.android.settings.R; import com.android.settings.testutils.FakeFeatureFactory; import org.junit.Before; @@ -87,7 +85,6 @@ public class KeyboardVibrationTogglePreferenceControllerTest { @Test public void getAvailabilityStatus_featureSupported_available() { - mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED); when(mResources.getBoolean( com.android.internal.R.bool.config_keyboardVibrationSettingsSupported)) .thenReturn(true); @@ -97,7 +94,6 @@ public class KeyboardVibrationTogglePreferenceControllerTest { @Test public void getAvailabilityStatus_featureNotSupported_unavailable() { - mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED); when(mResources.getBoolean( com.android.internal.R.bool.config_keyboardVibrationSettingsSupported)) .thenReturn(false); @@ -105,15 +101,6 @@ public class KeyboardVibrationTogglePreferenceControllerTest { assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); } - @Test - public void getAvailabilityStatus_keyboardCategoryDisabled_unavailable() { - mSetFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED); - when(mResources.getBoolean( - com.android.internal.R.bool.config_keyboardVibrationSettingsSupported)) - .thenReturn(true); - - assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); - } @Test public void updateState_mainVibrateDisabled_shouldReturnFalseForCheckedAndEnabled() { From 01b7062b34fbe8be7895be02835f29664dd6170c Mon Sep 17 00:00:00 2001 From: Nikhil Kumar Date: Fri, 2 Aug 2024 14:41:10 +0100 Subject: [PATCH 7/8] Restrict admin status change when DISALLOW_GRANT_ADMIN is present This change addresses a security gap where users with the "DISALLOW_GRANT_ADMIN" restriction could still grant admin privileges to others, potentially undermining supervised user models. We've extended the existing logic to ensure that restricted users cannot grant or receive admin privileges when the DISALLOW_GRANT_ADMIN restriction is in place. This prevents scenarios where supervised users could create new admins to bypass restrictions and factory reset the device. In addition to the core functionality improvement, significant code refactoring has been done to untangle complex multiple if conditions. The logic for determining when admin status changes are allowed is now clearer and more readable, taking into account both the "current user" (initiating the change) and the "target user" (whose privileges are being modified). Bug: 357056776 Test: atest UserDetailsSettingsTest and manually verified the user having DISALLOW_GRANT_ADMIN restriction is not allowed to change admin status of any other user. Flag: android.multiuser.unicorn_mode_refactoring_for_hsum_read_only Change-Id: If02c9355ee7ce285b5b7bcddae630d716d5bf333 --- .../settings/users/UserDetailsSettings.java | 44 +++++++++++++++++-- .../users/UserDetailsSettingsTest.java | 13 ++++++ 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java index 588f01aaa79..66c278ed733 100644 --- a/src/com/android/settings/users/UserDetailsSettings.java +++ b/src/com/android/settings/users/UserDetailsSettings.java @@ -370,11 +370,18 @@ public class UserDetailsSettings extends SettingsPreferenceFragment } mSwitchUserPref.setOnPreferenceClickListener(this); } - if (mUserInfo.isMain() || mUserInfo.isGuest() || !UserManager.isMultipleAdminEnabled() - || mUserManager.hasUserRestrictionForUser(UserManager.DISALLOW_GRANT_ADMIN, - mUserInfo.getUserHandle()) || !mUserManager.isAdminUser()) { - removePreference(KEY_GRANT_ADMIN); + if (android.multiuser.Flags.unicornModeRefactoringForHsumReadOnly()) { + if (isChangingAdminStatusRestricted()) { + removePreference(KEY_GRANT_ADMIN); + } + } else { + if (mUserInfo.isMain() || mUserInfo.isGuest() || !UserManager.isMultipleAdminEnabled() + || mUserManager.hasUserRestrictionForUser(UserManager.DISALLOW_GRANT_ADMIN, + mUserInfo.getUserHandle()) || !mUserManager.isAdminUser()) { + removePreference(KEY_GRANT_ADMIN); + } } + if (!mUserManager.isAdminUser()) { // non admin users can't remove users and allow calls removePreference(KEY_ENABLE_TELEPHONY); removePreference(KEY_REMOVE_USER); @@ -552,4 +559,33 @@ public class UserDetailsSettings extends SettingsPreferenceFragment // return true so there will be no setup prompt dialog shown to the user anymore. return isSecondaryUser(mUserInfo) && !mUserInfo.isInitialized(); } + + /** + * Determines if changing admin status is restricted. + * + *

Admin status change is restricted under the following conditions of current & target user. + * + *

    + *
  • The current user is NOT an admin user.
  • + *
  • OR multiple admin support is NOT enabled.
  • + *
  • OR the current user has DISALLOW_GRANT_ADMIN restriction applied
  • + * + *
  • OR the target user ('mUserInfo') is a main user OR a guest user.
  • + *
  • OR the target user ('mUserInfo') has DISALLOW_GRANT_ADMIN restriction.
  • + *
+ * + * @return true if changing admin status is restricted, false otherwise + */ + private boolean isChangingAdminStatusRestricted() { + boolean currentUserRestricted = !mUserManager.isAdminUser() + || !UserManager.isMultipleAdminEnabled() + || mUserManager.hasUserRestriction(UserManager.DISALLOW_GRANT_ADMIN); + + boolean targetUserRestricted = mUserInfo.isMain() + || mUserInfo.isGuest() + || mUserManager.hasUserRestrictionForUser(UserManager.DISALLOW_GRANT_ADMIN, + mUserInfo.getUserHandle()); + + return currentUserRestricted || targetUserRestricted; + } } diff --git a/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java b/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java index e035274a5de..482aa5d5b93 100644 --- a/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java +++ b/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java @@ -729,12 +729,25 @@ public class UserDetailsSettingsTest { public void initialize_restrictUserSelected_shouldNotShowGrantAdminPref_MultipleAdminEnabled() { setupSelectedUser(); ShadowUserManager.setIsMultipleAdminEnabled(true); + // target user has DISALLOW_GRANT_ADMIN restriction mUserManager.setUserRestriction(mUserInfo.getUserHandle(), UserManager.DISALLOW_GRANT_ADMIN, true); mFragment.initialize(mActivity, mArguments); verify(mFragment).removePreference(KEY_GRANT_ADMIN); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_UNICORN_MODE_REFACTORING_FOR_HSUM_READ_ONLY) + public void initialize_currentUserRestrict_shouldNotShowGrantAdminPref_MultipleAdminEnabled() { + setupSelectedUser(); + ShadowUserManager.setIsMultipleAdminEnabled(true); + // current user has DISALLOW_GRANT_ADMIN restriction + mUserManager.setUserRestriction(mContext.getUser(), + UserManager.DISALLOW_GRANT_ADMIN, true); + mFragment.initialize(mActivity, mArguments); + verify(mFragment).removePreference(KEY_GRANT_ADMIN); + } + @Test public void initialize_mainUserSelected_shouldShowGrantAdminPref_MultipleAdminEnabled() { setupSelectedMainUser(); From 75a070f3f0834a79643f0d4e268c0c1c6bd6f26a Mon Sep 17 00:00:00 2001 From: Weng Su Date: Fri, 2 Aug 2024 19:44:50 +0800 Subject: [PATCH 8/8] Restrict MobileDataSlice - Hide MobileDataSlice if the user is not allowed to configure mobile networks. Bug: 310630794 Flag: EXEMPT bugfix Test: manual test atest -c MobileDataSliceTest Change-Id: I35814733a915f011e284b082ce7a94898ce8a6fb --- .../network/telephony/MobileDataSlice.java | 39 +++++++++++++++---- .../telephony/MobileDataSliceTest.java | 34 +++++++++++++--- 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/src/com/android/settings/network/telephony/MobileDataSlice.java b/src/com/android/settings/network/telephony/MobileDataSlice.java index fd65e8562a2..f5e734d7e09 100644 --- a/src/com/android/settings/network/telephony/MobileDataSlice.java +++ b/src/com/android/settings/network/telephony/MobileDataSlice.java @@ -29,6 +29,7 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.os.Looper; +import android.os.UserManager; import android.provider.Settings; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; @@ -79,19 +80,24 @@ public class MobileDataSlice implements CustomSliceable { @Override public Slice getSlice() { + ListBuilder listBuilder = createListBuilder(); + if (!isConfigMobileNetworksAllowed()) { + return listBuilder.build(); + } + final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.ic_network_cell); final String title = mContext.getText(R.string.mobile_data_settings_title).toString(); @ColorInt final int color = Utils.getColorAccentDefaultColor(mContext); - // Return null until we can show a disabled-action Slice, blaming Airplane mode. + // Return empty slice until we can show a disabled-action Slice, blaming Airplane mode. if (isAirplaneModeEnabled()) { - return null; + return listBuilder.build(); } - // Return null until we can show a disabled-action Slice. + // Return empty slice until we can show a disabled-action Slice. if (!isMobileDataAvailable()) { - return null; + return listBuilder.build(); } final CharSequence summary = getSummary(); @@ -109,11 +115,15 @@ public class MobileDataSlice implements CustomSliceable { rowBuilder.setSubtitle(summary); } - final ListBuilder listBuilder = new ListBuilder(mContext, getUri(), - ListBuilder.INFINITY) + return listBuilder .setAccentColor(color) - .addRow(rowBuilder); - return listBuilder.build(); + .addRow(rowBuilder) + .build(); + } + + @VisibleForTesting + ListBuilder createListBuilder() { + return new ListBuilder(mContext, getUri(), ListBuilder.INFINITY); } @Override @@ -211,6 +221,19 @@ public class MobileDataSlice implements CustomSliceable { return mTelephonyManager.isDataEnabled(); } + @VisibleForTesting + boolean isConfigMobileNetworksAllowed() { + if (mContext == null) return true; + UserManager userManager = mContext.getSystemService(UserManager.class); + if (userManager == null) return true; + boolean isAllowed = userManager.isAdminUser() && !userManager.hasUserRestriction( + UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS); + if (!isAllowed) { + Log.w(TAG, "The user is not allowed to configure Mobile Networks."); + } + return isAllowed; + } + /** * Listener for mobile data state changes. * diff --git a/tests/robotests/src/com/android/settings/network/telephony/MobileDataSliceTest.java b/tests/robotests/src/com/android/settings/network/telephony/MobileDataSliceTest.java index 8445fe22c9f..9cd69b4e4b0 100644 --- a/tests/robotests/src/com/android/settings/network/telephony/MobileDataSliceTest.java +++ b/tests/robotests/src/com/android/settings/network/telephony/MobileDataSliceTest.java @@ -19,8 +19,10 @@ package com.android.settings.network.telephony; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -37,6 +39,7 @@ import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; import androidx.slice.SliceMetadata; import androidx.slice.SliceProvider; +import androidx.slice.builders.ListBuilder; import androidx.slice.core.SliceAction; import androidx.slice.widget.SliceLiveData; @@ -68,6 +71,7 @@ public class MobileDataSliceTest { private Context mContext; private MobileDataSlice mMobileDataSlice; + private ListBuilder mListBuilder; @Before public void setUp() { @@ -86,6 +90,8 @@ public class MobileDataSliceTest { SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); mMobileDataSlice = spy(new MobileDataSlice(mContext)); + mListBuilder = spy(mMobileDataSlice.createListBuilder()); + doReturn(mListBuilder).when(mMobileDataSlice).createListBuilder(); } @Test @@ -175,25 +181,41 @@ public class MobileDataSliceTest { @Test public void isMobileDataAvailable_noSubscriptions_slicePrimaryActionIsEmpty() { when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(new ArrayList<>()); - final Slice mobileData = mMobileDataSlice.getSlice(); - assertThat(mobileData).isNull(); + Slice mobileData = mMobileDataSlice.getSlice(); + + assertThat(mobileData).isNotNull(); + verify(mListBuilder, never()).addRow(any()); } @Test public void isMobileDataAvailable_nullSubscriptions_slicePrimaryActionIsEmpty() { when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(null); - final Slice mobileData = mMobileDataSlice.getSlice(); - assertThat(mobileData).isNull(); + Slice mobileData = mMobileDataSlice.getSlice(); + + assertThat(mobileData).isNotNull(); + verify(mListBuilder, never()).addRow(any()); } @Test public void airplaneModeEnabled_slicePrimaryActionIsEmpty() { doReturn(true).when(mMobileDataSlice).isAirplaneModeEnabled(); doReturn(mSubscriptionInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(SUB_ID); - final Slice mobileData = mMobileDataSlice.getSlice(); - assertThat(mobileData).isNull(); + Slice mobileData = mMobileDataSlice.getSlice(); + + assertThat(mobileData).isNotNull(); + verify(mListBuilder, never()).addRow(any()); + } + + @Test + public void getSlice_disallowConfigMobileNetworks_slicePrimaryActionIsEmpty() { + doReturn(false).when(mMobileDataSlice).isConfigMobileNetworksAllowed(); + + Slice mobileData = mMobileDataSlice.getSlice(); + + assertThat(mobileData).isNotNull(); + verify(mListBuilder, never()).addRow(any()); } }