Fix the security vulnerability issue in AppLocalePickerActivity

Examine whether the packages is allowed to display app locales list when creating the AppLocalePickerActivity, and examine whether the target user is the same as the calling user.

Bug: 257954050
Test: Follows the test step listed in b/257954050#comment14
Change-Id: I2e25a308bcba6ea0edee89c7a78465f766bdbeac
Merged-In: I2e25a308bcba6ea0edee89c7a78465f766bdbeac
(cherry picked from commit 5d7d1665fecd9f0dba90ccf5e827d3a295dafdcc)
Merged-In: I2e25a308bcba6ea0edee89c7a78465f766bdbeac
diff --git a/src/com/android/settings/localepicker/AppLocalePickerActivity.java b/src/com/android/settings/localepicker/AppLocalePickerActivity.java
index b7fef30..691344d 100644
--- a/src/com/android/settings/localepicker/AppLocalePickerActivity.java
+++ b/src/com/android/settings/localepicker/AppLocalePickerActivity.java
@@ -19,6 +19,7 @@
 import android.app.FragmentTransaction;
 import android.app.LocaleManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.LocaleList;
@@ -34,6 +35,7 @@
 import com.android.internal.app.LocaleStore;
 import com.android.settings.R;
 import com.android.settings.applications.AppInfoBase;
+import com.android.settings.applications.AppLocaleUtil;
 import com.android.settings.applications.appinfo.AppLocaleDetails;
 import com.android.settings.core.SettingsBaseActivity;
 
@@ -64,12 +66,17 @@
         }
         mContextAsUser = this;
         if (getIntent().hasExtra(AppInfoBase.ARG_PACKAGE_UID)) {
-            int userId = getIntent().getIntExtra(AppInfoBase.ARG_PACKAGE_UID, -1);
-            if (userId != -1) {
-                UserHandle userHandle = UserHandle.getUserHandleForUid(userId);
+            int uid = getIntent().getIntExtra(AppInfoBase.ARG_PACKAGE_UID, -1);
+            if (uid != -1) {
+                UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
                 mContextAsUser = createContextAsUser(userHandle, 0);
             }
         }
+        if (!canDisplayLocaleUi() || mContextAsUser.getUserId() != UserHandle.myUserId()) {
+            Log.w(TAG, "Not allow to display Locale Settings UI.");
+            finish();
+            return;
+        }
 
         setTitle(R.string.app_locale_picker_title);
         getActionBar().setDisplayHomeAsUpEnabled(true);
@@ -160,4 +167,10 @@
                 .replace(R.id.content_frame, mLocalePickerWithRegion)
                 .commit();
     }
+
+    private boolean canDisplayLocaleUi() {
+        return AppLocaleUtil.canDisplayLocaleUi(mContextAsUser, mPackageName,
+                mContextAsUser.getPackageManager().queryIntentActivities(
+                        AppLocaleUtil.LAUNCHER_ENTRY_INTENT, PackageManager.GET_META_DATA));
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java b/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java
index 332a39b..77b2abb 100644
--- a/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java
+++ b/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java
@@ -18,25 +18,35 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+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.annotation.Nullable;
 import android.app.Activity;
 import android.app.ApplicationPackageManager;
+import android.app.LocaleConfig;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.InstallSourceInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
 import android.net.Uri;
+import android.os.LocaleList;
 import android.os.Process;
 import android.os.UserHandle;
 import android.telephony.TelephonyManager;
 
+import androidx.annotation.ArrayRes;
+
 import com.android.internal.app.LocaleStore;
 import com.android.settings.applications.AppInfoBase;
 
-import java.util.Locale;
-
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -45,17 +55,26 @@
 import org.mockito.junit.MockitoRule;
 import org.robolectric.Robolectric;
 import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
 import org.robolectric.Shadows;
 import org.robolectric.android.controller.ActivityController;
 import org.robolectric.annotation.Config;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowPackageManager;
 import org.robolectric.shadows.ShadowTelephonyManager;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
 @RunWith(RobolectricTestRunner.class)
 @Config(
         shadows = {
                 AppLocalePickerActivityTest.ShadowApplicationPackageManager.class,
+                AppLocalePickerActivityTest.ShadowResources.class,
+                AppLocalePickerActivityTest.ShadowUserHandle.class,
+                AppLocalePickerActivityTest.ShadowLocaleConfig.class,
         })
 public class AppLocalePickerActivityTest {
     private static final String TEST_PACKAGE_NAME = "com.android.settings";
@@ -67,21 +86,99 @@
     @Rule
     public MockitoRule rule = MockitoJUnit.rule();
 
+    private Context mContext;
+    private ShadowPackageManager mPackageManager;
+
+    @Before
+    public void setUp() {
+        mContext = spy(RuntimeEnvironment.application);
+        mPackageManager = Shadows.shadowOf(mContext.getPackageManager());
+    }
+
+    @After
+    public void tearDown() {
+        mPackageManager.removePackage(TEST_PACKAGE_NAME);
+        ShadowResources.setDisAllowPackage(false);
+        ShadowApplicationPackageManager.setNoLaunchEntry(false);
+        ShadowUserHandle.setUserId(0);
+        ShadowLocaleConfig.setStatus(LocaleConfig.STATUS_SUCCESS);
+    }
+
     @Test
     public void launchAppLocalePickerActivity_hasPackageName_success() {
         ActivityController<TestAppLocalePickerActivity> controller =
                 initActivityController(true);
-
         controller.create();
 
         assertThat(controller.get().isFinishing()).isFalse();
     }
 
     @Test
+    public void launchAppLocalePickerActivity_appNoLocaleConfig_failed() {
+        ShadowLocaleConfig.setStatus(LocaleConfig.STATUS_NOT_SPECIFIED);
+
+        ActivityController<TestAppLocalePickerActivity> controller =
+                initActivityController(true);
+        controller.create();
+
+        assertThat(controller.get().isFinishing()).isTrue();
+    }
+
+    @Test
+    public void launchAppLocalePickerActivity_appSignPlatformKey_failed() {
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY;
+        applicationInfo.packageName = TEST_PACKAGE_NAME;
+
+        final PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = TEST_PACKAGE_NAME;
+        packageInfo.applicationInfo = applicationInfo;
+        mPackageManager.installPackage(packageInfo);
+
+        ActivityController<TestAppLocalePickerActivity> controller =
+                initActivityController(true);
+        controller.create();
+
+        assertThat(controller.get().isFinishing()).isTrue();
+    }
+
+    @Test
+    public void launchAppLocalePickerActivity_appMatchDisallowedPackage_failed() {
+        ShadowResources.setDisAllowPackage(true);
+
+        ActivityController<TestAppLocalePickerActivity> controller =
+                initActivityController(true);
+        controller.create();
+
+        assertThat(controller.get().isFinishing()).isTrue();
+    }
+
+    @Test
+    public void launchAppLocalePickerActivity_appNoLaunchEntry_failed() {
+        ShadowApplicationPackageManager.setNoLaunchEntry(true);
+
+        ActivityController<TestAppLocalePickerActivity> controller =
+                initActivityController(true);
+        controller.create();
+
+        assertThat(controller.get().isFinishing()).isTrue();
+    }
+
+    @Test
+    public void launchAppLocalePickerActivity_modifyAppLocalesOfAnotherUser_failed() {
+        ShadowUserHandle.setUserId(10);
+
+        ActivityController<TestAppLocalePickerActivity> controller =
+                initActivityController(true);
+        controller.create();
+
+        assertThat(controller.get().isFinishing()).isTrue();
+    }
+
+    @Test
     public void launchAppLocalePickerActivity_intentWithoutPackageName_failed() {
         ActivityController<TestAppLocalePickerActivity> controller =
                 initActivityController(false);
-
         controller.create();
 
         assertThat(controller.get().isFinishing()).isTrue();
@@ -125,7 +222,7 @@
         if (hasPackageName) {
             data.setData(TEST_PACKAGE_URI);
         }
-        data.putExtra(AppInfoBase.ARG_PACKAGE_UID, UserHandle.getUserId(Process.myUid()));
+        data.putExtra(AppInfoBase.ARG_PACKAGE_UID, Process.myUid());
         ActivityController<TestAppLocalePickerActivity> activityController =
                 Robolectric.buildActivity(TestAppLocalePickerActivity.class, data);
         Activity activity = activityController.get();
@@ -149,10 +246,75 @@
     @Implements(ApplicationPackageManager.class)
     public static class ShadowApplicationPackageManager extends
             org.robolectric.shadows.ShadowApplicationPackageManager {
+        private static boolean sNoLaunchEntry = false;
 
         @Implementation
         protected Object getInstallSourceInfo(String packageName) {
             return new InstallSourceInfo("", null, null, "");
         }
+
+        @Implementation
+        protected List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
+            if (sNoLaunchEntry) {
+                return new ArrayList();
+            } else {
+                return super.queryIntentActivities(intent, flags);
+            }
+        }
+
+        private static void setNoLaunchEntry(boolean noLaunchEntry) {
+            sNoLaunchEntry = noLaunchEntry;
+        }
+    }
+
+    @Implements(Resources.class)
+    public static class ShadowResources extends
+            org.robolectric.shadows.ShadowResources {
+        private static boolean sDisAllowPackage = false;
+
+        @Implementation
+        public String[] getStringArray(@ArrayRes int id) {
+            if (sDisAllowPackage) {
+                return new String[]{TEST_PACKAGE_NAME};
+            } else {
+                return new String[0];
+            }
+        }
+
+        private static void setDisAllowPackage(boolean disAllowPackage) {
+            sDisAllowPackage = disAllowPackage;
+        }
+    }
+
+    @Implements(UserHandle.class)
+    public static class ShadowUserHandle {
+        private static int sUserId = 0;
+        private static void setUserId(int userId) {
+            sUserId = userId;
+        }
+
+        @Implementation
+        public static int getUserId(int userId) {
+            return sUserId;
+        }
+    }
+
+    @Implements(LocaleConfig.class)
+    public static class ShadowLocaleConfig {
+        private static int sStatus = 0;
+
+        @Implementation
+        public @Nullable LocaleList getSupportedLocales() {
+            return LocaleList.forLanguageTags("en-US");
+        }
+
+        private static void setStatus(@LocaleConfig.Status int status) {
+            sStatus = status;
+        }
+
+        @Implementation
+        public @LocaleConfig.Status int getStatus() {
+            return sStatus;
+        }
     }
 }