Merge "Add new LocaleManagerService and its shell commands"
This commit is contained in:
43
core/java/android/app/ILocaleManager.aidl
Normal file
43
core/java/android/app/ILocaleManager.aidl
Normal file
@@ -0,0 +1,43 @@
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
||||
}
|
||||
@@ -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,
|
||||
@@ -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.
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* 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.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
3
services/core/java/com/android/server/locales/OWNERS
Normal file
3
services/core/java/com/android/server/locales/OWNERS
Normal file
@@ -0,0 +1,3 @@
|
||||
roosa@google.com
|
||||
pratyushmore@google.com
|
||||
goldmanj@google.com
|
||||
@@ -133,6 +133,7 @@ import com.android.server.input.InputManagerService;
|
||||
import com.android.server.inputmethod.InputMethodManagerService;
|
||||
import com.android.server.integrity.AppIntegrityManagerService;
|
||||
import com.android.server.lights.LightsService;
|
||||
import com.android.server.locales.LocaleManagerService;
|
||||
import com.android.server.location.LocationManagerService;
|
||||
import com.android.server.media.MediaRouterService;
|
||||
import com.android.server.media.metrics.MediaMetricsManagerService;
|
||||
@@ -1684,6 +1685,15 @@ public final class SystemServer implements Dumpable {
|
||||
mSystemServiceManager.startService(UiModeManagerService.class);
|
||||
t.traceEnd();
|
||||
|
||||
t.traceBegin("StartLocaleManagerService");
|
||||
try {
|
||||
mSystemServiceManager.startService(LocaleManagerService.class);
|
||||
} catch (Throwable e) {
|
||||
reportWtf("starting LocaleManagerService service", e);
|
||||
}
|
||||
t.traceEnd();
|
||||
|
||||
|
||||
if (!mOnlyCore) {
|
||||
t.traceBegin("UpdatePackagesIfNeeded");
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
roosa@google.com
|
||||
pratyushmore@google.com
|
||||
goldmanj@google.com
|
||||
Reference in New Issue
Block a user