From 8b79e2674a3b14ec8c5868b0974f6b6b38b4e486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Criyaghai=E2=80=9D?= Date: Thu, 21 Oct 2021 06:06:24 +0000 Subject: [PATCH] Report data to ApplicationLocalesChanged atom. Registers atom everytime the API setApplicationLocales is called. Bug: 205502201 Metrics council bug: 204852948 Test: make statsd_testdrive Test: atest ApplicationLocalesChangedAtomTests Test: atest LocaleManagerHostTest Test: atest CtsLocaleManagerTestCases Test: atest WmTests:ActivityTaskManagerServiceTests Change-Id: Ic9ea16c025a0d1ffbbed9851f1f9e27d7740739c --- .../locales/AppLocaleChangedAtomRecord.java | 53 +++++++++ .../server/locales/LocaleManagerService.java | 107 +++++++++++++----- 2 files changed, 132 insertions(+), 28 deletions(-) create mode 100644 services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java diff --git a/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java b/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java new file mode 100644 index 0000000000000..282e3c1d4b00d --- /dev/null +++ b/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2021 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 ApplicationLocalesChanged atom. + */ +public final class AppLocaleChangedAtomRecord { + final int mCallingUid; + int mTargetUid = INVALID_UID; + String mNewLocales = ""; + String mPrevLocales = ""; + int mStatus = FrameworkStatsLog + .APPLICATION_LOCALES_CHANGED__STATUS__STATUS_UNSPECIFIED; + + AppLocaleChangedAtomRecord(int callingUid) { + this.mCallingUid = callingUid; + } + + void setNewLocales(String newLocales) { + this.mNewLocales = newLocales; + } + + void setTargetUid(int targetUid) { + this.mTargetUid = targetUid; + } + + void setPrevLocales(String prevLocales) { + this.mPrevLocales = prevLocales; + } + + 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 6aabdb5b05df2..1657b220d32cf 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerService.java +++ b/services/core/java/com/android/server/locales/LocaleManagerService.java @@ -39,6 +39,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.wm.ActivityTaskManagerInternal; @@ -148,40 +149,52 @@ public class LocaleManagerService extends SystemService { */ public void setApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId, @NonNull LocaleList locales) throws RemoteException, IllegalArgumentException { - requireNonNull(appPackageName); - requireNonNull(locales); - - //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, - "setApplicationLocales", appPackageName); - - // This function handles two types of set operations: - // 1.) A normal, non-privileged app setting its own locale. - // 2.) A privileged system service setting locales of another package. - // The least privileged case is a normal app performing a set, so check that first and - // 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); - if (!isCallerOwner) { - enforceChangeConfigurationPermission(); - } - - final long token = Binder.clearCallingIdentity(); + AppLocaleChangedAtomRecord atomRecordForMetrics = new + AppLocaleChangedAtomRecord(Binder.getCallingUid()); try { - setApplicationLocalesUnchecked(appPackageName, userId, locales); + requireNonNull(appPackageName); + requireNonNull(locales); + atomRecordForMetrics.setNewLocales(locales.toLanguageTags()); + //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, + "setApplicationLocales", appPackageName); + + // This function handles two types of set operations: + // 1.) A normal, non-privileged app setting its own locale. + // 2.) A privileged system service setting locales of another package. + // The least privileged case is a normal app performing a set, so check that first and + // 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); + if (!isCallerOwner) { + enforceChangeConfigurationPermission(atomRecordForMetrics); + } + + final long token = Binder.clearCallingIdentity(); + try { + setApplicationLocalesUnchecked(appPackageName, userId, locales, + atomRecordForMetrics); + } finally { + Binder.restoreCallingIdentity(token); + } } finally { - Binder.restoreCallingIdentity(token); + logMetric(atomRecordForMetrics); } } private void setApplicationLocalesUnchecked(@NonNull String appPackageName, - @UserIdInt int userId, @NonNull LocaleList locales) { + @UserIdInt int userId, @NonNull LocaleList locales, + @NonNull AppLocaleChangedAtomRecord atomRecordForMetrics) { if (DEBUG) { Slog.d(TAG, "setApplicationLocales: setting locales for package " + appPackageName + " and user " + userId); } + + atomRecordForMetrics.setPrevLocales(getApplicationLocalesUnchecked(appPackageName, userId) + .toLanguageTags()); final ActivityTaskManagerInternal.PackageConfigurationUpdater updater = mActivityTaskManagerInternal.createPackageConfigurationUpdater(appPackageName, userId); @@ -194,6 +207,11 @@ public class LocaleManagerService extends SystemService { notifyRegisteredReceivers(appPackageName, userId, locales); mBackupHelper.notifyBackupManager(); + atomRecordForMetrics.setStatus( + FrameworkStatsLog.APPLICATION_LOCALES_CHANGED__STATUS__CONFIG_COMMITTED); + } else { + atomRecordForMetrics.setStatus(FrameworkStatsLog + .APPLICATION_LOCALES_CHANGED__STATUS__CONFIG_UNCOMMITTED); } } @@ -258,27 +276,50 @@ 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) { + private boolean isPackageOwnedByCaller(String appPackageName, int userId, + @Nullable AppLocaleChangedAtomRecord atomRecordForMetrics) { final int uid = mPackageManagerInternal .getPackageUid(appPackageName, /* flags */ 0, 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); + } throw new IllegalArgumentException("Unknown package: " + appPackageName + " for user " + userId); } + if (atomRecordForMetrics != null) { + atomRecordForMetrics.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. return UserHandle.isSameApp(Binder.getCallingUid(), uid); } - private void enforceChangeConfigurationPermission() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_CONFIGURATION, "setApplicationLocales"); + private void enforceChangeConfigurationPermission(@NonNull AppLocaleChangedAtomRecord + atomRecordForMetrics) { + try { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CHANGE_CONFIGURATION, "setApplicationLocales"); + } catch (SecurityException e) { + atomRecordForMetrics.setStatus(FrameworkStatsLog + .APPLICATION_LOCALES_CHANGED__STATUS__FAILURE_PERMISSION_ABSENT); + throw e; + } } /** @@ -312,6 +353,7 @@ public class LocaleManagerService extends SystemService { } } + @NonNull private LocaleList getApplicationLocalesUnchecked(@NonNull String appPackageName, @UserIdInt int userId) { if (DEBUG) { @@ -345,4 +387,13 @@ public class LocaleManagerService extends SystemService { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; // TODO(b/201766221): Implement when there is state. } + + private void logMetric(@NonNull AppLocaleChangedAtomRecord atomRecordForMetrics) { + FrameworkStatsLog.write(FrameworkStatsLog.APPLICATION_LOCALES_CHANGED, + atomRecordForMetrics.mCallingUid, + atomRecordForMetrics.mTargetUid, + atomRecordForMetrics.mNewLocales, + atomRecordForMetrics.mPrevLocales, + atomRecordForMetrics.mStatus); + } }