Loading core/java/android/app/LocaleConfig.java +40 −0 Original line number Original line Diff line number Diff line Loading @@ -38,7 +38,10 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Locale; import java.util.Set; import java.util.Set; Loading Loading @@ -262,6 +265,43 @@ public class LocaleConfig implements Parcelable { } } }; }; /** * Compare whether the LocaleConfig is the same. * * <p>If the elements of {@code mLocales} in LocaleConfig are the same but arranged in different * positions, they are also considered to be the same LocaleConfig. * * @param other The {@link LocaleConfig} to compare for. * * @return true if the LocaleConfig is the same, false otherwise. * * @hide */ public boolean isSameLocaleConfig(@Nullable LocaleConfig other) { if (other == this) { return true; } if (other != null) { if (mStatus != other.mStatus) { return false; } LocaleList otherLocales = other.mLocales; if (mLocales == null && otherLocales == null) { return true; } else if (mLocales != null && otherLocales != null) { List<String> hostStrList = Arrays.asList(mLocales.toLanguageTags().split(",")); List<String> targetStrList = Arrays.asList( otherLocales.toLanguageTags().split(",")); Collections.sort(hostStrList); Collections.sort(targetStrList); return hostStrList.equals(targetStrList); } } return false; } /** /** * Compare whether the locale is existed in the {@code mLocales} of the LocaleConfig. * Compare whether the locale is existed in the {@code mLocales} of the LocaleConfig. * * Loading services/core/java/com/android/server/locales/AppSupportedLocalesChangedAtomRecord.java 0 → 100644 +72 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2023 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 android.os.Process.INVALID_UID; import com.android.internal.util.FrameworkStatsLog; /** * Holds data used to report the AppSupportedLocalesChanged atom. */ public final class AppSupportedLocalesChangedAtomRecord { // The uid which invoked this update. final int mCallingUid; // The uid for which the override of app’s supported locales change is being done. int mTargetUid = INVALID_UID; // The total number of locales in the override LocaleConfig. int mNumLocales = -1; // Whether the override is removed LocaleConfig from the storage. boolean mOverrideRemoved = false; // Whether the new override LocaleConfig is the same as the app’s LocaleConfig. boolean mSameAsResConfig = false; // Whether the new override LocaleConfig is the same as the previously effective one. This means // a comparison with the previous override LocaleConfig if there was one, and a comparison with // the resource LocaleConfig if no override was present. boolean mSameAsPrevConfig = false; // Application supported locales changed status. int mStatus = FrameworkStatsLog .APP_SUPPORTED_LOCALES_CHANGED__STATUS__STATUS_UNSPECIFIED; AppSupportedLocalesChangedAtomRecord(int callingUid) { this.mCallingUid = callingUid; } void setTargetUid(int targetUid) { this.mTargetUid = targetUid; } void setNumLocales(int numLocales) { this.mNumLocales = numLocales; } void setOverrideRemoved(boolean overrideRemoved) { this.mOverrideRemoved = overrideRemoved; } void setSameAsResConfig(boolean sameAsResConfig) { this.mSameAsResConfig = sameAsResConfig; } void setSameAsPrevConfig(boolean sameAsPrevConfig) { this.mSameAsPrevConfig = sameAsPrevConfig; } void setStatus(int status) { this.mStatus = status; } } services/core/java/com/android/server/locales/LocaleManagerService.java +85 −37 Original line number Original line Diff line number Diff line Loading @@ -250,7 +250,7 @@ public class LocaleManagerService extends SystemService { // set locales if the package name is owned by the app. Next, check if the caller has // set locales if the package name is owned by the app. Next, check if the caller has // the necessary permission and set locales. // the necessary permission and set locales. boolean isCallerOwner = isPackageOwnedByCaller(appPackageName, userId, boolean isCallerOwner = isPackageOwnedByCaller(appPackageName, userId, atomRecordForMetrics); atomRecordForMetrics, null); if (!isCallerOwner) { if (!isCallerOwner) { enforceChangeConfigurationPermission(atomRecordForMetrics); enforceChangeConfigurationPermission(atomRecordForMetrics); } } Loading @@ -264,7 +264,7 @@ public class LocaleManagerService extends SystemService { Binder.restoreCallingIdentity(token); Binder.restoreCallingIdentity(token); } } } finally { } finally { logMetric(atomRecordForMetrics); logAppLocalesMetric(atomRecordForMetrics); } } } } Loading Loading @@ -354,33 +354,31 @@ public class LocaleManagerService extends SystemService { | Intent.FLAG_RECEIVER_FOREGROUND); | Intent.FLAG_RECEIVER_FOREGROUND); } } /** * Same as {@link LocaleManagerService#isPackageOwnedByCaller(String, int, * AppLocaleChangedAtomRecord)}, but for methods that do not log locale atom. */ private boolean isPackageOwnedByCaller(String appPackageName, int userId) { return isPackageOwnedByCaller(appPackageName, userId, /* atomRecordForMetrics= */null); } /** /** * Checks if the package is owned by the calling app or not for the given user id. * 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 * @throws IllegalArgumentException if package not found for given userid */ */ private boolean isPackageOwnedByCaller(String appPackageName, int userId, private boolean isPackageOwnedByCaller(String appPackageName, int userId, @Nullable AppLocaleChangedAtomRecord atomRecordForMetrics) { @Nullable AppLocaleChangedAtomRecord atomRecordForMetrics, @Nullable AppSupportedLocalesChangedAtomRecord appSupportedLocalesChangedAtomRecord) { final int uid = getPackageUid(appPackageName, userId); final int uid = getPackageUid(appPackageName, userId); if (uid < 0) { if (uid < 0) { Slog.w(TAG, "Unknown package " + appPackageName + " for user " + userId); Slog.w(TAG, "Unknown package " + appPackageName + " for user " + userId); if (atomRecordForMetrics != null) { if (atomRecordForMetrics != null) { atomRecordForMetrics.setStatus(FrameworkStatsLog atomRecordForMetrics.setStatus(FrameworkStatsLog .APPLICATION_LOCALES_CHANGED__STATUS__FAILURE_INVALID_TARGET_PACKAGE); .APPLICATION_LOCALES_CHANGED__STATUS__FAILURE_INVALID_TARGET_PACKAGE); } else if (appSupportedLocalesChangedAtomRecord != null) { appSupportedLocalesChangedAtomRecord.setStatus(FrameworkStatsLog .APP_SUPPORTED_LOCALES_CHANGED__STATUS__FAILURE_INVALID_TARGET_PACKAGE); } } throw new IllegalArgumentException("Unknown package: " + appPackageName throw new IllegalArgumentException("Unknown package: " + appPackageName + " for user " + userId); + " for user " + userId); } } if (atomRecordForMetrics != null) { if (atomRecordForMetrics != null) { atomRecordForMetrics.setTargetUid(uid); atomRecordForMetrics.setTargetUid(uid); } else if (appSupportedLocalesChangedAtomRecord != null) { appSupportedLocalesChangedAtomRecord.setTargetUid(uid); } } //Once valid package found, ignore the userId part for validating package ownership //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. //as apps with INTERACT_ACROSS_USERS permission could be changing locale for different user. Loading Loading @@ -425,7 +423,7 @@ public class LocaleManagerService extends SystemService { // current input method, and that app is querying locales of the current foreground app. If // current input method, and that app is querying locales of the current foreground app. If // neither conditions matched, check if the caller has the necessary permission and fetch // neither conditions matched, check if the caller has the necessary permission and fetch // locales. // locales. if (!isPackageOwnedByCaller(appPackageName, userId) if (!isPackageOwnedByCaller(appPackageName, userId, null, null) && !isCallerInstaller(appPackageName, userId) && !isCallerInstaller(appPackageName, userId) && !(isCallerFromCurrentInputMethod(userId) && !(isCallerFromCurrentInputMethod(userId) && mActivityManagerInternal.isAppForeground( && mActivityManagerInternal.isAppForeground( Loading Loading @@ -550,7 +548,7 @@ public class LocaleManagerService extends SystemService { return systemLocales; return systemLocales; } } private void logMetric(@NonNull AppLocaleChangedAtomRecord atomRecordForMetrics) { private void logAppLocalesMetric(@NonNull AppLocaleChangedAtomRecord atomRecordForMetrics) { FrameworkStatsLog.write(FrameworkStatsLog.APPLICATION_LOCALES_CHANGED, FrameworkStatsLog.write(FrameworkStatsLog.APPLICATION_LOCALES_CHANGED, atomRecordForMetrics.mCallingUid, atomRecordForMetrics.mCallingUid, atomRecordForMetrics.mTargetUid, atomRecordForMetrics.mTargetUid, Loading @@ -569,6 +567,9 @@ public class LocaleManagerService extends SystemService { return; return; } } AppSupportedLocalesChangedAtomRecord atomRecord = new AppSupportedLocalesChangedAtomRecord( Binder.getCallingUid()); try { requireNonNull(appPackageName); requireNonNull(appPackageName); //Allow apps with INTERACT_ACROSS_USERS permission to set locales for different user. //Allow apps with INTERACT_ACROSS_USERS permission to set locales for different user. Loading @@ -581,21 +582,24 @@ public class LocaleManagerService extends SystemService { // 1.) A normal, an app overrides its own LocaleConfig. // 1.) A normal, an app overrides its own LocaleConfig. // 2.) A privileged system application or service is granted the necessary permission to // 2.) A privileged system application or service is granted the necessary permission to // override a LocaleConfig of another package. // override a LocaleConfig of another package. if (!isPackageOwnedByCaller(appPackageName, userId)) { if (!isPackageOwnedByCaller(appPackageName, userId, null, atomRecord)) { enforceSetAppSpecificLocaleConfigPermission(); enforceSetAppSpecificLocaleConfigPermission(atomRecord); } } final long token = Binder.clearCallingIdentity(); final long token = Binder.clearCallingIdentity(); try { try { setOverrideLocaleConfigUnchecked(appPackageName, userId, localeConfig); setOverrideLocaleConfigUnchecked(appPackageName, userId, localeConfig, atomRecord); } finally { } finally { Binder.restoreCallingIdentity(token); Binder.restoreCallingIdentity(token); } } } finally { //TODO: Add metrics to monitor the usage by applications logAppSupportedLocalesChangedMetric(atomRecord); } } } private void setOverrideLocaleConfigUnchecked(@NonNull String appPackageName, private void setOverrideLocaleConfigUnchecked(@NonNull String appPackageName, @UserIdInt int userId, @Nullable LocaleConfig overridelocaleConfig) { @UserIdInt int userId, @Nullable LocaleConfig overridelocaleConfig, @NonNull AppSupportedLocalesChangedAtomRecord atomRecord) { synchronized (mWriteLock) { synchronized (mWriteLock) { if (DEBUG) { if (DEBUG) { Slog.d(TAG, Slog.d(TAG, Loading @@ -609,8 +613,18 @@ public class LocaleManagerService extends SystemService { Slog.d(TAG, "remove the override LocaleConfig"); Slog.d(TAG, "remove the override LocaleConfig"); file.delete(); file.delete(); } } atomRecord.setOverrideRemoved(true); atomRecord.setStatus(FrameworkStatsLog .APP_SUPPORTED_LOCALES_CHANGED__STATUS__SUCCESS); return; return; } else { } else { if (overridelocaleConfig.isSameLocaleConfig( getOverrideLocaleConfig(appPackageName, userId))) { Slog.d(TAG, "the same override, ignore it"); atomRecord.setSameAsPrevConfig(true); return; } LocaleList localeList = overridelocaleConfig.getSupportedLocales(); LocaleList localeList = overridelocaleConfig.getSupportedLocales(); // Normally the LocaleList object should not be null. However we reassign it as the // Normally the LocaleList object should not be null. However we reassign it as the // empty list in case it happens. // empty list in case it happens. Loading @@ -621,6 +635,7 @@ public class LocaleManagerService extends SystemService { Slog.d(TAG, Slog.d(TAG, "setOverrideLocaleConfig, localeList: " + localeList.toLanguageTags()); "setOverrideLocaleConfig, localeList: " + localeList.toLanguageTags()); } } atomRecord.setNumLocales(localeList.size()); // Store the override LocaleConfig to the file storage. // Store the override LocaleConfig to the file storage. final AtomicFile atomicFile = new AtomicFile(file); final AtomicFile atomicFile = new AtomicFile(file); Loading @@ -633,11 +648,25 @@ public class LocaleManagerService extends SystemService { if (stream != null) { if (stream != null) { atomicFile.failWrite(stream); atomicFile.failWrite(stream); } } atomRecord.setStatus(FrameworkStatsLog .APP_SUPPORTED_LOCALES_CHANGED__STATUS__FAILURE_WRITE_TO_STORAGE); return; return; } } atomicFile.finishWrite(stream); atomicFile.finishWrite(stream); // Clear per-app locales if they are not in the override LocaleConfig. // Clear per-app locales if they are not in the override LocaleConfig. removeUnsupportedAppLocales(appPackageName, userId, overridelocaleConfig); removeUnsupportedAppLocales(appPackageName, userId, overridelocaleConfig); try { Context appContext = mContext.createPackageContext(appPackageName, 0); if (overridelocaleConfig.isSameLocaleConfig( LocaleConfig.fromContextIgnoringOverride(appContext))) { Slog.d(TAG, "setOverrideLocaleConfig, same as the app's LocaleConfig"); atomRecord.setSameAsResConfig(true); } } catch (PackageManager.NameNotFoundException e) { Slog.e(TAG, "Unknown package name " + appPackageName); } atomRecord.setStatus(FrameworkStatsLog .APP_SUPPORTED_LOCALES_CHANGED__STATUS__SUCCESS); if (DEBUG) { if (DEBUG) { Slog.i(TAG, "Successfully written to " + atomicFile); Slog.i(TAG, "Successfully written to " + atomicFile); } } Loading Loading @@ -677,10 +706,17 @@ public class LocaleManagerService extends SystemService { } } } } private void enforceSetAppSpecificLocaleConfigPermission() { private void enforceSetAppSpecificLocaleConfigPermission( AppSupportedLocalesChangedAtomRecord atomRecord) { try { mContext.enforceCallingOrSelfPermission( mContext.enforceCallingOrSelfPermission( android.Manifest.permission.SET_APP_SPECIFIC_LOCALECONFIG, android.Manifest.permission.SET_APP_SPECIFIC_LOCALECONFIG, "setOverrideLocaleConfig"); "setOverrideLocaleConfig"); } catch (SecurityException e) { atomRecord.setStatus(FrameworkStatsLog .APP_SUPPORTED_LOCALES_CHANGED__STATUS__FAILURE_PERMISSION_ABSENT); throw e; } } } /** /** Loading Loading @@ -796,4 +832,16 @@ public class LocaleManagerService extends SystemService { final File dir = new File(Environment.getDataSystemDeDirectory(userId), LOCALE_CONFIGS); final File dir = new File(Environment.getDataSystemDeDirectory(userId), LOCALE_CONFIGS); return new File(dir, appPackageName + SUFFIX_FILE_NAME); return new File(dir, appPackageName + SUFFIX_FILE_NAME); } } private void logAppSupportedLocalesChangedMetric( @NonNull AppSupportedLocalesChangedAtomRecord atomRecord) { FrameworkStatsLog.write(FrameworkStatsLog.APP_SUPPORTED_LOCALES_CHANGED, atomRecord.mCallingUid, atomRecord.mTargetUid, atomRecord.mNumLocales, atomRecord.mOverrideRemoved, atomRecord.mSameAsResConfig, atomRecord.mSameAsPrevConfig, atomRecord.mStatus); } } } Loading
core/java/android/app/LocaleConfig.java +40 −0 Original line number Original line Diff line number Diff line Loading @@ -38,7 +38,10 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Locale; import java.util.Set; import java.util.Set; Loading Loading @@ -262,6 +265,43 @@ public class LocaleConfig implements Parcelable { } } }; }; /** * Compare whether the LocaleConfig is the same. * * <p>If the elements of {@code mLocales} in LocaleConfig are the same but arranged in different * positions, they are also considered to be the same LocaleConfig. * * @param other The {@link LocaleConfig} to compare for. * * @return true if the LocaleConfig is the same, false otherwise. * * @hide */ public boolean isSameLocaleConfig(@Nullable LocaleConfig other) { if (other == this) { return true; } if (other != null) { if (mStatus != other.mStatus) { return false; } LocaleList otherLocales = other.mLocales; if (mLocales == null && otherLocales == null) { return true; } else if (mLocales != null && otherLocales != null) { List<String> hostStrList = Arrays.asList(mLocales.toLanguageTags().split(",")); List<String> targetStrList = Arrays.asList( otherLocales.toLanguageTags().split(",")); Collections.sort(hostStrList); Collections.sort(targetStrList); return hostStrList.equals(targetStrList); } } return false; } /** /** * Compare whether the locale is existed in the {@code mLocales} of the LocaleConfig. * Compare whether the locale is existed in the {@code mLocales} of the LocaleConfig. * * Loading
services/core/java/com/android/server/locales/AppSupportedLocalesChangedAtomRecord.java 0 → 100644 +72 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2023 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 android.os.Process.INVALID_UID; import com.android.internal.util.FrameworkStatsLog; /** * Holds data used to report the AppSupportedLocalesChanged atom. */ public final class AppSupportedLocalesChangedAtomRecord { // The uid which invoked this update. final int mCallingUid; // The uid for which the override of app’s supported locales change is being done. int mTargetUid = INVALID_UID; // The total number of locales in the override LocaleConfig. int mNumLocales = -1; // Whether the override is removed LocaleConfig from the storage. boolean mOverrideRemoved = false; // Whether the new override LocaleConfig is the same as the app’s LocaleConfig. boolean mSameAsResConfig = false; // Whether the new override LocaleConfig is the same as the previously effective one. This means // a comparison with the previous override LocaleConfig if there was one, and a comparison with // the resource LocaleConfig if no override was present. boolean mSameAsPrevConfig = false; // Application supported locales changed status. int mStatus = FrameworkStatsLog .APP_SUPPORTED_LOCALES_CHANGED__STATUS__STATUS_UNSPECIFIED; AppSupportedLocalesChangedAtomRecord(int callingUid) { this.mCallingUid = callingUid; } void setTargetUid(int targetUid) { this.mTargetUid = targetUid; } void setNumLocales(int numLocales) { this.mNumLocales = numLocales; } void setOverrideRemoved(boolean overrideRemoved) { this.mOverrideRemoved = overrideRemoved; } void setSameAsResConfig(boolean sameAsResConfig) { this.mSameAsResConfig = sameAsResConfig; } void setSameAsPrevConfig(boolean sameAsPrevConfig) { this.mSameAsPrevConfig = sameAsPrevConfig; } void setStatus(int status) { this.mStatus = status; } }
services/core/java/com/android/server/locales/LocaleManagerService.java +85 −37 Original line number Original line Diff line number Diff line Loading @@ -250,7 +250,7 @@ public class LocaleManagerService extends SystemService { // set locales if the package name is owned by the app. Next, check if the caller has // set locales if the package name is owned by the app. Next, check if the caller has // the necessary permission and set locales. // the necessary permission and set locales. boolean isCallerOwner = isPackageOwnedByCaller(appPackageName, userId, boolean isCallerOwner = isPackageOwnedByCaller(appPackageName, userId, atomRecordForMetrics); atomRecordForMetrics, null); if (!isCallerOwner) { if (!isCallerOwner) { enforceChangeConfigurationPermission(atomRecordForMetrics); enforceChangeConfigurationPermission(atomRecordForMetrics); } } Loading @@ -264,7 +264,7 @@ public class LocaleManagerService extends SystemService { Binder.restoreCallingIdentity(token); Binder.restoreCallingIdentity(token); } } } finally { } finally { logMetric(atomRecordForMetrics); logAppLocalesMetric(atomRecordForMetrics); } } } } Loading Loading @@ -354,33 +354,31 @@ public class LocaleManagerService extends SystemService { | Intent.FLAG_RECEIVER_FOREGROUND); | Intent.FLAG_RECEIVER_FOREGROUND); } } /** * Same as {@link LocaleManagerService#isPackageOwnedByCaller(String, int, * AppLocaleChangedAtomRecord)}, but for methods that do not log locale atom. */ private boolean isPackageOwnedByCaller(String appPackageName, int userId) { return isPackageOwnedByCaller(appPackageName, userId, /* atomRecordForMetrics= */null); } /** /** * Checks if the package is owned by the calling app or not for the given user id. * 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 * @throws IllegalArgumentException if package not found for given userid */ */ private boolean isPackageOwnedByCaller(String appPackageName, int userId, private boolean isPackageOwnedByCaller(String appPackageName, int userId, @Nullable AppLocaleChangedAtomRecord atomRecordForMetrics) { @Nullable AppLocaleChangedAtomRecord atomRecordForMetrics, @Nullable AppSupportedLocalesChangedAtomRecord appSupportedLocalesChangedAtomRecord) { final int uid = getPackageUid(appPackageName, userId); final int uid = getPackageUid(appPackageName, userId); if (uid < 0) { if (uid < 0) { Slog.w(TAG, "Unknown package " + appPackageName + " for user " + userId); Slog.w(TAG, "Unknown package " + appPackageName + " for user " + userId); if (atomRecordForMetrics != null) { if (atomRecordForMetrics != null) { atomRecordForMetrics.setStatus(FrameworkStatsLog atomRecordForMetrics.setStatus(FrameworkStatsLog .APPLICATION_LOCALES_CHANGED__STATUS__FAILURE_INVALID_TARGET_PACKAGE); .APPLICATION_LOCALES_CHANGED__STATUS__FAILURE_INVALID_TARGET_PACKAGE); } else if (appSupportedLocalesChangedAtomRecord != null) { appSupportedLocalesChangedAtomRecord.setStatus(FrameworkStatsLog .APP_SUPPORTED_LOCALES_CHANGED__STATUS__FAILURE_INVALID_TARGET_PACKAGE); } } throw new IllegalArgumentException("Unknown package: " + appPackageName throw new IllegalArgumentException("Unknown package: " + appPackageName + " for user " + userId); + " for user " + userId); } } if (atomRecordForMetrics != null) { if (atomRecordForMetrics != null) { atomRecordForMetrics.setTargetUid(uid); atomRecordForMetrics.setTargetUid(uid); } else if (appSupportedLocalesChangedAtomRecord != null) { appSupportedLocalesChangedAtomRecord.setTargetUid(uid); } } //Once valid package found, ignore the userId part for validating package ownership //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. //as apps with INTERACT_ACROSS_USERS permission could be changing locale for different user. Loading Loading @@ -425,7 +423,7 @@ public class LocaleManagerService extends SystemService { // current input method, and that app is querying locales of the current foreground app. If // current input method, and that app is querying locales of the current foreground app. If // neither conditions matched, check if the caller has the necessary permission and fetch // neither conditions matched, check if the caller has the necessary permission and fetch // locales. // locales. if (!isPackageOwnedByCaller(appPackageName, userId) if (!isPackageOwnedByCaller(appPackageName, userId, null, null) && !isCallerInstaller(appPackageName, userId) && !isCallerInstaller(appPackageName, userId) && !(isCallerFromCurrentInputMethod(userId) && !(isCallerFromCurrentInputMethod(userId) && mActivityManagerInternal.isAppForeground( && mActivityManagerInternal.isAppForeground( Loading Loading @@ -550,7 +548,7 @@ public class LocaleManagerService extends SystemService { return systemLocales; return systemLocales; } } private void logMetric(@NonNull AppLocaleChangedAtomRecord atomRecordForMetrics) { private void logAppLocalesMetric(@NonNull AppLocaleChangedAtomRecord atomRecordForMetrics) { FrameworkStatsLog.write(FrameworkStatsLog.APPLICATION_LOCALES_CHANGED, FrameworkStatsLog.write(FrameworkStatsLog.APPLICATION_LOCALES_CHANGED, atomRecordForMetrics.mCallingUid, atomRecordForMetrics.mCallingUid, atomRecordForMetrics.mTargetUid, atomRecordForMetrics.mTargetUid, Loading @@ -569,6 +567,9 @@ public class LocaleManagerService extends SystemService { return; return; } } AppSupportedLocalesChangedAtomRecord atomRecord = new AppSupportedLocalesChangedAtomRecord( Binder.getCallingUid()); try { requireNonNull(appPackageName); requireNonNull(appPackageName); //Allow apps with INTERACT_ACROSS_USERS permission to set locales for different user. //Allow apps with INTERACT_ACROSS_USERS permission to set locales for different user. Loading @@ -581,21 +582,24 @@ public class LocaleManagerService extends SystemService { // 1.) A normal, an app overrides its own LocaleConfig. // 1.) A normal, an app overrides its own LocaleConfig. // 2.) A privileged system application or service is granted the necessary permission to // 2.) A privileged system application or service is granted the necessary permission to // override a LocaleConfig of another package. // override a LocaleConfig of another package. if (!isPackageOwnedByCaller(appPackageName, userId)) { if (!isPackageOwnedByCaller(appPackageName, userId, null, atomRecord)) { enforceSetAppSpecificLocaleConfigPermission(); enforceSetAppSpecificLocaleConfigPermission(atomRecord); } } final long token = Binder.clearCallingIdentity(); final long token = Binder.clearCallingIdentity(); try { try { setOverrideLocaleConfigUnchecked(appPackageName, userId, localeConfig); setOverrideLocaleConfigUnchecked(appPackageName, userId, localeConfig, atomRecord); } finally { } finally { Binder.restoreCallingIdentity(token); Binder.restoreCallingIdentity(token); } } } finally { //TODO: Add metrics to monitor the usage by applications logAppSupportedLocalesChangedMetric(atomRecord); } } } private void setOverrideLocaleConfigUnchecked(@NonNull String appPackageName, private void setOverrideLocaleConfigUnchecked(@NonNull String appPackageName, @UserIdInt int userId, @Nullable LocaleConfig overridelocaleConfig) { @UserIdInt int userId, @Nullable LocaleConfig overridelocaleConfig, @NonNull AppSupportedLocalesChangedAtomRecord atomRecord) { synchronized (mWriteLock) { synchronized (mWriteLock) { if (DEBUG) { if (DEBUG) { Slog.d(TAG, Slog.d(TAG, Loading @@ -609,8 +613,18 @@ public class LocaleManagerService extends SystemService { Slog.d(TAG, "remove the override LocaleConfig"); Slog.d(TAG, "remove the override LocaleConfig"); file.delete(); file.delete(); } } atomRecord.setOverrideRemoved(true); atomRecord.setStatus(FrameworkStatsLog .APP_SUPPORTED_LOCALES_CHANGED__STATUS__SUCCESS); return; return; } else { } else { if (overridelocaleConfig.isSameLocaleConfig( getOverrideLocaleConfig(appPackageName, userId))) { Slog.d(TAG, "the same override, ignore it"); atomRecord.setSameAsPrevConfig(true); return; } LocaleList localeList = overridelocaleConfig.getSupportedLocales(); LocaleList localeList = overridelocaleConfig.getSupportedLocales(); // Normally the LocaleList object should not be null. However we reassign it as the // Normally the LocaleList object should not be null. However we reassign it as the // empty list in case it happens. // empty list in case it happens. Loading @@ -621,6 +635,7 @@ public class LocaleManagerService extends SystemService { Slog.d(TAG, Slog.d(TAG, "setOverrideLocaleConfig, localeList: " + localeList.toLanguageTags()); "setOverrideLocaleConfig, localeList: " + localeList.toLanguageTags()); } } atomRecord.setNumLocales(localeList.size()); // Store the override LocaleConfig to the file storage. // Store the override LocaleConfig to the file storage. final AtomicFile atomicFile = new AtomicFile(file); final AtomicFile atomicFile = new AtomicFile(file); Loading @@ -633,11 +648,25 @@ public class LocaleManagerService extends SystemService { if (stream != null) { if (stream != null) { atomicFile.failWrite(stream); atomicFile.failWrite(stream); } } atomRecord.setStatus(FrameworkStatsLog .APP_SUPPORTED_LOCALES_CHANGED__STATUS__FAILURE_WRITE_TO_STORAGE); return; return; } } atomicFile.finishWrite(stream); atomicFile.finishWrite(stream); // Clear per-app locales if they are not in the override LocaleConfig. // Clear per-app locales if they are not in the override LocaleConfig. removeUnsupportedAppLocales(appPackageName, userId, overridelocaleConfig); removeUnsupportedAppLocales(appPackageName, userId, overridelocaleConfig); try { Context appContext = mContext.createPackageContext(appPackageName, 0); if (overridelocaleConfig.isSameLocaleConfig( LocaleConfig.fromContextIgnoringOverride(appContext))) { Slog.d(TAG, "setOverrideLocaleConfig, same as the app's LocaleConfig"); atomRecord.setSameAsResConfig(true); } } catch (PackageManager.NameNotFoundException e) { Slog.e(TAG, "Unknown package name " + appPackageName); } atomRecord.setStatus(FrameworkStatsLog .APP_SUPPORTED_LOCALES_CHANGED__STATUS__SUCCESS); if (DEBUG) { if (DEBUG) { Slog.i(TAG, "Successfully written to " + atomicFile); Slog.i(TAG, "Successfully written to " + atomicFile); } } Loading Loading @@ -677,10 +706,17 @@ public class LocaleManagerService extends SystemService { } } } } private void enforceSetAppSpecificLocaleConfigPermission() { private void enforceSetAppSpecificLocaleConfigPermission( AppSupportedLocalesChangedAtomRecord atomRecord) { try { mContext.enforceCallingOrSelfPermission( mContext.enforceCallingOrSelfPermission( android.Manifest.permission.SET_APP_SPECIFIC_LOCALECONFIG, android.Manifest.permission.SET_APP_SPECIFIC_LOCALECONFIG, "setOverrideLocaleConfig"); "setOverrideLocaleConfig"); } catch (SecurityException e) { atomRecord.setStatus(FrameworkStatsLog .APP_SUPPORTED_LOCALES_CHANGED__STATUS__FAILURE_PERMISSION_ABSENT); throw e; } } } /** /** Loading Loading @@ -796,4 +832,16 @@ public class LocaleManagerService extends SystemService { final File dir = new File(Environment.getDataSystemDeDirectory(userId), LOCALE_CONFIGS); final File dir = new File(Environment.getDataSystemDeDirectory(userId), LOCALE_CONFIGS); return new File(dir, appPackageName + SUFFIX_FILE_NAME); return new File(dir, appPackageName + SUFFIX_FILE_NAME); } } private void logAppSupportedLocalesChangedMetric( @NonNull AppSupportedLocalesChangedAtomRecord atomRecord) { FrameworkStatsLog.write(FrameworkStatsLog.APP_SUPPORTED_LOCALES_CHANGED, atomRecord.mCallingUid, atomRecord.mTargetUid, atomRecord.mNumLocales, atomRecord.mOverrideRemoved, atomRecord.mSameAsResConfig, atomRecord.mSameAsPrevConfig, atomRecord.mStatus); } } }