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 void run(AccountManagerFuture future) { 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) { 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 b6251fe47c8..0a81cc16793 --- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java +++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java @@ -395,7 +395,13 @@ public void onPrepareOptionsMenu(Menu menu) { 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( 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.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.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 @@ protected void retrieveAppEntry() { } } 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 @@ protected void retrieveAppEntry() { } } + 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) { diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index 183a2fbf5f5..0311ea3fbe9 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -25,8 +25,13 @@ 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.os.Process; +import android.os.RemoteException; +import android.os.UserHandle; import android.text.TextUtils; import android.util.ArraySet; import android.util.FeatureFlagUtils; @@ -38,6 +43,7 @@ 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 +61,7 @@ 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 +358,40 @@ private void launchDeepLinkIntentToRight() { 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; + } + + 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(); + 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. @@ -368,6 +409,19 @@ private void launchDeepLinkIntentToRight() { 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()), @@ -386,6 +440,44 @@ private void launchDeepLinkIntentToRight() { startActivity(targetIntent); } + // Check if calling app has privileged access to launch Activity of activityInfo. + private boolean hasPrivilegedAccess(int callingUid, ActivityInfo activityInfo) { + if (TextUtils.equals(PasswordUtils.getCallingAppPackageName(getActivityToken()), + getPackageName())) { + return true; + } + + 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( + this, getActivityToken(), permission); + } + private String getHighlightMenuKey() { final Intent intent = getIntent(); if (intent != null && TextUtils.equals(intent.getAction(), 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 @@ 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 void displayPreference(PreferenceScreen screen) { @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 @@ 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 void displayPreference(PreferenceScreen screen) { @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/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 @@ protected void handleNfcStateChanged(int newState) { } 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 void onPause() { } private boolean isToggleable() { - if (mUserManager.isGuestUser()) { + if (!mUserManager.isPrimaryUser()) { return false; } return true; 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 void onCreate(Bundle icicle) { .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); 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 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 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 void setUp() { MockitoAnnotations.initMocks(this); } + @After + public void tearDown() { + ShadowPasswordUtils.reset(); + } + @Test public void launch_shouldHaveAnimationForIaFragment() { final SettingsHomepageActivity activity = Robolectric.buildActivity( @@ -195,6 +204,32 @@ public void onStop_isNotDebuggable_shouldRemoveHideSystemOverlay() { & 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 {