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

Commit 5136c759 authored by Josh Hou's avatar Josh Hou Committed by Android (Google) Code Review
Browse files

Merge "Log data to AppSupportedLocalesChanged atom"

parents 74dee26c 33b3a23c
Loading
Loading
Loading
Loading
+40 −0
Original line number Diff line number Diff line
@@ -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.
     *
+72 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+85 −37
Original line number Diff line number Diff line
@@ -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,6 +567,9 @@ public class LocaleManagerService extends SystemService {
            return;
        }

        AppSupportedLocalesChangedAtomRecord atomRecord = new AppSupportedLocalesChangedAtomRecord(
                Binder.getCallingUid());
        try {
            requireNonNull(appPackageName);

            //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.
            // 2.) A privileged system application or service is granted the necessary permission to
            // override a LocaleConfig of another package.
        if (!isPackageOwnedByCaller(appPackageName, userId)) {
            enforceSetAppSpecificLocaleConfigPermission();
            if (!isPackageOwnedByCaller(appPackageName, userId, null, atomRecord)) {
                enforceSetAppSpecificLocaleConfigPermission(atomRecord);
            }

            final long token = Binder.clearCallingIdentity();
            try {
            setOverrideLocaleConfigUnchecked(appPackageName, userId, localeConfig);
                setOverrideLocaleConfigUnchecked(appPackageName, userId, localeConfig, atomRecord);
            } finally {
                Binder.restoreCallingIdentity(token);
            }

        //TODO: Add metrics to monitor the usage by applications
        } 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() {
    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);
    }
}