Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ad13719e authored by Ankita Vyas's avatar Ankita Vyas Committed by Android (Google) Code Review
Browse files

Merge "Allow the installer of an app to get the app's locales."

parents 112c5401 f9921942
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -5713,6 +5713,7 @@ package android.app {
  public class LocaleManager {
    method @NonNull public android.os.LocaleList getApplicationLocales();
    method @NonNull @RequiresPermission(value="android.permission.READ_APP_SPECIFIC_LOCALES", conditional=true) public android.os.LocaleList getApplicationLocales(@NonNull String);
    method public void setApplicationLocales(@NonNull android.os.LocaleList);
  }
+0 −1
Original line number Diff line number Diff line
@@ -799,7 +799,6 @@ package android.app {
  }
  public class LocaleManager {
    method @NonNull @RequiresPermission(android.Manifest.permission.READ_APP_SPECIFIC_LOCALES) public android.os.LocaleList getApplicationLocales(@NonNull String);
    method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void setApplicationLocales(@NonNull String, @NonNull android.os.LocaleList);
  }
+8 −6
Original line number Diff line number Diff line
@@ -95,18 +95,20 @@ public class LocaleManager {
    }

    /**
     * Returns the current UI locales for a specific app (described by package name).
     * Returns the current UI locales for a specified app (described by package name).
     *
     * <p>Returns a {@link LocaleList#getEmptyLocaleList()} if no app-specific locales are set.
     *
     * <b>Note:</b> Non-system apps should read Locale information via their in-process
     * LocaleLists.
     * <p>This API can be used by an app's installer
     * (per {@link android.content.pm.InstallSourceInfo#getInstallingPackageName}) to retrieve
     * the app's locales.
     * All other cases require {@code android.Manifest.permission#READ_APP_SPECIFIC_LOCALES}.
     * Apps should generally retrieve their own locales via their in-process LocaleLists,
     * or by calling {@link #getApplicationLocales()}.
     *
     * @param appPackageName the package name of the app for which to retrieve the locales.
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.READ_APP_SPECIFIC_LOCALES)
    @RequiresPermission(value = Manifest.permission.READ_APP_SPECIFIC_LOCALES, conditional = true)
    @UserHandleAware
    @NonNull
    public LocaleList getApplicationLocales(@NonNull String appPackageName) {
+46 −19
Original line number Diff line number Diff line
@@ -239,9 +239,7 @@ public class LocaleManagerService extends SystemService {
     */
    private void notifyInstallerOfAppWhoseLocaleChanged(String appPackageName, int userId,
            LocaleList locales) {
        try {
            String installingPackageName = mContext.getPackageManager()
                    .getInstallSourceInfo(appPackageName).getInstallingPackageName();
        String installingPackageName = getInstallingPackageName(appPackageName);
        if (installingPackageName != null) {
            Intent intent = createBaseIntent(Intent.ACTION_APPLICATION_LOCALE_CHANGED,
                    appPackageName, locales);
@@ -249,9 +247,6 @@ public class LocaleManagerService extends SystemService {
            intent.setPackage(installingPackageName);
            mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
        }
        } catch (PackageManager.NameNotFoundException e) {
            Slog.w(TAG, "Package not found " + appPackageName);
        }
    }

    /**
@@ -291,8 +286,7 @@ public class LocaleManagerService extends SystemService {
     */
    private boolean isPackageOwnedByCaller(String appPackageName, int userId,
            @Nullable AppLocaleChangedAtomRecord atomRecordForMetrics) {
        final int uid = mPackageManagerInternal
                .getPackageUid(appPackageName, /* flags */ 0, userId);
        final int uid = getPackageUid(appPackageName, userId);
        if (uid < 0) {
            Slog.w(TAG, "Unknown package " + appPackageName + " for user " + userId);
            if (atomRecordForMetrics != null) {
@@ -336,13 +330,17 @@ public class LocaleManagerService extends SystemService {
                false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL,
                "getApplicationLocales", appPackageName);

        // This function handles two types of query operations:
        // This function handles three types of query operations:
        // 1.) A normal, non-privileged app querying its own locale.
        // 2.) A privileged system service querying locales of another package.
        // 2.) The installer of the given app querying locales of a package installed
        // by said installer.
        // 3.) A privileged system service querying locales of another package.
        // The least privileged case is a normal app performing a query, so check that first and
        // get locales if the package name is owned by the app. Next, check if the caller has the
        // necessary permission and get locales.
        if (!isPackageOwnedByCaller(appPackageName, userId)) {
        // get locales if the package name is owned by the app. Next check if the calling app
        // is the installer of the given app and get locales. If neither conditions matched,
        // check if the caller has the necessary permission and fetch locales.
        if (!isPackageOwnedByCaller(appPackageName, userId)
                && !isCallerInstaller(appPackageName, userId)) {
            enforceReadAppSpecificLocalesPermission();
        }
        final long token = Binder.clearCallingIdentity();
@@ -374,12 +372,41 @@ public class LocaleManagerService extends SystemService {
        return locales != null ? locales : LocaleList.getEmptyLocaleList();
    }

    /**
     * Checks if the calling app is the installer of the app whose locale changed.
     */
    private boolean isCallerInstaller(String appPackageName, int userId) {
        String installingPackageName = getInstallingPackageName(appPackageName);
        if (installingPackageName != null) {
            // Get the uid of installer-on-record to compare with the calling uid.
            int installerUid = getPackageUid(installingPackageName, userId);
            return installerUid >= 0 && UserHandle.isSameApp(Binder.getCallingUid(), installerUid);
        }
        return false;
    }

    private void enforceReadAppSpecificLocalesPermission() {
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.READ_APP_SPECIFIC_LOCALES,
                "getApplicationLocales");
    }

    private int getPackageUid(String appPackageName, int userId) {
        return mPackageManagerInternal
                .getPackageUid(appPackageName, /* flags */ 0, userId);
    }

    @Nullable
    private String getInstallingPackageName(String packageName) {
        try {
            return mContext.getPackageManager()
                    .getInstallSourceInfo(packageName).getInstallingPackageName();
        } catch (PackageManager.NameNotFoundException e) {
            Slog.w(TAG, "Package not found " + packageName);
        }
        return null;
    }

    /**
     * Dumps useful info related to service.
     */
+22 −2
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

@@ -58,6 +59,7 @@ import org.mockito.Mock;
@RunWith(AndroidJUnit4.class)
public class LocaleManagerServiceTest {
    private static final String DEFAULT_PACKAGE_NAME = "com.android.myapp";
    private static final String DEFAULT_INSTALLER_PACKAGE_NAME = "com.android.myapp.installer";
    private static final int DEFAULT_USER_ID = 0;
    private static final int DEFAULT_UID = Binder.getCallingUid() + 100;
    private static final int INVALID_UID = -1;
@@ -66,7 +68,8 @@ public class LocaleManagerServiceTest {
            LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS);
    private static final InstallSourceInfo DEFAULT_INSTALL_SOURCE_INFO = new InstallSourceInfo(
            /* initiatingPackageName = */ null, /* initiatingPackageSigningInfo = */ null,
            /* originatingPackageName = */ null, /* installingPackageName = */ null);
            /* originatingPackageName = */ null,
            /* installingPackageName = */ DEFAULT_INSTALLER_PACKAGE_NAME);

    private LocaleManagerService mLocaleManagerService;
    private LocaleManagerBackupHelper mMockBackupHelper;
@@ -89,7 +92,7 @@ public class LocaleManagerServiceTest {
        mMockActivityManager = mock(ActivityManagerInternal.class);
        mMockPackageManagerInternal = mock(PackageManagerInternal.class);

        // For unit tests, set the default (null) installer info
        // For unit tests, set the default installer info
        PackageManager mockPackageManager = mock(PackageManager.class);
        doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mockPackageManager)
                .getInstallSourceInfo(anyString());
@@ -275,6 +278,23 @@ public class LocaleManagerServiceTest {
        assertEquals(DEFAULT_LOCALES, locales);
    }

    @Test
    public void testGetApplicationLocales_callerIsInstaller_returnsLocales()
            throws Exception {
        doReturn(DEFAULT_UID).when(mMockPackageManagerInternal)
                .getPackageUid(eq(DEFAULT_PACKAGE_NAME), anyLong(), anyInt());
        doReturn(Binder.getCallingUid()).when(mMockPackageManagerInternal)
                .getPackageUid(eq(DEFAULT_INSTALLER_PACKAGE_NAME), anyLong(), anyInt());
        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
                .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());

        LocaleList locales =
                mLocaleManagerService.getApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);

        verify(mMockContext, never()).enforceCallingOrSelfPermission(any(), any());
        assertEquals(DEFAULT_LOCALES, locales);
    }

    private static void assertNoLocalesStored(LocaleList locales) {
        assertNull(locales);
    }