Merge "Log data to AppSupportedLocalesChanged atom"

This commit is contained in:
Josh Hou
2023-02-23 06:13:42 +00:00
committed by Android (Google) Code Review
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.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.
*
* <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.
*

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
// 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);
}
}