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

This commit is contained in:
Ankita Vyas
2022-01-19 05:22:38 +00:00
committed by Android (Google) Code Review
5 changed files with 77 additions and 28 deletions

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -239,18 +239,13 @@ public class LocaleManagerService extends SystemService {
*/
private void notifyInstallerOfAppWhoseLocaleChanged(String appPackageName, int userId,
LocaleList locales) {
try {
String installingPackageName = mContext.getPackageManager()
.getInstallSourceInfo(appPackageName).getInstallingPackageName();
if (installingPackageName != null) {
Intent intent = createBaseIntent(Intent.ACTION_APPLICATION_LOCALE_CHANGED,
appPackageName, locales);
//Set package name to ensure that only installer of the app receives this intent.
intent.setPackage(installingPackageName);
mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
}
} catch (PackageManager.NameNotFoundException e) {
Slog.w(TAG, "Package not found " + appPackageName);
String installingPackageName = getInstallingPackageName(appPackageName);
if (installingPackageName != null) {
Intent intent = createBaseIntent(Intent.ACTION_APPLICATION_LOCALE_CHANGED,
appPackageName, locales);
//Set package name to ensure that only installer of the app receives this intent.
intent.setPackage(installingPackageName);
mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
}
}
@@ -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.
*/

View File

@@ -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);
}