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

Commit 78a74935 authored by Josh Hou's avatar Josh Hou Committed by Automerger Merge Worker
Browse files

Merge "Log data to AppSupportedLocalesChanged atom" into udc-dev am: 39a8d0c9

parents c64a9a4a 39a8d0c9
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);
    }
}