[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
This commit is contained in:
@@ -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()}.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
|
||||
Reference in New Issue
Block a user