Merge cherrypicks of ['googleplex-android-review.googlesource.com/30469373', 'googleplex-android-review.googlesource.com/30489443', 'googleplex-android-review.googlesource.com/30393495'] into security-aosp-udc-release.

Change-Id: Ib6666c11bff7209ddf056f11030c11ee762bdeea
diff --git a/src/com/android/settings/accounts/AccountTypePreferenceLoader.java b/src/com/android/settings/accounts/AccountTypePreferenceLoader.java
index 16519af..7cfeb1c 100644
--- a/src/com/android/settings/accounts/AccountTypePreferenceLoader.java
+++ b/src/com/android/settings/accounts/AccountTypePreferenceLoader.java
@@ -20,6 +20,7 @@
 import android.accounts.Account;
 import android.accounts.AuthenticatorDescription;
 import android.content.ClipData;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -185,9 +186,9 @@
                                     prefIntent, mUserHandle);
                             } else {
                                 Log.e(TAG,
-                                    "Refusing to launch authenticator intent because"
-                                        + "it exploits Settings permissions: "
-                                        + prefIntent);
+                                        "Refusing to launch authenticator intent because "
+                                                + "it exploits Settings permissions: "
+                                                + prefIntent);
                             }
                             return true;
                         }
@@ -241,13 +242,19 @@
     }
 
     /**
-     * Determines if the supplied Intent is safe. A safe intent is one that is
-     * will launch a exported=true activity or owned by the same uid as the
+     * Determines if the supplied Intent is safe. A safe intent is one that
+     * will launch an exported=true activity or owned by the same uid as the
      * authenticator supplying the intent.
      */
-    private boolean isSafeIntent(PackageManager pm, Intent intent, String acccountType) {
+    @VisibleForTesting
+    boolean isSafeIntent(PackageManager pm, Intent intent, String accountType) {
+        if (TextUtils.equals(intent.getScheme(), ContentResolver.SCHEME_CONTENT)) {
+            Log.e(TAG, "Intent with a content scheme is unsafe.");
+            return false;
+        }
+
         AuthenticatorDescription authDesc =
-            mAuthenticatorHelper.getAccountTypeDescription(acccountType);
+                mAuthenticatorHelper.getAccountTypeDescription(accountType);
         ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mUserHandle.getIdentifier());
         if (resolveInfo == null) {
             return false;
diff --git a/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreference.kt b/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreference.kt
index 1ef8936..1b605a8 100644
--- a/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreference.kt
@@ -19,14 +19,16 @@
 import android.content.Context
 import android.content.Intent
 import android.content.pm.ApplicationInfo
-import android.content.pm.PackageManager.ResolveInfoFlags
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
 import android.provider.Settings
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
-import androidx.lifecycle.liveData
+import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settings.R
 import com.android.settings.overlay.FeatureFactory
 import com.android.settingslib.spa.framework.compose.stateOf
@@ -36,44 +38,61 @@
 import com.android.settingslib.spaprivileged.model.app.userHandle
 import com.android.settingslib.spaprivileged.model.app.userId
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
 
+@OptIn(ExperimentalLifecycleComposeApi::class)
 @Composable
 fun AppTimeSpentPreference(app: ApplicationInfo) {
     val context = LocalContext.current
-    val presenter = remember { AppTimeSpentPresenter(context, app) }
-    if (!presenter.isAvailable()) return
+    val presenter = remember(app) { AppTimeSpentPresenter(context, app) }
+    val isAvailable by presenter.isAvailableFlow.collectAsStateWithLifecycle(initialValue = false)
+    if (!isAvailable) return
 
-    Preference(object : PreferenceModel {
-        override val title = stringResource(R.string.time_spent_in_app_pref_title)
-        override val summary = presenter.summaryLiveData.observeAsState(
-            initial = stringResource(R.string.summary_placeholder),
-        )
-        override val enabled = stateOf(presenter.isEnabled())
-        override val onClick = presenter::startActivity
-    })
+    val summary = presenter.summaryFlow.collectAsStateWithLifecycle(
+        initialValue = stringResource(R.string.summary_placeholder),
+    )
+    Preference(
+        object : PreferenceModel {
+            override val title = stringResource(R.string.time_spent_in_app_pref_title)
+            override val summary = summary
+            override val enabled = stateOf(presenter.isEnabled())
+            override val onClick = presenter::startActivity
+        }
+    )
 }
 
 private class AppTimeSpentPresenter(
     private val context: Context,
     private val app: ApplicationInfo,
 ) {
-    private val intent = Intent(Settings.ACTION_APP_USAGE_SETTINGS).apply {
-        putExtra(Intent.EXTRA_PACKAGE_NAME, app.packageName)
-    }
+    private val intent =
+        Intent(Settings.ACTION_APP_USAGE_SETTINGS).apply {
+            // Limit the package for safer intents, since string resource is not null,
+            // we restrict the target to this single package.
+            setPackage(context.getString(com.android.internal.R.string.config_systemWellbeing))
+            putExtra(Intent.EXTRA_PACKAGE_NAME, app.packageName)
+        }
+
     private val appFeatureProvider = FeatureFactory.getFactory(context)
         .getApplicationFeatureProvider(context)
 
-    fun isAvailable() = context.packageManager.queryIntentActivitiesAsUser(
-        intent, ResolveInfoFlags.of(0), app.userId
-    ).any { resolveInfo ->
-        resolveInfo?.activityInfo?.applicationInfo?.isSystemApp == true
-    }
+    val isAvailableFlow = flow { emit(resolveIntent() != null) }.flowOn(Dispatchers.Default)
+
+    // Resolve the intent first with PackageManager.MATCH_SYSTEM_ONLY flag to ensure that
+    // only system apps are resolved.
+    private fun resolveIntent(): ResolveInfo? =
+        context.packageManager.resolveActivityAsUser(
+            intent,
+            PackageManager.MATCH_SYSTEM_ONLY,
+            app.userId,
+        )
 
     fun isEnabled() = app.hasFlag(ApplicationInfo.FLAG_INSTALLED)
 
-    val summaryLiveData = liveData(Dispatchers.IO) {
-        emit(appFeatureProvider.getTimeSpentInApp(app.packageName).toString())
-    }
+    val summaryFlow =
+        flow { emit(appFeatureProvider.getTimeSpentInApp(app.packageName).toString()) }
+            .flowOn(Dispatchers.Default)
 
     fun startActivity() {
         context.startActivityAsUser(intent, app.userHandle)
diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java
index 28e02ec..b430068 100644
--- a/src/com/android/settings/users/UserSettings.java
+++ b/src/com/android/settings/users/UserSettings.java
@@ -464,7 +464,8 @@
     @Override
     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
         int pos = 0;
-        if (!isCurrentUserAdmin() && canSwitchUserNow() && !isCurrentUserGuest()) {
+        if (!isCurrentUserAdmin() && canSwitchUserNow() && !isCurrentUserGuest()
+                && !mUserManager.isProfile()) {
             String nickname = mUserManager.getUserName();
             MenuItem removeThisUser = menu.add(0, MENU_REMOVE_USER, pos++,
                     getResources().getString(R.string.user_remove_user_menu, nickname));
diff --git a/tests/robotests/src/com/android/settings/accounts/AccountTypePreferenceLoaderTest.java b/tests/robotests/src/com/android/settings/accounts/AccountTypePreferenceLoaderTest.java
index 405f6b3..5e56600 100644
--- a/tests/robotests/src/com/android/settings/accounts/AccountTypePreferenceLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/accounts/AccountTypePreferenceLoaderTest.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.accounts;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Answers.RETURNS_DEEP_STUBS;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
@@ -26,8 +28,11 @@
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.accounts.AuthenticatorDescription;
+import android.content.ClipData;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.net.Uri;
 import android.os.UserHandle;
 
 import androidx.preference.Preference;
@@ -129,4 +134,13 @@
         verify(mPrefLoader).updatePreferenceIntents(prefGroup4, acctType, mAccount);
         verify(mPrefLoader).updatePreferenceIntents(prefGroup41, acctType, mAccount);
     }
+
+    @Test
+    public void isSafeIntent_hasContextScheme_returnFalse() {
+        Intent intent = new Intent();
+        intent.setClipData(ClipData.newRawUri(null,
+                Uri.parse("content://com.android.settings.files/my_cache/NOTICE.html")));
+
+        assertThat(mPrefLoader.isSafeIntent(mPackageManager, intent, mAccount.type)).isFalse();
+    }
 }
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreferenceTest.kt
index 6cc3e3c..32b7e77 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreferenceTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreferenceTest.kt
@@ -17,17 +17,16 @@
 package com.android.settings.spa.app.appinfo
 
 import android.content.Context
-import android.content.pm.ActivityInfo
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
-import android.content.pm.PackageManager.ResolveInfoFlags
 import android.content.pm.ResolveInfo
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.assertIsEnabled
 import androidx.compose.ui.test.assertIsNotDisplayed
 import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.isEnabled
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.onRoot
@@ -35,12 +34,13 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settings.R
 import com.android.settings.testutils.FakeFeatureFactory
+import com.android.settingslib.spa.testutils.any
+import com.android.settingslib.spa.testutils.waitUntilExists
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.any
 import org.mockito.Mockito.anyInt
 import org.mockito.Spy
 import org.mockito.junit.MockitoJUnit
@@ -59,39 +59,26 @@
     private val context: Context = ApplicationProvider.getApplicationContext()
 
     @Mock
-    private lateinit var packageManager: PackageManager
+    private lateinit var mockPackageManager: PackageManager
 
     private val fakeFeatureFactory = FakeFeatureFactory()
     private val appFeatureProvider = fakeFeatureFactory.mockApplicationFeatureProvider
 
     @Before
     fun setUp() {
-        whenever(context.packageManager).thenReturn(packageManager)
+        whenever(context.packageManager).thenReturn(mockPackageManager)
         whenever(appFeatureProvider.getTimeSpentInApp(PACKAGE_NAME)).thenReturn(TIME_SPENT)
     }
 
-    private fun mockActivitiesQueryResult(resolveInfos: List<ResolveInfo>) {
+    private fun mockActivityQueryResult(resolveInfo: ResolveInfo?) {
         whenever(
-            packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), anyInt())
-        ).thenReturn(resolveInfos)
+            mockPackageManager.resolveActivityAsUser(any(), anyInt(), anyInt())
+        ).thenReturn(resolveInfo)
     }
 
     @Test
     fun noIntentHandler_notDisplay() {
-        mockActivitiesQueryResult(emptyList())
-
-        composeTestRule.setContent {
-            CompositionLocalProvider(LocalContext provides context) {
-                AppTimeSpentPreference(INSTALLED_APP)
-            }
-        }
-
-        composeTestRule.onRoot().assertIsNotDisplayed()
-    }
-
-    @Test
-    fun hasIntentHandler_notSystemApp_notDisplay() {
-        mockActivitiesQueryResult(listOf(ResolveInfo()))
+        mockActivityQueryResult(null)
 
         composeTestRule.setContent {
             CompositionLocalProvider(LocalContext provides context) {
@@ -104,7 +91,7 @@
 
     @Test
     fun installedApp_enabled() {
-        mockActivitiesQueryResult(listOf(MATCHED_RESOLVE_INFO))
+        mockActivityQueryResult(ResolveInfo())
 
         composeTestRule.setContent {
             CompositionLocalProvider(LocalContext provides context) {
@@ -112,18 +99,16 @@
             }
         }
 
-        composeTestRule.onNodeWithText(context.getString(R.string.time_spent_in_app_pref_title))
-            .assertIsDisplayed()
-            .assertIsEnabled()
+        composeTestRule.waitUntilExists(
+            hasText(context.getString(R.string.time_spent_in_app_pref_title)) and isEnabled()
+        )
         composeTestRule.onNodeWithText(TIME_SPENT).assertIsDisplayed()
     }
 
     @Test
     fun notInstalledApp_disabled() {
-        mockActivitiesQueryResult(listOf(MATCHED_RESOLVE_INFO))
-        val notInstalledApp = ApplicationInfo().apply {
-            packageName = PACKAGE_NAME
-        }
+        mockActivityQueryResult(ResolveInfo())
+        val notInstalledApp = ApplicationInfo().apply { packageName = PACKAGE_NAME }
 
         composeTestRule.setContent {
             CompositionLocalProvider(LocalContext provides context) {
@@ -131,25 +116,19 @@
             }
         }
 
-        composeTestRule.onNodeWithText(context.getString(R.string.time_spent_in_app_pref_title))
+        composeTestRule
+            .onNodeWithText(context.getString(R.string.time_spent_in_app_pref_title))
             .assertIsNotEnabled()
     }
 
     companion object {
-        private const val PACKAGE_NAME = "package name"
+        private const val PACKAGE_NAME = "package.name"
         private const val TIME_SPENT = "15 minutes"
 
-        private val INSTALLED_APP = ApplicationInfo().apply {
-            packageName = PACKAGE_NAME
-            flags = ApplicationInfo.FLAG_INSTALLED
-        }
-
-        private val MATCHED_RESOLVE_INFO = ResolveInfo().apply {
-            activityInfo = ActivityInfo().apply {
-                applicationInfo = ApplicationInfo().apply {
-                    flags = ApplicationInfo.FLAG_SYSTEM
-                }
+        private val INSTALLED_APP =
+            ApplicationInfo().apply {
+                packageName = PACKAGE_NAME
+                flags = ApplicationInfo.FLAG_INSTALLED
             }
-        }
     }
 }