Loading core/java/android/app/ILocaleManager.aidl 0 → 100644 +43 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.app; import android.os.LocaleList; /** * Internal interface used to control app-specific locales. * * <p>Use the {@link android.app.LocaleManager} class rather than going through * this Binder interface directly. See {@link android.app.LocaleManager} for * more complete documentation. * * @hide */ interface ILocaleManager { /** * Sets a specified app’s app-specific UI locales. */ void setApplicationLocales(String packageName, int userId, in LocaleList locales); /** * Returns the specified app's app-specific locales. */ LocaleList getApplicationLocales(String packageName, int userId); } No newline at end of file core/java/android/content/Context.java +10 −0 Original line number Diff line number Diff line Loading @@ -3801,6 +3801,7 @@ public abstract class Context { //@hide: TIME_ZONE_DETECTOR_SERVICE, PERMISSION_SERVICE, LIGHTS_SERVICE, LOCALE_SERVICE, //@hide: PEOPLE_SERVICE, //@hide: DEVICE_STATE_SERVICE, //@hide: SPEECH_RECOGNITION_SERVICE, Loading Loading @@ -5783,6 +5784,15 @@ public abstract class Context { */ public static final String DISPLAY_HASH_SERVICE = "display_hash"; /** * Use with {@link #getSystemService(String)} to retrieve a * {@link android.app.LocaleManager}. * * @see #getSystemService(String) * @hide */ public static final String LOCALE_SERVICE = "locale"; /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. Loading packages/Shell/AndroidManifest.xml +4 −0 Original line number Diff line number Diff line Loading @@ -464,6 +464,10 @@ <uses-permission android:name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION" /> <uses-permission android:name="android.permission.SUGGEST_EXTERNAL_TIME" /> <!-- Permissions needed for testing locale manager service --> <!-- todo(b/201957547): Add CTS test name when available--> <uses-permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES" /> <!-- Permission required for CTS test - android.server.biometrics --> <uses-permission android:name="android.permission.USE_BIOMETRIC" /> Loading services/core/java/com/android/server/locales/LocaleManagerService.java 0 → 100644 +246 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.locales; import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.ActivityManagerInternal; import android.app.ILocaleManager; import android.content.Context; import android.content.pm.PackageManagerInternal; import android.os.Binder; import android.os.LocaleList; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.UserHandle; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.wm.ActivityTaskManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; /** * The implementation of ILocaleManager.aidl. * * <p>This service is API entry point for storing app-specific UI locales */ public class LocaleManagerService extends SystemService { private static final String TAG = "LocaleManagerService"; private final Context mContext; private final LocaleManagerService.LocaleManagerBinderService mBinderService; private ActivityTaskManagerInternal mActivityTaskManagerInternal; private ActivityManagerInternal mActivityManagerInternal; private PackageManagerInternal mPackageManagerInternal; public static final boolean DEBUG = false; public LocaleManagerService(Context context) { super(context); mContext = context; mBinderService = new LocaleManagerBinderService(); mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class); mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); } @VisibleForTesting LocaleManagerService(Context context, ActivityTaskManagerInternal activityTaskManagerInternal, ActivityManagerInternal activityManagerInternal, PackageManagerInternal packageManagerInternal) { super(context); mContext = context; mBinderService = new LocaleManagerBinderService(); mActivityTaskManagerInternal = activityTaskManagerInternal; mActivityManagerInternal = activityManagerInternal; mPackageManagerInternal = packageManagerInternal; } @Override public void onStart() { publishBinderService(Context.LOCALE_SERVICE, mBinderService); } private final class LocaleManagerBinderService extends ILocaleManager.Stub { @Override public void setApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId, @NonNull LocaleList locales) throws RemoteException { LocaleManagerService.this.setApplicationLocales(appPackageName, userId, locales); } @Override @NonNull public LocaleList getApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId) throws RemoteException { return LocaleManagerService.this.getApplicationLocales(appPackageName, userId); } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { LocaleManagerService.this.dump(fd, pw, args); } @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) { (new LocaleManagerShellCommand(mBinderService)) .exec(this, in, out, err, args, callback, resultReceiver); } } /** * Sets the current UI locales for a specified app. */ public void setApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId, @NonNull LocaleList locales) throws RemoteException, IllegalArgumentException { requireNonNull(appPackageName); requireNonNull(locales); //Allow apps with INTERACT_ACROSS_USERS permission to set locales for different user. userId = mActivityManagerInternal.handleIncomingUser( Binder.getCallingPid(), Binder.getCallingUid(), userId, false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL, "setApplicationLocales", appPackageName); // This function handles two types of set operations: // 1.) A normal, non-privileged app setting its own locale. // 2.) A privileged system service setting locales of another package. // The least privileged case is a normal app performing a set, so check that first and // set locales if the package name is owned by the app. Next, check if the caller has the // necessary permission and set locales. boolean isCallerOwner = isPackageOwnedByCaller(appPackageName, userId); if (!isCallerOwner) { enforceChangeConfigurationPermission(); } final long token = Binder.clearCallingIdentity(); try { setApplicationLocalesUnchecked(appPackageName, userId, locales); } finally { Binder.restoreCallingIdentity(token); } } private void setApplicationLocalesUnchecked(@NonNull String appPackageName, @UserIdInt int userId, @NonNull LocaleList locales) { if (DEBUG) { Slog.d(TAG, "setApplicationLocales: setting locales for package " + appPackageName + " and user " + userId); } final ActivityTaskManagerInternal.PackageConfigurationUpdater updater = mActivityTaskManagerInternal.createPackageConfigurationUpdater(appPackageName, userId); updater.setLocales(locales).commit(); } /** * Checks if the package is owned by the calling app or not for the given user id. * * @throws IllegalArgumentException if package not found for given userid */ private boolean isPackageOwnedByCaller(String appPackageName, int userId) { final int uid = mPackageManagerInternal .getPackageUid(appPackageName, /* flags */ 0, userId); if (uid < 0) { Slog.w(TAG, "Unknown package " + appPackageName + " for user " + userId); throw new IllegalArgumentException("Unknown package: " + appPackageName + " for user " + userId); } //Once valid package found, ignore the userId part for validating package ownership //as apps with INTERACT_ACROSS_USERS permission could be changing locale for different user. return UserHandle.isSameApp(Binder.getCallingUid(), uid); } private void enforceChangeConfigurationPermission() { mContext.enforceCallingPermission( android.Manifest.permission.CHANGE_CONFIGURATION, "setApplicationLocales"); } /** * Returns the current UI locales for the specified app. */ @NonNull public LocaleList getApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId) throws RemoteException, IllegalArgumentException { requireNonNull(appPackageName); //Allow apps with INTERACT_ACROSS_USERS permission to query locales for different user. userId = mActivityManagerInternal.handleIncomingUser( Binder.getCallingPid(), Binder.getCallingUid(), userId, false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL, "getApplicationLocales", appPackageName); // This function handles two types of query operations: // 1.) A normal, non-privileged app querying its own locale. // 2.) 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 caller has the // necessary permission and get locales. if (!isPackageOwnedByCaller(appPackageName, userId)) { enforceReadAppSpecificLocalesPermission(); } final long token = Binder.clearCallingIdentity(); try { return getApplicationLocalesUnchecked(appPackageName, userId); } finally { Binder.restoreCallingIdentity(token); } } private LocaleList getApplicationLocalesUnchecked(@NonNull String appPackageName, @UserIdInt int userId) { if (DEBUG) { Slog.d(TAG, "getApplicationLocales: fetching locales for package " + appPackageName + " and user " + userId); } final ActivityTaskManagerInternal.PackageConfig appConfig = mActivityTaskManagerInternal.getApplicationConfig(appPackageName, userId); if (appConfig == null) { if (DEBUG) { Slog.d(TAG, "getApplicationLocales: application config not found for " + appPackageName + " and user id " + userId); } return LocaleList.getEmptyLocaleList(); } LocaleList locales = appConfig.mLocales; return locales != null ? locales : LocaleList.getEmptyLocaleList(); } private void enforceReadAppSpecificLocalesPermission() { mContext.enforceCallingPermission( android.Manifest.permission.READ_APP_SPECIFIC_LOCALES, "getApplicationLocales"); } /** * Dumps useful info related to service. */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; // TODO(b/201766221): Implement when there is state. } } services/core/java/com/android/server/locales/LocaleManagerShellCommand.java 0 → 100644 +159 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.locales; import android.app.ActivityManager; import android.app.ILocaleManager; import android.os.LocaleList; import android.os.RemoteException; import android.os.ShellCommand; import android.os.UserHandle; import java.io.PrintWriter; /** * Shell commands for {@link LocaleManagerService} */ public class LocaleManagerShellCommand extends ShellCommand { private final ILocaleManager mBinderService; LocaleManagerShellCommand(ILocaleManager localeManager) { mBinderService = localeManager; } @Override public int onCommand(String cmd) { if (cmd == null) { return handleDefaultCommands(cmd); } switch (cmd) { case "set-app-locales": return runSetAppLocales(); case "get-app-locales": return runGetAppLocales(); default: { return handleDefaultCommands(cmd); } } } @Override public void onHelp() { PrintWriter pw = getOutPrintWriter(); pw.println("Locale manager (locale) shell commands:"); pw.println(" help"); pw.println(" Print this help text."); pw.println(" set-app-locales <PACKAGE_NAME> [--user <USER_ID>] [--locales <LOCALE_INFO>]"); pw.println(" Set the locales for the specified app."); pw.println(" --user <USER_ID>: apply for the given user, " + "the current user is used when unspecified."); pw.println(" --locales <LOCALE_INFO>: The language tags of locale to be included " + "as a single String separated by commas"); pw.println(" Empty locale list is used when unspecified."); pw.println(" eg. en,en-US,hi "); pw.println(" get-app-locales <PACKAGE_NAME> [--user <USER_ID>]"); pw.println(" Get the locales for the specified app."); pw.println(" --user <USER_ID>: get for the given user, " + "the current user is used when unspecified."); } private int runSetAppLocales() { final PrintWriter err = getErrPrintWriter(); String packageName = getNextArg(); if (packageName != null) { int userId = ActivityManager.getCurrentUser(); LocaleList locales = LocaleList.getEmptyLocaleList(); do { String option = getNextOption(); if (option == null) { break; } switch (option) { case "--user": { userId = UserHandle.parseUserArg(getNextArgRequired()); break; } case "--locales": { locales = parseLocales(); break; } default: { throw new IllegalArgumentException("Unknown option: " + option); } } } while (true); try { mBinderService.setApplicationLocales(packageName, userId, locales); } catch (RemoteException e) { getOutPrintWriter().println("Remote Exception: " + e); } catch (IllegalArgumentException e) { getOutPrintWriter().println("Unknown package " + packageName + " for userId " + userId); } } else { err.println("Error: no package specified"); return -1; } return 0; } private int runGetAppLocales() { final PrintWriter err = getErrPrintWriter(); String packageName = getNextArg(); if (packageName != null) { int userId = ActivityManager.getCurrentUser(); do { String option = getNextOption(); if (option == null) { break; } if ("--user".equals(option)) { userId = UserHandle.parseUserArg(getNextArgRequired()); break; } else { throw new IllegalArgumentException("Unknown option: " + option); } } while (true); try { LocaleList locales = mBinderService.getApplicationLocales(packageName, userId); getOutPrintWriter().println("Locales for " + packageName + " for user " + userId + " are " + locales); } catch (RemoteException e) { getOutPrintWriter().println("Remote Exception: " + e); } catch (IllegalArgumentException e) { getOutPrintWriter().println("Unknown package " + packageName + " for userId " + userId); } } else { err.println("Error: no package specified"); return -1; } return 0; } private LocaleList parseLocales() { if (getRemainingArgsCount() <= 0) { return LocaleList.getEmptyLocaleList(); } String[] args = peekRemainingArgs(); String inputLocales = args[0]; LocaleList locales = LocaleList.forLanguageTags(inputLocales); return locales; } } Loading
core/java/android/app/ILocaleManager.aidl 0 → 100644 +43 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.app; import android.os.LocaleList; /** * Internal interface used to control app-specific locales. * * <p>Use the {@link android.app.LocaleManager} class rather than going through * this Binder interface directly. See {@link android.app.LocaleManager} for * more complete documentation. * * @hide */ interface ILocaleManager { /** * Sets a specified app’s app-specific UI locales. */ void setApplicationLocales(String packageName, int userId, in LocaleList locales); /** * Returns the specified app's app-specific locales. */ LocaleList getApplicationLocales(String packageName, int userId); } No newline at end of file
core/java/android/content/Context.java +10 −0 Original line number Diff line number Diff line Loading @@ -3801,6 +3801,7 @@ public abstract class Context { //@hide: TIME_ZONE_DETECTOR_SERVICE, PERMISSION_SERVICE, LIGHTS_SERVICE, LOCALE_SERVICE, //@hide: PEOPLE_SERVICE, //@hide: DEVICE_STATE_SERVICE, //@hide: SPEECH_RECOGNITION_SERVICE, Loading Loading @@ -5783,6 +5784,15 @@ public abstract class Context { */ public static final String DISPLAY_HASH_SERVICE = "display_hash"; /** * Use with {@link #getSystemService(String)} to retrieve a * {@link android.app.LocaleManager}. * * @see #getSystemService(String) * @hide */ public static final String LOCALE_SERVICE = "locale"; /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. Loading
packages/Shell/AndroidManifest.xml +4 −0 Original line number Diff line number Diff line Loading @@ -464,6 +464,10 @@ <uses-permission android:name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION" /> <uses-permission android:name="android.permission.SUGGEST_EXTERNAL_TIME" /> <!-- Permissions needed for testing locale manager service --> <!-- todo(b/201957547): Add CTS test name when available--> <uses-permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES" /> <!-- Permission required for CTS test - android.server.biometrics --> <uses-permission android:name="android.permission.USE_BIOMETRIC" /> Loading
services/core/java/com/android/server/locales/LocaleManagerService.java 0 → 100644 +246 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.locales; import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.ActivityManagerInternal; import android.app.ILocaleManager; import android.content.Context; import android.content.pm.PackageManagerInternal; import android.os.Binder; import android.os.LocaleList; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.UserHandle; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.wm.ActivityTaskManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; /** * The implementation of ILocaleManager.aidl. * * <p>This service is API entry point for storing app-specific UI locales */ public class LocaleManagerService extends SystemService { private static final String TAG = "LocaleManagerService"; private final Context mContext; private final LocaleManagerService.LocaleManagerBinderService mBinderService; private ActivityTaskManagerInternal mActivityTaskManagerInternal; private ActivityManagerInternal mActivityManagerInternal; private PackageManagerInternal mPackageManagerInternal; public static final boolean DEBUG = false; public LocaleManagerService(Context context) { super(context); mContext = context; mBinderService = new LocaleManagerBinderService(); mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class); mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); } @VisibleForTesting LocaleManagerService(Context context, ActivityTaskManagerInternal activityTaskManagerInternal, ActivityManagerInternal activityManagerInternal, PackageManagerInternal packageManagerInternal) { super(context); mContext = context; mBinderService = new LocaleManagerBinderService(); mActivityTaskManagerInternal = activityTaskManagerInternal; mActivityManagerInternal = activityManagerInternal; mPackageManagerInternal = packageManagerInternal; } @Override public void onStart() { publishBinderService(Context.LOCALE_SERVICE, mBinderService); } private final class LocaleManagerBinderService extends ILocaleManager.Stub { @Override public void setApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId, @NonNull LocaleList locales) throws RemoteException { LocaleManagerService.this.setApplicationLocales(appPackageName, userId, locales); } @Override @NonNull public LocaleList getApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId) throws RemoteException { return LocaleManagerService.this.getApplicationLocales(appPackageName, userId); } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { LocaleManagerService.this.dump(fd, pw, args); } @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) { (new LocaleManagerShellCommand(mBinderService)) .exec(this, in, out, err, args, callback, resultReceiver); } } /** * Sets the current UI locales for a specified app. */ public void setApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId, @NonNull LocaleList locales) throws RemoteException, IllegalArgumentException { requireNonNull(appPackageName); requireNonNull(locales); //Allow apps with INTERACT_ACROSS_USERS permission to set locales for different user. userId = mActivityManagerInternal.handleIncomingUser( Binder.getCallingPid(), Binder.getCallingUid(), userId, false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL, "setApplicationLocales", appPackageName); // This function handles two types of set operations: // 1.) A normal, non-privileged app setting its own locale. // 2.) A privileged system service setting locales of another package. // The least privileged case is a normal app performing a set, so check that first and // set locales if the package name is owned by the app. Next, check if the caller has the // necessary permission and set locales. boolean isCallerOwner = isPackageOwnedByCaller(appPackageName, userId); if (!isCallerOwner) { enforceChangeConfigurationPermission(); } final long token = Binder.clearCallingIdentity(); try { setApplicationLocalesUnchecked(appPackageName, userId, locales); } finally { Binder.restoreCallingIdentity(token); } } private void setApplicationLocalesUnchecked(@NonNull String appPackageName, @UserIdInt int userId, @NonNull LocaleList locales) { if (DEBUG) { Slog.d(TAG, "setApplicationLocales: setting locales for package " + appPackageName + " and user " + userId); } final ActivityTaskManagerInternal.PackageConfigurationUpdater updater = mActivityTaskManagerInternal.createPackageConfigurationUpdater(appPackageName, userId); updater.setLocales(locales).commit(); } /** * Checks if the package is owned by the calling app or not for the given user id. * * @throws IllegalArgumentException if package not found for given userid */ private boolean isPackageOwnedByCaller(String appPackageName, int userId) { final int uid = mPackageManagerInternal .getPackageUid(appPackageName, /* flags */ 0, userId); if (uid < 0) { Slog.w(TAG, "Unknown package " + appPackageName + " for user " + userId); throw new IllegalArgumentException("Unknown package: " + appPackageName + " for user " + userId); } //Once valid package found, ignore the userId part for validating package ownership //as apps with INTERACT_ACROSS_USERS permission could be changing locale for different user. return UserHandle.isSameApp(Binder.getCallingUid(), uid); } private void enforceChangeConfigurationPermission() { mContext.enforceCallingPermission( android.Manifest.permission.CHANGE_CONFIGURATION, "setApplicationLocales"); } /** * Returns the current UI locales for the specified app. */ @NonNull public LocaleList getApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId) throws RemoteException, IllegalArgumentException { requireNonNull(appPackageName); //Allow apps with INTERACT_ACROSS_USERS permission to query locales for different user. userId = mActivityManagerInternal.handleIncomingUser( Binder.getCallingPid(), Binder.getCallingUid(), userId, false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL, "getApplicationLocales", appPackageName); // This function handles two types of query operations: // 1.) A normal, non-privileged app querying its own locale. // 2.) 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 caller has the // necessary permission and get locales. if (!isPackageOwnedByCaller(appPackageName, userId)) { enforceReadAppSpecificLocalesPermission(); } final long token = Binder.clearCallingIdentity(); try { return getApplicationLocalesUnchecked(appPackageName, userId); } finally { Binder.restoreCallingIdentity(token); } } private LocaleList getApplicationLocalesUnchecked(@NonNull String appPackageName, @UserIdInt int userId) { if (DEBUG) { Slog.d(TAG, "getApplicationLocales: fetching locales for package " + appPackageName + " and user " + userId); } final ActivityTaskManagerInternal.PackageConfig appConfig = mActivityTaskManagerInternal.getApplicationConfig(appPackageName, userId); if (appConfig == null) { if (DEBUG) { Slog.d(TAG, "getApplicationLocales: application config not found for " + appPackageName + " and user id " + userId); } return LocaleList.getEmptyLocaleList(); } LocaleList locales = appConfig.mLocales; return locales != null ? locales : LocaleList.getEmptyLocaleList(); } private void enforceReadAppSpecificLocalesPermission() { mContext.enforceCallingPermission( android.Manifest.permission.READ_APP_SPECIFIC_LOCALES, "getApplicationLocales"); } /** * Dumps useful info related to service. */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; // TODO(b/201766221): Implement when there is state. } }
services/core/java/com/android/server/locales/LocaleManagerShellCommand.java 0 → 100644 +159 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.locales; import android.app.ActivityManager; import android.app.ILocaleManager; import android.os.LocaleList; import android.os.RemoteException; import android.os.ShellCommand; import android.os.UserHandle; import java.io.PrintWriter; /** * Shell commands for {@link LocaleManagerService} */ public class LocaleManagerShellCommand extends ShellCommand { private final ILocaleManager mBinderService; LocaleManagerShellCommand(ILocaleManager localeManager) { mBinderService = localeManager; } @Override public int onCommand(String cmd) { if (cmd == null) { return handleDefaultCommands(cmd); } switch (cmd) { case "set-app-locales": return runSetAppLocales(); case "get-app-locales": return runGetAppLocales(); default: { return handleDefaultCommands(cmd); } } } @Override public void onHelp() { PrintWriter pw = getOutPrintWriter(); pw.println("Locale manager (locale) shell commands:"); pw.println(" help"); pw.println(" Print this help text."); pw.println(" set-app-locales <PACKAGE_NAME> [--user <USER_ID>] [--locales <LOCALE_INFO>]"); pw.println(" Set the locales for the specified app."); pw.println(" --user <USER_ID>: apply for the given user, " + "the current user is used when unspecified."); pw.println(" --locales <LOCALE_INFO>: The language tags of locale to be included " + "as a single String separated by commas"); pw.println(" Empty locale list is used when unspecified."); pw.println(" eg. en,en-US,hi "); pw.println(" get-app-locales <PACKAGE_NAME> [--user <USER_ID>]"); pw.println(" Get the locales for the specified app."); pw.println(" --user <USER_ID>: get for the given user, " + "the current user is used when unspecified."); } private int runSetAppLocales() { final PrintWriter err = getErrPrintWriter(); String packageName = getNextArg(); if (packageName != null) { int userId = ActivityManager.getCurrentUser(); LocaleList locales = LocaleList.getEmptyLocaleList(); do { String option = getNextOption(); if (option == null) { break; } switch (option) { case "--user": { userId = UserHandle.parseUserArg(getNextArgRequired()); break; } case "--locales": { locales = parseLocales(); break; } default: { throw new IllegalArgumentException("Unknown option: " + option); } } } while (true); try { mBinderService.setApplicationLocales(packageName, userId, locales); } catch (RemoteException e) { getOutPrintWriter().println("Remote Exception: " + e); } catch (IllegalArgumentException e) { getOutPrintWriter().println("Unknown package " + packageName + " for userId " + userId); } } else { err.println("Error: no package specified"); return -1; } return 0; } private int runGetAppLocales() { final PrintWriter err = getErrPrintWriter(); String packageName = getNextArg(); if (packageName != null) { int userId = ActivityManager.getCurrentUser(); do { String option = getNextOption(); if (option == null) { break; } if ("--user".equals(option)) { userId = UserHandle.parseUserArg(getNextArgRequired()); break; } else { throw new IllegalArgumentException("Unknown option: " + option); } } while (true); try { LocaleList locales = mBinderService.getApplicationLocales(packageName, userId); getOutPrintWriter().println("Locales for " + packageName + " for user " + userId + " are " + locales); } catch (RemoteException e) { getOutPrintWriter().println("Remote Exception: " + e); } catch (IllegalArgumentException e) { getOutPrintWriter().println("Unknown package " + packageName + " for userId " + userId); } } else { err.println("Error: no package specified"); return -1; } return 0; } private LocaleList parseLocales() { if (getRemainingArgsCount() <= 0) { return LocaleList.getEmptyLocaleList(); } String[] args = peekRemainingArgs(); String inputLocales = args[0]; LocaleList locales = LocaleList.forLanguageTags(inputLocales); return locales; } }