Merge "Support B&R in gender"
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.backup;
|
||||
|
||||
import static android.app.backup.BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
|
||||
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.backup.BackupDataOutput;
|
||||
import android.app.backup.BlobBackupHelper;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.grammaticalinflection.GrammaticalInflectionManagerInternal;
|
||||
|
||||
public class AppGrammaticalGenderBackupHelper extends BlobBackupHelper {
|
||||
private static final int BLOB_VERSION = 1;
|
||||
private static final String KEY_APP_GENDER = "app_gender";
|
||||
|
||||
private final @UserIdInt int mUserId;
|
||||
private final GrammaticalInflectionManagerInternal mGrammarInflectionManagerInternal;
|
||||
|
||||
public AppGrammaticalGenderBackupHelper(int userId) {
|
||||
super(BLOB_VERSION, KEY_APP_GENDER);
|
||||
mUserId = userId;
|
||||
mGrammarInflectionManagerInternal = LocalServices.getService(
|
||||
GrammaticalInflectionManagerInternal.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performBackup(ParcelFileDescriptor oldStateFd, BackupDataOutput data,
|
||||
ParcelFileDescriptor newStateFd) {
|
||||
// Only backup the gender data if e2e encryption is present
|
||||
if ((data.getTransportFlags() & FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.performBackup(oldStateFd, data, newStateFd);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] getBackupPayload(String key) {
|
||||
return KEY_APP_GENDER.equals(key) && mGrammarInflectionManagerInternal != null ?
|
||||
mGrammarInflectionManagerInternal.getBackupPayload(mUserId) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyRestoredPayload(String key, byte[] payload) {
|
||||
if (KEY_APP_GENDER.equals(key) && mGrammarInflectionManagerInternal != null) {
|
||||
mGrammarInflectionManagerInternal.stageAndApplyRestoredPayload(payload, mUserId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
|
||||
private static final String SLICES_HELPER = "slices";
|
||||
private static final String PEOPLE_HELPER = "people";
|
||||
private static final String APP_LOCALES_HELPER = "app_locales";
|
||||
private static final String APP_GENDER_HELPER = "app_gender";
|
||||
|
||||
// These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME
|
||||
// are also used in the full-backup file format, so must not change unless steps are
|
||||
@@ -105,6 +106,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
|
||||
addHelper(SLICES_HELPER, new SliceBackupHelper(this));
|
||||
addHelper(PEOPLE_HELPER, new PeopleBackupHelper(mUserId));
|
||||
addHelper(APP_LOCALES_HELPER, new AppSpecificLocalesBackupHelper(mUserId));
|
||||
addHelper(APP_GENDER_HELPER, new AppGrammaticalGenderBackupHelper(mUserId));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.grammaticalinflection;
|
||||
|
||||
import android.app.backup.BackupManager;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class GrammaticalInflectionBackupHelper {
|
||||
private static final String TAG = GrammaticalInflectionBackupHelper.class.getSimpleName();
|
||||
private static final String SYSTEM_BACKUP_PACKAGE_KEY = "android";
|
||||
// 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 SparseArray<StagedData> mCache = new SparseArray<>();
|
||||
private final Object mCacheLock = new Object();
|
||||
private final PackageManager mPackageManager;
|
||||
private final GrammaticalInflectionService mGrammaticalGenderService;
|
||||
private final Clock mClock;
|
||||
|
||||
static class StagedData {
|
||||
final long mCreationTimeMillis;
|
||||
final HashMap<String, Integer> mPackageStates;
|
||||
|
||||
StagedData(long creationTimeMillis) {
|
||||
mCreationTimeMillis = creationTimeMillis;
|
||||
mPackageStates = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
public GrammaticalInflectionBackupHelper(GrammaticalInflectionService grammaticalGenderService,
|
||||
PackageManager packageManager) {
|
||||
mGrammaticalGenderService = grammaticalGenderService;
|
||||
mPackageManager = packageManager;
|
||||
mClock = Clock.systemUTC();
|
||||
}
|
||||
|
||||
public byte[] getBackupPayload(int userId) {
|
||||
synchronized (mCacheLock) {
|
||||
cleanStagedDataForOldEntries();
|
||||
}
|
||||
|
||||
HashMap<String, Integer> pkgGenderInfo = new HashMap<>();
|
||||
for (ApplicationInfo appInfo : mPackageManager.getInstalledApplicationsAsUser(
|
||||
PackageManager.ApplicationInfoFlags.of(0), userId)) {
|
||||
int gender = mGrammaticalGenderService.getApplicationGrammaticalGender(
|
||||
appInfo.packageName, userId);
|
||||
if (gender != Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED) {
|
||||
pkgGenderInfo.put(appInfo.packageName, gender);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pkgGenderInfo.isEmpty()) {
|
||||
return convertToByteArray(pkgGenderInfo);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void stageAndApplyRestoredPayload(byte[] payload, int userId) {
|
||||
synchronized (mCacheLock) {
|
||||
cleanStagedDataForOldEntries();
|
||||
|
||||
HashMap<String, Integer> pkgInfo = readFromByteArray(payload);
|
||||
if (pkgInfo.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
StagedData stagedData = new StagedData(mClock.millis());
|
||||
for (Map.Entry<String, Integer> info : pkgInfo.entrySet()) {
|
||||
// If app installed, restore immediately, otherwise put it in cache.
|
||||
if (isPackageInstalledForUser(info.getKey(), userId)) {
|
||||
if (!hasSetBeforeRestoring(info.getKey(), userId)) {
|
||||
mGrammaticalGenderService.setRequestedApplicationGrammaticalGender(
|
||||
info.getKey(), userId, info.getValue());
|
||||
}
|
||||
} else {
|
||||
if (info.getValue() != Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED) {
|
||||
stagedData.mPackageStates.put(info.getKey(), info.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mCache.append(userId, stagedData);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasSetBeforeRestoring(String pkgName, int userId) {
|
||||
return mGrammaticalGenderService.getApplicationGrammaticalGender(pkgName, userId)
|
||||
!= Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
|
||||
}
|
||||
|
||||
public void onPackageAdded(String packageName, int uid) {
|
||||
synchronized (mCacheLock) {
|
||||
int userId = UserHandle.getUserId(uid);
|
||||
StagedData cache = mCache.get(userId);
|
||||
if (cache != null && cache.mPackageStates.containsKey(packageName)) {
|
||||
int grammaticalGender = cache.mPackageStates.get(packageName);
|
||||
if (grammaticalGender != Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED) {
|
||||
mGrammaticalGenderService.setRequestedApplicationGrammaticalGender(
|
||||
packageName, userId, grammaticalGender);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onPackageDataCleared() {
|
||||
notifyBackupManager();
|
||||
}
|
||||
|
||||
public void onPackageRemoved() {
|
||||
notifyBackupManager();
|
||||
}
|
||||
|
||||
public static void notifyBackupManager() {
|
||||
BackupManager.dataChanged(SYSTEM_BACKUP_PACKAGE_KEY);
|
||||
}
|
||||
|
||||
private byte[] convertToByteArray(HashMap<String, Integer> pkgGenderInfo) {
|
||||
try (final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
final ObjectOutputStream objStream = new ObjectOutputStream(out)) {
|
||||
objStream.writeObject(pkgGenderInfo);
|
||||
return out.toByteArray();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "cannot convert payload to byte array.", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private HashMap<String, Integer> readFromByteArray(byte[] payload) {
|
||||
HashMap<String, Integer> data = new HashMap<>();
|
||||
|
||||
try (ByteArrayInputStream byteIn = new ByteArrayInputStream(payload);
|
||||
ObjectInputStream in = new ObjectInputStream(byteIn)) {
|
||||
data = (HashMap<String, Integer>) in.readObject();
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
Log.e(TAG, "cannot convert payload to HashMap.", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private void cleanStagedDataForOldEntries() {
|
||||
for (int i = 0; i < mCache.size(); i++) {
|
||||
int userId = mCache.keyAt(i);
|
||||
StagedData stagedData = mCache.get(userId);
|
||||
if (stagedData.mCreationTimeMillis
|
||||
< mClock.millis() - STAGE_DATA_RETENTION_PERIOD.toMillis()) {
|
||||
mCache.remove(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPackageInstalledForUser(String packageName, int userId) {
|
||||
PackageInfo pkgInfo = null;
|
||||
try {
|
||||
pkgInfo = mPackageManager.getPackageInfoAsUser(packageName, /* flags= */ 0, userId);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// The package is not installed
|
||||
}
|
||||
return pkgInfo != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.grammaticalinflection;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* System-server internal interface to the {@link android.app.GrammaticalInflectionManager}.
|
||||
*
|
||||
* @hide Only for use within the system server.
|
||||
*/
|
||||
public abstract class GrammaticalInflectionManagerInternal {
|
||||
/**
|
||||
* Returns the app-gender to be backed up as a data-blob.
|
||||
*/
|
||||
public abstract @Nullable byte[] getBackupPayload(int userId);
|
||||
|
||||
/**
|
||||
* Restores the app-gender that were previously backed up.
|
||||
*
|
||||
* <p>This method will parse the input data blob and restore the gender for apps which are
|
||||
* present on the device. It will stage the gender data for the apps which are not installed
|
||||
* at the time this is called, to be referenced later when the app is installed.
|
||||
*/
|
||||
public abstract void stageAndApplyRestoredPayload(byte[] payload, int userId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.grammaticalinflection;
|
||||
|
||||
import com.android.internal.content.PackageMonitor;
|
||||
|
||||
public class GrammaticalInflectionPackageMonitor extends PackageMonitor {
|
||||
private GrammaticalInflectionBackupHelper mBackupHelper;
|
||||
|
||||
GrammaticalInflectionPackageMonitor(GrammaticalInflectionBackupHelper backupHelper) {
|
||||
mBackupHelper = backupHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageAdded(String packageName, int uid) {
|
||||
mBackupHelper.onPackageAdded(packageName, uid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageDataCleared(String packageName, int uid) {
|
||||
mBackupHelper.onPackageDataCleared();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageRemoved(String packageName, int uid) {
|
||||
mBackupHelper.onPackageRemoved();
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,12 @@ package com.android.server.grammaticalinflection;
|
||||
|
||||
import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.app.IGrammaticalInflectionManager;
|
||||
import android.content.Context;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.Process;
|
||||
import android.os.SystemProperties;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
@@ -34,6 +37,7 @@ import com.android.server.wm.ActivityTaskManagerInternal;
|
||||
*/
|
||||
public class GrammaticalInflectionService extends SystemService {
|
||||
|
||||
private final GrammaticalInflectionBackupHelper mBackupHelper;
|
||||
private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
|
||||
private static final String GRAMMATICAL_INFLECTION_ENABLED =
|
||||
"i18n.grammatical_Inflection.enabled";
|
||||
@@ -46,17 +50,20 @@ public class GrammaticalInflectionService extends SystemService {
|
||||
* </p>
|
||||
*
|
||||
* @param context The system server context.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public GrammaticalInflectionService(Context context) {
|
||||
super(context);
|
||||
mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
|
||||
mBackupHelper = new GrammaticalInflectionBackupHelper(
|
||||
this, context.getPackageManager());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
publishBinderService(Context.GRAMMATICAL_INFLECTION_SERVICE, mService);
|
||||
LocalServices.addService(GrammaticalInflectionManagerInternal.class,
|
||||
new GrammaticalInflectionManagerInternalImpl());
|
||||
}
|
||||
|
||||
private final IBinder mService = new IGrammaticalInflectionManager.Stub() {
|
||||
@@ -68,7 +75,40 @@ public class GrammaticalInflectionService extends SystemService {
|
||||
}
|
||||
};
|
||||
|
||||
private void setRequestedApplicationGrammaticalGender(
|
||||
private final class GrammaticalInflectionManagerInternalImpl
|
||||
extends GrammaticalInflectionManagerInternal {
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public byte[] getBackupPayload(int userId) {
|
||||
checkCallerIsSystem();
|
||||
return mBackupHelper.getBackupPayload(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stageAndApplyRestoredPayload(byte[] payload, int userId) {
|
||||
mBackupHelper.stageAndApplyRestoredPayload(payload, userId);
|
||||
}
|
||||
|
||||
private void checkCallerIsSystem() {
|
||||
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
|
||||
throw new SecurityException("Caller is not system.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected int getApplicationGrammaticalGender(String appPackageName, int userId) {
|
||||
final ActivityTaskManagerInternal.PackageConfig appConfig =
|
||||
mActivityTaskManagerInternal.getApplicationConfig(appPackageName, userId);
|
||||
|
||||
if (appConfig == null || appConfig.mGrammaticalGender == null) {
|
||||
return GRAMMATICAL_GENDER_NOT_SPECIFIED;
|
||||
} else {
|
||||
return appConfig.mGrammaticalGender;
|
||||
}
|
||||
}
|
||||
|
||||
protected void setRequestedApplicationGrammaticalGender(
|
||||
String appPackageName, int userId, int gender) {
|
||||
if (!SystemProperties.getBoolean(GRAMMATICAL_INFLECTION_ENABLED, true)) {
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.grammaticalinflection;
|
||||
|
||||
import static junit.framework.Assert.assertNull;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class GrammaticalInflectionBackupTest {
|
||||
private static final int DEFAULT_USER_ID = 0;
|
||||
private static final String DEFAULT_PACKAGE_NAME = "com.test.package.name";
|
||||
|
||||
@Rule
|
||||
public final MockitoRule mockito = MockitoJUnit.rule();
|
||||
|
||||
@Mock
|
||||
private PackageManager mMockPackageManager;
|
||||
@Mock
|
||||
private GrammaticalInflectionService mGrammaticalInflectionService;
|
||||
|
||||
private GrammaticalInflectionBackupHelper mBackupHelper;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mBackupHelper = new GrammaticalInflectionBackupHelper(
|
||||
mGrammaticalInflectionService, mMockPackageManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackupPayload_noAppsInstalled_returnsNull() {
|
||||
assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackupPayload_AppsInstalled_returnsGender()
|
||||
throws IOException, ClassNotFoundException {
|
||||
mockAppInstalled();
|
||||
mockGetApplicationGrammaticalGender(Configuration.GRAMMATICAL_GENDER_MASCULINE);
|
||||
|
||||
HashMap<String, Integer> payload =
|
||||
readFromByteArray(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
|
||||
|
||||
// verify the payload
|
||||
HashMap<String, Integer> expectationMap = new HashMap<>();
|
||||
expectationMap.put(DEFAULT_PACKAGE_NAME, Configuration.GRAMMATICAL_GENDER_MASCULINE);
|
||||
assertTrue(Maps.difference(payload, expectationMap).areEqual());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyPayload_onPackageAdded_setApplicationGrammaticalGender()
|
||||
throws IOException {
|
||||
mockAppInstalled();
|
||||
|
||||
HashMap<String, Integer> testData = new HashMap<>();
|
||||
testData.put(DEFAULT_PACKAGE_NAME, Configuration.GRAMMATICAL_GENDER_NEUTRAL);
|
||||
mBackupHelper.stageAndApplyRestoredPayload(convertToByteArray(testData), DEFAULT_USER_ID);
|
||||
mBackupHelper.onPackageAdded(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
|
||||
|
||||
verify(mGrammaticalInflectionService).setRequestedApplicationGrammaticalGender(
|
||||
eq(DEFAULT_PACKAGE_NAME),
|
||||
eq(DEFAULT_USER_ID),
|
||||
eq(Configuration.GRAMMATICAL_GENDER_NEUTRAL));
|
||||
}
|
||||
|
||||
private void mockAppInstalled() {
|
||||
ApplicationInfo dummyApp = new ApplicationInfo();
|
||||
dummyApp.packageName = DEFAULT_PACKAGE_NAME;
|
||||
doReturn(List.of(dummyApp)).when(mMockPackageManager)
|
||||
.getInstalledApplicationsAsUser(any(), anyInt());
|
||||
}
|
||||
|
||||
private void mockGetApplicationGrammaticalGender(int grammaticalGender) {
|
||||
doReturn(grammaticalGender).when(mGrammaticalInflectionService)
|
||||
.getApplicationGrammaticalGender(
|
||||
eq(DEFAULT_PACKAGE_NAME), eq(DEFAULT_USER_ID));
|
||||
}
|
||||
|
||||
private byte[] convertToByteArray(HashMap<String, Integer> pkgGenderInfo) throws IOException{
|
||||
try (final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
final ObjectOutputStream objStream = new ObjectOutputStream(out)) {
|
||||
objStream.writeObject(pkgGenderInfo);
|
||||
return out.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private HashMap<String, Integer> readFromByteArray(byte[] payload)
|
||||
throws IOException, ClassNotFoundException {
|
||||
HashMap<String, Integer> data;
|
||||
|
||||
try (ByteArrayInputStream byteIn = new ByteArrayInputStream(payload);
|
||||
ObjectInputStream in = new ObjectInputStream(byteIn)) {
|
||||
data = (HashMap<String, Integer>) in.readObject();
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
throw e;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user