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.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.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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
|
// 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user