Loading core/java/android/app/LocaleManager.java +1 −0 Original line number Diff line number Diff line Loading @@ -127,6 +127,7 @@ public class LocaleManager { * <p>This API can be used by an app's installer * (per {@link android.content.pm.InstallSourceInfo#getInstallingPackageName}) to retrieve * the app's locales. * <p>This API can be used by the current input method to retrieve locales of another packages. * 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()}. Loading services/core/java/com/android/server/locales/LocaleManagerService.java +35 −9 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ILocaleManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; Loading @@ -38,6 +39,8 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -357,17 +360,20 @@ public class LocaleManagerService extends SystemService { false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL, "getApplicationLocales", /* callerPackage= */ null); // This function handles three types of query operations: // This function handles four types of query operations: // 1.) A normal, non-privileged app querying its own locale. // 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 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. // 2.) The installer of the given app querying locales of a package installed by said // installer. // 3.) The current input method querying locales of another package. // 4.) 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 calling app is the // installer of the given app and get locales. Finally check if the calling app is the // current input method. If neither conditions matched, check if the caller has the // necessary permission and fetch locales. if (!isPackageOwnedByCaller(appPackageName, userId) && !isCallerInstaller(appPackageName, userId)) { && !isCallerInstaller(appPackageName, userId) && !isCallerFromCurrentInputMethod(userId)) { enforceReadAppSpecificLocalesPermission(); } final long token = Binder.clearCallingIdentity(); Loading Loading @@ -412,6 +418,26 @@ public class LocaleManagerService extends SystemService { return false; } /** * Checks if the calling app is the current input method. */ private boolean isCallerFromCurrentInputMethod(int userId) { String currentInputMethod = Settings.Secure.getStringForUser( mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD, userId); if (!TextUtils.isEmpty(currentInputMethod)) { String inputMethodPkgName = ComponentName .unflattenFromString(currentInputMethod) .getPackageName(); int inputMethodUid = getPackageUid(inputMethodPkgName, userId); return inputMethodUid >= 0 && UserHandle.isSameApp(Binder.getCallingUid(), inputMethodUid); } return false; } private void enforceReadAppSpecificLocalesPermission() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.READ_APP_SPECIFIC_LOCALES, Loading services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java +35 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.locales; import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.fail; Loading @@ -35,13 +37,16 @@ import static org.mockito.Mockito.verify; import android.Manifest; import android.app.ActivityManagerInternal; import android.content.ComponentName; import android.content.Context; import android.content.pm.InstallSourceInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.os.Binder; import android.os.LocaleList; import android.provider.Settings; import androidx.test.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.internal.content.PackageMonitor; Loading Loading @@ -111,6 +116,8 @@ public class LocaleManagerServiceTest { doReturn(DEFAULT_USER_ID).when(mMockActivityManager) .handleIncomingUser(anyInt(), anyInt(), eq(DEFAULT_USER_ID), anyBoolean(), anyInt(), anyString(), anyString()); doReturn(InstrumentationRegistry.getContext().getContentResolver()) .when(mMockContext).getContentResolver(); mMockBackupHelper = mock(ShadowLocaleManagerBackupHelper.class); mLocaleManagerService = new LocaleManagerService(mMockContext, mMockActivityTaskManager, Loading Loading @@ -299,6 +306,25 @@ public class LocaleManagerServiceTest { assertEquals(DEFAULT_LOCALES, locales); } @Test public void testGetApplicationLocales_callerIsCurrentInputMethod_returnsLocales() throws Exception { doReturn(DEFAULT_UID).when(mMockPackageManager) .getPackageUidAsUser(anyString(), any(), anyInt()); doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES)) .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt()); String imPkgName = getCurrentInputMethodPackageName(); doReturn(Binder.getCallingUid()).when(mMockPackageManager) .getPackageUidAsUser(eq(imPkgName), any(), 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); } Loading @@ -311,4 +337,13 @@ public class LocaleManagerServiceTest { private void setUpPassingPermissionCheckFor(String permission) { doNothing().when(mMockContext).enforceCallingOrSelfPermission(eq(permission), any()); } private String getCurrentInputMethodPackageName() { String im = Settings.Secure.getString( InstrumentationRegistry.getContext().getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); ComponentName cn = ComponentName.unflattenFromString(im); assertThat(cn).isNotNull(); return cn.getPackageName(); } } services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java +4 −0 Original line number Diff line number Diff line Loading @@ -47,6 +47,8 @@ import android.util.AtomicFile; import android.util.TypedXmlPullParser; import android.util.Xml; import androidx.test.InstrumentationRegistry; import com.android.internal.content.PackageMonitor; import com.android.internal.util.XmlUtils; import com.android.server.wm.ActivityTaskManagerInternal; Loading Loading @@ -124,6 +126,8 @@ public class SystemAppUpdateTrackerTest { doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager) .getInstallSourceInfo(anyString()); doReturn(mMockPackageManager).when(mMockContext).getPackageManager(); doReturn(InstrumentationRegistry.getContext().getContentResolver()) .when(mMockContext).getContentResolver(); mStoragefile = new AtomicFile(new File( Environment.getExternalStorageDirectory(), "systemUpdateUnitTests.xml")); Loading Loading
core/java/android/app/LocaleManager.java +1 −0 Original line number Diff line number Diff line Loading @@ -127,6 +127,7 @@ public class LocaleManager { * <p>This API can be used by an app's installer * (per {@link android.content.pm.InstallSourceInfo#getInstallingPackageName}) to retrieve * the app's locales. * <p>This API can be used by the current input method to retrieve locales of another packages. * 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()}. Loading
services/core/java/com/android/server/locales/LocaleManagerService.java +35 −9 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ILocaleManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; Loading @@ -38,6 +39,8 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -357,17 +360,20 @@ public class LocaleManagerService extends SystemService { false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL, "getApplicationLocales", /* callerPackage= */ null); // This function handles three types of query operations: // This function handles four types of query operations: // 1.) A normal, non-privileged app querying its own locale. // 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 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. // 2.) The installer of the given app querying locales of a package installed by said // installer. // 3.) The current input method querying locales of another package. // 4.) 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 calling app is the // installer of the given app and get locales. Finally check if the calling app is the // current input method. If neither conditions matched, check if the caller has the // necessary permission and fetch locales. if (!isPackageOwnedByCaller(appPackageName, userId) && !isCallerInstaller(appPackageName, userId)) { && !isCallerInstaller(appPackageName, userId) && !isCallerFromCurrentInputMethod(userId)) { enforceReadAppSpecificLocalesPermission(); } final long token = Binder.clearCallingIdentity(); Loading Loading @@ -412,6 +418,26 @@ public class LocaleManagerService extends SystemService { return false; } /** * Checks if the calling app is the current input method. */ private boolean isCallerFromCurrentInputMethod(int userId) { String currentInputMethod = Settings.Secure.getStringForUser( mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD, userId); if (!TextUtils.isEmpty(currentInputMethod)) { String inputMethodPkgName = ComponentName .unflattenFromString(currentInputMethod) .getPackageName(); int inputMethodUid = getPackageUid(inputMethodPkgName, userId); return inputMethodUid >= 0 && UserHandle.isSameApp(Binder.getCallingUid(), inputMethodUid); } return false; } private void enforceReadAppSpecificLocalesPermission() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.READ_APP_SPECIFIC_LOCALES, Loading
services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java +35 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.locales; import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.fail; Loading @@ -35,13 +37,16 @@ import static org.mockito.Mockito.verify; import android.Manifest; import android.app.ActivityManagerInternal; import android.content.ComponentName; import android.content.Context; import android.content.pm.InstallSourceInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.os.Binder; import android.os.LocaleList; import android.provider.Settings; import androidx.test.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.internal.content.PackageMonitor; Loading Loading @@ -111,6 +116,8 @@ public class LocaleManagerServiceTest { doReturn(DEFAULT_USER_ID).when(mMockActivityManager) .handleIncomingUser(anyInt(), anyInt(), eq(DEFAULT_USER_ID), anyBoolean(), anyInt(), anyString(), anyString()); doReturn(InstrumentationRegistry.getContext().getContentResolver()) .when(mMockContext).getContentResolver(); mMockBackupHelper = mock(ShadowLocaleManagerBackupHelper.class); mLocaleManagerService = new LocaleManagerService(mMockContext, mMockActivityTaskManager, Loading Loading @@ -299,6 +306,25 @@ public class LocaleManagerServiceTest { assertEquals(DEFAULT_LOCALES, locales); } @Test public void testGetApplicationLocales_callerIsCurrentInputMethod_returnsLocales() throws Exception { doReturn(DEFAULT_UID).when(mMockPackageManager) .getPackageUidAsUser(anyString(), any(), anyInt()); doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES)) .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt()); String imPkgName = getCurrentInputMethodPackageName(); doReturn(Binder.getCallingUid()).when(mMockPackageManager) .getPackageUidAsUser(eq(imPkgName), any(), 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); } Loading @@ -311,4 +337,13 @@ public class LocaleManagerServiceTest { private void setUpPassingPermissionCheckFor(String permission) { doNothing().when(mMockContext).enforceCallingOrSelfPermission(eq(permission), any()); } private String getCurrentInputMethodPackageName() { String im = Settings.Secure.getString( InstrumentationRegistry.getContext().getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); ComponentName cn = ComponentName.unflattenFromString(im); assertThat(cn).isNotNull(); return cn.getPackageName(); } }
services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java +4 −0 Original line number Diff line number Diff line Loading @@ -47,6 +47,8 @@ import android.util.AtomicFile; import android.util.TypedXmlPullParser; import android.util.Xml; import androidx.test.InstrumentationRegistry; import com.android.internal.content.PackageMonitor; import com.android.internal.util.XmlUtils; import com.android.server.wm.ActivityTaskManagerInternal; Loading Loading @@ -124,6 +126,8 @@ public class SystemAppUpdateTrackerTest { doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager) .getInstallSourceInfo(anyString()); doReturn(mMockPackageManager).when(mMockContext).getPackageManager(); doReturn(InstrumentationRegistry.getContext().getContentResolver()) .when(mMockContext).getContentResolver(); mStoragefile = new AtomicFile(new File( Environment.getExternalStorageDirectory(), "systemUpdateUnitTests.xml")); Loading