Loading services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java 0 → 100644 +53 −0 Original line number Diff line number Diff line /* * 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; } } services/core/java/com/android/server/locales/LocaleManagerService.java +79 −28 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -148,9 +149,12 @@ public class LocaleManagerService extends SystemService { */ public void setApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId, @NonNull LocaleList locales) throws RemoteException, IllegalArgumentException { AppLocaleChangedAtomRecord atomRecordForMetrics = new AppLocaleChangedAtomRecord(Binder.getCallingUid()); try { 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, Loading @@ -161,27 +165,36 @@ public class LocaleManagerService extends SystemService { // 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); // 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(); enforceChangeConfigurationPermission(atomRecordForMetrics); } final long token = Binder.clearCallingIdentity(); try { setApplicationLocalesUnchecked(appPackageName, userId, locales); setApplicationLocalesUnchecked(appPackageName, userId, locales, atomRecordForMetrics); } finally { Binder.restoreCallingIdentity(token); } } finally { 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); Loading @@ -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); } } Loading Loading @@ -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() { 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; } } /** Loading Loading @@ -312,6 +353,7 @@ public class LocaleManagerService extends SystemService { } } @NonNull private LocaleList getApplicationLocalesUnchecked(@NonNull String appPackageName, @UserIdInt int userId) { if (DEBUG) { Loading Loading @@ -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); } } Loading
services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java 0 → 100644 +53 −0 Original line number Diff line number Diff line /* * 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; } }
services/core/java/com/android/server/locales/LocaleManagerService.java +79 −28 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -148,9 +149,12 @@ public class LocaleManagerService extends SystemService { */ public void setApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId, @NonNull LocaleList locales) throws RemoteException, IllegalArgumentException { AppLocaleChangedAtomRecord atomRecordForMetrics = new AppLocaleChangedAtomRecord(Binder.getCallingUid()); try { 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, Loading @@ -161,27 +165,36 @@ public class LocaleManagerService extends SystemService { // 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); // 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(); enforceChangeConfigurationPermission(atomRecordForMetrics); } final long token = Binder.clearCallingIdentity(); try { setApplicationLocalesUnchecked(appPackageName, userId, locales); setApplicationLocalesUnchecked(appPackageName, userId, locales, atomRecordForMetrics); } finally { Binder.restoreCallingIdentity(token); } } finally { 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); Loading @@ -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); } } Loading Loading @@ -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() { 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; } } /** Loading Loading @@ -312,6 +353,7 @@ public class LocaleManagerService extends SystemService { } } @NonNull private LocaleList getApplicationLocalesUnchecked(@NonNull String appPackageName, @UserIdInt int userId) { if (DEBUG) { Loading Loading @@ -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); } }