Merge "Log data to AppSupportedLocalesChanged atom" into udc-dev am: 39a8d0c9d1

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21553369

Change-Id: I44a734d1b964fe2fe94c465f5a18a9b5098b0e9e
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Josh Hou
2023-03-01 09:05:03 +00:00
committed by Automerger Merge Worker
3 changed files with 200 additions and 40 deletions

View File

@@ -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;
@@ -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.
* *

View File

@@ -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;
}
}

View File

@@ -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);
} }
@@ -264,7 +264,7 @@ public class LocaleManagerService extends SystemService {
Binder.restoreCallingIdentity(token); Binder.restoreCallingIdentity(token);
} }
} finally { } finally {
logMetric(atomRecordForMetrics); logAppLocalesMetric(atomRecordForMetrics);
} }
} }
@@ -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.
@@ -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(
@@ -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,
@@ -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.
@@ -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,
@@ -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.
@@ -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);
@@ -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);
} }
@@ -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;
}
} }
/** /**
@@ -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);
}
} }