diff options
| author | 2022-10-13 14:39:42 +0800 | |
|---|---|---|
| committer | 2022-10-18 22:43:00 +0800 | |
| commit | 4645a257c7bef5c964b44ece77d6f7773e5f33e9 (patch) | |
| tree | 0e5cf95e839218074ac37e904ee04ff4f80eb30c | |
| parent | d4c74dd729da90d7f6503120d41d35cf05668ac3 (diff) | |
[Panlingual] Expose the API for the current IME
Give the current IME the ability to fetch the specific app’s locales in order to enable the corresponding keyboard
Bug: 248406206
Test: atest LocaleManagerServiceTest
atest LocaleManagerTests
Change-Id: I34f59136ede15d094447ce5b637e51c7248635be
4 files changed, 75 insertions, 9 deletions
diff --git a/core/java/android/app/LocaleManager.java b/core/java/android/app/LocaleManager.java index 794c6946f7a8..be53a6249788 100644 --- a/core/java/android/app/LocaleManager.java +++ b/core/java/android/app/LocaleManager.java @@ -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()}. diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java index fc7be7ff8d1c..364f6db28f03 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerService.java +++ b/services/core/java/com/android/server/locales/LocaleManagerService.java @@ -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; @@ -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; @@ -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(); @@ -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, diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java index 1dcdbac8a7c2..dbcd38c35958 100644 --- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java @@ -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; @@ -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; @@ -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, @@ -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); } @@ -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(); + } } diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java index 808b74e31029..853eea133fbd 100644 --- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java @@ -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; @@ -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")); |