Add unit tests for app-locales restore logic.
Bug: 204986584 Test: atest FrameworksServicesTests:LocaleManagerBackupRestoreTest Change-Id: I225615b2c4114e8b3a45d705ea5df1e2b982612d
This commit is contained in:
@@ -17,28 +17,44 @@
|
||||
package com.android.server.locales;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertNull;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.os.Binder;
|
||||
import android.os.Environment;
|
||||
import android.os.LocaleList;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SimpleClock;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.TypedXmlPullParser;
|
||||
import android.util.TypedXmlSerializer;
|
||||
import android.util.Xml;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.content.PackageMonitor;
|
||||
import com.android.internal.util.XmlUtils;
|
||||
|
||||
import org.junit.Before;
|
||||
@@ -48,10 +64,15 @@ import org.mockito.Mock;
|
||||
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.nio.charset.StandardCharsets;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -64,13 +85,19 @@ import java.util.Map;
|
||||
public class LocaleManagerBackupRestoreTest {
|
||||
private static final String DEFAULT_PACKAGE_NAME = "com.android.myapp";
|
||||
private static final String DEFAULT_LOCALE_TAGS = "en-XC,ar-XB";
|
||||
private static final String STAGE_LOCALES_XML_TAG = "locales";
|
||||
private static final String TEST_LOCALES_XML_TAG = "locales";
|
||||
private static final int DEFAULT_USER_ID = 0;
|
||||
private static final int INVALID_UID = -1;
|
||||
private static final int WORK_PROFILE_USER_ID = 10;
|
||||
private static final int DEFAULT_UID = Binder.getCallingUid() + 100;
|
||||
private static final long DEFAULT_CREATION_TIME_MILLIS = 1000;
|
||||
private static final Duration RETENTION_PERIOD = Duration.ofDays(3);
|
||||
private static final LocaleList DEFAULT_LOCALES =
|
||||
LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS);
|
||||
private static final Map<String, String> DEFAULT_PACKAGE_LOCALES_MAP = Map.of(
|
||||
DEFAULT_PACKAGE_NAME, DEFAULT_LOCALE_TAGS);
|
||||
private static final File STAGED_LOCALES_DIR = new File(
|
||||
Environment.getExternalStorageDirectory(), "lmsUnitTests");
|
||||
|
||||
|
||||
private LocaleManagerBackupHelper mBackupHelper;
|
||||
private long mCurrentTimeMillis;
|
||||
@@ -80,7 +107,11 @@ public class LocaleManagerBackupRestoreTest {
|
||||
@Mock
|
||||
private PackageManagerInternal mMockPackageManagerInternal;
|
||||
@Mock
|
||||
private PackageManager mMockPackageManager;
|
||||
@Mock
|
||||
private LocaleManagerService mMockLocaleManagerService;
|
||||
BroadcastReceiver mUserMonitor;
|
||||
PackageMonitor mPackageMonitor;
|
||||
|
||||
private final Clock mClock = new SimpleClock(ZoneOffset.UTC) {
|
||||
@Override
|
||||
@@ -101,13 +132,20 @@ public class LocaleManagerBackupRestoreTest {
|
||||
public void setUp() throws Exception {
|
||||
mMockContext = mock(Context.class);
|
||||
mMockPackageManagerInternal = mock(PackageManagerInternal.class);
|
||||
|
||||
mMockPackageManager = mock(PackageManager.class);
|
||||
mMockLocaleManagerService = mock(LocaleManagerService.class);
|
||||
|
||||
mBackupHelper = new LocaleManagerBackupHelper(mMockContext, mMockLocaleManagerService,
|
||||
mMockPackageManagerInternal,
|
||||
new File(Environment.getExternalStorageDirectory(), "lmsUnitTests"),
|
||||
mClock);
|
||||
doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
|
||||
|
||||
mBackupHelper = spy(new ShadowLocaleManagerBackupHelper(mMockContext,
|
||||
mMockLocaleManagerService, mMockPackageManagerInternal,
|
||||
new File(Environment.getExternalStorageDirectory(), "lmsUnitTests"), mClock));
|
||||
doNothing().when(mBackupHelper).notifyBackupManager();
|
||||
|
||||
mUserMonitor = mBackupHelper.getUserMonitor();
|
||||
mPackageMonitor = mBackupHelper.getPackageMonitor();
|
||||
setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS);
|
||||
cleanStagedFiles();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -164,6 +202,414 @@ public class LocaleManagerBackupRestoreTest {
|
||||
verifyPayloadForAppLocales(DEFAULT_PACKAGE_LOCALES_MAP, payload);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestore_nullPayload_nothingRestoredAndNoStageFile() throws Exception {
|
||||
mBackupHelper.stageAndApplyRestoredPayload(/* payload= */ null, DEFAULT_USER_ID);
|
||||
|
||||
verifyNothingRestored();
|
||||
checkStageFileDoesNotExist(DEFAULT_USER_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestore_zeroLengthPayload_nothingRestoredAndNoStageFile() throws Exception {
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
mBackupHelper.stageAndApplyRestoredPayload(/* payload= */ out.toByteArray(),
|
||||
DEFAULT_USER_ID);
|
||||
|
||||
verifyNothingRestored();
|
||||
checkStageFileDoesNotExist(DEFAULT_USER_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestore_allAppsInstalled_noStageFileCreated() throws Exception {
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
|
||||
|
||||
setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
|
||||
setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
|
||||
|
||||
mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
|
||||
|
||||
// Locales were restored
|
||||
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
|
||||
DEFAULT_USER_ID, DEFAULT_LOCALES);
|
||||
|
||||
// Stage file wasn't created.
|
||||
checkStageFileDoesNotExist(DEFAULT_USER_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestore_noAppsInstalled_everythingStaged() throws Exception {
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
|
||||
|
||||
setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
|
||||
|
||||
mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
|
||||
|
||||
verifyNothingRestored();
|
||||
verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP,
|
||||
getStageFileIfExists(DEFAULT_USER_ID), DEFAULT_CREATION_TIME_MILLIS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestore_someAppsInstalled_partiallyStaged() throws Exception {
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
HashMap<String, String> pkgLocalesMap = new HashMap<>();
|
||||
|
||||
String pkgNameA = "com.android.myAppA", pkgNameB = "com.android.myAppB";
|
||||
String langTagsA = "ru", langTagsB = "hi,fr";
|
||||
pkgLocalesMap.put(pkgNameA, langTagsA);
|
||||
pkgLocalesMap.put(pkgNameB, langTagsB);
|
||||
writeTestPayload(out, pkgLocalesMap);
|
||||
|
||||
setUpPackageInstalled(pkgNameA);
|
||||
setUpPackageNotInstalled(pkgNameB);
|
||||
setUpLocalesForPackage(pkgNameA, LocaleList.getEmptyLocaleList());
|
||||
|
||||
mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
|
||||
|
||||
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
|
||||
LocaleList.forLanguageTags(langTagsA));
|
||||
|
||||
pkgLocalesMap.remove(pkgNameA);
|
||||
verifyStageFileContent(pkgLocalesMap, getStageFileIfExists(DEFAULT_USER_ID),
|
||||
DEFAULT_CREATION_TIME_MILLIS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestore_appLocalesAlreadySet_nothingRestoredAndNoStageFile() throws Exception {
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
|
||||
|
||||
setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
|
||||
setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.forLanguageTags("hi,mr"));
|
||||
|
||||
mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
|
||||
|
||||
// Since locales are already set, we should not restore anything for it.
|
||||
verifyNothingRestored();
|
||||
// Stage file wasn't created
|
||||
checkStageFileDoesNotExist(DEFAULT_USER_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestore_appLocalesSetForSomeApps_restoresOnlyForAppsHavingNoLocalesSet()
|
||||
throws Exception {
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
HashMap<String, String> pkgLocalesMap = new HashMap<>();
|
||||
|
||||
String pkgNameA = "com.android.myAppA", pkgNameB = "com.android.myAppB", pkgNameC =
|
||||
"com.android.myAppC";
|
||||
String langTagsA = "ru", langTagsB = "hi,fr", langTagsC = "zh,es";
|
||||
pkgLocalesMap.put(pkgNameA, langTagsA);
|
||||
pkgLocalesMap.put(pkgNameB, langTagsB);
|
||||
pkgLocalesMap.put(pkgNameC, langTagsC);
|
||||
writeTestPayload(out, pkgLocalesMap);
|
||||
|
||||
// Both app A & B are installed on the device but A has locales already set.
|
||||
setUpPackageInstalled(pkgNameA);
|
||||
setUpPackageInstalled(pkgNameB);
|
||||
setUpPackageNotInstalled(pkgNameC);
|
||||
setUpLocalesForPackage(pkgNameA, LocaleList.forLanguageTags("mr,fr"));
|
||||
setUpLocalesForPackage(pkgNameB, LocaleList.getEmptyLocaleList());
|
||||
setUpLocalesForPackage(pkgNameC, LocaleList.getEmptyLocaleList());
|
||||
|
||||
mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
|
||||
|
||||
// Restore locales only for myAppB.
|
||||
verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameA), anyInt(),
|
||||
any());
|
||||
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
|
||||
LocaleList.forLanguageTags(langTagsB));
|
||||
verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameC), anyInt(),
|
||||
any());
|
||||
|
||||
// App C is staged.
|
||||
pkgLocalesMap.remove(pkgNameA);
|
||||
pkgLocalesMap.remove(pkgNameB);
|
||||
verifyStageFileContent(pkgLocalesMap, getStageFileIfExists(DEFAULT_USER_ID),
|
||||
DEFAULT_CREATION_TIME_MILLIS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestore_restoreInvokedAgain_creationTimeChanged() throws Exception {
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
|
||||
|
||||
setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
|
||||
|
||||
mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
|
||||
|
||||
verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, getStageFileIfExists(DEFAULT_USER_ID),
|
||||
DEFAULT_CREATION_TIME_MILLIS);
|
||||
|
||||
final long newCreationTime = DEFAULT_CREATION_TIME_MILLIS + 100;
|
||||
setCurrentTimeMillis(newCreationTime);
|
||||
mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
|
||||
|
||||
verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, getStageFileIfExists(DEFAULT_USER_ID),
|
||||
newCreationTime);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestore_appInstalledAfterSUW_restoresFromStage() throws Exception {
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
HashMap<String, String> pkgLocalesMap = new HashMap<>();
|
||||
|
||||
String pkgNameA = "com.android.myAppA", pkgNameB = "com.android.myAppB";
|
||||
String langTagsA = "ru", langTagsB = "hi,fr";
|
||||
pkgLocalesMap.put(pkgNameA, langTagsA);
|
||||
pkgLocalesMap.put(pkgNameB, langTagsB);
|
||||
writeTestPayload(out, pkgLocalesMap);
|
||||
|
||||
setUpPackageNotInstalled(pkgNameA);
|
||||
setUpPackageNotInstalled(pkgNameB);
|
||||
setUpLocalesForPackage(pkgNameA, LocaleList.getEmptyLocaleList());
|
||||
setUpLocalesForPackage(pkgNameB, LocaleList.getEmptyLocaleList());
|
||||
|
||||
mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
|
||||
|
||||
verifyNothingRestored();
|
||||
|
||||
setUpPackageInstalled(pkgNameA);
|
||||
|
||||
mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID);
|
||||
|
||||
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
|
||||
LocaleList.forLanguageTags(langTagsA));
|
||||
|
||||
pkgLocalesMap.remove(pkgNameA);
|
||||
verifyStageFileContent(pkgLocalesMap, getStageFileIfExists(DEFAULT_USER_ID),
|
||||
DEFAULT_CREATION_TIME_MILLIS);
|
||||
|
||||
setUpPackageInstalled(pkgNameB);
|
||||
|
||||
mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID);
|
||||
|
||||
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
|
||||
LocaleList.forLanguageTags(langTagsB));
|
||||
checkStageFileDoesNotExist(DEFAULT_USER_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestore_appInstalledAfterSUWAndLocalesAlreadySet_restoresNothing()
|
||||
throws Exception {
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
|
||||
|
||||
// Package is not present on the device when the SUW restore is going on.
|
||||
setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
|
||||
|
||||
mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
|
||||
|
||||
verifyNothingRestored();
|
||||
verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, getStageFileIfExists(DEFAULT_USER_ID),
|
||||
DEFAULT_CREATION_TIME_MILLIS);
|
||||
|
||||
// App is installed later (post SUW).
|
||||
setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
|
||||
setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.forLanguageTags("hi,mr"));
|
||||
|
||||
mPackageMonitor.onPackageAdded(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
|
||||
|
||||
// Since locales are already set, we should not restore anything for it.
|
||||
verifyNothingRestored();
|
||||
checkStageFileDoesNotExist(DEFAULT_USER_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStageFileDeletion_backupPassRunAfterRetentionPeriod_stageFileDeleted()
|
||||
throws Exception {
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
|
||||
|
||||
setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
|
||||
|
||||
mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
|
||||
|
||||
verifyNothingRestored();
|
||||
verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, getStageFileIfExists(DEFAULT_USER_ID),
|
||||
DEFAULT_CREATION_TIME_MILLIS);
|
||||
|
||||
// Retention period has not elapsed.
|
||||
setCurrentTimeMillis(
|
||||
DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.minusHours(1).toMillis());
|
||||
doReturn(List.of()).when(mMockPackageManagerInternal)
|
||||
.getInstalledApplications(anyLong(), anyInt(), anyInt());
|
||||
assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
|
||||
|
||||
// Stage file should NOT be deleted.
|
||||
checkStageFileExists(DEFAULT_USER_ID);
|
||||
|
||||
// Exactly RETENTION_PERIOD amount of time has passed so stage file should still not be
|
||||
// removed.
|
||||
setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.toMillis());
|
||||
doReturn(List.of()).when(mMockPackageManagerInternal)
|
||||
.getInstalledApplications(anyLong(), anyInt(), anyInt());
|
||||
assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
|
||||
|
||||
// Stage file should NOT be deleted.
|
||||
checkStageFileExists(DEFAULT_USER_ID);
|
||||
|
||||
// Retention period has now expired, stage file should be deleted.
|
||||
setCurrentTimeMillis(
|
||||
DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.plusSeconds(1).toMillis());
|
||||
doReturn(List.of()).when(mMockPackageManagerInternal)
|
||||
.getInstalledApplications(anyLong(), anyInt(), anyInt());
|
||||
assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
|
||||
|
||||
// Stage file should be deleted.
|
||||
checkStageFileDoesNotExist(DEFAULT_USER_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserRemoval_userRemoved_stageFileDeleted() throws Exception {
|
||||
final ByteArrayOutputStream outDefault = new ByteArrayOutputStream();
|
||||
writeTestPayload(outDefault, DEFAULT_PACKAGE_LOCALES_MAP);
|
||||
|
||||
final ByteArrayOutputStream outWorkProfile = new ByteArrayOutputStream();
|
||||
String anotherPackage = "com.android.anotherapp";
|
||||
String anotherLangTags = "mr,zh";
|
||||
HashMap<String, String> pkgLocalesMapWorkProfile = new HashMap<>();
|
||||
pkgLocalesMapWorkProfile.put(anotherPackage, anotherLangTags);
|
||||
writeTestPayload(outWorkProfile, pkgLocalesMapWorkProfile);
|
||||
|
||||
// DEFAULT_PACKAGE_NAME is NOT installed on the device.
|
||||
setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
|
||||
setUpPackageNotInstalled(anotherPackage);
|
||||
|
||||
mBackupHelper.stageAndApplyRestoredPayload(outDefault.toByteArray(), DEFAULT_USER_ID);
|
||||
mBackupHelper.stageAndApplyRestoredPayload(outWorkProfile.toByteArray(),
|
||||
WORK_PROFILE_USER_ID);
|
||||
|
||||
verifyNothingRestored();
|
||||
|
||||
// Verify stage file contents.
|
||||
AtomicFile stageFileDefaultUser = getStageFileIfExists(DEFAULT_USER_ID);
|
||||
verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, stageFileDefaultUser,
|
||||
DEFAULT_CREATION_TIME_MILLIS);
|
||||
|
||||
AtomicFile stageFileWorkProfile = getStageFileIfExists(WORK_PROFILE_USER_ID);
|
||||
verifyStageFileContent(pkgLocalesMapWorkProfile, stageFileWorkProfile,
|
||||
DEFAULT_CREATION_TIME_MILLIS);
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_USER_REMOVED);
|
||||
intent.putExtra(Intent.EXTRA_USER_HANDLE, DEFAULT_USER_ID);
|
||||
mUserMonitor.onReceive(mMockContext, intent);
|
||||
|
||||
// Stage file should be removed only for DEFAULT_USER_ID.
|
||||
checkStageFileDoesNotExist(DEFAULT_USER_ID);
|
||||
verifyStageFileContent(pkgLocalesMapWorkProfile, stageFileWorkProfile,
|
||||
DEFAULT_CREATION_TIME_MILLIS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadStageFiles_invalidNameFormat_stageFileDeleted() throws Exception {
|
||||
// Stage file name should be : staged_locales_<user_id_int>.xml
|
||||
File stageFile = new File(STAGED_LOCALES_DIR, "xyz.xml");
|
||||
assertTrue(stageFile.createNewFile());
|
||||
assertTrue(stageFile.isFile());
|
||||
|
||||
// Putting valid xml data in file.
|
||||
FileOutputStream out = new FileOutputStream(stageFile);
|
||||
writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP, /* forStage= */
|
||||
true, /* creationTimeMillis= */ 0);
|
||||
out.flush();
|
||||
out.close();
|
||||
|
||||
verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP,
|
||||
new AtomicFile(stageFile), /* creationTimeMillis= */ 0);
|
||||
|
||||
mBackupHelper = new LocaleManagerBackupHelper(mMockContext, mMockLocaleManagerService,
|
||||
mMockPackageManagerInternal, STAGED_LOCALES_DIR, mClock);
|
||||
assertFalse(stageFile.isFile());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadStageFiles_userIdNotParseable_stageFileDeleted() throws Exception {
|
||||
// Stage file name should be : staged_locales_<user_id_int>.xml
|
||||
File stageFile = new File(STAGED_LOCALES_DIR, "staged_locales_abc.xml");
|
||||
assertTrue(stageFile.createNewFile());
|
||||
assertTrue(stageFile.isFile());
|
||||
|
||||
// Putting valid xml data in file.
|
||||
FileOutputStream out = new FileOutputStream(stageFile);
|
||||
writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP, /* forStage= */
|
||||
true, /* creationTimeMillis= */ 0);
|
||||
out.flush();
|
||||
out.close();
|
||||
|
||||
verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP,
|
||||
new AtomicFile(stageFile), /* creationTimeMillis= */ 0);
|
||||
|
||||
mBackupHelper = new LocaleManagerBackupHelper(mMockContext, mMockLocaleManagerService,
|
||||
mMockPackageManagerInternal, STAGED_LOCALES_DIR, mClock);
|
||||
assertFalse(stageFile.isFile());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadStageFiles_invalidContent_stageFileDeleted() throws Exception {
|
||||
File stageFile = new File(STAGED_LOCALES_DIR, "staged_locales_0.xml");
|
||||
assertTrue(stageFile.createNewFile());
|
||||
assertTrue(stageFile.isFile());
|
||||
|
||||
FileOutputStream out = new FileOutputStream(stageFile);
|
||||
out.write("some_non_xml_string".getBytes());
|
||||
out.close();
|
||||
|
||||
mBackupHelper = new LocaleManagerBackupHelper(mMockContext, mMockLocaleManagerService,
|
||||
mMockPackageManagerInternal, STAGED_LOCALES_DIR, mClock);
|
||||
assertFalse(stageFile.isFile());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadStageFiles_validContent_doesLazyRestore() throws Exception {
|
||||
File stageFile = new File(STAGED_LOCALES_DIR, "staged_locales_0.xml");
|
||||
assertTrue(stageFile.createNewFile());
|
||||
assertTrue(stageFile.isFile());
|
||||
|
||||
// Putting valid xml data in file.
|
||||
FileOutputStream out = new FileOutputStream(stageFile);
|
||||
writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP, /* forStage= */
|
||||
true, DEFAULT_CREATION_TIME_MILLIS);
|
||||
out.flush();
|
||||
out.close();
|
||||
|
||||
verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP,
|
||||
new AtomicFile(stageFile), DEFAULT_CREATION_TIME_MILLIS);
|
||||
|
||||
mBackupHelper = new LocaleManagerBackupHelper(mMockContext, mMockLocaleManagerService,
|
||||
mMockPackageManagerInternal, STAGED_LOCALES_DIR, mClock);
|
||||
mPackageMonitor = mBackupHelper.getPackageMonitor();
|
||||
|
||||
// Stage file still exists.
|
||||
assertTrue(stageFile.isFile());
|
||||
|
||||
// App is installed later.
|
||||
setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
|
||||
setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
|
||||
|
||||
mPackageMonitor.onPackageAdded(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
|
||||
|
||||
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
|
||||
DEFAULT_USER_ID, DEFAULT_LOCALES);
|
||||
|
||||
// Stage file gets deleted here because all staged locales have been applied.
|
||||
assertFalse(stageFile.isFile());
|
||||
}
|
||||
|
||||
private void setUpPackageInstalled(String packageName) throws Exception {
|
||||
doReturn(new PackageInfo()).when(mMockPackageManager).getPackageInfoAsUser(
|
||||
eq(packageName), anyInt(), anyInt());
|
||||
}
|
||||
|
||||
private void setUpPackageNotInstalled(String packageName) throws Exception {
|
||||
doReturn(null).when(mMockPackageManager).getPackageInfoAsUser(eq(packageName),
|
||||
anyInt(), anyInt());
|
||||
}
|
||||
|
||||
private void setUpLocalesForPackage(String packageName, LocaleList locales) throws Exception {
|
||||
doReturn(locales).when(mMockLocaleManagerService).getApplicationLocales(
|
||||
eq(packageName), anyInt());
|
||||
@@ -176,15 +622,38 @@ public class LocaleManagerBackupRestoreTest {
|
||||
.getInstalledApplications(anyLong(), anyInt(), anyInt());
|
||||
}
|
||||
|
||||
private void verifyPayloadForAppLocales(Map<String, String> expectedPkgLocalesMap,
|
||||
/**
|
||||
* Verifies that nothing was restored for any package.
|
||||
*
|
||||
* <p>If {@link LocaleManagerService#setApplicationLocales} is not invoked, we can conclude
|
||||
* that nothing was restored.
|
||||
*/
|
||||
private void verifyNothingRestored() throws Exception {
|
||||
verify(mMockLocaleManagerService, times(0)).setApplicationLocales(anyString(), anyInt(),
|
||||
any());
|
||||
}
|
||||
|
||||
|
||||
private static void verifyPayloadForAppLocales(Map<String, String> expectedPkgLocalesMap,
|
||||
byte[] payload)
|
||||
throws IOException, XmlPullParserException {
|
||||
verifyPayloadForAppLocales(expectedPkgLocalesMap, payload, /* forStage= */ false, -1);
|
||||
}
|
||||
|
||||
private static void verifyPayloadForAppLocales(Map<String, String> expectedPkgLocalesMap,
|
||||
byte[] payload, boolean forStage, long expectedCreationTime)
|
||||
throws IOException, XmlPullParserException {
|
||||
final ByteArrayInputStream stream = new ByteArrayInputStream(payload);
|
||||
final TypedXmlPullParser parser = Xml.newFastPullParser();
|
||||
parser.setInput(stream, StandardCharsets.UTF_8.name());
|
||||
|
||||
Map<String, String> backupDataMap = new HashMap<>();
|
||||
XmlUtils.beginDocument(parser, STAGE_LOCALES_XML_TAG);
|
||||
XmlUtils.beginDocument(parser, TEST_LOCALES_XML_TAG);
|
||||
if (forStage) {
|
||||
long actualCreationTime = parser.getAttributeLong(/* namespace= */ null,
|
||||
"creationTimeMillis");
|
||||
assertEquals(expectedCreationTime, actualCreationTime);
|
||||
}
|
||||
int depth = parser.getDepth();
|
||||
while (XmlUtils.nextElementWithin(parser, depth)) {
|
||||
if (parser.getName().equals("package")) {
|
||||
@@ -196,4 +665,76 @@ public class LocaleManagerBackupRestoreTest {
|
||||
|
||||
assertEquals(expectedPkgLocalesMap, backupDataMap);
|
||||
}
|
||||
|
||||
private static void writeTestPayload(OutputStream stream, Map<String, String> pkgLocalesMap)
|
||||
throws IOException {
|
||||
writeTestPayload(stream, pkgLocalesMap, /* forStage= */ false, /* creationTimeMillis= */
|
||||
-1);
|
||||
}
|
||||
|
||||
private static void writeTestPayload(OutputStream stream, Map<String, String> pkgLocalesMap,
|
||||
boolean forStage, long creationTimeMillis)
|
||||
throws IOException {
|
||||
if (pkgLocalesMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
TypedXmlSerializer out = Xml.newFastSerializer();
|
||||
out.setOutput(stream, StandardCharsets.UTF_8.name());
|
||||
out.startDocument(/* encoding= */ null, /* standalone= */ true);
|
||||
out.startTag(/* namespace= */ null, TEST_LOCALES_XML_TAG);
|
||||
|
||||
if (forStage) {
|
||||
out.attribute(/* namespace= */ null, "creationTimeMillis",
|
||||
Long.toString(creationTimeMillis));
|
||||
}
|
||||
|
||||
for (String pkg : pkgLocalesMap.keySet()) {
|
||||
out.startTag(/* namespace= */ null, "package");
|
||||
out.attribute(/* namespace= */ null, "name", pkg);
|
||||
out.attribute(/* namespace= */ null, "locales", pkgLocalesMap.get(pkg));
|
||||
out.endTag(/*namespace= */ null, "package");
|
||||
}
|
||||
|
||||
out.endTag(/* namespace= */ null, TEST_LOCALES_XML_TAG);
|
||||
out.endDocument();
|
||||
}
|
||||
|
||||
private static void verifyStageFileContent(Map<String, String> expectedPkgLocalesMap,
|
||||
AtomicFile stageFile,
|
||||
long creationTimeMillis)
|
||||
throws Exception {
|
||||
assertNotNull(stageFile);
|
||||
try (InputStream stagedDataInputStream = stageFile.openRead()) {
|
||||
verifyPayloadForAppLocales(expectedPkgLocalesMap, stagedDataInputStream.readAllBytes(),
|
||||
/* forStage= */ true, creationTimeMillis);
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkStageFileDoesNotExist(int userId) {
|
||||
assertNull(getStageFileIfExists(userId));
|
||||
}
|
||||
|
||||
private static void checkStageFileExists(int userId) {
|
||||
assertNotNull(getStageFileIfExists(userId));
|
||||
}
|
||||
|
||||
private static AtomicFile getStageFileIfExists(int userId) {
|
||||
File file = new File(STAGED_LOCALES_DIR, String.format("staged_locales_%d.xml", userId));
|
||||
if (file.isFile()) {
|
||||
return new AtomicFile(file);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void cleanStagedFiles() {
|
||||
File[] files = STAGED_LOCALES_DIR.listFiles();
|
||||
if (files != null) {
|
||||
for (File f : files) {
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user