summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Josh Hou <joshhou@google.com> 2022-10-13 14:39:42 +0800
committer Josh Hou <joshhou@google.com> 2022-10-18 22:43:00 +0800
commit4645a257c7bef5c964b44ece77d6f7773e5f33e9 (patch)
tree0e5cf95e839218074ac37e904ee04ff4f80eb30c
parentd4c74dd729da90d7f6503120d41d35cf05668ac3 (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
-rw-r--r--core/java/android/app/LocaleManager.java1
-rw-r--r--services/core/java/com/android/server/locales/LocaleManagerService.java44
-rw-r--r--services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java35
-rw-r--r--services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java4
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"));