Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4645a257 authored by Josh Hou's avatar Josh Hou
Browse files

[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
parent d4c74dd7
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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()}.
+35 −9
Original line number Diff line number Diff line
@@ -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,
+35 −0
Original line number Diff line number Diff line
@@ -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();
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -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"));