From 94fd98eaecd1fe55aec4909a793944ef334208a6 Mon Sep 17 00:00:00 2001 From: Josh Hou Date: Wed, 15 Feb 2023 17:38:10 +0800 Subject: [PATCH] Log data to AppSupportedLocalesChanged atom When setOverrideLocaleConfig is called, the atom AppSupportedLocalesChanged can be logged Bug: 269415842 Test: atest CtsLocaleManagerHostTestCases atest AppSupportedLocalesChangedAtomTest manual test Change-Id: Iabe111189857335de8db9cbb89150fa16900d293 Merged-In: Iabe111189857335de8db9cbb89150fa16900d293 --- core/java/android/app/LocaleConfig.java | 40 ++++++ .../AppSupportedLocalesChangedAtomRecord.java | 72 ++++++++++ .../server/locales/LocaleManagerService.java | 128 ++++++++++++------ 3 files changed, 200 insertions(+), 40 deletions(-) create mode 100644 services/core/java/com/android/server/locales/AppSupportedLocalesChangedAtomRecord.java diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java index 69693fc3977b2..f4cd60d088045 100644 --- a/core/java/android/app/LocaleConfig.java +++ b/core/java/android/app/LocaleConfig.java @@ -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; @@ -262,6 +265,43 @@ public class LocaleConfig implements Parcelable { } }; + /** + * Compare whether the LocaleConfig is the same. + * + *

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 hostStrList = Arrays.asList(mLocales.toLanguageTags().split(",")); + List 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. * diff --git a/services/core/java/com/android/server/locales/AppSupportedLocalesChangedAtomRecord.java b/services/core/java/com/android/server/locales/AppSupportedLocalesChangedAtomRecord.java new file mode 100644 index 0000000000000..de429f008fcf3 --- /dev/null +++ b/services/core/java/com/android/server/locales/AppSupportedLocalesChangedAtomRecord.java @@ -0,0 +1,72 @@ +/* + * 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; + } +} diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java index 99d30f54ce217..e5f589718deb9 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerService.java +++ b/services/core/java/com/android/server/locales/LocaleManagerService.java @@ -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); } @@ -264,7 +264,7 @@ public class LocaleManagerService extends SystemService { Binder.restoreCallingIdentity(token); } } finally { - logMetric(atomRecordForMetrics); + logAppLocalesMetric(atomRecordForMetrics); } } @@ -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. @@ -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( @@ -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, @@ -569,33 +567,39 @@ public class LocaleManagerService extends SystemService { return; } - requireNonNull(appPackageName); - - //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, - "setOverrideLocaleConfig", /* callerPackage= */ null); - - // This function handles two types of set operations: - // 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(); - } - - final long token = Binder.clearCallingIdentity(); + AppSupportedLocalesChangedAtomRecord atomRecord = new AppSupportedLocalesChangedAtomRecord( + Binder.getCallingUid()); try { - setOverrideLocaleConfigUnchecked(appPackageName, userId, localeConfig); - } finally { - Binder.restoreCallingIdentity(token); - } + requireNonNull(appPackageName); - //TODO: Add metrics to monitor the usage by applications + //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, + "setOverrideLocaleConfig", /* callerPackage= */ null); + + // This function handles two types of set operations: + // 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, null, atomRecord)) { + enforceSetAppSpecificLocaleConfigPermission(atomRecord); + } + + final long token = Binder.clearCallingIdentity(); + try { + setOverrideLocaleConfigUnchecked(appPackageName, userId, localeConfig, atomRecord); + } finally { + Binder.restoreCallingIdentity(token); + } + } 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, @@ -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. @@ -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); @@ -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); } @@ -677,10 +706,17 @@ public class LocaleManagerService extends SystemService { } } - private void enforceSetAppSpecificLocaleConfigPermission() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.SET_APP_SPECIFIC_LOCALECONFIG, - "setOverrideLocaleConfig"); + 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; + } } /** @@ -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); + } }