Merge "Log data to AppSupportedLocalesChanged atom" into udc-dev
This commit is contained in:
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user