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

Commit e5fffb8e authored by Nishant Kumar Singh's avatar Nishant Kumar Singh
Browse files

Add the backup logic for app-locales.

Bug: 203266156
Test: atest FrameworksServicesTests:LocaleManagerBackupRestoreTest
Change-Id: I38dae4f797b184d255479fca235de5af9a3bf3d1
parent 21684a25
Loading
Loading
Loading
Loading
+151 −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 com.android.server.locales.LocaleManagerService.DEBUG;

import android.annotation.NonNull;
import android.app.backup.BackupManager;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
import android.os.LocaleList;
import android.os.RemoteException;
import android.util.Slog;
import android.util.TypedXmlSerializer;
import android.util.Xml;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;

/**
 * Helper class for managing backup and restore of app-specific locales.
 */
final class LocaleManagerBackupHelper {
    private static final String TAG = "LocaleManagerBkpHelper"; // must be < 23 chars
    private final LocaleManagerService mLocaleManagerService;
    private final PackageManagerInternal mPackageManagerInternal;
    private static final String SYSTEM_BACKUP_PACKAGE_KEY = "android";

    LocaleManagerBackupHelper(LocaleManagerService localeManagerService,
            PackageManagerInternal pmInternal) {
        mLocaleManagerService = localeManagerService;
        mPackageManagerInternal = pmInternal;
    }

    /**
     * @see LocaleManagerInternal#getBackupPayload(int userId)
     */
    public byte[] getBackupPayload(int userId) {
        if (DEBUG) {
            Slog.d(TAG, "getBackupPayload invoked for user id " + userId);
        }

        ArrayList<BackupPackageState> pkgStates = new ArrayList<>();
        for (ApplicationInfo appInfo : mPackageManagerInternal.getInstalledApplications(/*flags*/0,
                userId, Binder.getCallingUid())) {
            try {
                LocaleList appLocales = mLocaleManagerService.getApplicationLocales(
                        appInfo.packageName,
                        userId);
                // Backup locales only for apps which do have app-specific overrides.
                if (!appLocales.isEmpty()) {
                    if (DEBUG) {
                        Slog.d(TAG, "Add package=" + appInfo.packageName + " locales="
                                + appLocales.toLanguageTags() + " to backup payload");
                    }
                    pkgStates.add(new BackupPackageState(appInfo.packageName,
                            appLocales.toLanguageTags()));
                }
            } catch (RemoteException | IllegalArgumentException e) {
                Slog.e(TAG, "Exception when getting locales for package: " + appInfo.packageName,
                        e);
            }
        }

        if (pkgStates.isEmpty()) {
            if (DEBUG) {
                Slog.d(TAG, "Final payload=null");
            }
            // Returning null here will ensure deletion of the entry for LMS from the backup data.
            return null;
        }

        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            writeToXml(out, pkgStates);
        } catch (IOException e) {
            Slog.e(TAG, "Could not write to xml for backup ", e);
            return null;
        }

        if (DEBUG) {
            try {
                Slog.d(TAG, "Final payload=" + out.toString("UTF-8"));
            } catch (UnsupportedEncodingException e) {
                Slog.w(TAG, "Could not encode payload to UTF-8", e);
            }
        }
        return out.toByteArray();
    }

    /**
     * Notifies the backup manager to include the "android" package in the next backup pass.
     */
    public void notifyBackupManager() {
        BackupManager.dataChanged(SYSTEM_BACKUP_PACKAGE_KEY);
    }


    /**
     * Converts the list of app backup data into a serialized xml stream.
     */
    private static void writeToXml(OutputStream stream,
            @NonNull ArrayList<BackupPackageState> pkgStates) throws IOException {
        TypedXmlSerializer out = Xml.newFastSerializer();
        out.setOutput(stream, StandardCharsets.UTF_8.name());
        out.startDocument(/* encoding= */ null, /* standalone= */ true);
        out.startTag(/* namespace= */ null, TAG);

        for (BackupPackageState pkgState : pkgStates) {
            out.startTag(/* namespace= */ null, "package");
            out.attribute(/* namespace= */ null, "name", pkgState.mPackageName);
            out.attribute(/* namespace= */ null, "locales", pkgState.mLanguageTags);
            out.endTag(/*namespace= */ null, "package");
        }

        out.endTag(/* namespace= */ null, TAG);
        out.endDocument();
    }

    /**
     * Data structure containing the backup data details for an app.
     */
    private static class BackupPackageState {
        final @NonNull String mPackageName;
        final @NonNull String mLanguageTags;

        BackupPackageState(@NonNull String packageNameArg, @NonNull String languageTagsArg) {
            mPackageName = packageNameArg;
            mLanguageTags = languageTagsArg;
        }
    }
}
+31 −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 android.annotation.Nullable;

/**
 * System-server internal interface to the {@link LocaleManagerService}.
 *
 * @hide Only for use within the system server.
 */
public abstract class LocaleManagerInternal {
    /**
     * Returns the app-specific locales to be backed up as a data-blob.
     */
    public abstract @Nullable byte[] getBackupPayload(int userId);
}
+32 −1
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static java.util.Objects.requireNonNull;

import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.ILocaleManager;
@@ -29,6 +30,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
import android.os.LocaleList;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
@@ -51,11 +53,14 @@ import java.io.PrintWriter;
 */
public class LocaleManagerService extends SystemService {
    private static final String TAG = "LocaleManagerService";
    private final Context mContext;
    final Context mContext;
    private final LocaleManagerService.LocaleManagerBinderService mBinderService;
    private ActivityTaskManagerInternal mActivityTaskManagerInternal;
    private ActivityManagerInternal mActivityManagerInternal;
    private PackageManagerInternal mPackageManagerInternal;

    private LocaleManagerBackupHelper mBackupHelper;

    public static final boolean DEBUG = false;

    public LocaleManagerService(Context context) {
@@ -82,6 +87,24 @@ public class LocaleManagerService extends SystemService {
    @Override
    public void onStart() {
        publishBinderService(Context.LOCALE_SERVICE, mBinderService);
        mBackupHelper = new LocaleManagerBackupHelper(LocaleManagerService.this,
                mPackageManagerInternal);
        LocalServices.addService(LocaleManagerInternal.class, new LocaleManagerInternalImpl());
    }

    private final class LocaleManagerInternalImpl extends LocaleManagerInternal {

        @Override
        public @Nullable byte[] getBackupPayload(int userId) {
            checkCallerIsSystem();
            return mBackupHelper.getBackupPayload(userId);
        }

        private void checkCallerIsSystem() {
            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
                throw new SecurityException("Caller is not system.");
            }
        }
    }

    private final class LocaleManagerBinderService extends ILocaleManager.Stub {
@@ -110,6 +133,12 @@ public class LocaleManagerService extends SystemService {
            (new LocaleManagerShellCommand(mBinderService))
                    .exec(this, in, out, err, args, callback, resultReceiver);
        }

    }

    @VisibleForTesting
    LocaleManagerBackupHelper getBackupHelper() {
        return mBackupHelper;
    }

    /**
@@ -161,6 +190,8 @@ public class LocaleManagerService extends SystemService {
            notifyAppWhoseLocaleChanged(appPackageName, userId, locales);
            notifyInstallerOfAppWhoseLocaleChanged(appPackageName, userId, locales);
            notifyRegisteredReceivers(appPackageName, userId, locales);

            mBackupHelper.notifyBackupManager();
        }
    }