Loading services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java +28 −157 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import static android.os.UserHandle.USER_NULL; import static com.android.server.locales.LocaleManagerService.DEBUG; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.backup.BackupManager; import android.content.BroadcastReceiver; Loading @@ -34,7 +33,6 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.os.BestClock; import android.os.Binder; import android.os.Environment; import android.os.HandlerThread; import android.os.LocaleList; import android.os.Process; Loading @@ -42,7 +40,6 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.text.TextUtils; import android.util.AtomicFile; import android.util.Slog; import android.util.SparseArray; import android.util.TypedXmlPullParser; Loading @@ -53,17 +50,12 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; import com.android.internal.util.XmlUtils; import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; Loading @@ -71,8 +63,6 @@ import java.time.Clock; import java.time.Duration; import java.time.ZoneOffset; import java.util.HashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Helper class for managing backup and restore of app-specific locales. Loading @@ -87,33 +77,28 @@ class LocaleManagerBackupHelper { private static final String ATTR_LOCALES = "locales"; private static final String ATTR_CREATION_TIME_MILLIS = "creationTimeMillis"; private static final String STAGE_FILE_NAME = "staged_locales"; private static final String SYSTEM_BACKUP_PACKAGE_KEY = "android"; private static final Pattern STAGE_FILE_NAME_PATTERN = Pattern.compile( TextUtils.formatSimple("(^%s_)(\\d+)(\\.xml$)", STAGE_FILE_NAME)); private static final int USER_ID_GROUP_INDEX_IN_PATTERN = 2; private static final Duration STAGE_FILE_RETENTION_PERIOD = Duration.ofDays(3); // Stage data would be deleted on reboot since it's stored in memory. So it's retained until // retention period OR next reboot, whichever happens earlier. private static final Duration STAGE_DATA_RETENTION_PERIOD = Duration.ofDays(3); private final LocaleManagerService mLocaleManagerService; private final PackageManagerInternal mPackageManagerInternal; private final File mStagedLocalesDir; private final Clock mClock; private final Context mContext; private final Object mStagedDataLock = new Object(); // Staged data map keyed by user-id to handle multi-user scenario / work profiles. We are using // SparseArray because it is more memory-efficient than a HashMap. private final SparseArray<StagedData> mStagedData = new SparseArray<>(); private final SparseArray<StagedData> mStagedData; private final PackageMonitor mPackageMonitor; private final BroadcastReceiver mUserMonitor; LocaleManagerBackupHelper(LocaleManagerService localeManagerService, PackageManagerInternal pmInternal) { this(localeManagerService.mContext, localeManagerService, pmInternal, new File(Environment.getDataSystemCeDirectory(), "app_locales"), getDefaultClock()); this(localeManagerService.mContext, localeManagerService, pmInternal, getDefaultClock(), new SparseArray<>()); } private static @NonNull Clock getDefaultClock() { Loading @@ -123,14 +108,12 @@ class LocaleManagerBackupHelper { @VisibleForTesting LocaleManagerBackupHelper(Context context, LocaleManagerService localeManagerService, PackageManagerInternal pmInternal, File stagedLocalesDir, Clock clock) { PackageManagerInternal pmInternal, Clock clock, SparseArray<StagedData> stagedData) { mContext = context; mLocaleManagerService = localeManagerService; mPackageManagerInternal = pmInternal; mClock = clock; mStagedLocalesDir = stagedLocalesDir; loadAllStageFiles(); mStagedData = stagedData; HandlerThread broadcastHandlerThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND); Loading @@ -157,67 +140,6 @@ class LocaleManagerBackupHelper { return mPackageMonitor; } /** * Loads the staged data into memory by reading all the files in the staged directory. * * <p><b>Note:</b> We don't ned to hold the lock here because this is only called in the * constructor (before any broadcast receivers are registered). */ private void loadAllStageFiles() { File[] files = mStagedLocalesDir.listFiles(); if (files == null) { return; } for (File file : files) { String fileName = file.getName(); Matcher matcher = STAGE_FILE_NAME_PATTERN.matcher(fileName); if (!matcher.matches()) { file.delete(); Slog.w(TAG, TextUtils.formatSimple("Deleted %s. Reason: %s.", fileName, "Unrecognized file")); continue; } try { final int userId = Integer.parseInt(matcher.group(USER_ID_GROUP_INDEX_IN_PATTERN)); StagedData stagedData = readStageFile(file); if (stagedData != null) { mStagedData.put(userId, stagedData); } else { file.delete(); Slog.w(TAG, TextUtils.formatSimple("Deleted %s. Reason: %s.", fileName, "Could not read file")); } } catch (NumberFormatException e) { file.delete(); Slog.w(TAG, TextUtils.formatSimple("Deleted %s. Reason: %s.", fileName, "Could not parse user id from file name")); } } } /** * Loads the stage file from the disk and parses it into a list of app backups. */ private @Nullable StagedData readStageFile(@NonNull File file) { InputStream stagedDataInputStream = null; AtomicFile stageFile = new AtomicFile(file); try { stagedDataInputStream = stageFile.openRead(); final TypedXmlPullParser parser = Xml.newFastPullParser(); parser.setInput(stagedDataInputStream, StandardCharsets.UTF_8.name()); XmlUtils.beginDocument(parser, LOCALES_XML_TAG); long creationTimeMillis = parser.getAttributeLong(/* namespace= */ null, ATTR_CREATION_TIME_MILLIS); return new StagedData(creationTimeMillis, readFromXml(parser)); } catch (IOException | XmlPullParserException e) { Slog.e(TAG, "Could not parse stage file ", e); } finally { IoUtils.closeQuietly(stagedDataInputStream); } return null; } /** * @see LocaleManagerInternal#getBackupPayload(int userId) */ Loading Loading @@ -261,9 +183,7 @@ class LocaleManagerBackupHelper { final ByteArrayOutputStream out = new ByteArrayOutputStream(); try { // Passing arbitrary value for creationTimeMillis since it is ignored when forStage // is false. writeToXml(out, pkgStates, /* forStage= */ false, /* creationTimeMillis= */ -1); writeToXml(out, pkgStates); } catch (IOException e) { Slog.e(TAG, "Could not write to xml for backup ", e); return null; Loading @@ -284,7 +204,7 @@ class LocaleManagerBackupHelper { int userId = mStagedData.keyAt(i); StagedData stagedData = mStagedData.get(userId); if (stagedData.mCreationTimeMillis < mClock.millis() - STAGE_FILE_RETENTION_PERIOD.toMillis()) { < mClock.millis() - STAGE_DATA_RETENTION_PERIOD.toMillis()) { deleteStagedDataLocked(userId); } } Loading @@ -305,7 +225,7 @@ class LocaleManagerBackupHelper { final ByteArrayInputStream inputStream = new ByteArrayInputStream(payload); HashMap<String, String> pkgStates = new HashMap<>(); HashMap<String, String> pkgStates; try { // Parse the input blob into a list of BackupPackageState. final TypedXmlPullParser parser = Xml.newFastPullParser(); Loading @@ -315,6 +235,7 @@ class LocaleManagerBackupHelper { pkgStates = readFromXml(parser); } catch (IOException | XmlPullParserException e) { Slog.e(TAG, "Could not parse payload ", e); return; } // We need a lock here to prevent race conditions when accessing the stage file. Loading @@ -323,7 +244,7 @@ class LocaleManagerBackupHelper { // performed simultaneously. synchronized (mStagedDataLock) { // Backups for apps which are yet to be installed. mStagedData.put(userId, new StagedData(mClock.millis(), new HashMap<>())); StagedData stagedData = new StagedData(mClock.millis(), new HashMap<>()); for (String pkgName : pkgStates.keySet()) { String languageTags = pkgStates.get(pkgName); Loading @@ -333,7 +254,7 @@ class LocaleManagerBackupHelper { checkExistingLocalesAndApplyRestore(pkgName, languageTags, userId); } else { // Stage the data if the app isn't installed. mStagedData.get(userId).mPackageStates.put(pkgName, languageTags); stagedData.mPackageStates.put(pkgName, languageTags); if (DEBUG) { Slog.d(TAG, "Add locales=" + languageTags + " package=" + pkgName + " for lazy restore."); Loading @@ -341,7 +262,9 @@ class LocaleManagerBackupHelper { } } writeStageFileLocked(userId); if (!stagedData.mPackageStates.isEmpty()) { mStagedData.put(userId, stagedData); } } } Loading Loading @@ -396,55 +319,10 @@ class LocaleManagerBackupHelper { } } /** * Converts the list of app backups into xml and writes it onto the disk. */ private void writeStageFileLocked(int userId) { StagedData stagedData = mStagedData.get(userId); if (stagedData.mPackageStates.isEmpty()) { deleteStagedDataLocked(userId); return; } final FileOutputStream stagedDataOutputStream; AtomicFile stageFile = new AtomicFile( new File(mStagedLocalesDir, TextUtils.formatSimple("%s_%d.xml", STAGE_FILE_NAME, userId))); try { stagedDataOutputStream = stageFile.startWrite(); } catch (IOException e) { Slog.e(TAG, "Failed to save stage file"); return; } try { writeToXml(stagedDataOutputStream, stagedData.mPackageStates, /* forStage= */ true, stagedData.mCreationTimeMillis); stageFile.finishWrite(stagedDataOutputStream); if (DEBUG) { Slog.d(TAG, "Stage file written."); } } catch (IOException e) { Slog.e(TAG, "Could not write stage file", e); stageFile.failWrite(stagedDataOutputStream); } } private void deleteStagedDataLocked(@UserIdInt int userId) { AtomicFile stageFile = getStageFileIfExistsLocked(userId); if (stageFile != null) { stageFile.delete(); } mStagedData.remove(userId); } private @Nullable AtomicFile getStageFileIfExistsLocked(@UserIdInt int userId) { final File stageFile = new File(mStagedLocalesDir, TextUtils.formatSimple("%s_%d.xml", STAGE_FILE_NAME, userId)); return stageFile.isFile() ? new AtomicFile(stageFile) : null; } /** * Parses the backup data from the serialized xml input stream. */ Loading @@ -468,15 +346,8 @@ class LocaleManagerBackupHelper { /** * Converts the list of app backup data into a serialized xml stream. * * @param forStage Flag to indicate whether this method is called for the purpose of * staging the data. Note that if this is false, {@code creationTimeMillis} is ignored because * we only need it for the stage data. * @param creationTimeMillis The timestamp when the stage data was created. This is required * to determine when to delete the stage data. */ private static void writeToXml(OutputStream stream, @NonNull HashMap<String, String> pkgStates, boolean forStage, long creationTimeMillis) private static void writeToXml(OutputStream stream, @NonNull HashMap<String, String> pkgStates) throws IOException { if (pkgStates.isEmpty()) { // No need to write anything at all if pkgStates is empty. Loading @@ -488,11 +359,6 @@ class LocaleManagerBackupHelper { out.startDocument(/* encoding= */ null, /* standalone= */ true); out.startTag(/* namespace= */ null, LOCALES_XML_TAG); if (forStage) { out.attribute(/* namespace= */ null, ATTR_CREATION_TIME_MILLIS, Long.toString(creationTimeMillis)); } for (String pkg : pkgStates.keySet()) { out.startTag(/* namespace= */ null, PACKAGE_XML_TAG); out.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, pkg); Loading @@ -504,7 +370,7 @@ class LocaleManagerBackupHelper { out.endDocument(); } private static class StagedData { static class StagedData { final long mCreationTimeMillis; final HashMap<String, String> mPackageStates; Loading @@ -517,7 +383,7 @@ class LocaleManagerBackupHelper { /** * Broadcast listener to capture user removed event. * * <p>The stage file is deleted when a user is removed. * <p>The stage data is deleted when a user is removed. */ private final class UserMonitor extends BroadcastReceiver { @Override Loading Loading @@ -546,6 +412,8 @@ class LocaleManagerBackupHelper { public void onPackageAdded(String packageName, int uid) { try { synchronized (mStagedDataLock) { cleanStagedDataForOldEntriesLocked(); int userId = UserHandle.getUserId(uid); if (mStagedData.contains(userId)) { // Perform lazy restore only if the staged data exists. Loading Loading @@ -589,7 +457,7 @@ class LocaleManagerBackupHelper { // Check if the package is installed indeed if (!isPackageInstalledForUser(packageName, userId)) { Slog.e(TAG, packageName + " not installed for user " + userId + ". Could not restore locales from stage file"); + ". Could not restore locales from stage data"); return; } Loading @@ -603,8 +471,11 @@ class LocaleManagerBackupHelper { // Remove the restored entry from the staged data list. stagedData.mPackageStates.remove(pkgName); // Update the file on the disk. writeStageFileLocked(userId); // Remove the stage data entry for user if there are no more packages to restore. if (stagedData.mPackageStates.isEmpty()) { mStagedData.remove(userId); } // No need to loop further after restoring locales because the staged data will // contain at most one entry for the newly added package. Loading services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java +123 −221 File changed.Preview size limit exceeded, changes collapsed. Show changes services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java +4 −3 Original line number Diff line number Diff line Loading @@ -18,8 +18,8 @@ package com.android.server.locales; import android.content.Context; import android.content.pm.PackageManagerInternal; import android.util.SparseArray; import java.io.File; import java.time.Clock; /** Loading @@ -30,7 +30,8 @@ import java.time.Clock; public class ShadowLocaleManagerBackupHelper extends LocaleManagerBackupHelper { ShadowLocaleManagerBackupHelper(Context context, LocaleManagerService localeManagerService, PackageManagerInternal pmInternal, File stagedLocalesDir, Clock clock) { super(context, localeManagerService, pmInternal, stagedLocalesDir, clock); PackageManagerInternal pmInternal, Clock clock, SparseArray<LocaleManagerBackupHelper.StagedData> stagedData) { super(context, localeManagerService, pmInternal, clock, stagedData); } } Loading
services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java +28 −157 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import static android.os.UserHandle.USER_NULL; import static com.android.server.locales.LocaleManagerService.DEBUG; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.backup.BackupManager; import android.content.BroadcastReceiver; Loading @@ -34,7 +33,6 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.os.BestClock; import android.os.Binder; import android.os.Environment; import android.os.HandlerThread; import android.os.LocaleList; import android.os.Process; Loading @@ -42,7 +40,6 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.text.TextUtils; import android.util.AtomicFile; import android.util.Slog; import android.util.SparseArray; import android.util.TypedXmlPullParser; Loading @@ -53,17 +50,12 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; import com.android.internal.util.XmlUtils; import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; Loading @@ -71,8 +63,6 @@ import java.time.Clock; import java.time.Duration; import java.time.ZoneOffset; import java.util.HashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Helper class for managing backup and restore of app-specific locales. Loading @@ -87,33 +77,28 @@ class LocaleManagerBackupHelper { private static final String ATTR_LOCALES = "locales"; private static final String ATTR_CREATION_TIME_MILLIS = "creationTimeMillis"; private static final String STAGE_FILE_NAME = "staged_locales"; private static final String SYSTEM_BACKUP_PACKAGE_KEY = "android"; private static final Pattern STAGE_FILE_NAME_PATTERN = Pattern.compile( TextUtils.formatSimple("(^%s_)(\\d+)(\\.xml$)", STAGE_FILE_NAME)); private static final int USER_ID_GROUP_INDEX_IN_PATTERN = 2; private static final Duration STAGE_FILE_RETENTION_PERIOD = Duration.ofDays(3); // Stage data would be deleted on reboot since it's stored in memory. So it's retained until // retention period OR next reboot, whichever happens earlier. private static final Duration STAGE_DATA_RETENTION_PERIOD = Duration.ofDays(3); private final LocaleManagerService mLocaleManagerService; private final PackageManagerInternal mPackageManagerInternal; private final File mStagedLocalesDir; private final Clock mClock; private final Context mContext; private final Object mStagedDataLock = new Object(); // Staged data map keyed by user-id to handle multi-user scenario / work profiles. We are using // SparseArray because it is more memory-efficient than a HashMap. private final SparseArray<StagedData> mStagedData = new SparseArray<>(); private final SparseArray<StagedData> mStagedData; private final PackageMonitor mPackageMonitor; private final BroadcastReceiver mUserMonitor; LocaleManagerBackupHelper(LocaleManagerService localeManagerService, PackageManagerInternal pmInternal) { this(localeManagerService.mContext, localeManagerService, pmInternal, new File(Environment.getDataSystemCeDirectory(), "app_locales"), getDefaultClock()); this(localeManagerService.mContext, localeManagerService, pmInternal, getDefaultClock(), new SparseArray<>()); } private static @NonNull Clock getDefaultClock() { Loading @@ -123,14 +108,12 @@ class LocaleManagerBackupHelper { @VisibleForTesting LocaleManagerBackupHelper(Context context, LocaleManagerService localeManagerService, PackageManagerInternal pmInternal, File stagedLocalesDir, Clock clock) { PackageManagerInternal pmInternal, Clock clock, SparseArray<StagedData> stagedData) { mContext = context; mLocaleManagerService = localeManagerService; mPackageManagerInternal = pmInternal; mClock = clock; mStagedLocalesDir = stagedLocalesDir; loadAllStageFiles(); mStagedData = stagedData; HandlerThread broadcastHandlerThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND); Loading @@ -157,67 +140,6 @@ class LocaleManagerBackupHelper { return mPackageMonitor; } /** * Loads the staged data into memory by reading all the files in the staged directory. * * <p><b>Note:</b> We don't ned to hold the lock here because this is only called in the * constructor (before any broadcast receivers are registered). */ private void loadAllStageFiles() { File[] files = mStagedLocalesDir.listFiles(); if (files == null) { return; } for (File file : files) { String fileName = file.getName(); Matcher matcher = STAGE_FILE_NAME_PATTERN.matcher(fileName); if (!matcher.matches()) { file.delete(); Slog.w(TAG, TextUtils.formatSimple("Deleted %s. Reason: %s.", fileName, "Unrecognized file")); continue; } try { final int userId = Integer.parseInt(matcher.group(USER_ID_GROUP_INDEX_IN_PATTERN)); StagedData stagedData = readStageFile(file); if (stagedData != null) { mStagedData.put(userId, stagedData); } else { file.delete(); Slog.w(TAG, TextUtils.formatSimple("Deleted %s. Reason: %s.", fileName, "Could not read file")); } } catch (NumberFormatException e) { file.delete(); Slog.w(TAG, TextUtils.formatSimple("Deleted %s. Reason: %s.", fileName, "Could not parse user id from file name")); } } } /** * Loads the stage file from the disk and parses it into a list of app backups. */ private @Nullable StagedData readStageFile(@NonNull File file) { InputStream stagedDataInputStream = null; AtomicFile stageFile = new AtomicFile(file); try { stagedDataInputStream = stageFile.openRead(); final TypedXmlPullParser parser = Xml.newFastPullParser(); parser.setInput(stagedDataInputStream, StandardCharsets.UTF_8.name()); XmlUtils.beginDocument(parser, LOCALES_XML_TAG); long creationTimeMillis = parser.getAttributeLong(/* namespace= */ null, ATTR_CREATION_TIME_MILLIS); return new StagedData(creationTimeMillis, readFromXml(parser)); } catch (IOException | XmlPullParserException e) { Slog.e(TAG, "Could not parse stage file ", e); } finally { IoUtils.closeQuietly(stagedDataInputStream); } return null; } /** * @see LocaleManagerInternal#getBackupPayload(int userId) */ Loading Loading @@ -261,9 +183,7 @@ class LocaleManagerBackupHelper { final ByteArrayOutputStream out = new ByteArrayOutputStream(); try { // Passing arbitrary value for creationTimeMillis since it is ignored when forStage // is false. writeToXml(out, pkgStates, /* forStage= */ false, /* creationTimeMillis= */ -1); writeToXml(out, pkgStates); } catch (IOException e) { Slog.e(TAG, "Could not write to xml for backup ", e); return null; Loading @@ -284,7 +204,7 @@ class LocaleManagerBackupHelper { int userId = mStagedData.keyAt(i); StagedData stagedData = mStagedData.get(userId); if (stagedData.mCreationTimeMillis < mClock.millis() - STAGE_FILE_RETENTION_PERIOD.toMillis()) { < mClock.millis() - STAGE_DATA_RETENTION_PERIOD.toMillis()) { deleteStagedDataLocked(userId); } } Loading @@ -305,7 +225,7 @@ class LocaleManagerBackupHelper { final ByteArrayInputStream inputStream = new ByteArrayInputStream(payload); HashMap<String, String> pkgStates = new HashMap<>(); HashMap<String, String> pkgStates; try { // Parse the input blob into a list of BackupPackageState. final TypedXmlPullParser parser = Xml.newFastPullParser(); Loading @@ -315,6 +235,7 @@ class LocaleManagerBackupHelper { pkgStates = readFromXml(parser); } catch (IOException | XmlPullParserException e) { Slog.e(TAG, "Could not parse payload ", e); return; } // We need a lock here to prevent race conditions when accessing the stage file. Loading @@ -323,7 +244,7 @@ class LocaleManagerBackupHelper { // performed simultaneously. synchronized (mStagedDataLock) { // Backups for apps which are yet to be installed. mStagedData.put(userId, new StagedData(mClock.millis(), new HashMap<>())); StagedData stagedData = new StagedData(mClock.millis(), new HashMap<>()); for (String pkgName : pkgStates.keySet()) { String languageTags = pkgStates.get(pkgName); Loading @@ -333,7 +254,7 @@ class LocaleManagerBackupHelper { checkExistingLocalesAndApplyRestore(pkgName, languageTags, userId); } else { // Stage the data if the app isn't installed. mStagedData.get(userId).mPackageStates.put(pkgName, languageTags); stagedData.mPackageStates.put(pkgName, languageTags); if (DEBUG) { Slog.d(TAG, "Add locales=" + languageTags + " package=" + pkgName + " for lazy restore."); Loading @@ -341,7 +262,9 @@ class LocaleManagerBackupHelper { } } writeStageFileLocked(userId); if (!stagedData.mPackageStates.isEmpty()) { mStagedData.put(userId, stagedData); } } } Loading Loading @@ -396,55 +319,10 @@ class LocaleManagerBackupHelper { } } /** * Converts the list of app backups into xml and writes it onto the disk. */ private void writeStageFileLocked(int userId) { StagedData stagedData = mStagedData.get(userId); if (stagedData.mPackageStates.isEmpty()) { deleteStagedDataLocked(userId); return; } final FileOutputStream stagedDataOutputStream; AtomicFile stageFile = new AtomicFile( new File(mStagedLocalesDir, TextUtils.formatSimple("%s_%d.xml", STAGE_FILE_NAME, userId))); try { stagedDataOutputStream = stageFile.startWrite(); } catch (IOException e) { Slog.e(TAG, "Failed to save stage file"); return; } try { writeToXml(stagedDataOutputStream, stagedData.mPackageStates, /* forStage= */ true, stagedData.mCreationTimeMillis); stageFile.finishWrite(stagedDataOutputStream); if (DEBUG) { Slog.d(TAG, "Stage file written."); } } catch (IOException e) { Slog.e(TAG, "Could not write stage file", e); stageFile.failWrite(stagedDataOutputStream); } } private void deleteStagedDataLocked(@UserIdInt int userId) { AtomicFile stageFile = getStageFileIfExistsLocked(userId); if (stageFile != null) { stageFile.delete(); } mStagedData.remove(userId); } private @Nullable AtomicFile getStageFileIfExistsLocked(@UserIdInt int userId) { final File stageFile = new File(mStagedLocalesDir, TextUtils.formatSimple("%s_%d.xml", STAGE_FILE_NAME, userId)); return stageFile.isFile() ? new AtomicFile(stageFile) : null; } /** * Parses the backup data from the serialized xml input stream. */ Loading @@ -468,15 +346,8 @@ class LocaleManagerBackupHelper { /** * Converts the list of app backup data into a serialized xml stream. * * @param forStage Flag to indicate whether this method is called for the purpose of * staging the data. Note that if this is false, {@code creationTimeMillis} is ignored because * we only need it for the stage data. * @param creationTimeMillis The timestamp when the stage data was created. This is required * to determine when to delete the stage data. */ private static void writeToXml(OutputStream stream, @NonNull HashMap<String, String> pkgStates, boolean forStage, long creationTimeMillis) private static void writeToXml(OutputStream stream, @NonNull HashMap<String, String> pkgStates) throws IOException { if (pkgStates.isEmpty()) { // No need to write anything at all if pkgStates is empty. Loading @@ -488,11 +359,6 @@ class LocaleManagerBackupHelper { out.startDocument(/* encoding= */ null, /* standalone= */ true); out.startTag(/* namespace= */ null, LOCALES_XML_TAG); if (forStage) { out.attribute(/* namespace= */ null, ATTR_CREATION_TIME_MILLIS, Long.toString(creationTimeMillis)); } for (String pkg : pkgStates.keySet()) { out.startTag(/* namespace= */ null, PACKAGE_XML_TAG); out.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, pkg); Loading @@ -504,7 +370,7 @@ class LocaleManagerBackupHelper { out.endDocument(); } private static class StagedData { static class StagedData { final long mCreationTimeMillis; final HashMap<String, String> mPackageStates; Loading @@ -517,7 +383,7 @@ class LocaleManagerBackupHelper { /** * Broadcast listener to capture user removed event. * * <p>The stage file is deleted when a user is removed. * <p>The stage data is deleted when a user is removed. */ private final class UserMonitor extends BroadcastReceiver { @Override Loading Loading @@ -546,6 +412,8 @@ class LocaleManagerBackupHelper { public void onPackageAdded(String packageName, int uid) { try { synchronized (mStagedDataLock) { cleanStagedDataForOldEntriesLocked(); int userId = UserHandle.getUserId(uid); if (mStagedData.contains(userId)) { // Perform lazy restore only if the staged data exists. Loading Loading @@ -589,7 +457,7 @@ class LocaleManagerBackupHelper { // Check if the package is installed indeed if (!isPackageInstalledForUser(packageName, userId)) { Slog.e(TAG, packageName + " not installed for user " + userId + ". Could not restore locales from stage file"); + ". Could not restore locales from stage data"); return; } Loading @@ -603,8 +471,11 @@ class LocaleManagerBackupHelper { // Remove the restored entry from the staged data list. stagedData.mPackageStates.remove(pkgName); // Update the file on the disk. writeStageFileLocked(userId); // Remove the stage data entry for user if there are no more packages to restore. if (stagedData.mPackageStates.isEmpty()) { mStagedData.remove(userId); } // No need to loop further after restoring locales because the staged data will // contain at most one entry for the newly added package. Loading
services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java +123 −221 File changed.Preview size limit exceeded, changes collapsed. Show changes
services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java +4 −3 Original line number Diff line number Diff line Loading @@ -18,8 +18,8 @@ package com.android.server.locales; import android.content.Context; import android.content.pm.PackageManagerInternal; import android.util.SparseArray; import java.io.File; import java.time.Clock; /** Loading @@ -30,7 +30,8 @@ import java.time.Clock; public class ShadowLocaleManagerBackupHelper extends LocaleManagerBackupHelper { ShadowLocaleManagerBackupHelper(Context context, LocaleManagerService localeManagerService, PackageManagerInternal pmInternal, File stagedLocalesDir, Clock clock) { super(context, localeManagerService, pmInternal, stagedLocalesDir, clock); PackageManagerInternal pmInternal, Clock clock, SparseArray<LocaleManagerBackupHelper.StagedData> stagedData) { super(context, localeManagerService, pmInternal, clock, stagedData); } }