Loading core/java/android/app/LocaleConfig.java +40 −0 Original line number Diff line number Diff line Loading @@ -38,7 +38,10 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; 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. * Loading services/core/java/com/android/server/locales/AppSupportedLocalesChangedAtomRecord.java 0 → 100644 +72 −0 Original line number 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 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 // the necessary permission and set locales. boolean isCallerOwner = isPackageOwnedByCaller(appPackageName, userId, atomRecordForMetrics); atomRecordForMetrics, null); if (!isCallerOwner) { enforceChangeConfigurationPermission(atomRecordForMetrics); } Loading @@ -264,7 +264,7 @@ public class LocaleManagerService extends SystemService { Binder.restoreCallingIdentity(token); } } finally { logMetric(atomRecordForMetrics); logAppLocalesMetric(atomRecordForMetrics); } } Loading Loading @@ -354,33 +354,31 @@ public class LocaleManagerService extends SystemService { | 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. * * @throws IllegalArgumentException if package not found for given userid */ private boolean isPackageOwnedByCaller(String appPackageName, int userId, @Nullable AppLocaleChangedAtomRecord atomRecordForMetrics) { @Nullable AppLocaleChangedAtomRecord atomRecordForMetrics, @Nullable AppSupportedLocalesChangedAtomRecord appSupportedLocalesChangedAtomRecord) { final int uid = getPackageUid(appPackageName, userId); if (uid < 0) { Slog.w(TAG, "Unknown package " + appPackageName + " for user " + userId); if (atomRecordForMetrics != null) { atomRecordForMetrics.setStatus(FrameworkStatsLog .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 + " for user " + userId); } if (atomRecordForMetrics != null) { atomRecordForMetrics.setTargetUid(uid); } else if (appSupportedLocalesChangedAtomRecord != null) { appSupportedLocalesChangedAtomRecord.setTargetUid(uid); } //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. 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 // neither conditions matched, check if the caller has the necessary permission and fetch // locales. if (!isPackageOwnedByCaller(appPackageName, userId) if (!isPackageOwnedByCaller(appPackageName, userId, null, null) && !isCallerInstaller(appPackageName, userId) && !(isCallerFromCurrentInputMethod(userId) && mActivityManagerInternal.isAppForeground( Loading Loading @@ -550,7 +548,7 @@ public class LocaleManagerService extends SystemService { return systemLocales; } private void logMetric(@NonNull AppLocaleChangedAtomRecord atomRecordForMetrics) { private void logAppLocalesMetric(@NonNull AppLocaleChangedAtomRecord atomRecordForMetrics) { FrameworkStatsLog.write(FrameworkStatsLog.APPLICATION_LOCALES_CHANGED, atomRecordForMetrics.mCallingUid, atomRecordForMetrics.mTargetUid, Loading @@ -569,6 +567,9 @@ public class LocaleManagerService extends SystemService { return; } AppSupportedLocalesChangedAtomRecord atomRecord = new AppSupportedLocalesChangedAtomRecord( Binder.getCallingUid()); try { requireNonNull(appPackageName); //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. // 2.) A privileged system application or service is granted the necessary permission to // override a LocaleConfig of another package. if (!isPackageOwnedByCaller(appPackageName, userId)) { enforceSetAppSpecificLocaleConfigPermission(); if (!isPackageOwnedByCaller(appPackageName, userId, null, atomRecord)) { enforceSetAppSpecificLocaleConfigPermission(atomRecord); } final long token = Binder.clearCallingIdentity(); try { setOverrideLocaleConfigUnchecked(appPackageName, userId, localeConfig); setOverrideLocaleConfigUnchecked(appPackageName, userId, localeConfig, atomRecord); } finally { Binder.restoreCallingIdentity(token); } //TODO: Add metrics to monitor the usage by applications } finally { logAppSupportedLocalesChangedMetric(atomRecord); } } private void setOverrideLocaleConfigUnchecked(@NonNull String appPackageName, @UserIdInt int userId, @Nullable LocaleConfig overridelocaleConfig) { @UserIdInt int userId, @Nullable LocaleConfig overridelocaleConfig, @NonNull AppSupportedLocalesChangedAtomRecord atomRecord) { synchronized (mWriteLock) { if (DEBUG) { Slog.d(TAG, Loading @@ -609,8 +613,18 @@ public class LocaleManagerService extends SystemService { Slog.d(TAG, "remove the override LocaleConfig"); file.delete(); } atomRecord.setOverrideRemoved(true); atomRecord.setStatus(FrameworkStatsLog .APP_SUPPORTED_LOCALES_CHANGED__STATUS__SUCCESS); return; } else { if (overridelocaleConfig.isSameLocaleConfig( getOverrideLocaleConfig(appPackageName, userId))) { Slog.d(TAG, "the same override, ignore it"); atomRecord.setSameAsPrevConfig(true); return; } LocaleList localeList = overridelocaleConfig.getSupportedLocales(); // Normally the LocaleList object should not be null. However we reassign it as the // empty list in case it happens. Loading @@ -621,6 +635,7 @@ public class LocaleManagerService extends SystemService { Slog.d(TAG, "setOverrideLocaleConfig, localeList: " + localeList.toLanguageTags()); } atomRecord.setNumLocales(localeList.size()); // Store the override LocaleConfig to the file storage. final AtomicFile atomicFile = new AtomicFile(file); Loading @@ -633,11 +648,25 @@ public class LocaleManagerService extends SystemService { if (stream != null) { atomicFile.failWrite(stream); } atomRecord.setStatus(FrameworkStatsLog .APP_SUPPORTED_LOCALES_CHANGED__STATUS__FAILURE_WRITE_TO_STORAGE); return; } atomicFile.finishWrite(stream); // Clear per-app locales if they are not in the override LocaleConfig. 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) { 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( android.Manifest.permission.SET_APP_SPECIFIC_LOCALECONFIG, "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); 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 Diff line number Diff line Loading @@ -38,7 +38,10 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; 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. * Loading
services/core/java/com/android/server/locales/AppSupportedLocalesChangedAtomRecord.java 0 → 100644 +72 −0 Original line number 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 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 // the necessary permission and set locales. boolean isCallerOwner = isPackageOwnedByCaller(appPackageName, userId, atomRecordForMetrics); atomRecordForMetrics, null); if (!isCallerOwner) { enforceChangeConfigurationPermission(atomRecordForMetrics); } Loading @@ -264,7 +264,7 @@ public class LocaleManagerService extends SystemService { Binder.restoreCallingIdentity(token); } } finally { logMetric(atomRecordForMetrics); logAppLocalesMetric(atomRecordForMetrics); } } Loading Loading @@ -354,33 +354,31 @@ public class LocaleManagerService extends SystemService { | 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. * * @throws IllegalArgumentException if package not found for given userid */ private boolean isPackageOwnedByCaller(String appPackageName, int userId, @Nullable AppLocaleChangedAtomRecord atomRecordForMetrics) { @Nullable AppLocaleChangedAtomRecord atomRecordForMetrics, @Nullable AppSupportedLocalesChangedAtomRecord appSupportedLocalesChangedAtomRecord) { final int uid = getPackageUid(appPackageName, userId); if (uid < 0) { Slog.w(TAG, "Unknown package " + appPackageName + " for user " + userId); if (atomRecordForMetrics != null) { atomRecordForMetrics.setStatus(FrameworkStatsLog .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 + " for user " + userId); } if (atomRecordForMetrics != null) { atomRecordForMetrics.setTargetUid(uid); } else if (appSupportedLocalesChangedAtomRecord != null) { appSupportedLocalesChangedAtomRecord.setTargetUid(uid); } //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. 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 // neither conditions matched, check if the caller has the necessary permission and fetch // locales. if (!isPackageOwnedByCaller(appPackageName, userId) if (!isPackageOwnedByCaller(appPackageName, userId, null, null) && !isCallerInstaller(appPackageName, userId) && !(isCallerFromCurrentInputMethod(userId) && mActivityManagerInternal.isAppForeground( Loading Loading @@ -550,7 +548,7 @@ public class LocaleManagerService extends SystemService { return systemLocales; } private void logMetric(@NonNull AppLocaleChangedAtomRecord atomRecordForMetrics) { private void logAppLocalesMetric(@NonNull AppLocaleChangedAtomRecord atomRecordForMetrics) { FrameworkStatsLog.write(FrameworkStatsLog.APPLICATION_LOCALES_CHANGED, atomRecordForMetrics.mCallingUid, atomRecordForMetrics.mTargetUid, Loading @@ -569,6 +567,9 @@ public class LocaleManagerService extends SystemService { return; } AppSupportedLocalesChangedAtomRecord atomRecord = new AppSupportedLocalesChangedAtomRecord( Binder.getCallingUid()); try { requireNonNull(appPackageName); //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. // 2.) A privileged system application or service is granted the necessary permission to // override a LocaleConfig of another package. if (!isPackageOwnedByCaller(appPackageName, userId)) { enforceSetAppSpecificLocaleConfigPermission(); if (!isPackageOwnedByCaller(appPackageName, userId, null, atomRecord)) { enforceSetAppSpecificLocaleConfigPermission(atomRecord); } final long token = Binder.clearCallingIdentity(); try { setOverrideLocaleConfigUnchecked(appPackageName, userId, localeConfig); setOverrideLocaleConfigUnchecked(appPackageName, userId, localeConfig, atomRecord); } finally { Binder.restoreCallingIdentity(token); } //TODO: Add metrics to monitor the usage by applications } finally { logAppSupportedLocalesChangedMetric(atomRecord); } } private void setOverrideLocaleConfigUnchecked(@NonNull String appPackageName, @UserIdInt int userId, @Nullable LocaleConfig overridelocaleConfig) { @UserIdInt int userId, @Nullable LocaleConfig overridelocaleConfig, @NonNull AppSupportedLocalesChangedAtomRecord atomRecord) { synchronized (mWriteLock) { if (DEBUG) { Slog.d(TAG, Loading @@ -609,8 +613,18 @@ public class LocaleManagerService extends SystemService { Slog.d(TAG, "remove the override LocaleConfig"); file.delete(); } atomRecord.setOverrideRemoved(true); atomRecord.setStatus(FrameworkStatsLog .APP_SUPPORTED_LOCALES_CHANGED__STATUS__SUCCESS); return; } else { if (overridelocaleConfig.isSameLocaleConfig( getOverrideLocaleConfig(appPackageName, userId))) { Slog.d(TAG, "the same override, ignore it"); atomRecord.setSameAsPrevConfig(true); return; } LocaleList localeList = overridelocaleConfig.getSupportedLocales(); // Normally the LocaleList object should not be null. However we reassign it as the // empty list in case it happens. Loading @@ -621,6 +635,7 @@ public class LocaleManagerService extends SystemService { Slog.d(TAG, "setOverrideLocaleConfig, localeList: " + localeList.toLanguageTags()); } atomRecord.setNumLocales(localeList.size()); // Store the override LocaleConfig to the file storage. final AtomicFile atomicFile = new AtomicFile(file); Loading @@ -633,11 +648,25 @@ public class LocaleManagerService extends SystemService { if (stream != null) { atomicFile.failWrite(stream); } atomRecord.setStatus(FrameworkStatsLog .APP_SUPPORTED_LOCALES_CHANGED__STATUS__FAILURE_WRITE_TO_STORAGE); return; } atomicFile.finishWrite(stream); // Clear per-app locales if they are not in the override LocaleConfig. 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) { 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( android.Manifest.permission.SET_APP_SPECIFIC_LOCALECONFIG, "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); 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); } }