From 8551585b5924a12633543862696bb9785a16d96a Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Fri, 14 Jan 2022 23:13:54 +0800 Subject: [PATCH 01/43] Do not let guest user disable secure nfc Bug: 209446496 Test: manual Merged-In: I7253f7f08fde04e30400a30d9a0d24f1ceff04b0 Change-Id: I7253f7f08fde04e30400a30d9a0d24f1ceff04b0 (cherry picked from commit d9e3e6e4b1c3cb1a04dba0f530505843ef44a748) (cherry picked from commit a579ca7554dcbfd3fce1c90451fb54cb676cfdda) Merged-In:I7253f7f08fde04e30400a30d9a0d24f1ceff04b0 --- .../NfcAndPaymentFragment.java | 19 ++++++++++++++++++- .../settings/nfc/SecureNfcEnabler.java | 14 +++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/connecteddevice/NfcAndPaymentFragment.java b/src/com/android/settings/connecteddevice/NfcAndPaymentFragment.java index 4ebc0cdcccd..feb757f9e41 100644 --- a/src/com/android/settings/connecteddevice/NfcAndPaymentFragment.java +++ b/src/com/android/settings/connecteddevice/NfcAndPaymentFragment.java @@ -17,6 +17,12 @@ package com.android.settings.connecteddevice; import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; +import android.os.UserHandle; +import android.os.UserManager; + import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; @@ -54,5 +60,16 @@ public class NfcAndPaymentFragment extends DashboardFragment { * For Search. */ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = - new BaseSearchIndexProvider(R.xml.nfc_and_payment_settings); + new BaseSearchIndexProvider(R.xml.nfc_and_payment_settings) { + @Override + protected boolean isPageSearchEnabled(Context context) { + final UserManager userManager = context.getSystemService(UserManager.class); + final UserInfo myUserInfo = userManager.getUserInfo(UserHandle.myUserId()); + if (myUserInfo.isGuest()) { + return false; + } + final PackageManager pm = context.getPackageManager(); + return pm.hasSystemFeature(PackageManager.FEATURE_NFC); + } + }; } diff --git a/src/com/android/settings/nfc/SecureNfcEnabler.java b/src/com/android/settings/nfc/SecureNfcEnabler.java index 9acaf6461f2..f31a382a571 100644 --- a/src/com/android/settings/nfc/SecureNfcEnabler.java +++ b/src/com/android/settings/nfc/SecureNfcEnabler.java @@ -18,9 +18,8 @@ package com.android.settings.nfc; import android.content.Context; import android.nfc.NfcAdapter; -import android.provider.Settings; +import android.os.UserManager; -import androidx.annotation.VisibleForTesting; import androidx.preference.SwitchPreference; import com.android.settings.R; @@ -32,10 +31,12 @@ import com.android.settings.R; */ public class SecureNfcEnabler extends BaseNfcEnabler { private final SwitchPreference mPreference; + private final UserManager mUserManager; public SecureNfcEnabler(Context context, SwitchPreference preference) { super(context); mPreference = preference; + mUserManager = context.getSystemService(UserManager.class); } @Override @@ -48,7 +49,7 @@ public class SecureNfcEnabler extends BaseNfcEnabler { case NfcAdapter.STATE_ON: mPreference.setSummary(R.string.nfc_secure_toggle_summary); mPreference.setChecked(mPreference.isChecked()); - mPreference.setEnabled(true); + mPreference.setEnabled(isToggleable()); break; case NfcAdapter.STATE_TURNING_ON: mPreference.setEnabled(false); @@ -58,4 +59,11 @@ public class SecureNfcEnabler extends BaseNfcEnabler { break; } } + + private boolean isToggleable() { + if (mUserManager.isGuestUser()) { + return false; + } + return true; + } } -- GitLab From 21bf0e6616ffc16e4683a09fb687541ea145d347 Mon Sep 17 00:00:00 2001 From: lucaslin Date: Wed, 9 Mar 2022 10:52:43 +0800 Subject: [PATCH 02/43] Hide private DNS settings UI in Guest mode Hide private DNS settings UI in Guest mode to prevent guest users modifying global private DNS settings. Bug: 206987762 Test: 1. make RunSettingsRoboTests \ ROBOTEST_FILTER=PrivateDnsPreferenceControllerTest 2. Switch to Guest user and check if the private DNS UI is hidden or not. Change-Id: Iebfb8684da3be32110decd9e8447dd07b1c40387 (cherry picked from commit 52e863b5a212889d4f8cb89a4028c42af59c9327) Merged-In: Iebfb8684da3be32110decd9e8447dd07b1c40387 --- .../network/PrivateDnsPreferenceController.java | 9 ++++++--- .../network/PrivateDnsPreferenceControllerTest.java | 11 +++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/com/android/settings/network/PrivateDnsPreferenceController.java b/src/com/android/settings/network/PrivateDnsPreferenceController.java index 07d57140ec1..ed6f9ed955d 100644 --- a/src/com/android/settings/network/PrivateDnsPreferenceController.java +++ b/src/com/android/settings/network/PrivateDnsPreferenceController.java @@ -85,9 +85,12 @@ public class PrivateDnsPreferenceController extends BasePreferenceController @Override public int getAvailabilityStatus() { - return mContext.getResources().getBoolean(R.bool.config_show_private_dns_settings) - ? AVAILABLE - : UNSUPPORTED_ON_DEVICE; + if (!mContext.getResources().getBoolean(R.bool.config_show_private_dns_settings)) { + return UNSUPPORTED_ON_DEVICE; + } + final UserManager userManager = mContext.getSystemService(UserManager.class); + if (userManager.isGuestUser()) return DISABLED_FOR_USER; + return AVAILABLE; } @Override diff --git a/tests/robotests/src/com/android/settings/network/PrivateDnsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/PrivateDnsPreferenceControllerTest.java index e31d959622a..057b6cbf0b9 100644 --- a/tests/robotests/src/com/android/settings/network/PrivateDnsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/network/PrivateDnsPreferenceControllerTest.java @@ -26,6 +26,7 @@ import static androidx.lifecycle.Lifecycle.Event.ON_START; import static androidx.lifecycle.Lifecycle.Event.ON_STOP; import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.DISABLED_FOR_USER; import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; import static com.google.common.truth.Truth.assertThat; @@ -35,6 +36,7 @@ import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.CALLS_REAL_METHODS; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; @@ -109,6 +111,8 @@ public class PrivateDnsPreferenceControllerTest { private Network mNetwork; @Mock private Preference mPreference; + @Mock + private UserManager mUserManager; @Captor private ArgumentCaptor mCallbackCaptor; private PrivateDnsPreferenceController mController; @@ -127,6 +131,7 @@ public class PrivateDnsPreferenceControllerTest { mShadowContentResolver = Shadow.extract(mContentResolver); when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)) .thenReturn(mConnectivityManager); + when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); doNothing().when(mConnectivityManager).registerDefaultNetworkCallback( mCallbackCaptor.capture(), nullable(Handler.class)); @@ -173,6 +178,12 @@ public class PrivateDnsPreferenceControllerTest { assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); } + @Test + public void getAvailabilityStatus_disabledForGuestUser() { + doReturn(true).when(mUserManager).isGuestUser(); + assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_FOR_USER); + } + @Test public void goThroughLifecycle_shouldRegisterUnregisterSettingsObserver() { mLifecycle.handleLifecycleEvent(ON_START); -- GitLab From ea01af3dc661108c0528941699b475169eab32cd Mon Sep 17 00:00:00 2001 From: Alex Johnston Date: Thu, 3 Mar 2022 14:30:44 +0000 Subject: [PATCH 03/43] Change default USB configuration to a RestrictedPreference Test: manual with TestDPC and Settings DefaultUsbConfigurationPreferenceControllerTest Bug: 201519976 205996517 Change-Id: I1def7b37184d6d81f29a5e6e4793b92012dacd9b Merged-In: I1def7b37184d6d81f29a5e6e4793b92012dacd9b (cherry picked from commit bc4fd99ddb494683420264e6b749a5ce80df90b9) Merged-In: I1def7b37184d6d81f29a5e6e4793b92012dacd9b --- res/xml/development_settings.xml | 2 +- .../DefaultUsbConfigurationPreferenceController.java | 4 ++-- .../DefaultUsbConfigurationPreferenceControllerTest.java | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index 621351681cd..e389747bf67 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -293,7 +293,7 @@ android:title="@string/tethering_hardware_offload" android:summary="@string/tethering_hardware_offload_summary" /> - diff --git a/src/com/android/settings/development/DefaultUsbConfigurationPreferenceController.java b/src/com/android/settings/development/DefaultUsbConfigurationPreferenceController.java index be7704fd735..7c3d3b120e0 100644 --- a/src/com/android/settings/development/DefaultUsbConfigurationPreferenceController.java +++ b/src/com/android/settings/development/DefaultUsbConfigurationPreferenceController.java @@ -24,7 +24,7 @@ import android.os.UserHandle; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; -import com.android.settingslib.RestrictedSwitchPreference; +import com.android.settingslib.RestrictedPreference; import com.android.settingslib.development.DeveloperOptionsPreferenceController; public class DefaultUsbConfigurationPreferenceController extends @@ -32,7 +32,7 @@ public class DefaultUsbConfigurationPreferenceController extends private static final String PREFERENCE_KEY = "default_usb_configuration"; - private RestrictedSwitchPreference mPreference; + private RestrictedPreference mPreference; public DefaultUsbConfigurationPreferenceController(Context context) { super(context); diff --git a/tests/robotests/src/com/android/settings/development/DefaultUsbConfigurationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/DefaultUsbConfigurationPreferenceControllerTest.java index c9b13e27a0c..a386473020a 100644 --- a/tests/robotests/src/com/android/settings/development/DefaultUsbConfigurationPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/DefaultUsbConfigurationPreferenceControllerTest.java @@ -32,7 +32,7 @@ import android.os.UserHandle; import androidx.preference.PreferenceScreen; import com.android.settingslib.RestrictedLockUtils; -import com.android.settingslib.RestrictedSwitchPreference; +import com.android.settingslib.RestrictedPreference; import org.junit.Before; import org.junit.Test; @@ -48,7 +48,7 @@ public class DefaultUsbConfigurationPreferenceControllerTest { private static final ComponentName TEST_COMPONENT_NAME = new ComponentName("test", "test"); @Mock - private RestrictedSwitchPreference mPreference; + private RestrictedPreference mPreference; @Mock private PreferenceScreen mPreferenceScreen; @Mock -- GitLab From ab020e22053391841c30e93b378258a7b31f1f17 Mon Sep 17 00:00:00 2001 From: Weng Su Date: Fri, 25 Mar 2022 21:39:02 +0800 Subject: [PATCH 04/43] Restrict secondary users to share Wi-Fi network - Remove "Share" and "Forget" options from the long press menu - Add SafetyNet Logging for security report Bug: 206986392 Test: manual test make RunSettingsRoboTests \ ROBOTEST_FILTER=NetworkProviderSettingsTest Change-Id: Ic434f0583cba557228c72508a501347ffa3141e1 Merged-In: Ic434f0583cba557228c72508a501347ffa3141e1 (cherry picked from commit 4c4a1f58c16d05c96dafae1047b44fe6e6a9a8c0) Merged-In: Ic434f0583cba557228c72508a501347ffa3141e1 --- .../network/NetworkProviderSettings.java | 34 +++++- .../network/NetworkProviderSettingsTest.java | 100 +++++++++++++++++- 2 files changed, 127 insertions(+), 7 deletions(-) diff --git a/src/com/android/settings/network/NetworkProviderSettings.java b/src/com/android/settings/network/NetworkProviderSettings.java index aedb2990ab7..b3e1fd78eb9 100644 --- a/src/com/android/settings/network/NetworkProviderSettings.java +++ b/src/com/android/settings/network/NetworkProviderSettings.java @@ -36,9 +36,11 @@ import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.Handler; import android.os.PowerManager; +import android.os.UserManager; import android.provider.Settings; import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.util.EventLog; import android.util.FeatureFlagUtils; import android.util.Log; import android.view.ContextMenu; @@ -206,6 +208,8 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment * by the Test DPC tool in AFW mode. */ protected boolean mIsRestricted; + @VisibleForTesting + boolean mIsAdmin = true; @VisibleForTesting AirplaneModeEnabler mAirplaneModeEnabler; @@ -287,6 +291,13 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment addPreferences(); mIsRestricted = isUiRestricted(); + mIsAdmin = isAdminUser(); + } + + private boolean isAdminUser() { + final UserManager userManager = getSystemService(UserManager.class); + if (userManager == null) return true; + return userManager.isAdminUser(); } private void addPreferences() { @@ -552,7 +563,9 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment } if (mSelectedWifiEntry.canDisconnect()) { - menu.add(Menu.NONE, MENU_ID_SHARE, 0 /* order */, R.string.share); + if (mSelectedWifiEntry.canShare()) { + addShareMenuIfSuitable(menu); + } menu.add(Menu.NONE, MENU_ID_DISCONNECT, 1 /* order */, R.string.wifi_disconnect_button_text); } @@ -560,7 +573,7 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment // "forget" for normal saved network. And "disconnect" for ephemeral network because it // could only be disconnected and be put in blocklists so it won't be used again. if (canForgetNetwork()) { - menu.add(Menu.NONE, MENU_ID_FORGET, 0 /* order */, R.string.forget); + addForgetMenuIfSuitable(menu); } WifiConfiguration config = mSelectedWifiEntry.getWifiConfiguration(); @@ -575,6 +588,23 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment } } + @VisibleForTesting + void addShareMenuIfSuitable(ContextMenu menu) { + if (mIsAdmin) { + menu.add(Menu.NONE, MENU_ID_SHARE, 0 /* order */, R.string.share); + return; + } + Log.w(TAG, "Don't add the Wi-Fi share menu because the user is not an admin."); + EventLog.writeEvent(0x534e4554, "206986392", -1 /* UID */, "User is not an admin"); + } + + @VisibleForTesting + void addForgetMenuIfSuitable(ContextMenu menu) { + if (mIsAdmin) { + menu.add(Menu.NONE, MENU_ID_FORGET, 0 /* order */, R.string.forget); + } + } + private boolean canForgetNetwork() { return mSelectedWifiEntry.canForget() && !WifiUtils.isNetworkLockedDown(getActivity(), mSelectedWifiEntry.getWifiConfiguration()); diff --git a/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java b/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java index 31b6f271658..17f1a43613d 100644 --- a/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java +++ b/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java @@ -15,6 +15,9 @@ */ package com.android.settings.network; +import static com.android.settings.network.NetworkProviderSettings.MENU_ID_DISCONNECT; +import static com.android.settings.network.NetworkProviderSettings.MENU_ID_FORGET; +import static com.android.settings.network.NetworkProviderSettings.MENU_ID_SHARE; import static com.android.settings.wifi.WifiConfigUiBase2.MODE_CONNECT; import static com.android.settings.wifi.WifiConfigUiBase2.MODE_MODIFY; @@ -78,6 +81,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; @@ -109,6 +113,8 @@ public class NetworkProviderSettingsTest { @Mock private WifiPickerTracker mMockWifiPickerTracker; @Mock + private WifiEntry mWifiEntry; + @Mock private PreferenceManager mPreferenceManager; @Mock private InternetResetHelper mInternetResetHelper; @@ -117,6 +123,8 @@ public class NetworkProviderSettingsTest { @Mock private LayoutPreference mResetInternetPreference; @Mock + private ContextMenu mContextMenu; + @Mock private MenuItem mMenuItem; @Mock InternetUpdater mInternetUpdater; @@ -322,12 +330,54 @@ public class NetworkProviderSettingsTest { final View view = mock(View.class); when(view.getTag()).thenReturn(connectedWifiEntryPreference); - final ContextMenu menu = mock(ContextMenu.class); - mNetworkProviderSettings.onCreateContextMenu(menu, view, null /* info */); + mNetworkProviderSettings.onCreateContextMenu(mContextMenu, view, null /* info */); - verify(menu).add(anyInt(), eq(NetworkProviderSettings.MENU_ID_FORGET), anyInt(), anyInt()); - verify(menu).add(anyInt(), eq(NetworkProviderSettings.MENU_ID_DISCONNECT), anyInt(), - anyInt()); + verify(mContextMenu).add(anyInt(), eq(MENU_ID_FORGET), anyInt(), anyInt()); + verify(mContextMenu).add(anyInt(), eq(MENU_ID_DISCONNECT), anyInt(), anyInt()); + } + + @Test + public void onCreateContextMenu_canShare_shouldHaveShareMenuForConnectedWifiEntry() { + final FragmentActivity activity = mock(FragmentActivity.class); + when(activity.getApplicationContext()).thenReturn(mContext); + when(mNetworkProviderSettings.getActivity()).thenReturn(activity); + + when(mWifiEntry.canDisconnect()).thenReturn(true); + when(mWifiEntry.canShare()).thenReturn(true); + when(mWifiEntry.canForget()).thenReturn(true); + when(mWifiEntry.isSaved()).thenReturn(true); + when(mWifiEntry.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED); + + final LongPressWifiEntryPreference connectedWifiEntryPreference = + mNetworkProviderSettings.createLongPressWifiEntryPreference(mWifiEntry); + final View view = mock(View.class); + when(view.getTag()).thenReturn(connectedWifiEntryPreference); + + mNetworkProviderSettings.onCreateContextMenu(mContextMenu, view, null /* info */); + + verify(mContextMenu).add(anyInt(), eq(MENU_ID_SHARE), anyInt(), anyInt()); + } + + @Test + public void onCreateContextMenu_canNotShare_shouldDisappearShareMenuForConnectedWifiEntry() { + final FragmentActivity activity = mock(FragmentActivity.class); + when(activity.getApplicationContext()).thenReturn(mContext); + when(mNetworkProviderSettings.getActivity()).thenReturn(activity); + + when(mWifiEntry.canDisconnect()).thenReturn(true); + when(mWifiEntry.canShare()).thenReturn(false); + when(mWifiEntry.canForget()).thenReturn(true); + when(mWifiEntry.isSaved()).thenReturn(true); + when(mWifiEntry.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED); + + final LongPressWifiEntryPreference connectedWifiEntryPreference = + mNetworkProviderSettings.createLongPressWifiEntryPreference(mWifiEntry); + final View view = mock(View.class); + when(view.getTag()).thenReturn(connectedWifiEntryPreference); + + mNetworkProviderSettings.onCreateContextMenu(mContextMenu, view, null /* info */); + + verify(mContextMenu, never()).add(anyInt(), eq(MENU_ID_SHARE), anyInt(), anyInt()); } @Test @@ -602,6 +652,46 @@ public class NetworkProviderSettingsTest { verify(mAirplaneModeEnabler).stop(); } + @Test + public void addShareMenuIfSuitable_isAdmin_addMenu() { + mNetworkProviderSettings.mIsAdmin = true; + Mockito.reset(mContextMenu); + + mNetworkProviderSettings.addShareMenuIfSuitable(mContextMenu); + + verify(mContextMenu).add(anyInt(), eq(MENU_ID_SHARE), anyInt(), anyInt()); + } + + @Test + public void addShareMenuIfSuitable_isNotAdmin_notAddMenu() { + mNetworkProviderSettings.mIsAdmin = false; + Mockito.reset(mContextMenu); + + mNetworkProviderSettings.addShareMenuIfSuitable(mContextMenu); + + verify(mContextMenu, never()).add(anyInt(), eq(MENU_ID_SHARE), anyInt(), anyInt()); + } + + @Test + public void addForgetMenuIfSuitable_isAdmin_addMenu() { + mNetworkProviderSettings.mIsAdmin = true; + Mockito.reset(mContextMenu); + + mNetworkProviderSettings.addForgetMenuIfSuitable(mContextMenu); + + verify(mContextMenu).add(anyInt(), eq(MENU_ID_FORGET), anyInt(), anyInt()); + } + + @Test + public void addForgetMenuIfSuitable_isNotAdmin_notAddMenu() { + mNetworkProviderSettings.mIsAdmin = false; + Mockito.reset(mContextMenu); + + mNetworkProviderSettings.addForgetMenuIfSuitable(mContextMenu); + + verify(mContextMenu, never()).add(anyInt(), eq(MENU_ID_FORGET), anyInt(), anyInt()); + } + @Implements(PreferenceFragmentCompat.class) public static class ShadowPreferenceFragmentCompat { -- GitLab From db3d2deddc7e7f52d15a57f0e517e8572edef597 Mon Sep 17 00:00:00 2001 From: Arc Wang Date: Thu, 7 Apr 2022 11:33:16 +0800 Subject: [PATCH 05/43] Hide non-system overlay window on ActivityPicker To improve security. Bug: 181962311 Test: manual Show an AlertDialog and observe if it will hide after below command. adb shell am start -a android.intent.action.PICK_ACTIVITY -n com.android.settings/.ActivityPicker Change-Id: I800f0f39a469a95eb36eeaaeb2aa60a39fd916d3 Merged-In: I800f0f39a469a95eb36eeaaeb2aa60a39fd916d3 (cherry picked from commit b95bd5b449df7489455ce681bce5fd08de303b3b) Merged-In: I800f0f39a469a95eb36eeaaeb2aa60a39fd916d3 --- src/com/android/settings/ActivityPicker.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/com/android/settings/ActivityPicker.java b/src/com/android/settings/ActivityPicker.java index ae61944cbe1..f75ce3740aa 100644 --- a/src/com/android/settings/ActivityPicker.java +++ b/src/com/android/settings/ActivityPicker.java @@ -16,6 +16,8 @@ package com.android.settings; +import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; + import android.app.Activity; import android.content.Context; import android.content.DialogInterface; @@ -71,6 +73,8 @@ public class ActivityPicker extends AlertActivity implements @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + getWindow().addPrivateFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); final Intent intent = getIntent(); -- GitLab From 2ac9c6416240c2e1bc8ffa0e6d3ff2912a9b681f Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Wed, 6 Apr 2022 17:30:27 +0800 Subject: [PATCH 06/43] Fix LaunchAnyWhere in AppRestrictionsFragment If the intent's package equals to the app's package, this intent will be allowed to startActivityForResult. But this check is unsafe, because if the component of this intent is set, the package field will just be ignored. So if we set the component to any activity we like and set package to the app's package, it will pass the assertSafeToStartCustomActivity check and now we can launch anywhere. Bug: 223578534 Test: robotest and manual verify Change-Id: I40496105bae313fe5cff2a36dfe329c1e2b5bbe4 (cherry picked from commit 90e095dbe372f29823ad4788c0cc2d781ae3bb24) (cherry picked from commit 019eb77224b0671458ad447f15a2a29935c866c6) Merged-In: I40496105bae313fe5cff2a36dfe329c1e2b5bbe4 --- src/com/android/settings/users/AppRestrictionsFragment.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/com/android/settings/users/AppRestrictionsFragment.java b/src/com/android/settings/users/AppRestrictionsFragment.java index c67a687d180..db7612f7f10 100644 --- a/src/com/android/settings/users/AppRestrictionsFragment.java +++ b/src/com/android/settings/users/AppRestrictionsFragment.java @@ -661,10 +661,7 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen } private void assertSafeToStartCustomActivity(Intent intent) { - // Activity can be started if it belongs to the same app - if (intent.getPackage() != null && intent.getPackage().equals(packageName)) { - return; - } + EventLog.writeEvent(0x534e4554, "223578534", -1 /* UID */, ""); ResolveInfo resolveInfo = mPackageManager.resolveActivity( intent, PackageManager.MATCH_DEFAULT_ONLY); -- GitLab From b256e2e735e86e6994b3f461fd13f10501b8e04e Mon Sep 17 00:00:00 2001 From: Arc Wang Date: Fri, 6 May 2022 17:42:30 +0800 Subject: [PATCH 07/43] Verify ringtone from ringtone picker is audio To improve privacy. Bug: 221041256 Test: atest com.android.settings.DefaultRingtonePreferenceTest Change-Id: I0a9ca163f5ae91b67c9f957fde4c6db326b8718d Merged-In: I0a9ca163f5ae91b67c9f957fde4c6db326b8718d (cherry picked from commit e4c22580c9a66a3d5523782c2daa707531210227) (cherry picked from commit 1b7fa6f8fc42924af0b0cfbad25f3405de706a9f) Merged-In: I0a9ca163f5ae91b67c9f957fde4c6db326b8718d --- .../settings/DefaultRingtonePreference.java | 21 +++++ .../DefaultRingtonePreferenceTest.java | 81 +++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 tests/unit/src/com/android/settings/DefaultRingtonePreferenceTest.java diff --git a/src/com/android/settings/DefaultRingtonePreference.java b/src/com/android/settings/DefaultRingtonePreference.java index 9f9f832b100..914c4b214d1 100644 --- a/src/com/android/settings/DefaultRingtonePreference.java +++ b/src/com/android/settings/DefaultRingtonePreference.java @@ -22,6 +22,9 @@ import android.content.Intent; import android.media.RingtoneManager; import android.net.Uri; import android.util.AttributeSet; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; public class DefaultRingtonePreference extends RingtonePreference { private static final String TAG = "DefaultRingtonePreference"; @@ -43,6 +46,24 @@ public class DefaultRingtonePreference extends RingtonePreference { @Override protected void onSaveRingtone(Uri ringtoneUri) { + String mimeType = getContext().getContentResolver().getType(ringtoneUri); + if (mimeType == null) { + Log.e(TAG, "onSaveRingtone for URI:" + ringtoneUri + + " ignored: failure to find mimeType (no access from this context?)"); + return; + } + + if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg"))) { + Log.e(TAG, "onSaveRingtone for URI:" + ringtoneUri + + " ignored: associated mimeType:" + mimeType + " is not an audio type"); + return; + } + + setActualDefaultRingtoneUri(ringtoneUri); + } + + @VisibleForTesting + void setActualDefaultRingtoneUri(Uri ringtoneUri) { RingtoneManager.setActualDefaultRingtoneUri(mUserContext, getRingtoneType(), ringtoneUri); } diff --git a/tests/unit/src/com/android/settings/DefaultRingtonePreferenceTest.java b/tests/unit/src/com/android/settings/DefaultRingtonePreferenceTest.java new file mode 100644 index 00000000000..b9dea0167b5 --- /dev/null +++ b/tests/unit/src/com/android/settings/DefaultRingtonePreferenceTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2022 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; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.media.RingtoneManager; +import android.net.Uri; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Unittest for DefaultRingtonePreference. */ +@RunWith(AndroidJUnit4.class) +public class DefaultRingtonePreferenceTest { + + private DefaultRingtonePreference mDefaultRingtonePreference; + + @Mock + private ContentResolver mContentResolver; + @Mock + private Uri mRingtoneUri; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + Context context = spy(ApplicationProvider.getApplicationContext()); + doReturn(mContentResolver).when(context).getContentResolver(); + + mDefaultRingtonePreference = spy(new DefaultRingtonePreference(context, null /* attrs */)); + doReturn(context).when(mDefaultRingtonePreference).getContext(); + when(mDefaultRingtonePreference.getRingtoneType()) + .thenReturn(RingtoneManager.TYPE_RINGTONE); + mDefaultRingtonePreference.setUserId(1); + } + + @Test + public void onSaveRingtone_nullMimeType_shouldNotSetRingtone() { + when(mContentResolver.getType(mRingtoneUri)).thenReturn(null); + + mDefaultRingtonePreference.onSaveRingtone(mRingtoneUri); + + verify(mDefaultRingtonePreference, never()).setActualDefaultRingtoneUri(mRingtoneUri); + } + + @Test + public void onSaveRingtone_notAudioMimeType_shouldNotSetRingtone() { + when(mContentResolver.getType(mRingtoneUri)).thenReturn("text/plain"); + + mDefaultRingtonePreference.onSaveRingtone(mRingtoneUri); + + verify(mDefaultRingtonePreference, never()).setActualDefaultRingtoneUri(mRingtoneUri); + } +} -- GitLab From d05d20278ba7ccd2a71abe994669eb77cbdafc85 Mon Sep 17 00:00:00 2001 From: Hugh Chen Date: Tue, 10 May 2022 09:39:12 +0000 Subject: [PATCH 08/43] RESTRICT AUTOMERGE Make bluetooth not discoverable via SliceDeepLinkTrampoline - Don't let device be discovered when the user launch "Connected Devices settings" through SliceDeepLinkTrampoline. Bug: 228450811 Test: make -j42 RunSettingsRoboTests and use test apk to manually test to verify the device is not discoversable when open "Connected settings" through test apk. Change-Id: I5490b58675b1fd9fc36305766867f65caa6ccb6c (cherry picked from commit 205752dcf2062eb3deeb7f3b7d1eb8af7d8b2634) (cherry picked from commit 8bae22248940fe0549c7e6cfab07948f1e4f6b37) Merged-In: I5490b58675b1fd9fc36305766867f65caa6ccb6c --- .../ConnectedDeviceDashboardFragment.java | 17 ++++++++--- .../ConnectedDeviceDashboardFragmentTest.java | 28 +++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java index c8eb488df09..7e6eefe2e41 100644 --- a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java +++ b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java @@ -39,6 +39,7 @@ public class ConnectedDeviceDashboardFragment extends DashboardFragment { private static final String SETTINGS_PACKAGE_NAME = "com.android.settings"; private static final String SYSTEMUI_PACKAGE_NAME = "com.android.systemui"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final String SLICE_ACTION = "com.android.settings.SEARCH_RESULT_TRAMPOLINE"; @VisibleForTesting static final String KEY_CONNECTED_DEVICES = "connected_device_list"; @@ -72,8 +73,10 @@ public class ConnectedDeviceDashboardFragment extends DashboardFragment { SettingsUIDeviceConfig.BT_NEAR_BY_SUGGESTION_ENABLED, true); String callingAppPackageName = PasswordUtils.getCallingAppPackageName( getActivity().getActivityToken()); + String action = getIntent() != null ? getIntent().getAction() : ""; if (DEBUG) { - Log.d(TAG, "onAttach() calling package name is : " + callingAppPackageName); + Log.d(TAG, "onAttach() calling package name is : " + callingAppPackageName + + ", action : " + action); } use(AvailableMediaDeviceGroupController.class).init(this); use(ConnectedDeviceGroupController.class).init(this); @@ -81,9 +84,15 @@ public class ConnectedDeviceDashboardFragment extends DashboardFragment { use(SlicePreferenceController.class).setSliceUri(nearbyEnabled ? Uri.parse(getString(R.string.config_nearby_devices_slice_uri)) : null); - use(DiscoverableFooterPreferenceController.class).setAlwaysDiscoverable( - TextUtils.equals(SETTINGS_PACKAGE_NAME, callingAppPackageName) - || TextUtils.equals(SYSTEMUI_PACKAGE_NAME, callingAppPackageName)); + use(DiscoverableFooterPreferenceController.class) + .setAlwaysDiscoverable(isAlwaysDiscoverable(callingAppPackageName, action)); + } + + @VisibleForTesting + boolean isAlwaysDiscoverable(String callingAppPackageName, String action) { + return TextUtils.equals(SLICE_ACTION, action) ? false + : TextUtils.equals(SETTINGS_PACKAGE_NAME, callingAppPackageName) + || TextUtils.equals(SYSTEMUI_PACKAGE_NAME, callingAppPackageName); } /** diff --git a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java index 97d54854f40..5f0f2b9dddf 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java @@ -54,16 +54,24 @@ public class ConnectedDeviceDashboardFragmentTest { private static final String KEY_DISCOVERABLE_FOOTER = "discoverable_footer"; private static final String KEY_SEE_ALL = "previously_connected_devices_see_all"; private static final String KEY_ADD_BT_DEVICES = "add_bt_devices"; + private static final String SETTINGS_PACKAGE_NAME = "com.android.settings"; + private static final String SYSTEMUI_PACKAGE_NAME = "com.android.systemui"; + private static final String SLICE_ACTION = "com.android.settings.SEARCH_RESULT_TRAMPOLINE"; + private static final String TEST_APP_NAME = "com.testapp.settings"; + private static final String TEST_ACTION = "com.testapp.settings.ACTION_START"; + @Mock private PackageManager mPackageManager; private Context mContext; + private ConnectedDeviceDashboardFragment mFragment; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); + mFragment = new ConnectedDeviceDashboardFragment(); doReturn(mPackageManager).when(mContext).getPackageManager(); doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); } @@ -87,6 +95,26 @@ public class ConnectedDeviceDashboardFragmentTest { KEY_NEARBY_DEVICES, KEY_DISCOVERABLE_FOOTER, KEY_SEE_ALL); } + @Test + public void isAlwaysDiscoverable_callingAppIsNotFromSystemApp_returnsFalse() { + assertThat(mFragment.isAlwaysDiscoverable(TEST_APP_NAME, TEST_ACTION)).isFalse(); + } + + @Test + public void isAlwaysDiscoverable_callingAppIsFromSettings_returnsTrue() { + assertThat(mFragment.isAlwaysDiscoverable(SETTINGS_PACKAGE_NAME, TEST_ACTION)).isTrue(); + } + + @Test + public void isAlwaysDiscoverable_callingAppIsFromSystemUI_returnsTrue() { + assertThat(mFragment.isAlwaysDiscoverable(SYSTEMUI_PACKAGE_NAME, TEST_ACTION)).isTrue(); + } + + @Test + public void isAlwaysDiscoverable_actionIsFromSlice_returnsFalse() { + assertThat(mFragment.isAlwaysDiscoverable(SYSTEMUI_PACKAGE_NAME, SLICE_ACTION)).isFalse(); + } + @Test public void getPreferenceControllers_containSlicePrefController() { final List controllers = -- GitLab From 9ff50f623ffbc0d52e74e94bac23280428b66394 Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Wed, 4 May 2022 18:01:15 +0800 Subject: [PATCH 09/43] Do not let guest user disable secuer nfc via SettingsSlice Do not let guest user switch the secure nfc preferernce setting. Bug: 228314987 Test: manual Change-Id: I60a832e32d83bb57d968af2f8b92d94e2ac7c6a2 (cherry picked from commit 2290b0af8cb4b640709fa904f73ce3e69208f872) Merged-In: I60a832e32d83bb57d968af2f8b92d94e2ac7c6a2 --- .../nfc/SecureNfcPreferenceController.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/nfc/SecureNfcPreferenceController.java b/src/com/android/settings/nfc/SecureNfcPreferenceController.java index e1d27d0ba78..cf43ec30ded 100644 --- a/src/com/android/settings/nfc/SecureNfcPreferenceController.java +++ b/src/com/android/settings/nfc/SecureNfcPreferenceController.java @@ -17,6 +17,7 @@ package com.android.settings.nfc; import android.content.Context; import android.nfc.NfcAdapter; +import android.os.UserManager; import androidx.preference.PreferenceScreen; import androidx.preference.SwitchPreference; @@ -32,10 +33,12 @@ public class SecureNfcPreferenceController extends TogglePreferenceController private final NfcAdapter mNfcAdapter; private SecureNfcEnabler mSecureNfcEnabler; + private final UserManager mUserManager; public SecureNfcPreferenceController(Context context, String key) { super(context, key); mNfcAdapter = NfcAdapter.getDefaultAdapter(context); + mUserManager = context.getSystemService(UserManager.class); } @Override @@ -58,7 +61,11 @@ public class SecureNfcPreferenceController extends TogglePreferenceController @Override public boolean setChecked(boolean isChecked) { - return mNfcAdapter.enableSecureNfc(isChecked); + if (isToggleable()) { + return mNfcAdapter.enableSecureNfc(isChecked); + } else { + return false; + } } @Override @@ -100,4 +107,12 @@ public class SecureNfcPreferenceController extends TogglePreferenceController mSecureNfcEnabler.pause(); } } + + private boolean isToggleable() { + if (mUserManager.isGuestUser()) { + return false; + } + return true; + } + } -- GitLab From f52008b45e348dfbef09f8c29e4786b66e582f7c Mon Sep 17 00:00:00 2001 From: Lin Yuan Date: Thu, 26 May 2022 18:49:21 -0400 Subject: [PATCH 10/43] RESTRICT AUTOMERGE Fix: policy enforcement for location wifi scanning Make DISALLOW_CONFIG_LOCATION effectively disallow wifi scanning and bluetooth scanning settings for location services. screenshots: http://shortn/_EUOdqrOcnS, http://shortn/_j320QDm1Zo Bug: 228315522 Bug: 228315529 Test: atest SettingsRoboTests, on device Change-Id: I78291579a79e915a27ebdd051b3caf3fc04efc41 (cherry picked from commit fcae147f58be3b6441ce9e03bc59515af0d53ccc) (cherry picked from commit a12fff673b59d7674b2ba04f30e92a9941fff7ac) Merged-In: I78291579a79e915a27ebdd051b3caf3fc04efc41 --- res/xml/location_services.xml | 4 ++-- ...ServicesBluetoothScanningPreferenceController.java | 11 +++++++++++ ...ationServicesWifiScanningPreferenceController.java | 11 +++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/res/xml/location_services.xml b/res/xml/location_services.xml index 2de4e7b2446..04eff2343a3 100644 --- a/res/xml/location_services.xml +++ b/res/xml/location_services.xml @@ -23,13 +23,13 @@ android:layout="@layout/preference_category_no_label" settings:controller="com.android.settings.location.LocationInjectedServicesPreferenceController"/> - - Date: Fri, 22 Apr 2022 00:40:06 +0000 Subject: [PATCH 11/43] Extract app label from component name in notification access confirmation UI Bug: 228178437 Test: Manually tested on POC Change-Id: I8613d9b87a53d4641c0689bca9c961c66a2e9415 Merged-In: I8613d9b87a53d4641c0689bca9c961c66a2e9415 (cherry picked from commit c548f102e5e06957ed6ed881e9f0bd12dc08f4b6) Merged-In: I8613d9b87a53d4641c0689bca9c961c66a2e9415 --- ...otificationAccessConfirmationActivity.java | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java b/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java index a7d9f6889cd..dfe6df2a5ca 100644 --- a/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java +++ b/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java @@ -20,7 +20,6 @@ package com.android.settings.notification; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import static com.android.internal.notification.NotificationAccessConfirmationActivityContract.EXTRA_COMPONENT_NAME; -import static com.android.internal.notification.NotificationAccessConfirmationActivityContract.EXTRA_PACKAGE_TITLE; import static com.android.internal.notification.NotificationAccessConfirmationActivityContract.EXTRA_USER_ID; import android.Manifest; @@ -30,10 +29,13 @@ import android.app.NotificationManager; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.os.Bundle; import android.os.UserHandle; +import android.text.TextUtils; import android.util.Slog; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; @@ -63,15 +65,38 @@ public class NotificationAccessConfirmationActivity extends Activity mComponentName = getIntent().getParcelableExtra(EXTRA_COMPONENT_NAME); mUserId = getIntent().getIntExtra(EXTRA_USER_ID, UserHandle.USER_NULL); - String pkgTitle = getIntent().getStringExtra(EXTRA_PACKAGE_TITLE); + CharSequence mAppLabel; + + if (mComponentName == null || mComponentName.getPackageName() == null) { + finish(); + return; + } + + try { + ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo( + mComponentName.getPackageName(), 0); + mAppLabel = applicationInfo.loadSafeLabel(getPackageManager(), + PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX, + PackageItemInfo.SAFE_LABEL_FLAG_TRIM + | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE); + } catch (PackageManager.NameNotFoundException e) { + Slog.e(LOG_TAG, "Couldn't find app with package name for " + mComponentName, e); + finish(); + return; + } + + if (TextUtils.isEmpty(mAppLabel)) { + finish(); + return; + } AlertController.AlertParams p = new AlertController.AlertParams(this); p.mTitle = getString( R.string.notification_listener_security_warning_title, - pkgTitle); + mAppLabel); p.mMessage = getString( R.string.notification_listener_security_warning_summary, - pkgTitle); + mAppLabel); p.mPositiveButtonText = getString(R.string.allow); p.mPositiveButtonListener = (a, b) -> onAllow(); p.mNegativeButtonText = getString(R.string.deny); -- GitLab From 35c2ee8e11b0d6f8bb539183d2fb74c052cf214a Mon Sep 17 00:00:00 2001 From: Arc Wang Date: Mon, 16 May 2022 14:36:19 +0800 Subject: [PATCH 12/43] [DO NOT MERGE] Fix Settings crash when setting a null ringtone Ringtone picker may callback a null ringtone Uri if users select None. This change pass null ringtone Uri to RingtoneManager and return. Bug: 232502532 Bug: 221041256 Test: maunal Settings - Sound & Vibration -> Phone ringtone -> My Sounds -> None Change-Id: I044b680871472a3c272f6264c4ef272df542112e Merged-In: I044b680871472a3c272f6264c4ef272df542112e (cherry picked from commit 46e00dc6dfe02a870fe953c18d2002b7e5662058) Merged-In: I044b680871472a3c272f6264c4ef272df542112e --- src/com/android/settings/DefaultRingtonePreference.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/com/android/settings/DefaultRingtonePreference.java b/src/com/android/settings/DefaultRingtonePreference.java index 914c4b214d1..824a5a05dc1 100644 --- a/src/com/android/settings/DefaultRingtonePreference.java +++ b/src/com/android/settings/DefaultRingtonePreference.java @@ -46,6 +46,11 @@ public class DefaultRingtonePreference extends RingtonePreference { @Override protected void onSaveRingtone(Uri ringtoneUri) { + if (ringtoneUri == null) { + setActualDefaultRingtoneUri(ringtoneUri); + return; + } + String mimeType = getContext().getContentResolver().getType(ringtoneUri); if (mimeType == null) { Log.e(TAG, "onSaveRingtone for URI:" + ringtoneUri -- GitLab From ea7692e3ea2aa32be52ba7ead47cf9cb305b4247 Mon Sep 17 00:00:00 2001 From: Tsung-Mao Fang Date: Fri, 27 May 2022 15:52:30 +0800 Subject: [PATCH 13/43] [DO NOT MERGE] Fix can't change notification sound for work profile. Use correct user id context to query the type, so we won't get empty result unexpectedly. If we get the null result, then we won't set sound sucessfully. Bug: 233580016 Bug: 221041256 Test: Manual test and set work profile sound works. Change-Id: I7f8fb737a7c6f77a380f3f075a5c89a1970e39ad Merged-In: I7f8fb737a7c6f77a380f3f075a5c89a1970e39ad (cherry picked from commit 90968c8437dbac230808742f108c0f733b38f28c) Merged-In: I7f8fb737a7c6f77a380f3f075a5c89a1970e39ad --- src/com/android/settings/DefaultRingtonePreference.java | 2 +- .../src/com/android/settings/DefaultRingtonePreferenceTest.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/DefaultRingtonePreference.java b/src/com/android/settings/DefaultRingtonePreference.java index 824a5a05dc1..9bf626c9898 100644 --- a/src/com/android/settings/DefaultRingtonePreference.java +++ b/src/com/android/settings/DefaultRingtonePreference.java @@ -51,7 +51,7 @@ public class DefaultRingtonePreference extends RingtonePreference { return; } - String mimeType = getContext().getContentResolver().getType(ringtoneUri); + String mimeType = mUserContext.getContentResolver().getType(ringtoneUri); if (mimeType == null) { Log.e(TAG, "onSaveRingtone for URI:" + ringtoneUri + " ignored: failure to find mimeType (no access from this context?)"); diff --git a/tests/unit/src/com/android/settings/DefaultRingtonePreferenceTest.java b/tests/unit/src/com/android/settings/DefaultRingtonePreferenceTest.java index b9dea0167b5..7877684dce6 100644 --- a/tests/unit/src/com/android/settings/DefaultRingtonePreferenceTest.java +++ b/tests/unit/src/com/android/settings/DefaultRingtonePreferenceTest.java @@ -59,6 +59,7 @@ public class DefaultRingtonePreferenceTest { when(mDefaultRingtonePreference.getRingtoneType()) .thenReturn(RingtoneManager.TYPE_RINGTONE); mDefaultRingtonePreference.setUserId(1); + mDefaultRingtonePreference.mUserContext = context; } @Test -- GitLab From 445cc2e28de68893a665cb71e518688af58cad81 Mon Sep 17 00:00:00 2001 From: Jason Chiu Date: Thu, 23 Jun 2022 12:12:23 +0800 Subject: [PATCH 14/43] [DO NOT MERGE] Make bluetooth not discoverable via large screen deep link flow Deep links on large screen devices starts a homepage activity on the left pane, and then starts the target activity on the right pane. This flow overrides the calling package, and the target activity can't know who initially calls it. Thus, we store the initial calling package in the intent, so the Connected devices page is able to make bluetooth not discoverable when it's called from unintended apps on large screen devices. Bug: 234440688 Test: robotest, manual Change-Id: I4ddcd4e083c002ece9d10aabdb4af4a41de55ce7 (cherry picked from commit 846d0286a8c1608796a64d9f6748c52bc3612bc1) Merged-In: I4ddcd4e083c002ece9d10aabdb4af4a41de55ce7 --- src/com/android/settings/SettingsActivity.java | 16 ++++++++++++++++ .../ConnectedDeviceDashboardFragment.java | 6 +++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index d3d3604a2cc..ea6d983fd01 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -68,6 +68,7 @@ import com.android.settings.homepage.SettingsHomepageActivity; import com.android.settings.homepage.SliceDeepLinkHomepageActivity; import com.android.settings.homepage.TopLevelSettings; import com.android.settings.overlay.FeatureFactory; +import com.android.settings.password.PasswordUtils; import com.android.settings.wfd.WifiDisplaySettings; import com.android.settings.widget.SettingsMainSwitchBar; import com.android.settingslib.core.instrumentation.Instrumentable; @@ -150,6 +151,8 @@ public class SettingsActivity extends SettingsBaseActivity */ public static final String EXTRA_IS_FROM_SLICE = "is_from_slice"; + public static final String EXTRA_INITIAL_CALLING_PACKAGE = "initial_calling_package"; + /** * Personal or Work profile tab of {@link ProfileSelectFragment} *

0: Personal tab. @@ -399,6 +402,8 @@ public class SettingsActivity extends SettingsBaseActivity } private void launchHomepageForTwoPaneDeepLink(Intent intent) { + intent.putExtra(EXTRA_INITIAL_CALLING_PACKAGE, PasswordUtils.getCallingAppPackageName( + getActivityToken())); final Intent trampolineIntent; if (intent.getBooleanExtra(EXTRA_IS_FROM_SLICE, false)) { // Get menu key for slice deep link case. @@ -459,6 +464,17 @@ public class SettingsActivity extends SettingsBaseActivity return true; } + /** Returns the initial calling package name that launches the activity. */ + public String getInitialCallingPackage() { + String callingPackage = PasswordUtils.getCallingAppPackageName(getActivityToken()); + if (!TextUtils.equals(callingPackage, getPackageName())) { + return callingPackage; + } + + String initialCallingPackage = getIntent().getStringExtra(EXTRA_INITIAL_CALLING_PACKAGE); + return TextUtils.isEmpty(initialCallingPackage) ? callingPackage : initialCallingPackage; + } + /** Returns the initial fragment name that the activity will launch. */ @VisibleForTesting public String getInitialFragmentName(Intent intent) { diff --git a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java index 7e6eefe2e41..ea8a5f560f9 100644 --- a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java +++ b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java @@ -25,9 +25,9 @@ import android.util.Log; import androidx.annotation.VisibleForTesting; import com.android.settings.R; +import com.android.settings.SettingsActivity; import com.android.settings.core.SettingsUIDeviceConfig; import com.android.settings.dashboard.DashboardFragment; -import com.android.settings.password.PasswordUtils; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.slices.SlicePreferenceController; import com.android.settingslib.search.SearchIndexable; @@ -71,8 +71,8 @@ public class ConnectedDeviceDashboardFragment extends DashboardFragment { super.onAttach(context); final boolean nearbyEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, SettingsUIDeviceConfig.BT_NEAR_BY_SUGGESTION_ENABLED, true); - String callingAppPackageName = PasswordUtils.getCallingAppPackageName( - getActivity().getActivityToken()); + String callingAppPackageName = ((SettingsActivity) getActivity()) + .getInitialCallingPackage(); String action = getIntent() != null ? getIntent().getAction() : ""; if (DEBUG) { Log.d(TAG, "onAttach() calling package name is : " + callingAppPackageName -- GitLab From 3c6ad102348d6dbc87379cc1dc5f60826345a17f Mon Sep 17 00:00:00 2001 From: Milton Wu Date: Mon, 8 Aug 2022 09:05:00 +0000 Subject: [PATCH 15/43] [DO NOT MERGE] Add FLAG_SECURE for ChooseLockPassword and Pattern Prevent ChooseLockPassword and ChooseLockPatten being projected to remote views, add FLAG_SECURE for these screens. Bug: 179725730 Test: Check these 2 screens not projected to chromecast Test: robo test for SetupChooseLockPatternTest ChooseLockPatternTest SetupChooseLockPasswordTest ChooseLockPasswordTest Change-Id: I7449a24427c966c1aa4280a7b7e7e70b60997cca Merged-In: I7449a24427c966c1aa4280a7b7e7e70b60997cca (cherry picked from commit 98239c0da68917a0622c24e9af16ce06768a68f2) (cherry picked from commit 27bddff2aca2b6095eba52f3a55532c511d77767) Merged-In: I7449a24427c966c1aa4280a7b7e7e70b60997cca --- .../settings/password/ChooseLockPassword.java | 2 ++ .../settings/password/ChooseLockPattern.java | 2 ++ .../password/ChooseLockPasswordTest.java | 16 ++++++++++++++++ .../settings/password/ChooseLockPatternTest.java | 10 ++++++++++ 4 files changed, 30 insertions(+) diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java index c03362510d0..24bfe46bf02 100644 --- a/src/com/android/settings/password/ChooseLockPassword.java +++ b/src/com/android/settings/password/ChooseLockPassword.java @@ -61,6 +61,7 @@ import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import android.view.inputmethod.EditorInfo; import android.widget.ImeAwareEditText; import android.widget.TextView; @@ -205,6 +206,7 @@ public class ChooseLockPassword extends SettingsActivity { ThemeHelper.trySetDynamicColor(this); super.onCreate(savedInstanceState); findViewById(R.id.content_parent).setFitsSystemWindows(false); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); } public static class ChooseLockPasswordFragment extends InstrumentedFragment diff --git a/src/com/android/settings/password/ChooseLockPattern.java b/src/com/android/settings/password/ChooseLockPattern.java index 016906aea10..64c0d89bd57 100644 --- a/src/com/android/settings/password/ChooseLockPattern.java +++ b/src/com/android/settings/password/ChooseLockPattern.java @@ -39,6 +39,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import android.widget.TextView; import androidx.fragment.app.Fragment; @@ -173,6 +174,7 @@ public class ChooseLockPattern extends SettingsActivity { ThemeHelper.trySetDynamicColor(this); super.onCreate(savedInstanceState); findViewById(R.id.content_parent).setFitsSystemWindows(false); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); } @Override diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java index e789b61f188..5299190051f 100644 --- a/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java +++ b/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java @@ -27,6 +27,7 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; +import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static com.android.internal.widget.LockPatternUtils.PASSWORD_TYPE_KEY; import static com.android.settings.password.ChooseLockGeneric.CONFIRM_CREDENTIALS; @@ -162,6 +163,21 @@ public class ChooseLockPasswordTest { .isNotNull(); } + @Test + public void activity_shouldHaveSecureFlag() { + PasswordPolicy policy = new PasswordPolicy(); + policy.quality = PASSWORD_QUALITY_ALPHABETIC; + policy.length = 10; + + Intent intent = createIntentForPasswordValidation( + /* minMetrics */ policy.getMinMetrics(), + /* minComplexity= */ PASSWORD_COMPLEXITY_NONE, + /* passwordType= */ PASSWORD_QUALITY_ALPHABETIC); + ChooseLockPassword activity = buildChooseLockPasswordActivity(intent); + final int flags = activity.getWindow().getAttributes().flags; + assertThat(flags & FLAG_SECURE).isEqualTo(FLAG_SECURE); + } + @Test public void processAndValidatePasswordRequirements_noMinPasswordComplexity() { PasswordPolicy policy = new PasswordPolicy(); diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockPatternTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockPatternTest.java index f5cc39435ad..1fc10fc75f2 100644 --- a/tests/robotests/src/com/android/settings/password/ChooseLockPatternTest.java +++ b/tests/robotests/src/com/android/settings/password/ChooseLockPatternTest.java @@ -16,6 +16,8 @@ package com.android.settings.password; +import static android.view.WindowManager.LayoutParams.FLAG_SECURE; + import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -107,6 +109,14 @@ public class ChooseLockPatternTest { assertThat(iconView.getVisibility()).isEqualTo(View.GONE); } + @Test + public void activity_shouldHaveSecureFlag() { + final ChooseLockPattern activity = Robolectric.buildActivity( + ChooseLockPattern.class, new IntentBuilder(application).build()).setup().get(); + final int flags = activity.getWindow().getAttributes().flags; + assertThat(flags & FLAG_SECURE).isEqualTo(FLAG_SECURE); + } + private ChooseLockPattern createActivity(boolean addFingerprintExtra) { return Robolectric.buildActivity( ChooseLockPattern.class, -- GitLab From 9b3570921f0486e35156a340774aa3fec14bfd67 Mon Sep 17 00:00:00 2001 From: Arc Wang Date: Wed, 2 Nov 2022 09:37:32 +0800 Subject: [PATCH 16/43] Remove Intent selector from 2-pane deep link Intent To guard against the arbitrary Intent injection through Selector. Bug: 246300272 Test: make RunSettingsRoboTests ROBOTEST_FILTER=SettingsActivityTest Change-Id: I76fbf3ff7a6611ebb3d07f73845a64efe1771769 Merged-In: I8b3b936de490f09f4be960fdafc6e66a1d858ee2 (cherry picked from commit dd7d2d766a259d88044b737401381190b4e1878f) Merged-In: I76fbf3ff7a6611ebb3d07f73845a64efe1771769 --- .../android/settings/SettingsActivity.java | 4 +++ .../settings/SettingsActivityTest.java | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index ea6d983fd01..91799465524 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -379,6 +379,10 @@ public class SettingsActivity extends SettingsBaseActivity */ public static Intent getTrampolineIntent(Intent intent, String highlightMenuKey) { final Intent detailIntent = new Intent(intent); + // Guard against the arbitrary Intent injection. + if (detailIntent.getSelector() != null) { + detailIntent.setSelector(null); + } // It's a deep link intent, SettingsHomepageActivity will set SplitPairRule and start it. final Intent trampolineIntent = new Intent(ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY) .setPackage(Utils.SETTINGS_PACKAGE_NAME) diff --git a/tests/robotests/src/com/android/settings/SettingsActivityTest.java b/tests/robotests/src/com/android/settings/SettingsActivityTest.java index 89f84496a6e..5bf2089da68 100644 --- a/tests/robotests/src/com/android/settings/SettingsActivityTest.java +++ b/tests/robotests/src/com/android/settings/SettingsActivityTest.java @@ -16,6 +16,8 @@ package com.android.settings; +import static android.provider.Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI; + import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT; import static com.google.common.truth.Truth.assertThat; @@ -30,6 +32,7 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.content.Context; import android.content.Intent; +import android.net.Uri; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; @@ -49,6 +52,7 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; @@ -114,6 +118,29 @@ public class SettingsActivityTest { assertThat(((ListenerFragment) fragments.get(1)).mOnActivityResultCalled).isTrue(); } + @Test + public void getTrampolineIntent_intentSelector_shouldNotChangeIntentAction() { + Intent targetIntent = new Intent().setClassName("android", + "com.android.internal.app.PlatLogoActivity"); + Intent intent = new Intent(android.provider.Settings.ACTION_DISPLAY_SETTINGS); + intent.setComponent(intent.resolveActivity(mContext.getPackageManager())); + intent.setSelector(new + Intent().setData(Uri.fromParts(targetIntent.toUri(Intent.URI_INTENT_SCHEME), /* ssp= */ "", + /* fragment= */ null))); + + Intent resultIntent = SettingsActivity.getTrampolineIntent(intent, "menu_key"); + + String intentUriString = + resultIntent.getStringExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI); + Intent parsedIntent = null; + try { + parsedIntent = Intent.parseUri(intentUriString, Intent.URI_INTENT_SCHEME); + } catch (URISyntaxException e) { + // Do nothng. + } + assertThat(parsedIntent.getAction()).isEqualTo(intent.getAction()); + } + public static class ListenerFragment extends Fragment implements OnActivityResultListener { private boolean mOnActivityResultCalled; -- GitLab From dffd89a4be712814cca10157ec26c3aa4fd0823b Mon Sep 17 00:00:00 2001 From: changbetty Date: Mon, 7 Nov 2022 07:58:14 +0000 Subject: [PATCH 17/43] RESTRICT AUTOMERGE Make bluetooth switch not discoverable via SliceDeepLinkTrampoline Bug: 244423101 Test: make RunSettingsRoboTests ROBOTEST_FILTER=BluetoothSwitchPreferenceControllerTest Test: make RunSettingsRoboTests ROBOTEST_FILTER=BluetoothDashboardFragmentTest Test: manual test by test apk Change-Id: I13562d227e06627fac33239a9d21fd405a18d012 (cherry picked from commit e644704beeb809bf5c8d629d4dc76aa8f6d16f3b) Merged-In: I13562d227e06627fac33239a9d21fd405a18d012 --- .../BluetoothSwitchPreferenceController.java | 19 +++++- .../BluetoothDashboardFragment.java | 25 ++++++++ ...uetoothSwitchPreferenceControllerTest.java | 25 +++++++- .../BluetoothDashboardFragmentTest.java | 61 +++++++++++++++++++ 4 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/connecteddevice/BluetoothDashboardFragmentTest.java diff --git a/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceController.java b/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceController.java index 3bf913209c2..ca27299279f 100644 --- a/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceController.java +++ b/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceController.java @@ -45,6 +45,7 @@ public class BluetoothSwitchPreferenceController private SwitchWidgetController mSwitch; private Context mContext; private FooterPreference mFooterPreference; + private boolean mIsAlwaysDiscoverable; @VisibleForTesting AlwaysDiscoverable mAlwaysDiscoverable; @@ -78,7 +79,9 @@ public class BluetoothSwitchPreferenceController @Override public void onStart() { mBluetoothEnabler.resume(mContext); - mAlwaysDiscoverable.start(); + if (mIsAlwaysDiscoverable) { + mAlwaysDiscoverable.start(); + } if (mSwitch != null) { updateText(mSwitch.isChecked()); } @@ -87,7 +90,19 @@ public class BluetoothSwitchPreferenceController @Override public void onStop() { mBluetoothEnabler.pause(); - mAlwaysDiscoverable.stop(); + if (mIsAlwaysDiscoverable) { + mAlwaysDiscoverable.stop(); + } + } + + /** + * Set whether the device can be discovered. By default the value will be {@code false}. + * + * @param isAlwaysDiscoverable {@code true} if the device can be discovered, + * otherwise {@code false} + */ + public void setAlwaysDiscoverable(boolean isAlwaysDiscoverable) { + mIsAlwaysDiscoverable = isAlwaysDiscoverable; } @Override diff --git a/src/com/android/settings/connecteddevice/BluetoothDashboardFragment.java b/src/com/android/settings/connecteddevice/BluetoothDashboardFragment.java index 4591b7f216b..b30aee4d216 100644 --- a/src/com/android/settings/connecteddevice/BluetoothDashboardFragment.java +++ b/src/com/android/settings/connecteddevice/BluetoothDashboardFragment.java @@ -18,12 +18,17 @@ package com.android.settings.connecteddevice; import android.app.settings.SettingsEnums; import android.content.Context; import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.bluetooth.BluetoothDeviceRenamePreferenceController; import com.android.settings.bluetooth.BluetoothSwitchPreferenceController; import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.password.PasswordUtils; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.widget.MainSwitchBarController; import com.android.settings.widget.SettingsMainSwitchBar; @@ -40,6 +45,10 @@ public class BluetoothDashboardFragment extends DashboardFragment { private static final String TAG = "BluetoothDashboardFrag"; private static final String KEY_BLUETOOTH_SCREEN_FOOTER = "bluetooth_screen_footer"; + private static final String SETTINGS_PACKAGE_NAME = "com.android.settings"; + private static final String SYSTEMUI_PACKAGE_NAME = "com.android.systemui"; + private static final String SLICE_ACTION = "com.android.settings.SEARCH_RESULT_TRAMPOLINE"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private FooterPreference mFooterPreference; private SettingsMainSwitchBar mSwitchBar; @@ -80,17 +89,33 @@ public class BluetoothDashboardFragment extends DashboardFragment { @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); + String callingAppPackageName = PasswordUtils.getCallingAppPackageName( + getActivity().getActivityToken()); + String action = getIntent() != null ? getIntent().getAction() : ""; + if (DEBUG) { + Log.d(TAG, "onActivityCreated() calling package name is : " + callingAppPackageName + + ", action : " + action); + } SettingsActivity activity = (SettingsActivity) getActivity(); mSwitchBar = activity.getSwitchBar(); mSwitchBar.setTitle(getContext().getString(R.string.bluetooth_main_switch_title)); mController = new BluetoothSwitchPreferenceController(activity, new MainSwitchBarController(mSwitchBar), mFooterPreference); + mController.setAlwaysDiscoverable(isAlwaysDiscoverable(callingAppPackageName, action)); Lifecycle lifecycle = getSettingsLifecycle(); if (lifecycle != null) { lifecycle.addObserver(mController); } } + + @VisibleForTesting + boolean isAlwaysDiscoverable(String callingAppPackageName, String action) { + return TextUtils.equals(SLICE_ACTION, action) ? false + : TextUtils.equals(SETTINGS_PACKAGE_NAME, callingAppPackageName) + || TextUtils.equals(SYSTEMUI_PACKAGE_NAME, callingAppPackageName); + } + /** * For Search. */ diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceControllerTest.java index 3c5a91d2188..50c82d3c0d8 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceControllerTest.java @@ -18,13 +18,14 @@ package com.android.settings.bluetooth; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.content.Context; import android.provider.Settings; - import android.text.TextUtils; + import com.android.settings.R; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.utils.AnnotationSpan; @@ -109,16 +110,34 @@ public class BluetoothSwitchPreferenceControllerTest { } @Test - public void onStart_shouldStartAlwaysDiscoverable() { + public void onStart_setAlwaysDiscoverableAsTrue_shouldStartAlwaysDiscoverable() { + mController.setAlwaysDiscoverable(true); mController.onStart(); verify(mAlwaysDiscoverable).start(); } @Test - public void onStop_shouldStopAlwaysDiscoverable() { + public void onStart_setAlwaysDiscoverableAsFalse_shouldStartAlwaysDiscoverable() { + mController.setAlwaysDiscoverable(false); + mController.onStart(); + + verify(mAlwaysDiscoverable, never()).start(); + } + + @Test + public void onStop_setAlwaysDiscoverableAsTrue_shouldStopAlwaysDiscoverable() { + mController.setAlwaysDiscoverable(true); mController.onStop(); verify(mAlwaysDiscoverable).stop(); } + + @Test + public void onStop__setAlwaysDiscoverableAsFalse_shouldStopAlwaysDiscoverable() { + mController.setAlwaysDiscoverable(false); + mController.onStop(); + + verify(mAlwaysDiscoverable, never()).stop(); + } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/BluetoothDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/BluetoothDashboardFragmentTest.java new file mode 100644 index 00000000000..2aa2fa8e8df --- /dev/null +++ b/tests/robotests/src/com/android/settings/connecteddevice/BluetoothDashboardFragmentTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2022 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.connecteddevice; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class BluetoothDashboardFragmentTest { + private static final String SETTINGS_PACKAGE_NAME = "com.android.settings"; + private static final String SYSTEMUI_PACKAGE_NAME = "com.android.systemui"; + private static final String SLICE_ACTION = "com.android.settings.SEARCH_RESULT_TRAMPOLINE"; + private static final String TEST_APP_NAME = "com.testapp.settings"; + private static final String TEST_ACTION = "com.testapp.settings.ACTION_START"; + + private BluetoothDashboardFragment mFragment; + + @Before + public void setUp() { + mFragment = new BluetoothDashboardFragment(); + } + + + @Test + public void isAlwaysDiscoverable_callingAppIsNotFromSystemApp_returnsFalse() { + assertThat(mFragment.isAlwaysDiscoverable(TEST_APP_NAME, TEST_ACTION)).isFalse(); + } + + @Test + public void isAlwaysDiscoverable_callingAppIsFromSettings_returnsTrue() { + assertThat(mFragment.isAlwaysDiscoverable(SETTINGS_PACKAGE_NAME, TEST_ACTION)).isTrue(); + } + + @Test + public void isAlwaysDiscoverable_callingAppIsFromSystemUI_returnsTrue() { + assertThat(mFragment.isAlwaysDiscoverable(SYSTEMUI_PACKAGE_NAME, TEST_ACTION)).isTrue(); + } + + @Test + public void isAlwaysDiscoverable_actionIsFromSlice_returnsFalse() { + assertThat(mFragment.isAlwaysDiscoverable(SYSTEMUI_PACKAGE_NAME, SLICE_ACTION)).isFalse(); + } +} -- GitLab From 4e674cbb4b70a16aafc0e0fa46d1643efdbdc84f Mon Sep 17 00:00:00 2001 From: Arc Wang Date: Mon, 21 Nov 2022 15:04:24 +0800 Subject: [PATCH 18/43] Settings 2-pane deep link vulnerabilities Settings app must not start an deep link Activity if 1. The deep link Activity is not exported. or 2. Calling package does not have the permission to start the deep link Activity. Bug: 250589026 Test: make RunSettingsRoboTests ROBOTEST_FILTER=SettingsHomepageActivityTest Change-Id: I9a3bddfa5d9d1d2e924dd6f3e5e07dca6c11664f Merged-In: I9a3bddfa5d9d1d2e924dd6f3e5e07dca6c11664f (cherry picked from commit fef9b97498b66d3e069feb2836701118bc41a63f) Merged-In: I9a3bddfa5d9d1d2e924dd6f3e5e07dca6c11664f --- .../homepage/SettingsHomepageActivity.java | 36 +++++++++++++++++++ .../SettingsHomepageActivityTest.java | 35 ++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index 183a2fbf5f5..4726059455e 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -25,6 +25,8 @@ import android.app.ActivityManager; import android.app.settings.SettingsEnums; import android.content.ComponentName; import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.os.Bundle; import android.text.TextUtils; @@ -38,6 +40,7 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.Toolbar; +import androidx.annotation.VisibleForTesting; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; @@ -55,6 +58,7 @@ import com.android.settings.core.CategoryMixin; import com.android.settings.core.FeatureFlags; import com.android.settings.homepage.contextualcards.ContextualCardsFragment; import com.android.settings.overlay.FeatureFactory; +import com.android.settings.password.PasswordUtils; import com.android.settingslib.Utils; import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin; @@ -351,6 +355,32 @@ public class SettingsHomepageActivity extends FragmentActivity implements finish(); return; } + + if (!TextUtils.equals(PasswordUtils.getCallingAppPackageName(getActivityToken()), + getPackageName())) { + ActivityInfo targetActivityInfo = null; + try { + targetActivityInfo = getPackageManager().getActivityInfo(targetComponentName, + /* flags= */ 0); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Failed to get target ActivityInfo: " + e); + finish(); + return; + } + + if (!targetActivityInfo.exported) { + Log.e(TAG, "Must not launch an unexported Actvity for deep link"); + finish(); + return; + } + + if (!isCallingAppPermitted(targetActivityInfo.permission)) { + Log.e(TAG, "Calling app must have the permission of deep link Activity"); + finish(); + return; + } + } + targetIntent.setComponent(targetComponentName); // To prevent launchDeepLinkIntentToRight again for configuration change. @@ -386,6 +416,12 @@ public class SettingsHomepageActivity extends FragmentActivity implements startActivity(targetIntent); } + @VisibleForTesting + boolean isCallingAppPermitted(String permission) { + return TextUtils.isEmpty(permission) || PasswordUtils.isCallingAppPermitted( + this, getActivityToken(), permission); + } + private String getHighlightMenuKey() { final Intent intent = getIntent(); if (intent != null && TextUtils.equals(intent.getAction(), diff --git a/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java b/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java index 4d203a8a6b0..4de8b005c3c 100644 --- a/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java +++ b/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java @@ -20,6 +20,8 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -37,9 +39,11 @@ import androidx.fragment.app.Fragment; import com.android.settings.R; import com.android.settings.dashboard.suggestions.SuggestionFeatureProviderImpl; import com.android.settings.homepage.contextualcards.slices.BatteryFixSliceTest; +import com.android.settings.testutils.shadow.ShadowPasswordUtils; import com.android.settings.testutils.shadow.ShadowUserManager; import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -66,6 +70,11 @@ public class SettingsHomepageActivityTest { MockitoAnnotations.initMocks(this); } + @After + public void tearDown() { + ShadowPasswordUtils.reset(); + } + @Test public void launch_shouldHaveAnimationForIaFragment() { final SettingsHomepageActivity activity = Robolectric.buildActivity( @@ -195,6 +204,32 @@ public class SettingsHomepageActivityTest { & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS).isEqualTo(0); } + @Test + @Config(shadows = {ShadowPasswordUtils.class}) + public void isCallingAppPermitted_emptyPermission_returnTrue() { + SettingsHomepageActivity homepageActivity = spy(new SettingsHomepageActivity()); + + assertTrue(homepageActivity.isCallingAppPermitted("")); + } + + @Test + @Config(shadows = {ShadowPasswordUtils.class}) + public void isCallingAppPermitted_noGrantedPermission_returnFalse() { + SettingsHomepageActivity homepageActivity = spy(new SettingsHomepageActivity()); + + assertFalse(homepageActivity.isCallingAppPermitted("android.permission.TEST")); + } + + @Test + @Config(shadows = {ShadowPasswordUtils.class}) + public void isCallingAppPermitted_grantedPermission_returnTrue() { + SettingsHomepageActivity homepageActivity = spy(new SettingsHomepageActivity()); + String permission = "android.permission.TEST"; + ShadowPasswordUtils.addGrantedPermission(permission); + + assertTrue(homepageActivity.isCallingAppPermitted(permission)); + } + @Implements(SuggestionFeatureProviderImpl.class) public static class ShadowSuggestionFeatureProviderImpl { -- GitLab From b7f20c0ada8c09a71bdbacc50b65f9da833fbe21 Mon Sep 17 00:00:00 2001 From: Arc Wang Date: Mon, 12 Dec 2022 14:44:16 +0800 Subject: [PATCH 19/43] Allow 2-pane deep link to access unexported Activity If an Activity is not exported, the Activity still can be launched by components of the same application, applications with the same user ID, or privileged system components. Bug: 261678674 Bug: 250589026 Test: manual visual Launcher -> context menu -> Wallpaper & style Change-Id: I662df6cb287361b135e2c596abe946ddeb03bda4 Merged-In: I662df6cb287361b135e2c596abe946ddeb03bda4 (cherry picked from commit 960c96474dcf17ee7f306e1496f5fab223b2500e) Merged-In: I662df6cb287361b135e2c596abe946ddeb03bda4 --- .../homepage/SettingsHomepageActivity.java | 66 +++++++++++++++---- 1 file changed, 54 insertions(+), 12 deletions(-) diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index 4726059455e..073ce6ae3a0 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -29,6 +29,9 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.os.Bundle; +import android.os.Process; +import android.os.RemoteException; +import android.os.UserHandle; import android.text.TextUtils; import android.util.ArraySet; import android.util.FeatureFlagUtils; @@ -356,20 +359,19 @@ public class SettingsHomepageActivity extends FragmentActivity implements return; } - if (!TextUtils.equals(PasswordUtils.getCallingAppPackageName(getActivityToken()), - getPackageName())) { - ActivityInfo targetActivityInfo = null; - try { - targetActivityInfo = getPackageManager().getActivityInfo(targetComponentName, - /* flags= */ 0); - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "Failed to get target ActivityInfo: " + e); - finish(); - return; - } + ActivityInfo targetActivityInfo = null; + try { + targetActivityInfo = getPackageManager().getActivityInfo(targetComponentName, + /* flags= */ 0); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Failed to get target ActivityInfo: " + e); + finish(); + return; + } + if (!hasPrivilegedAccess(targetActivityInfo)) { if (!targetActivityInfo.exported) { - Log.e(TAG, "Must not launch an unexported Actvity for deep link"); + Log.e(TAG, "Target Activity is not exported"); finish(); return; } @@ -416,6 +418,46 @@ public class SettingsHomepageActivity extends FragmentActivity implements startActivity(targetIntent); } + // Check if calling app has privileged access to launch Activity of activityInfo. + private boolean hasPrivilegedAccess(ActivityInfo activityInfo) { + if (TextUtils.equals(PasswordUtils.getCallingAppPackageName(getActivityToken()), + getPackageName())) { + return true; + } + + int callingUid = -1; + try { + callingUid = ActivityManager.getService().getLaunchedFromUid(getActivityToken()); + } catch (RemoteException re) { + Log.e(TAG, "Not able to get callingUid: " + re); + return false; + } + + int targetUid = -1; + try { + targetUid = getPackageManager().getApplicationInfo(activityInfo.packageName, + /* flags= */ 0).uid; + } catch (PackageManager.NameNotFoundException nnfe) { + Log.e(TAG, "Not able to get targetUid: " + nnfe); + return false; + } + + // When activityInfo.exported is false, Activity still can be launched if applications have + // the same user ID. + if (UserHandle.isSameApp(callingUid, targetUid)) { + return true; + } + + // When activityInfo.exported is false, Activity still can be launched if calling app has + // root or system privilege. + int callingAppId = UserHandle.getAppId(callingUid); + if (callingAppId == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID) { + return true; + } + + return false; + } + @VisibleForTesting boolean isCallingAppPermitted(String permission) { return TextUtils.isEmpty(permission) || PasswordUtils.isCallingAppPermitted( -- GitLab From c1cbfa27a55da2316209713044078b2512f6ae79 Mon Sep 17 00:00:00 2001 From: Arc Wang Date: Wed, 14 Dec 2022 11:08:53 +0800 Subject: [PATCH 20/43] Check Uri permission for FLAG_GRANT_READ/WRITE_URI_PERMISSION To improve security, calling app must be granted Uri permission if it sets FLAG_GRANT_READ/WRITE_URI_PERMISSION in the Intent of ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY. Bug: 250589026 Test: manual Change-Id: I48f88c662b843212b1066369badff84cf98935a8 Merged-In: I48f88c662b843212b1066369badff84cf98935a8 (cherry picked from commit 17cd85b946a71a3afaa569da326ac029f28c7794) Merged-In: I48f88c662b843212b1066369badff84cf98935a8 --- .../homepage/SettingsHomepageActivity.java | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index 073ce6ae3a0..0311ea3fbe9 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -369,7 +369,16 @@ public class SettingsHomepageActivity extends FragmentActivity implements return; } - if (!hasPrivilegedAccess(targetActivityInfo)) { + int callingUid = -1; + try { + callingUid = ActivityManager.getService().getLaunchedFromUid(getActivityToken()); + } catch (RemoteException re) { + Log.e(TAG, "Not able to get callingUid: " + re); + finish(); + return; + } + + if (!hasPrivilegedAccess(callingUid, targetActivityInfo)) { if (!targetActivityInfo.exported) { Log.e(TAG, "Target Activity is not exported"); finish(); @@ -400,6 +409,19 @@ public class SettingsHomepageActivity extends FragmentActivity implements targetIntent.setData(intent.getParcelableExtra( SettingsHomepageActivity.EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_DATA)); + // Only allow FLAG_GRANT_READ/WRITE_URI_PERMISSION if calling app has the permission to + // access specified Uri. + int uriPermissionFlags = targetIntent.getFlags() + & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + if (targetIntent.getData() != null + && uriPermissionFlags != 0 + && checkUriPermission(targetIntent.getData(), /* pid= */ -1, callingUid, + uriPermissionFlags) == PackageManager.PERMISSION_DENIED) { + Log.e(TAG, "Calling app must have the permission to access Uri and grant permission"); + finish(); + return; + } + // Set 2-pane pair rule for the deep link page. ActivityEmbeddingRulesController.registerTwoPanePairRule(this, new ComponentName(getApplicationContext(), getClass()), @@ -419,20 +441,12 @@ public class SettingsHomepageActivity extends FragmentActivity implements } // Check if calling app has privileged access to launch Activity of activityInfo. - private boolean hasPrivilegedAccess(ActivityInfo activityInfo) { + private boolean hasPrivilegedAccess(int callingUid, ActivityInfo activityInfo) { if (TextUtils.equals(PasswordUtils.getCallingAppPackageName(getActivityToken()), getPackageName())) { return true; } - int callingUid = -1; - try { - callingUid = ActivityManager.getService().getLaunchedFromUid(getActivityToken()); - } catch (RemoteException re) { - Log.e(TAG, "Not able to get callingUid: " + re); - return false; - } - int targetUid = -1; try { targetUid = getPackageManager().getApplicationInfo(activityInfo.packageName, -- GitLab From 3ba0bed404cceaf6a5c937c9c31272c8ee0c4d53 Mon Sep 17 00:00:00 2001 From: Tsung-Mao Fang Date: Mon, 3 Jan 2022 18:25:04 +0800 Subject: [PATCH 21/43] FRP bypass defense in the settings app Over the last few years, there have been a number of Factory Reset Protection bypass bugs in the SUW flow. It's unlikely to defense all points from individual apps. Therefore, we decide to block some critical pages when user doesn't complete the SUW flow. Test: Can't open the certain pages in the suw flow. Bug: 258422561 Fix: 200746457 Bug: 202975040 Fix: 213091525 Fix: 213090835 Fix: 201561699 Fix: 213090827 Fix: 213090875 Change-Id: Ia18f367109df5af7da0a5acad7702898a459d32e Merged-In: Ia18f367109df5af7da0a5acad7702898a459d32e (cherry picked from commit 1cf31d17aae6798e6174f6b4eaf60603352aa7f7) Merged-In: Ia18f367109df5af7da0a5acad7702898a459d32e --- .../settings/SettingsPreferenceFragment.java | 22 +++++- .../accounts/AccountDashboardFragment.java | 5 ++ .../appinfo/AppInfoDashboardFragment.java | 5 ++ .../DevelopmentSettingsDashboardFragment.java | 5 ++ .../system/ResetDashboardFragment.java | 5 ++ .../SettingsPreferenceFragmentTest.java | 74 +++++++++++++++++++ .../AccountDashboardFragmentTest.java | 5 ++ .../appinfo/AppInfoDashboardFragmentTest.java | 5 ++ ...elopmentSettingsDashboardFragmentTest.java | 5 ++ .../system/ResetDashboardFragmentTest.java | 40 ++++++++++ 10 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 tests/robotests/src/com/android/settings/system/ResetDashboardFragmentTest.java diff --git a/src/com/android/settings/SettingsPreferenceFragment.java b/src/com/android/settings/SettingsPreferenceFragment.java index 1d6a48d0465..d3e1c49c06f 100644 --- a/src/com/android/settings/SettingsPreferenceFragment.java +++ b/src/com/android/settings/SettingsPreferenceFragment.java @@ -54,6 +54,7 @@ import com.android.settingslib.search.Indexable; import com.android.settingslib.widget.LayoutPreference; import com.google.android.material.appbar.AppBarLayout; +import com.google.android.setupcompat.util.WizardManagerHelper; import java.util.UUID; @@ -63,7 +64,7 @@ import java.util.UUID; public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceFragment implements DialogCreatable, HelpResourceProvider, Indexable { - private static final String TAG = "SettingsPreference"; + private static final String TAG = "SettingsPreferenceFragment"; private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted"; @@ -121,6 +122,15 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF public HighlightablePreferenceGroupAdapter mAdapter; private boolean mPreferenceHighlighted = false; + @Override + public void onAttach(Context context) { + if (shouldSkipForInitialSUW() && !WizardManagerHelper.isDeviceProvisioned(getContext())) { + Log.w(TAG, "Skip " + getClass().getSimpleName() + " before SUW completed."); + finish(); + } + super.onAttach(context); + } + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -267,6 +277,16 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF || (mAdapter.getPreferenceAdapterPosition(preference) != RecyclerView.NO_POSITION)); } + /** + * Whether UI should be skipped in the initial SUW flow. + * + * @return {@code true} when UI should be skipped in the initial SUW flow. + * {@code false} when UI should not be skipped in the initial SUW flow. + */ + protected boolean shouldSkipForInitialSUW() { + return false; + } + protected void onDataSetChanged() { highlightPreferenceIfNeeded(); updateEmptyView(); diff --git a/src/com/android/settings/accounts/AccountDashboardFragment.java b/src/com/android/settings/accounts/AccountDashboardFragment.java index f57b124919d..a2b6182a715 100644 --- a/src/com/android/settings/accounts/AccountDashboardFragment.java +++ b/src/com/android/settings/accounts/AccountDashboardFragment.java @@ -84,6 +84,11 @@ public class AccountDashboardFragment extends DashboardFragment { return controllers; } + @Override + protected boolean shouldSkipForInitialSUW() { + return true; + } + static void buildAutofillPreferenceControllers( Context context, List controllers) { controllers.add(new DefaultAutofillPreferenceController(context)); diff --git a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java index e1ea8e47afa..dbe03d9b2f8 100755 --- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java +++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java @@ -503,6 +503,11 @@ public class AppInfoDashboardFragment extends DashboardFragment return true; } + @Override + protected boolean shouldSkipForInitialSUW() { + return true; + } + private void uninstallPkg(String packageName, boolean allUsers, boolean andDisable) { stopListeningToPackageRemove(); // Create new intent to launch Uninstaller activity diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index fbab1fd1240..74013b34ff2 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -215,6 +215,11 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra } } + @Override + protected boolean shouldSkipForInitialSUW() { + return true; + } + /** * Long-pressing a developer options quick settings tile will by default (see * QS_TILE_PREFERENCES in the manifest) take you to the developer options page. diff --git a/src/com/android/settings/system/ResetDashboardFragment.java b/src/com/android/settings/system/ResetDashboardFragment.java index e5fc8f14fd6..c352b9292b1 100644 --- a/src/com/android/settings/system/ResetDashboardFragment.java +++ b/src/com/android/settings/system/ResetDashboardFragment.java @@ -64,6 +64,11 @@ public class ResetDashboardFragment extends DashboardFragment { use(EraseEuiccDataController.class).setFragment(this); } + @Override + protected boolean shouldSkipForInitialSUW() { + return true; + } + private static List buildPreferenceControllers(Context context, Lifecycle lifecycle) { final List controllers = new ArrayList<>(); diff --git a/tests/robotests/src/com/android/settings/SettingsPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/SettingsPreferenceFragmentTest.java index cb53f69751c..648931134c5 100644 --- a/tests/robotests/src/com/android/settings/SettingsPreferenceFragmentTest.java +++ b/tests/robotests/src/com/android/settings/SettingsPreferenceFragmentTest.java @@ -23,11 +23,13 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; import android.os.Bundle; +import android.provider.Settings; import android.view.View; import android.widget.FrameLayout; @@ -41,6 +43,7 @@ import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.shadow.ShadowFragment; import com.android.settings.widget.WorkOnlyCategory; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -64,7 +67,9 @@ public class SettingsPreferenceFragmentTest { private PreferenceScreen mPreferenceScreen; private Context mContext; private TestFragment mFragment; + private TestFragment2 mFragment2; private View mEmptyView; + private int mInitDeviceProvisionedValue; @Before public void setUp() { @@ -72,13 +77,24 @@ public class SettingsPreferenceFragmentTest { FakeFeatureFactory.setupForTest(); mContext = RuntimeEnvironment.application; mFragment = spy(new TestFragment()); + mFragment2 = spy(new TestFragment2()); doReturn(mActivity).when(mFragment).getActivity(); when(mFragment.getContext()).thenReturn(mContext); + when(mFragment2.getContext()).thenReturn(mContext); mEmptyView = new View(mContext); ReflectionHelpers.setField(mFragment, "mEmptyView", mEmptyView); doReturn(ITEM_COUNT).when(mPreferenceScreen).getPreferenceCount(); + + mInitDeviceProvisionedValue = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 0); + } + + @After + public void tearDown() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, mInitDeviceProvisionedValue); } @Test @@ -210,8 +226,66 @@ public class SettingsPreferenceFragmentTest { assertThat(mFragment.mPinnedHeaderFrameLayout.getVisibility()).isEqualTo(View.INVISIBLE); } + @Test + public void onAttach_shouldNotSkipForSUWAndDeviceIsProvisioned_notCallFinish() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 1); + + mFragment.onAttach(mContext); + + verify(mFragment, never()).finish(); + } + + @Test + public void onAttach_shouldNotSkipForSUWAndDeviceIsNotProvisioned_notCallFinish() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 0); + + mFragment.onAttach(mContext); + + verify(mFragment, never()).finish(); + } + + @Test + public void onAttach_shouldSkipForSUWAndDeviceIsDeviceProvisioned_notCallFinish() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 1); + + mFragment2.onAttach(mContext); + + verify(mFragment2, never()).finish(); + } + + @Test + public void onAttach_shouldSkipForSUWAndDeviceProvisioned_notCallFinish() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 0); + + mFragment2.onAttach(mContext); + + verify(mFragment2, times(1)).finish(); + } + public static class TestFragment extends SettingsPreferenceFragment { + @Override + protected boolean shouldSkipForInitialSUW() { + return false; + } + + @Override + public int getMetricsCategory() { + return 0; + } + } + + public static class TestFragment2 extends SettingsPreferenceFragment { + + @Override + protected boolean shouldSkipForInitialSUW() { + return true; + } + @Override public int getMetricsCategory() { return 0; diff --git a/tests/robotests/src/com/android/settings/accounts/AccountDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/accounts/AccountDashboardFragmentTest.java index fe57090827d..921587e837f 100644 --- a/tests/robotests/src/com/android/settings/accounts/AccountDashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/accounts/AccountDashboardFragmentTest.java @@ -114,4 +114,9 @@ public class AccountDashboardFragmentTest { assertThat(indexRaws).isNotEmpty(); } + + @Test + public void shouldSkipForInitialSUW_returnTrue() { + assertThat(mFragment.shouldSkipForInitialSUW()).isTrue(); + } } diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppInfoDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppInfoDashboardFragmentTest.java index 95d765935a9..2cec3d113c6 100644 --- a/tests/robotests/src/com/android/settings/applications/appinfo/AppInfoDashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppInfoDashboardFragmentTest.java @@ -384,6 +384,11 @@ public final class AppInfoDashboardFragmentTest { .isTrue(); } + @Test + public void shouldSkipForInitialSUW_returnTrue() { + assertThat(mFragment.shouldSkipForInitialSUW()).isTrue(); + } + @Implements(AppUtils.class) public static class ShadowAppUtils { diff --git a/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java index 2d4082b9c75..bd4ee56a9c6 100644 --- a/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java @@ -278,6 +278,11 @@ public class DevelopmentSettingsDashboardFragmentTest { verify(controller).onDisableLogPersistDialogRejected(); } + @Test + public void shouldSkipForInitialSUW_returnTrue() { + assertThat(mDashboard.shouldSkipForInitialSUW()).isTrue(); + } + @Implements(EnableDevelopmentSettingWarningDialog.class) public static class ShadowEnableDevelopmentSettingWarningDialog { diff --git a/tests/robotests/src/com/android/settings/system/ResetDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/system/ResetDashboardFragmentTest.java new file mode 100644 index 00000000000..c1d47887a70 --- /dev/null +++ b/tests/robotests/src/com/android/settings/system/ResetDashboardFragmentTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 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.system; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class ResetDashboardFragmentTest { + + private ResetDashboardFragment mFragment; + + @Before + public void setup() { + mFragment = new ResetDashboardFragment(); + } + + @Test + public void shouldSkipForInitialSUW_returnTrue() { + assertThat(mFragment.shouldSkipForInitialSUW()).isTrue(); + } +} -- GitLab From d83753d64e9c6e90cc1a9cc25a80f8bdc722b797 Mon Sep 17 00:00:00 2001 From: Yanting Yang Date: Wed, 4 Jan 2023 09:40:38 +0000 Subject: [PATCH 22/43] Add DISALLOW_APPS_CONTROL check into uninstall app for all users Settings App info page supports a "Uninstall for all users" function when multiple users are enabled. It bypasses the restriction of DISALLOW_APPS_CONTROL which breaks the user isolation guideline. To fix this vulnerability, we should check the DISALLOW_APPS_CONTROL restriction to provide the "Uninstall for all users" function. Bug: 258653813 Test: manual & robotests Change-Id: I5d3bbcbaac439c4f7a1e6a9ade7775ff4f2f2ec6 Merged-In: I5d3bbcbaac439c4f7a1e6a9ade7775ff4f2f2ec6 (cherry picked from commit 9191ec13e25e28fa9d6afbbb0573557c7b891520) Merged-In: I5d3bbcbaac439c4f7a1e6a9ade7775ff4f2f2ec6 --- .../applications/appinfo/AppInfoDashboardFragment.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) mode change 100755 => 100644 src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java diff --git a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java old mode 100755 new mode 100644 index dbe03d9b2f8..2a705e220c0 --- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java +++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java @@ -385,7 +385,13 @@ public class AppInfoDashboardFragment extends DashboardFragment return; } super.onPrepareOptionsMenu(menu); - menu.findItem(UNINSTALL_ALL_USERS_MENU).setVisible(shouldShowUninstallForAll(mAppEntry)); + final MenuItem uninstallAllUsersItem = menu.findItem(UNINSTALL_ALL_USERS_MENU); + uninstallAllUsersItem.setVisible( + shouldShowUninstallForAll(mAppEntry) && !mAppsControlDisallowedBySystem); + if (uninstallAllUsersItem.isVisible()) { + RestrictedLockUtilsInternal.setMenuItemAsDisabledByAdmin(getActivity(), + uninstallAllUsersItem, mAppsControlDisallowedAdmin); + } mUpdatedSysApp = (mAppEntry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; final MenuItem uninstallUpdatesItem = menu.findItem(UNINSTALL_UPDATES); final boolean uninstallUpdateDisabled = getContext().getResources().getBoolean( -- GitLab From bf50f86066573c26f139d77be9cf6cfa6454179a Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Thu, 28 Jul 2022 19:42:27 +0800 Subject: [PATCH 23/43] Only primary user is allowed to control secure nfc Bug: 238298970 Test: manual Merged-In: I945490ef1e62af479a732c9a260ed94bdd8bc313 Change-Id: I945490ef1e62af479a732c9a260ed94bdd8bc313 (cherry picked from commit 0e57ff90cdae3575c243d21d490e2b6384d33397) Merged-In: I945490ef1e62af479a732c9a260ed94bdd8bc313 --- src/com/android/settings/nfc/SecureNfcEnabler.java | 2 +- src/com/android/settings/nfc/SecureNfcPreferenceController.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/nfc/SecureNfcEnabler.java b/src/com/android/settings/nfc/SecureNfcEnabler.java index f31a382a571..ad5c4ab7e83 100644 --- a/src/com/android/settings/nfc/SecureNfcEnabler.java +++ b/src/com/android/settings/nfc/SecureNfcEnabler.java @@ -61,7 +61,7 @@ public class SecureNfcEnabler extends BaseNfcEnabler { } private boolean isToggleable() { - if (mUserManager.isGuestUser()) { + if (!mUserManager.isPrimaryUser()) { return false; } return true; diff --git a/src/com/android/settings/nfc/SecureNfcPreferenceController.java b/src/com/android/settings/nfc/SecureNfcPreferenceController.java index cf43ec30ded..460eca3c142 100644 --- a/src/com/android/settings/nfc/SecureNfcPreferenceController.java +++ b/src/com/android/settings/nfc/SecureNfcPreferenceController.java @@ -109,7 +109,7 @@ public class SecureNfcPreferenceController extends TogglePreferenceController } private boolean isToggleable() { - if (mUserManager.isGuestUser()) { + if (!mUserManager.isPrimaryUser()) { return false; } return true; -- GitLab From 6c81f3bc6c4f13bb0060a89e6452722feb391c19 Mon Sep 17 00:00:00 2001 From: Valentin Iftime Date: Mon, 6 Feb 2023 15:11:28 +0100 Subject: [PATCH 24/43] [DO NOT MERGE] Enforce INTERACT_ACROSS_USERS_FULL permission for NotificationAccessDetails When using EXTRA_USER_HANDLE, check for INTERACT_ACROSS_USERS_FULL permission on calling package. Bug: 259385017 Test: 1. Build a test app that creates and starts an intent to NOTIFICATION_LISTENER_DETAIL_SETTINGS while setting the intent extra android.intent.extra.user_handle to UserHandle(secondaryUserId). 2. Create and switch to a secondary user Settings > System > Multiple users > Allow multiple users > Add user > Switch to New user 3. Open Settings > Notifications > Device & app notifications and choose an app from the list (uses android.permission.BIND_NOTIFICATION_LISTENER_SERVICE). Enable Device & app notifications for selected app and disable all attributed permissions. 4. Switch back to the Owner user. 5. Get the userId of the secondary user: adb shell pm list users. 6. Open the test app and enter the userId for the secondary user and the component name that uses android.permission.BIND_NOTIFICATION_LISTENER_SERVICE. 8. In the settings window that open, enable all 4 sub-options. 9. Switch to the secondary user and note that the all sub-options for the app are disabled. Change-Id: I875b9f2fc32c252acdcf8374a14067836e0f1ac6 (cherry picked from commit on googleplex-android-review.googlesource.com host: d374ca1324396068477b682c6a5a3eaf6d6da208) Merged-In: I875b9f2fc32c252acdcf8374a14067836e0f1ac6 --- .../NotificationAccessDetails.java | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java index da25f17c138..6532413b610 100644 --- a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java +++ b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java @@ -16,8 +16,11 @@ package com.android.settings.applications.specialaccess.notificationaccess; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + import static com.android.settings.applications.AppInfoBase.ARG_PACKAGE_NAME; +import android.Manifest; import android.app.Activity; import android.app.NotificationManager; import android.app.settings.SettingsEnums; @@ -39,6 +42,7 @@ import android.os.UserManager; import android.provider.Settings; import android.service.notification.NotificationListenerFilter; import android.service.notification.NotificationListenerService; +import android.text.TextUtils; import android.util.Log; import android.util.Slog; @@ -53,6 +57,7 @@ import com.android.settings.bluetooth.Utils; import com.android.settings.core.SubSettingLauncher; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.notification.NotificationBackend; +import com.android.settings.password.PasswordUtils; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtilsInternal; @@ -208,8 +213,12 @@ public class NotificationAccessDetails extends DashboardFragment { } } if (intent != null && intent.hasExtra(Intent.EXTRA_USER_HANDLE)) { - mUserId = ((UserHandle) intent.getParcelableExtra( - Intent.EXTRA_USER_HANDLE)).getIdentifier(); + if (hasInteractAcrossUsersPermission()) { + mUserId = ((UserHandle) intent.getParcelableExtra( + Intent.EXTRA_USER_HANDLE)).getIdentifier(); + } else { + finish(); + } } else { mUserId = UserHandle.myUserId(); } @@ -224,6 +233,26 @@ public class NotificationAccessDetails extends DashboardFragment { } } + private boolean hasInteractAcrossUsersPermission() { + final String callingPackageName = PasswordUtils.getCallingAppPackageName( + getActivity().getActivityToken()); + + if (TextUtils.isEmpty(callingPackageName)) { + Log.w(TAG, "Not able to get calling package name for permission check"); + return false; + } + + if (getContext().getPackageManager().checkPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingPackageName) + != PERMISSION_GRANTED) { + Log.w(TAG, "Package " + callingPackageName + " does not have required permission " + + Manifest.permission.INTERACT_ACROSS_USERS_FULL); + return false; + } + + return true; + } + // Dialogs only have access to the parent fragment, not the controller, so pass the information // along to keep business logic out of this file public void disable(final ComponentName cn) { -- GitLab From cb6e1d8e4afb01f39feba88e8145b9cf90d3fde2 Mon Sep 17 00:00:00 2001 From: Dmitry Dementyev Date: Tue, 7 Mar 2023 10:36:41 -0800 Subject: [PATCH 25/43] Convert argument to intent in AddAccountSettings. Bug: 265798353 Test: manual (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:c7e8052b527434ed8660e3babdab718f7f3cd7da) Merged-In: I0051e5d5fc9fd3691504cb5fbb959f701e0bce6a Change-Id: I0051e5d5fc9fd3691504cb5fbb959f701e0bce6a --- src/com/android/settings/accounts/AddAccountSettings.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/accounts/AddAccountSettings.java b/src/com/android/settings/accounts/AddAccountSettings.java index 81db4df3290..85e942b1990 100644 --- a/src/com/android/settings/accounts/AddAccountSettings.java +++ b/src/com/android/settings/accounts/AddAccountSettings.java @@ -103,7 +103,8 @@ public class AddAccountSettings extends Activity { intent.putExtras(addAccountOptions) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); - startActivityForResultAsUser(intent, ADD_ACCOUNT_REQUEST, mUserHandle); + startActivityForResultAsUser( + new Intent(intent), ADD_ACCOUNT_REQUEST, mUserHandle); } else { setResult(RESULT_OK); if (mPendingIntent != null) { -- GitLab From 6376a4f1275f26d5b9f02de1ef23deb1af0be1ed Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Tue, 7 Mar 2023 15:44:29 -0500 Subject: [PATCH 26/43] Don't show NLSes with excessively long component names Test: install test app with long CN Test: ServiceListingTest Bug: 260570119 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:90ced5d2ecdb13d7d8f4e4052b4bd30cc2a37979) Merged-In: I3ffd02f6cf6bf282e7fc264fd070ed3add4d8571 Change-Id: I3ffd02f6cf6bf282e7fc264fd070ed3add4d8571 --- .../settings/notification/NotificationAccessSettings.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/com/android/settings/notification/NotificationAccessSettings.java b/src/com/android/settings/notification/NotificationAccessSettings.java index 10954a185e1..d94498e68f7 100644 --- a/src/com/android/settings/notification/NotificationAccessSettings.java +++ b/src/com/android/settings/notification/NotificationAccessSettings.java @@ -62,6 +62,7 @@ public class NotificationAccessSettings extends EmptyTextSettings { private static final String TAG = "NotifAccessSettings"; private static final String ALLOWED_KEY = "allowed"; private static final String NOT_ALLOWED_KEY = "not_allowed"; + private static final int MAX_CN_LENGTH = 500; private static final ManagedServiceSettings.Config CONFIG = new ManagedServiceSettings.Config.Builder() @@ -98,6 +99,12 @@ public class NotificationAccessSettings extends EmptyTextSettings { .setNoun(CONFIG.noun) .setSetting(CONFIG.setting) .setTag(CONFIG.tag) + .setValidator(info -> { + if (info.getComponentName().flattenToString().length() > MAX_CN_LENGTH) { + return false; + } + return true; + }) .build(); mServiceListing.addCallback(this::updateList); -- GitLab From afc0ffc654b5ea5fd67822317e7cee7382e5f1b0 Mon Sep 17 00:00:00 2001 From: Lin Yuan Date: Tue, 25 Apr 2023 12:30:03 -0400 Subject: [PATCH 27/43] Fix: Bluetooth and Wifi scanning location MainSwitch page policy transparency. When DISALLOW_CONFIG_LOCATION is set, make location service's MainSwitchPreference pages for wifi scanning and bluetooth scanning unavailable too, so that intent direct access is disabled. screenshot: http://shortn/_kkK3BMTSh1 Bug: 277333746 Bug: 277333781 Test: atest SettingsRoboTests, on device (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:7591fff234886e79c5d0210a2cf3282a69de9be9) Merged-In: I52f9a11b1dd78a5e5dbb1bbde3cda7381c87ae39 Change-Id: I52f9a11b1dd78a5e5dbb1bbde3cda7381c87ae39 --- .../BluetoothScanningMainSwitchPreferenceController.java | 7 ++++++- .../WifiScanningMainSwitchPreferenceController.java | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/location/BluetoothScanningMainSwitchPreferenceController.java b/src/com/android/settings/location/BluetoothScanningMainSwitchPreferenceController.java index b491ec953a2..78e31848acb 100644 --- a/src/com/android/settings/location/BluetoothScanningMainSwitchPreferenceController.java +++ b/src/com/android/settings/location/BluetoothScanningMainSwitchPreferenceController.java @@ -18,6 +18,7 @@ package com.android.settings.location; import android.content.Context; import android.provider.Settings; import android.widget.Switch; +import android.os.UserManager; import androidx.preference.PreferenceScreen; @@ -33,9 +34,11 @@ public class BluetoothScanningMainSwitchPreferenceController extends TogglePrefe implements OnMainSwitchChangeListener { private static final String KEY_BLUETOOTH_SCANNING_SWITCH = "bluetooth_always_scanning_switch"; + private final UserManager mUserManager; public BluetoothScanningMainSwitchPreferenceController(Context context) { super(context, KEY_BLUETOOTH_SCANNING_SWITCH); + mUserManager = UserManager.get(context); } @Override @@ -49,7 +52,9 @@ public class BluetoothScanningMainSwitchPreferenceController extends TogglePrefe @Override public int getAvailabilityStatus() { return mContext.getResources().getBoolean(R.bool.config_show_location_scanning) - ? AVAILABLE + ? (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_LOCATION) + ? DISABLED_DEPENDENT_SETTING + : AVAILABLE) : UNSUPPORTED_ON_DEVICE; } diff --git a/src/com/android/settings/location/WifiScanningMainSwitchPreferenceController.java b/src/com/android/settings/location/WifiScanningMainSwitchPreferenceController.java index 546f1e1e399..e22b0a08026 100644 --- a/src/com/android/settings/location/WifiScanningMainSwitchPreferenceController.java +++ b/src/com/android/settings/location/WifiScanningMainSwitchPreferenceController.java @@ -18,6 +18,7 @@ package com.android.settings.location; import android.content.Context; import android.net.wifi.WifiManager; import android.widget.Switch; +import android.os.UserManager; import androidx.preference.PreferenceScreen; @@ -34,10 +35,12 @@ public class WifiScanningMainSwitchPreferenceController extends TogglePreference private static final String KEY_WIFI_SCANNING_SWITCH = "wifi_always_scanning_switch"; private final WifiManager mWifiManager; + private final UserManager mUserManager; public WifiScanningMainSwitchPreferenceController(Context context) { super(context, KEY_WIFI_SCANNING_SWITCH); mWifiManager = context.getSystemService(WifiManager.class); + mUserManager = UserManager.get(context); } @Override @@ -52,7 +55,9 @@ public class WifiScanningMainSwitchPreferenceController extends TogglePreference @Override public int getAvailabilityStatus() { return mContext.getResources().getBoolean(R.bool.config_show_location_scanning) - ? AVAILABLE + ? (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_LOCATION) + ? DISABLED_DEPENDENT_SETTING + : AVAILABLE) : UNSUPPORTED_ON_DEVICE; } -- GitLab From cb449a2de298943de8cc8fb784497d9fe0e7c9df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Mon, 5 Jun 2023 18:24:04 +0200 Subject: [PATCH 28/43] Don't hide approved NLSes in Settings Note that an NLS that shouldn't be approvable (because its name is too long) but was already approved (either before the max length check was introduced, or through other means) will disappear from the list if the user revokes its access. This might be somewhat confusing, but since this is a very-edge case already it's fine. Bug: 282932362 Test: manual (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ff255c6eda1528f01a167a9a65b7f8e414d28584) Merged-In: I4c9faea68e6d16b1a4ec7f472b5433cac1704c06 Change-Id: I4c9faea68e6d16b1a4ec7f472b5433cac1704c06 --- .../NotificationAccessSettings.java | 25 +-- .../notification/NotificationBackend.java | 3 + .../NotificationAccessSettingsTest.java | 144 ++++++++++++++++++ 3 files changed, 160 insertions(+), 12 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/notification/NotificationAccessSettingsTest.java diff --git a/src/com/android/settings/notification/NotificationAccessSettings.java b/src/com/android/settings/notification/NotificationAccessSettings.java index d94498e68f7..ab2a177bcf8 100644 --- a/src/com/android/settings/notification/NotificationAccessSettings.java +++ b/src/com/android/settings/notification/NotificationAccessSettings.java @@ -40,6 +40,7 @@ import android.widget.Toast; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; +import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.applications.AppInfoBase; @@ -60,8 +61,8 @@ import java.util.List; @SearchIndexable public class NotificationAccessSettings extends EmptyTextSettings { private static final String TAG = "NotifAccessSettings"; - private static final String ALLOWED_KEY = "allowed"; - private static final String NOT_ALLOWED_KEY = "not_allowed"; + static final String ALLOWED_KEY = "allowed"; + static final String NOT_ALLOWED_KEY = "not_allowed"; private static final int MAX_CN_LENGTH = 500; private static final ManagedServiceSettings.Config CONFIG = @@ -77,9 +78,9 @@ public class NotificationAccessSettings extends EmptyTextSettings { .setEmptyText(R.string.no_notification_listeners) .build(); - private NotificationManager mNm; + @VisibleForTesting NotificationManager mNm; protected Context mContext; - private PackageManager mPm; + @VisibleForTesting PackageManager mPm; private DevicePolicyManager mDpm; private ServiceListing mServiceListing; private IconDrawableFactory mIconDrawableFactory; @@ -99,12 +100,6 @@ public class NotificationAccessSettings extends EmptyTextSettings { .setNoun(CONFIG.noun) .setSetting(CONFIG.setting) .setTag(CONFIG.tag) - .setValidator(info -> { - if (info.getComponentName().flattenToString().length() > MAX_CN_LENGTH) { - return false; - } - return true; - }) .build(); mServiceListing.addCallback(this::updateList); @@ -135,7 +130,8 @@ public class NotificationAccessSettings extends EmptyTextSettings { mServiceListing.setListening(false); } - private void updateList(List services) { + @VisibleForTesting + void updateList(List services) { final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); final int managedProfileId = Utils.getManagedProfileId(um, UserHandle.myUserId()); @@ -148,6 +144,11 @@ public class NotificationAccessSettings extends EmptyTextSettings { services.sort(new PackageItemInfo.DisplayNameComparator(mPm)); for (ServiceInfo service : services) { final ComponentName cn = new ComponentName(service.packageName, service.name); + boolean isAllowed = mNm.isNotificationListenerAccessGranted(cn); + if (!isAllowed && cn.flattenToString().length() > MAX_CN_LENGTH) { + continue; + } + CharSequence title = null; try { title = mPm.getApplicationInfoAsUser( @@ -192,7 +193,7 @@ public class NotificationAccessSettings extends EmptyTextSettings { return true; }); pref.setKey(cn.flattenToString()); - if (mNm.isNotificationListenerAccessGranted(cn)) { + if (isAllowed) { allowedCategory.addPreference(pref); } else { notAllowedCategory.addPreference(pref); diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java index e448dda20aa..d95b48f3932 100644 --- a/src/com/android/settings/notification/NotificationBackend.java +++ b/src/com/android/settings/notification/NotificationBackend.java @@ -143,6 +143,9 @@ public class NotificationBackend { static public CharSequence getDeviceList(ICompanionDeviceManager cdm, LocalBluetoothManager lbm, String pkg, int userId) { + if (cdm == null) { + return ""; + } boolean multiple = false; StringBuilder sb = new StringBuilder(); diff --git a/tests/robotests/src/com/android/settings/notification/NotificationAccessSettingsTest.java b/tests/robotests/src/com/android/settings/notification/NotificationAccessSettingsTest.java new file mode 100644 index 00000000000..e644c2975b7 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/NotificationAccessSettingsTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2023 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.notification; + +import static com.android.settings.notification.NotificationAccessSettings.ALLOWED_KEY; +import static com.android.settings.notification.NotificationAccessSettings.NOT_ALLOWED_KEY; + +import static com.google.common.base.Preconditions.checkNotNull; +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.mock; +import static org.mockito.Mockito.when; + +import android.app.NotificationManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; + +import androidx.fragment.app.FragmentActivity; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceScreen; + +import com.android.settings.testutils.shadow.ShadowBluetoothUtils; +import com.android.settingslib.bluetooth.LocalBluetoothManager; + +import com.google.common.base.Strings; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +import java.util.ArrayList; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowBluetoothUtils.class}) +public class NotificationAccessSettingsTest { + + private Context mContext; + private NotificationAccessSettings mAccessSettings; + @Mock + private NotificationManager mNotificationManager; + @Mock + private PackageManager mPackageManager; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mContext = RuntimeEnvironment.application; + ShadowApplication shadowApp = ShadowApplication.getInstance(); + shadowApp.setSystemService(Context.NOTIFICATION_SERVICE, mNotificationManager); + + mAccessSettings = new NotificationAccessSettings(); + FragmentActivity activity = Robolectric.buildActivity(FragmentActivity.class).setup().get(); + activity.getSupportFragmentManager().beginTransaction().add(mAccessSettings, null).commit(); + + when(mPackageManager.getApplicationInfoAsUser(any(), anyInt(), anyInt())).then( + (Answer) invocation -> { + ApplicationInfo appInfo = mock(ApplicationInfo.class); + when(appInfo.loadLabel(any())).thenReturn(invocation.getArgument(0)); + return appInfo; + }); + + mAccessSettings.mNm = mNotificationManager; + mAccessSettings.mPm = mPackageManager; + ShadowBluetoothUtils.sLocalBluetoothManager = mock(LocalBluetoothManager.class); + } + + @Test + public void updateList_enabledLongName_shown() { + ComponentName longCn = new ComponentName("test.pkg1", + Strings.repeat("Blah", 200) + "Service"); + ComponentName shortCn = new ComponentName("test.pkg2", "ReasonableService"); + ArrayList services = new ArrayList<>(); + services.add(newServiceInfo(longCn.getPackageName(), longCn.getClassName(), 1)); + services.add(newServiceInfo(shortCn.getPackageName(), shortCn.getClassName(), 2)); + when(mNotificationManager.isNotificationListenerAccessGranted(any())).thenReturn(true); + + mAccessSettings.updateList(services); + + PreferenceScreen screen = mAccessSettings.getPreferenceScreen(); + PreferenceCategory allowed = checkNotNull(screen.findPreference(ALLOWED_KEY)); + PreferenceCategory notAllowed = checkNotNull(screen.findPreference(NOT_ALLOWED_KEY)); + assertThat(allowed.getPreferenceCount()).isEqualTo(2); + assertThat(allowed.getPreference(0).getKey()).isEqualTo(longCn.flattenToString()); + assertThat(allowed.getPreference(1).getKey()).isEqualTo(shortCn.flattenToString()); + assertThat(notAllowed.getPreferenceCount()).isEqualTo(0); + } + + @Test + public void updateList_disabledLongName_notShown() { + ComponentName longCn = new ComponentName("test.pkg1", + Strings.repeat("Blah", 200) + "Service"); + ComponentName shortCn = new ComponentName("test.pkg2", "ReasonableService"); + ArrayList services = new ArrayList<>(); + services.add(newServiceInfo(longCn.getPackageName(), longCn.getClassName(), 1)); + services.add(newServiceInfo(shortCn.getPackageName(), shortCn.getClassName(), 2)); + when(mNotificationManager.isNotificationListenerAccessGranted(any())).thenReturn(false); + + mAccessSettings.updateList(services); + + PreferenceScreen screen = mAccessSettings.getPreferenceScreen(); + PreferenceCategory allowed = checkNotNull(screen.findPreference(ALLOWED_KEY)); + PreferenceCategory notAllowed = checkNotNull(screen.findPreference(NOT_ALLOWED_KEY)); + assertThat(allowed.getPreferenceCount()).isEqualTo(0); + assertThat(notAllowed.getPreferenceCount()).isEqualTo(1); + assertThat(notAllowed.getPreference(0).getKey()).isEqualTo(shortCn.flattenToString()); + } + + private static ServiceInfo newServiceInfo(String packageName, String serviceName, int uid) { + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.packageName = packageName; + serviceInfo.name = serviceName; + serviceInfo.applicationInfo = new ApplicationInfo(); + serviceInfo.applicationInfo.uid = uid; + return serviceInfo; + } +} -- GitLab From cf4420a504727a75454878051731aefe10850cea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Thu, 15 Jun 2023 18:37:52 +0200 Subject: [PATCH 29/43] Settings: don't try to allow NLSes with too-long component names * NotificationAccessConfirmationActivity (triggered through CompanionDeviceManager) -> Don't show the dialog, bail out early similarly to other invalid inputs. * NotificationAccessSettings (from Special App Access) -> No changes, but use the canonical constant now. * ApprovalPreferenceController (used in NotificationAccessDetails) -> Disable the toggle, unless the NLS was previously approved (in which case it can still be removed). Fixes: 260570119 Fixes: 286043036 Test: atest + manually (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:f0367c98d0510f325f8fe76166b188be8820373c) Merged-In: Ifc048311746c027e3683cdcf65f1079d04cf7c56 Change-Id: Ifc048311746c027e3683cdcf65f1079d04cf7c56 --- .../ApprovalPreferenceController.java | 3 +++ ...otificationAccessConfirmationActivity.java | 4 +++- .../NotificationAccessSettings.java | 4 ++-- .../ApprovalPreferenceControllerTest.java | 19 +++++++++++++++++++ 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java b/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java index a43b9fd9145..9235494f359 100644 --- a/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java +++ b/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java @@ -81,6 +81,9 @@ public class ApprovalPreferenceController extends BasePreferenceController { final SwitchPreference preference = (SwitchPreference) pref; final CharSequence label = mPkgInfo.applicationInfo.loadLabel(mPm); preference.setChecked(isServiceEnabled(mCn)); + final boolean isAllowedCn = mCn.flattenToShortString().length() + <= NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH; + preference.setEnabled(preference.isChecked() || isAllowedCn); preference.setOnPreferenceChangeListener((p, newValue) -> { final boolean access = (Boolean) newValue; if (!access) { diff --git a/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java b/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java index dfe6df2a5ca..a6b565ae6ba 100644 --- a/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java +++ b/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java @@ -67,7 +67,9 @@ public class NotificationAccessConfirmationActivity extends Activity mUserId = getIntent().getIntExtra(EXTRA_USER_ID, UserHandle.USER_NULL); CharSequence mAppLabel; - if (mComponentName == null || mComponentName.getPackageName() == null) { + if (mComponentName == null || mComponentName.getPackageName() == null + || mComponentName.flattenToString().length() + > NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) { finish(); return; } diff --git a/src/com/android/settings/notification/NotificationAccessSettings.java b/src/com/android/settings/notification/NotificationAccessSettings.java index ab2a177bcf8..00543a2f3c0 100644 --- a/src/com/android/settings/notification/NotificationAccessSettings.java +++ b/src/com/android/settings/notification/NotificationAccessSettings.java @@ -63,7 +63,6 @@ public class NotificationAccessSettings extends EmptyTextSettings { private static final String TAG = "NotifAccessSettings"; static final String ALLOWED_KEY = "allowed"; static final String NOT_ALLOWED_KEY = "not_allowed"; - private static final int MAX_CN_LENGTH = 500; private static final ManagedServiceSettings.Config CONFIG = new ManagedServiceSettings.Config.Builder() @@ -145,7 +144,8 @@ public class NotificationAccessSettings extends EmptyTextSettings { for (ServiceInfo service : services) { final ComponentName cn = new ComponentName(service.packageName, service.name); boolean isAllowed = mNm.isNotificationListenerAccessGranted(cn); - if (!isAllowed && cn.flattenToString().length() > MAX_CN_LENGTH) { + if (!isAllowed && cn.flattenToString().length() + > NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) { continue; } diff --git a/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java index 064f8134c67..5316adc1c3a 100644 --- a/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java @@ -77,6 +77,25 @@ public class ApprovalPreferenceControllerTest { mController.setPkgInfo(mPkgInfo); } + @Test + public void updateState_enabled() { + SwitchPreference pref = new SwitchPreference(mContext); + mController.updateState(pref); + assertThat(pref.isEnabled()).isTrue(); + } + + @Test + public void updateState_invalidCn_disabled() { + ComponentName longCn = new ComponentName("com.example.package", + com.google.common.base.Strings.repeat("Blah", 150)); + mController.setCn(longCn); + SwitchPreference pref = new SwitchPreference(mContext); + + mController.updateState(pref); + + assertThat(pref.isEnabled()).isFalse(); + } + @Test public void updateState_checked() { when(mNm.isNotificationListenerAccessGranted(mCn)).thenReturn(true); -- GitLab From 931304bc302d640fe68d598e7f5bbcd9f24f9781 Mon Sep 17 00:00:00 2001 From: Taran Singh Date: Fri, 19 May 2023 23:17:47 +0000 Subject: [PATCH 30/43] DO NOT MERGE: Prevent non-system IME from becoming device admin Currently selected IME can inject KeyEvent on DeviceAdminAdd screen to activate itself as device admin and cause various DoS attacks. This CL ensures KeyEvent on "Activate" button can only come from system apps. Bug: 280793427 Test: atest DeviceAdminActivationTest (cherry picked from commit 70a501d02e0a6aefd874767a15378ba998759373) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:76b536abc8ee503846da6b3060f74b69e1a20f15) Merged-In: I6470d1684d707f4b1e86f8b456be0b4e0af5f188 Change-Id: I6470d1684d707f4b1e86f8b456be0b4e0af5f188 --- .../deviceadmin/DeviceAdminAdd.java | 125 +++++++++--------- 1 file changed, 66 insertions(+), 59 deletions(-) diff --git a/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminAdd.java b/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminAdd.java index 7b2f4ba15af..2a4250a4fcb 100644 --- a/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminAdd.java +++ b/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminAdd.java @@ -52,6 +52,7 @@ import android.text.TextUtils.TruncateAt; import android.util.EventLog; import android.util.Log; import android.view.Display; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -142,12 +143,12 @@ public class DeviceAdminAdd extends CollapsingToolbarBaseActivity { mHandler = new Handler(getMainLooper()); - mDPM = (DevicePolicyManager)getSystemService(Context.DEVICE_POLICY_SERVICE); - mAppOps = (AppOpsManager)getSystemService(Context.APP_OPS_SERVICE); - mLayoutInflaternflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mDPM = getSystemService(DevicePolicyManager.class); + mAppOps = getSystemService(AppOpsManager.class); + mLayoutInflaternflater = getSystemService(LayoutInflater.class); PackageManager packageManager = getPackageManager(); - if ((getIntent().getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { + if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { Log.w(TAG, "Cannot start ADD_DEVICE_ADMIN as a new task"); finish(); return; @@ -157,7 +158,7 @@ public class DeviceAdminAdd extends CollapsingToolbarBaseActivity { EXTRA_CALLED_FROM_SUPPORT_DIALOG, false); String action = getIntent().getAction(); - ComponentName who = (ComponentName)getIntent().getParcelableExtra( + ComponentName who = (ComponentName) getIntent().getParcelableExtra( DevicePolicyManager.EXTRA_DEVICE_ADMIN); if (who == null) { String packageName = getIntent().getStringExtra(EXTRA_DEVICE_ADMIN_PACKAGE_NAME); @@ -215,7 +216,7 @@ public class DeviceAdminAdd extends CollapsingToolbarBaseActivity { PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS); int count = avail == null ? 0 : avail.size(); boolean found = false; - for (int i=0; i { + if (!mActionButton.isEnabled()) { + showPolicyTransparencyDialogIfRequired(); + return; + } + if (mAdding) { + addAndFinish(); + } else if (isManagedProfile(mDeviceAdmin) + && mDeviceAdmin.getComponent().equals(mDPM.getProfileOwner())) { + final int userId = UserHandle.myUserId(); + UserDialogs.createRemoveDialog(DeviceAdminAdd.this, userId, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + UserManager um = UserManager.get(DeviceAdminAdd.this); + um.removeUser(userId); + finish(); } - ).show(); - } else if (mUninstalling) { - mDPM.uninstallPackageWithActiveAdmins(mDeviceAdmin.getPackageName()); - finish(); - } else if (!mWaitingForRemoveMsg) { - try { - // Don't allow the admin to put a dialog up in front - // of us while we interact with the user. - ActivityManager.getService().stopAppSwitches(); - } catch (RemoteException e) { - } - mWaitingForRemoveMsg = true; - mDPM.getRemoveWarning(mDeviceAdmin.getComponent(), - new RemoteCallback(new RemoteCallback.OnResultListener() { - @Override - public void onResult(Bundle result) { - CharSequence msg = result != null - ? result.getCharSequence( - DeviceAdminReceiver.EXTRA_DISABLE_WARNING) - : null; - continueRemoveAction(msg); - } - }, mHandler)); - // Don't want to wait too long. - getWindow().getDecorView().getHandler().postDelayed(new Runnable() { - @Override public void run() { - continueRemoveAction(null); } - }, 2*1000); + ).show(); + } else if (mUninstalling) { + mDPM.uninstallPackageWithActiveAdmins(mDeviceAdmin.getPackageName()); + finish(); + } else if (!mWaitingForRemoveMsg) { + try { + // Don't allow the admin to put a dialog up in front + // of us while we interact with the user. + ActivityManager.getService().stopAppSwitches(); + } catch (RemoteException e) { } + mWaitingForRemoveMsg = true; + mDPM.getRemoveWarning(mDeviceAdmin.getComponent(), + new RemoteCallback(new RemoteCallback.OnResultListener() { + @Override + public void onResult(Bundle result) { + CharSequence msg = result != null + ? result.getCharSequence( + DeviceAdminReceiver.EXTRA_DISABLE_WARNING) + : null; + continueRemoveAction(msg); + } + }, mHandler)); + // Don't want to wait too long. + getWindow().getDecorView().getHandler().postDelayed( + () -> continueRemoveAction(null), 2 * 1000); + } + }; + restrictedAction.setOnKeyListener((view, keyCode, keyEvent) -> { + if ((keyEvent.getFlags() & KeyEvent.FLAG_FROM_SYSTEM) == 0) { + Log.e(TAG, "Can not activate device-admin with KeyEvent from non-system app."); + // Consume event to suppress click. + return true; } + // Fallback to view click handler. + return false; }); + restrictedAction.setOnClickListener(restrictedActionClickListener); } /** -- GitLab From 50b0c8d0cb2c7357725b6cc62198b5200d9477ac Mon Sep 17 00:00:00 2001 From: Weng Su Date: Fri, 7 Jul 2023 19:52:04 +0800 Subject: [PATCH 31/43] [RESTRICT AUTOMERGE] Restrict ApnEditor settings - Finish ApnEditor settings if user is not an admin - Finish ApnEditor settings if user has DISALLOW_CONFIG_MOBILE_NETWORKS restriction Bug: 279902472 Test: manual test atest -c ApnEditorTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:6afcad76262ff87e0dd7c6d0394e00fcff0c1c6b) Merged-In: Iecdbbff7e21dfb11e3ba385858747a220cfd3e04 Change-Id: Iecdbbff7e21dfb11e3ba385858747a220cfd3e04 --- .../settings/network/apn/ApnEditor.java | 23 +++++++++++++++ .../settings/network/apn/ApnEditorTest.java | 29 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/com/android/settings/network/apn/ApnEditor.java b/src/com/android/settings/network/apn/ApnEditor.java index 03db1b89be7..21566fb7389 100644 --- a/src/com/android/settings/network/apn/ApnEditor.java +++ b/src/com/android/settings/network/apn/ApnEditor.java @@ -25,6 +25,7 @@ import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.PersistableBundle; +import android.os.UserManager; import android.provider.Telephony; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionInfo; @@ -266,6 +267,11 @@ public class ApnEditor extends SettingsPreferenceFragment @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); + if (isUserRestricted()) { + Log.e(TAG, "This setting isn't available due to user restriction."); + finish(); + return; + } setLifecycleForAllControllers(); @@ -1409,6 +1415,23 @@ public class ApnEditor extends SettingsPreferenceFragment return apnData; } + @VisibleForTesting + boolean isUserRestricted() { + UserManager userManager = getContext().getSystemService(UserManager.class); + if (userManager == null) { + return false; + } + if (!userManager.isAdminUser()) { + Log.e(TAG, "User is not an admin"); + return true; + } + if (userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) { + Log.e(TAG, "User is not allowed to configure mobile network"); + return true; + } + return false; + } + @VisibleForTesting static class ApnData { /** diff --git a/tests/robotests/src/com/android/settings/network/apn/ApnEditorTest.java b/tests/robotests/src/com/android/settings/network/apn/ApnEditorTest.java index 0a430cd09d1..b2a30caf935 100644 --- a/tests/robotests/src/com/android/settings/network/apn/ApnEditorTest.java +++ b/tests/robotests/src/com/android/settings/network/apn/ApnEditorTest.java @@ -34,6 +34,7 @@ import android.content.Intent; import android.content.res.Resources; import android.database.Cursor; import android.net.Uri; +import android.os.UserManager; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; @@ -102,6 +103,8 @@ public class ApnEditorTest { @Mock private FragmentActivity mActivity; @Mock + private UserManager mUserManager; + @Mock private ProxySubscriptionManager mProxySubscriptionMgr; @Captor @@ -127,6 +130,11 @@ public class ApnEditorTest { doReturn(mContext.getTheme()).when(mActivity).getTheme(); doReturn(mContext.getContentResolver()).when(mActivity).getContentResolver(); + doReturn(mUserManager).when(mContext).getSystemService(UserManager.class); + doReturn(true).when(mUserManager).isAdminUser(); + doReturn(false).when(mUserManager) + .hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS); + setMockPreference(mContext); mApnEditorUT.mApnData = new FakeApnData(APN_DATA); mApnEditorUT.sNotSet = "Not Set"; @@ -451,6 +459,27 @@ public class ApnEditorTest { assertThat(ApnEditor.formatInteger("not an int")).isEqualTo("not an int"); } + @Test + @Config(shadows = ShadowFragment.class) + public void onCreate_notAdminUser_shouldFinish() { + doReturn(false).when(mUserManager).isAdminUser(); + + mApnEditorUT.onCreate(null); + + verify(mApnEditorUT).finish(); + } + + @Test + @Config(shadows = ShadowFragment.class) + public void onCreate_hasUserRestriction_shouldFinish() { + doReturn(true).when(mUserManager) + .hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS); + + mApnEditorUT.onCreate(null); + + verify(mApnEditorUT).finish(); + } + @Test @Config(shadows = ShadowFragment.class) public void onCreate_noAction_shouldFinishAndNoCrash() { -- GitLab From 5b8c54c7dc05c59422fa4306e87f75b291bb9aa7 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Thu, 27 Jul 2023 21:45:05 +0000 Subject: [PATCH 32/43] RESTRICT AUTOMERGE: Catch exceptions from setLockCredential() When LockPatternUtils#setLockCredential() fails, it can either return false or throw an exception. Catch the exception and treat it the same way as a false return value, to prevent crashing com.android.settings. Bug: 253043065 Test: Tried setting lockscreen credential while in secure FRP mode using smartlock setup activity launched by intent via adb. Verified that com.android.settings no longer crashes due to the exception from LockPatternUtils#setLockCredential(). (cherry picked from commit 05f1eff1c9c3f82797f1a0f92ff7665b9f463488) (moved change into ChooseLockPassword.java and ChooseLockPattern.java, which are merged into SaveAndFinishWorker.java on udc-qpr-dev and main) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:6e92ca106eec83105943b701bd5653a6d6cc50f4) Merged-In: I48b9119c19fb6378b1f88d36433ee4f4c8501d76 Change-Id: I48b9119c19fb6378b1f88d36433ee4f4c8501d76 --- .../android/settings/password/ChooseLockPassword.java | 9 +++++++-- src/com/android/settings/password/ChooseLockPattern.java | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java index 24bfe46bf02..8577d82b41d 100644 --- a/src/com/android/settings/password/ChooseLockPassword.java +++ b/src/com/android/settings/password/ChooseLockPassword.java @@ -1014,8 +1014,13 @@ public class ChooseLockPassword extends SettingsActivity { @Override protected Pair saveAndVerifyInBackground() { - final boolean success = mUtils.setLockCredential( - mChosenPassword, mCurrentCredential, mUserId); + boolean success; + try { + success = mUtils.setLockCredential(mChosenPassword, mCurrentCredential, mUserId); + } catch (RuntimeException e) { + Log.e(TAG, "Failed to set lockscreen credential", e); + success = false; + } if (success) { unifyProfileCredentialIfRequested(); } diff --git a/src/com/android/settings/password/ChooseLockPattern.java b/src/com/android/settings/password/ChooseLockPattern.java index 64c0d89bd57..3a676d6c481 100644 --- a/src/com/android/settings/password/ChooseLockPattern.java +++ b/src/com/android/settings/password/ChooseLockPattern.java @@ -909,8 +909,13 @@ public class ChooseLockPattern extends SettingsActivity { @Override protected Pair saveAndVerifyInBackground() { final int userId = mUserId; - final boolean success = mUtils.setLockCredential(mChosenPattern, mCurrentCredential, - userId); + boolean success; + try { + success = mUtils.setLockCredential(mChosenPattern, mCurrentCredential, userId); + } catch (RuntimeException e) { + Log.e(TAG, "Failed to set lockscreen credential", e); + success = false; + } if (success) { unifyProfileCredentialIfRequested(); } -- GitLab From 9d707ec6d10448a5faed135a8967242809b99411 Mon Sep 17 00:00:00 2001 From: Valentin Iftime Date: Tue, 3 Oct 2023 17:28:34 +0200 Subject: [PATCH 33/43] Validate ringtone URIs before setting Add checks URIs for content from other users. Fail for users that are not profiles of the current user. Test: atest DefaultRingtonePreferenceTest Bug: 299614635 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:7ba175eaeb6e8f1ea54e2ec13685d1cf1e9aad1c) Merged-In: Ib266b285a3a1c6c5265ae2321159e61e08e349f6 Change-Id: Ib266b285a3a1c6c5265ae2321159e61e08e349f6 --- .../settings/DefaultRingtonePreference.java | 11 +-- .../android/settings/RingtonePreference.java | 82 +++++++++++++++++++ .../app/NotificationSoundPreference.java | 12 ++- .../DefaultRingtonePreferenceTest.java | 75 ++++++++++++++++- 4 files changed, 165 insertions(+), 15 deletions(-) diff --git a/src/com/android/settings/DefaultRingtonePreference.java b/src/com/android/settings/DefaultRingtonePreference.java index 9bf626c9898..4c654887227 100644 --- a/src/com/android/settings/DefaultRingtonePreference.java +++ b/src/com/android/settings/DefaultRingtonePreference.java @@ -51,16 +51,9 @@ public class DefaultRingtonePreference extends RingtonePreference { return; } - String mimeType = mUserContext.getContentResolver().getType(ringtoneUri); - if (mimeType == null) { + if (!isValidRingtoneUri(ringtoneUri)) { Log.e(TAG, "onSaveRingtone for URI:" + ringtoneUri - + " ignored: failure to find mimeType (no access from this context?)"); - return; - } - - if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg"))) { - Log.e(TAG, "onSaveRingtone for URI:" + ringtoneUri - + " ignored: associated mimeType:" + mimeType + " is not an audio type"); + + " ignored: invalid ringtone Uri"); return; } diff --git a/src/com/android/settings/RingtonePreference.java b/src/com/android/settings/RingtonePreference.java index 8f9c618d5e8..d283e390fc7 100644 --- a/src/com/android/settings/RingtonePreference.java +++ b/src/com/android/settings/RingtonePreference.java @@ -16,6 +16,8 @@ package com.android.settings; +import android.content.ContentProvider; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.res.TypedArray; @@ -23,9 +25,11 @@ import android.media.AudioAttributes; import android.media.RingtoneManager; import android.net.Uri; import android.os.UserHandle; +import android.os.UserManager; import android.provider.Settings.System; import android.text.TextUtils; import android.util.AttributeSet; +import android.util.Log; import androidx.preference.Preference; import androidx.preference.PreferenceManager; @@ -239,4 +243,82 @@ public class RingtonePreference extends Preference { return true; } + public boolean isDefaultRingtone(Uri ringtoneUri) { + // null URIs are valid (None/silence) + return ringtoneUri == null || RingtoneManager.isDefault(ringtoneUri); + } + + protected boolean isValidRingtoneUri(Uri ringtoneUri) { + if (isDefaultRingtone(ringtoneUri)) { + return true; + } + + // Return early for android resource URIs + if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(ringtoneUri.getScheme())) { + return true; + } + + String mimeType = mUserContext.getContentResolver().getType(ringtoneUri); + if (mimeType == null) { + Log.e(TAG, "isValidRingtoneUri for URI:" + ringtoneUri + + " failed: failure to find mimeType (no access from this context?)"); + return false; + } + + if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg") + || mimeType.equals("application/x-flac"))) { + Log.e(TAG, "isValidRingtoneUri for URI:" + ringtoneUri + + " failed: associated mimeType:" + mimeType + " is not an audio type"); + return false; + } + + // Validate userId from URIs: content://{userId}@... + final int userIdFromUri = ContentProvider.getUserIdFromUri(ringtoneUri, mUserId); + if (userIdFromUri != mUserId) { + final UserManager userManager = mUserContext.getSystemService(UserManager.class); + + if (!userManager.isSameProfileGroup(mUserId, userIdFromUri)) { + Log.e(TAG, + "isValidRingtoneUri for URI:" + ringtoneUri + " failed: user " + userIdFromUri + + " and user " + mUserId + " are not in the same profile group"); + return false; + } + + final int parentUserId; + final int profileUserId; + if (userManager.isProfile()) { + profileUserId = mUserId; + parentUserId = userIdFromUri; + } else { + parentUserId = mUserId; + profileUserId = userIdFromUri; + } + + final UserHandle parent = userManager.getProfileParent(UserHandle.of(profileUserId)); + if (parent == null || parent.getIdentifier() != parentUserId) { + Log.e(TAG, + "isValidRingtoneUri for URI:" + ringtoneUri + " failed: user " + profileUserId + + " is not a profile of user " + parentUserId); + return false; + } + + // Allow parent <-> managed profile sharing, unless restricted + if (userManager.hasUserRestrictionForUser( + UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, UserHandle.of(parentUserId))) { + Log.e(TAG, + "isValidRingtoneUri for URI:" + ringtoneUri + " failed: user " + parentUserId + + " has restriction: " + UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE); + return false; + } + + if (!userManager.isManagedProfile(profileUserId)) { + Log.e(TAG, "isValidRingtoneUri for URI:" + ringtoneUri + + " failed: user " + profileUserId + " is not a managed profile"); + return false; + } + } + + return true; + } + } diff --git a/src/com/android/settings/notification/app/NotificationSoundPreference.java b/src/com/android/settings/notification/app/NotificationSoundPreference.java index 136b21ffd36..b55f9bd7ce8 100644 --- a/src/com/android/settings/notification/app/NotificationSoundPreference.java +++ b/src/com/android/settings/notification/app/NotificationSoundPreference.java @@ -25,10 +25,13 @@ import android.net.Uri; import android.os.AsyncTask; import android.util.AttributeSet; +import android.util.Log; import com.android.settings.R; import com.android.settings.RingtonePreference; public class NotificationSoundPreference extends RingtonePreference { + private static final String TAG = "NotificationSoundPreference"; + private Uri mRingtone; public NotificationSoundPreference(Context context, AttributeSet attrs) { @@ -50,8 +53,13 @@ public class NotificationSoundPreference extends RingtonePreference { public boolean onActivityResult(int requestCode, int resultCode, Intent data) { if (data != null) { Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); - setRingtone(uri); - callChangeListener(uri); + if (isValidRingtoneUri(uri)) { + setRingtone(uri); + callChangeListener(uri); + } else { + Log.e(TAG, "onActivityResult for URI:" + uri + + " ignored: invalid ringtone Uri"); + } } return true; diff --git a/tests/unit/src/com/android/settings/DefaultRingtonePreferenceTest.java b/tests/unit/src/com/android/settings/DefaultRingtonePreferenceTest.java index 7877684dce6..360a8a555b4 100644 --- a/tests/unit/src/com/android/settings/DefaultRingtonePreferenceTest.java +++ b/tests/unit/src/com/android/settings/DefaultRingtonePreferenceTest.java @@ -16,16 +16,19 @@ package com.android.settings; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.ContentInterface; import android.content.ContentResolver; import android.content.Context; -import android.media.RingtoneManager; import android.net.Uri; +import android.os.UserHandle; +import android.os.UserManager; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -34,17 +37,22 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; /** Unittest for DefaultRingtonePreference. */ @RunWith(AndroidJUnit4.class) public class DefaultRingtonePreferenceTest { + private static final int OWNER_USER_ID = 1; + private static final int OTHER_USER_ID = 10; + private static final int INVALID_RINGTONE_TYPE = 0; private DefaultRingtonePreference mDefaultRingtonePreference; @Mock private ContentResolver mContentResolver; @Mock + private UserManager mUserManager; private Uri mRingtoneUri; @Before @@ -52,14 +60,24 @@ public class DefaultRingtonePreferenceTest { MockitoAnnotations.initMocks(this); Context context = spy(ApplicationProvider.getApplicationContext()); - doReturn(mContentResolver).when(context).getContentResolver(); + mContentResolver = ContentResolver.wrap(Mockito.mock(ContentInterface.class)); + when(context.getContentResolver()).thenReturn(mContentResolver); mDefaultRingtonePreference = spy(new DefaultRingtonePreference(context, null /* attrs */)); doReturn(context).when(mDefaultRingtonePreference).getContext(); + + // Use INVALID_RINGTONE_TYPE to return early in RingtoneManager.setActualDefaultRingtoneUri when(mDefaultRingtonePreference.getRingtoneType()) - .thenReturn(RingtoneManager.TYPE_RINGTONE); - mDefaultRingtonePreference.setUserId(1); + .thenReturn(INVALID_RINGTONE_TYPE); + + mDefaultRingtonePreference.setUserId(OWNER_USER_ID); mDefaultRingtonePreference.mUserContext = context; + when(mDefaultRingtonePreference.isDefaultRingtone(any(Uri.class))).thenReturn(false); + + when(context.getSystemServiceName(UserManager.class)).thenReturn(Context.USER_SERVICE); + when(context.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + + mRingtoneUri = Uri.parse("content://none"); } @Test @@ -79,4 +97,53 @@ public class DefaultRingtonePreferenceTest { verify(mDefaultRingtonePreference, never()).setActualDefaultRingtoneUri(mRingtoneUri); } + + @Test + public void onSaveRingtone_notManagedProfile_shouldNotSetRingtone() { + mRingtoneUri = Uri.parse("content://" + OTHER_USER_ID + "@ringtone"); + when(mContentResolver.getType(mRingtoneUri)).thenReturn("audio/*"); + when(mUserManager.isSameProfileGroup(OWNER_USER_ID, OTHER_USER_ID)).thenReturn(true); + when(mUserManager.getProfileParent(UserHandle.of(OTHER_USER_ID))).thenReturn( + UserHandle.of(OWNER_USER_ID)); + when(mUserManager.isManagedProfile(OTHER_USER_ID)).thenReturn(false); + + mDefaultRingtonePreference.onSaveRingtone(mRingtoneUri); + + verify(mDefaultRingtonePreference, never()).setActualDefaultRingtoneUri(mRingtoneUri); + } + + @Test + public void onSaveRingtone_notSameUser_shouldNotSetRingtone() { + mRingtoneUri = Uri.parse("content://" + OTHER_USER_ID + "@ringtone"); + when(mContentResolver.getType(mRingtoneUri)).thenReturn("audio/*"); + when(mUserManager.isSameProfileGroup(OWNER_USER_ID, OTHER_USER_ID)).thenReturn(false); + + mDefaultRingtonePreference.onSaveRingtone(mRingtoneUri); + + verify(mDefaultRingtonePreference, never()).setActualDefaultRingtoneUri(mRingtoneUri); + } + + @Test + public void onSaveRingtone_isManagedProfile_shouldSetRingtone() { + mRingtoneUri = Uri.parse("content://" + OTHER_USER_ID + "@ringtone"); + when(mContentResolver.getType(mRingtoneUri)).thenReturn("audio/*"); + when(mUserManager.isSameProfileGroup(OWNER_USER_ID, OTHER_USER_ID)).thenReturn(true); + when(mUserManager.getProfileParent(UserHandle.of(OTHER_USER_ID))).thenReturn( + UserHandle.of(OWNER_USER_ID)); + when(mUserManager.isManagedProfile(OTHER_USER_ID)).thenReturn(true); + + mDefaultRingtonePreference.onSaveRingtone(mRingtoneUri); + + verify(mDefaultRingtonePreference).setActualDefaultRingtoneUri(mRingtoneUri); + } + + @Test + public void onSaveRingtone_defaultUri_shouldSetRingtone() { + mRingtoneUri = Uri.parse("default_ringtone"); + when(mDefaultRingtonePreference.isDefaultRingtone(any(Uri.class))).thenReturn(true); + + mDefaultRingtonePreference.onSaveRingtone(mRingtoneUri); + + verify(mDefaultRingtonePreference).setActualDefaultRingtoneUri(mRingtoneUri); + } } -- GitLab From ccabd3d0e7e921941d54180970d5e7de260d32e9 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Thu, 2 Nov 2023 11:43:00 +0800 Subject: [PATCH 34/43] Limit wifi item edit content's max length to 500 Bug: 293199910 Test: manual - on "Add network" (cherry picked from commit 855053ca4124f2d515b21c469096f8c18bd4829d) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:092668676af741719d50ac0f121a8f8461aa21ad) Merged-In: I303b8c6e0f3c3a1174a047ba98f302042e5db9ae Change-Id: I303b8c6e0f3c3a1174a047ba98f302042e5db9ae --- res/values/styles.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/res/values/styles.xml b/res/values/styles.xml index 8402dec73ca..9a9477bb5da 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -148,6 +148,7 @@ @android:style/TextAppearance.DeviceDefault.Medium ?android:attr/textColorSecondary @dimen/min_tap_target_size + 500