Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8b79e267 authored by “riyaghai”'s avatar “riyaghai”
Browse files

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
parent 32532065
Loading
Loading
Loading
Loading
+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;
    }
}
+79 −28
Original line number Diff line number Diff line
@@ -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,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,
@@ -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);
@@ -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() {
    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);
    }
}