Add ability to exclude settings from being restored.

Bug: 143129051
Test: atest SettingsProviderTest
Change-Id: Id5d367b0fb8386dbae4eb3e29c8c1e9ba3c547d6
This commit is contained in:
Al Sutton
2019-10-31 15:53:43 +00:00
parent b6314a1a1c
commit 0371b06a5b
4 changed files with 219 additions and 27 deletions

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2019 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.
-->
<!-- These are arrays of settings which should not be restored to this device -->
<resources>
<string-array name="restore_blocked_device_specific_settings" />
<string-array name="restore_blocked_global_settings" />
<string-array name="restore_blocked_secure_settings" />
<string-array name="restore_blocked_system_settings" />
</resources>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<overlayable name="SettingsToNotRestore">
<policy type="product|system|vendor">
<item type="array" name="restore_blocked_device_specific_settings" />
<item type="array" name="restore_blocked_global_settings" />
<item type="array" name="restore_blocked_secure_settings" />
<item type="array" name="restore_blocked_system_settings" />
</policy>
</overlayable>
</resources>

View File

@@ -66,6 +66,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.time.DateTimeException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
@@ -250,7 +251,13 @@ public class SettingsBackupAgent extends BackupAgentHelper {
@Override
public void onRestore(BackupDataInput data, int appVersionCode,
ParcelFileDescriptor newState) throws IOException {
ParcelFileDescriptor newState) {
throw new RuntimeException("SettingsBackupAgent has been migrated to use key exclusion");
}
@Override
public void onRestore(BackupDataInput data, long appVersionCode,
ParcelFileDescriptor newState, Set<String> dynamicBlockList) throws IOException {
if (DEBUG) {
Log.d(TAG, "onRestore(): appVersionCode: " + appVersionCode
@@ -266,7 +273,7 @@ public class SettingsBackupAgent extends BackupAgentHelper {
}
// versionCode of com.android.providers.settings corresponds to SDK_INT
mRestoredFromSdkInt = appVersionCode;
mRestoredFromSdkInt = (int) appVersionCode;
HashSet<String> movedToGlobal = new HashSet<String>();
Settings.System.getMovedToGlobalSettings(movedToGlobal);
@@ -292,16 +299,29 @@ public class SettingsBackupAgent extends BackupAgentHelper {
switch (key) {
case KEY_SYSTEM :
restoreSettings(data, Settings.System.CONTENT_URI, movedToGlobal,
movedToSecure);
movedToSecure, R.array.restore_blocked_system_settings,
dynamicBlockList);
mSettingsHelper.applyAudioSettings();
break;
case KEY_SECURE :
restoreSettings(data, Settings.Secure.CONTENT_URI, movedToGlobal, null);
restoreSettings(
data,
Settings.Secure.CONTENT_URI,
movedToGlobal,
null,
R.array.restore_blocked_secure_settings,
dynamicBlockList);
break;
case KEY_GLOBAL :
restoreSettings(data, Settings.Global.CONTENT_URI, null, movedToSecure);
restoreSettings(
data,
Settings.Global.CONTENT_URI,
null,
movedToSecure,
R.array.restore_blocked_global_settings,
dynamicBlockList);
break;
case KEY_WIFI_SUPPLICANT :
@@ -345,7 +365,10 @@ public class SettingsBackupAgent extends BackupAgentHelper {
case KEY_DEVICE_SPECIFIC_CONFIG:
byte[] restoredDeviceSpecificConfig = new byte[size];
data.readEntityData(restoredDeviceSpecificConfig, 0, size);
restoreDeviceSpecificConfig(restoredDeviceSpecificConfig);
restoreDeviceSpecificConfig(
restoredDeviceSpecificConfig,
R.array.restore_blocked_device_specific_settings,
dynamicBlockList);
break;
default :
@@ -394,14 +417,22 @@ public class SettingsBackupAgent extends BackupAgentHelper {
byte[] buffer = new byte[nBytes];
in.readFully(buffer, 0, nBytes);
restoreSettings(buffer, nBytes, Settings.System.CONTENT_URI, movedToGlobal,
movedToSecure);
movedToSecure, R.array.restore_blocked_system_settings,
Collections.emptySet());
// secure settings
nBytes = in.readInt();
if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of secure settings data");
if (nBytes > buffer.length) buffer = new byte[nBytes];
in.readFully(buffer, 0, nBytes);
restoreSettings(buffer, nBytes, Settings.Secure.CONTENT_URI, movedToGlobal, null);
restoreSettings(
buffer,
nBytes,
Settings.Secure.CONTENT_URI,
movedToGlobal,
null,
R.array.restore_blocked_secure_settings,
Collections.emptySet());
// Global only if sufficiently new
if (version >= FULL_BACKUP_ADDED_GLOBAL) {
@@ -411,7 +442,8 @@ public class SettingsBackupAgent extends BackupAgentHelper {
in.readFully(buffer, 0, nBytes);
movedToGlobal.clear(); // no redirection; this *is* the global namespace
restoreSettings(buffer, nBytes, Settings.Global.CONTENT_URI, movedToGlobal,
movedToSecure);
movedToSecure, R.array.restore_blocked_global_settings,
Collections.emptySet());
}
// locale
@@ -612,8 +644,13 @@ public class SettingsBackupAgent extends BackupAgentHelper {
return baos.toByteArray();
}
private void restoreSettings(BackupDataInput data, Uri contentUri,
HashSet<String> movedToGlobal, Set<String> movedToSecure) {
private void restoreSettings(
BackupDataInput data,
Uri contentUri,
HashSet<String> movedToGlobal,
Set<String> movedToSecure,
int blockedSettingsArrayId,
Set<String> dynamicBlockList) {
byte[] settings = new byte[data.getDataSize()];
try {
data.readEntityData(settings, 0, settings.length);
@@ -621,16 +658,44 @@ public class SettingsBackupAgent extends BackupAgentHelper {
Log.e(TAG, "Couldn't read entity data");
return;
}
restoreSettings(settings, settings.length, contentUri, movedToGlobal, movedToSecure);
restoreSettings(
settings,
settings.length,
contentUri,
movedToGlobal,
movedToSecure,
blockedSettingsArrayId,
dynamicBlockList);
}
private void restoreSettings(byte[] settings, int bytes, Uri contentUri,
HashSet<String> movedToGlobal, Set<String> movedToSecure) {
restoreSettings(settings, 0, bytes, contentUri, movedToGlobal, movedToSecure);
private void restoreSettings(
byte[] settings,
int bytes,
Uri contentUri,
HashSet<String> movedToGlobal,
Set<String> movedToSecure,
int blockedSettingsArrayId,
Set<String> dynamicBlockList) {
restoreSettings(
settings,
0,
bytes,
contentUri,
movedToGlobal,
movedToSecure,
blockedSettingsArrayId,
dynamicBlockList);
}
private void restoreSettings(byte[] settings, int pos, int bytes, Uri contentUri,
HashSet<String> movedToGlobal, Set<String> movedToSecure) {
private void restoreSettings(
byte[] settings,
int pos,
int bytes,
Uri contentUri,
HashSet<String> movedToGlobal,
Set<String> movedToSecure,
int blockedSettingsArrayId,
Set<String> dynamicBlockList) {
if (DEBUG) {
Log.i(TAG, "restoreSettings: " + contentUri);
}
@@ -662,9 +727,20 @@ public class SettingsBackupAgent extends BackupAgentHelper {
SettingsHelper settingsHelper = mSettingsHelper;
ContentResolver cr = getContentResolver();
final int whiteListSize = whitelist.length;
for (int i = 0; i < whiteListSize; i++) {
String key = whitelist[i];
Set<String> blockedSettings = getBlockedSettings(blockedSettingsArrayId);
for (String key : whitelist) {
boolean isBlockedBySystem = blockedSettings != null && blockedSettings.contains(key);
if (isBlockedBySystem || isBlockedByDynamicList(dynamicBlockList, contentUri, key)) {
Log.i(
TAG,
"Key "
+ key
+ " removed from restore by "
+ (isBlockedBySystem ? "system" : "dynamic")
+ " block list");
continue;
}
String value = null;
boolean hasValueToRestore = false;
@@ -722,6 +798,19 @@ public class SettingsBackupAgent extends BackupAgentHelper {
}
}
private boolean isBlockedByDynamicList(Set<String> dynamicBlockList, Uri areaUri, String key) {
String contentKey = Uri.withAppendedPath(areaUri, key).toString();
return dynamicBlockList.contains(contentKey);
}
// There may be other sources of blocked settings, so I'm separating out this
// code to make it easy to modify in the future.
@VisibleForTesting
protected Set<String> getBlockedSettings(int blockedSettingsArrayId) {
String[] blockedSettings = getResources().getStringArray(blockedSettingsArrayId);
return new HashSet<>(Arrays.asList(blockedSettings));
}
private boolean isValidSettingValue(String key, String value,
Map<String, Validator> validators) {
if (key == null || validators == null) {
@@ -998,10 +1087,13 @@ public class SettingsBackupAgent extends BackupAgentHelper {
* Restore the device specific settings.
*
* @param data The byte array holding a backed up version of another devices settings.
* @param blockedSettingsArrayId The string array resource holding the settings not to restore.
* @param dynamicBlocklist The dynamic list of settings not to restore fed into this agent.
* @return true if the restore succeeded, false if it was stopped.
*/
@VisibleForTesting
boolean restoreDeviceSpecificConfig(byte[] data) {
boolean restoreDeviceSpecificConfig(byte[] data, int blockedSettingsArrayId,
Set<String> dynamicBlocklist) {
// We're using an AtomicInteger to wrap the position int and allow called methods to
// modify it.
AtomicInteger pos = new AtomicInteger(0);
@@ -1013,7 +1105,14 @@ public class SettingsBackupAgent extends BackupAgentHelper {
int dataStart = pos.get();
restoreSettings(
data, dataStart, data.length, Settings.Secure.CONTENT_URI, null, null);
data,
dataStart,
data.length,
Settings.Secure.CONTENT_URI,
null,
null,
blockedSettingsArrayId,
dynamicBlocklist);
updateWindowManagerIfNeeded(originalDensity);

View File

@@ -37,14 +37,14 @@ import android.test.mock.MockContentResolver;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.annotations.VisibleForTesting;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -85,11 +85,32 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
assertEquals("Not all values backed up.", TEST_VALUES.keySet(), helper.mReadEntries);
mAgentUnderTest.restoreDeviceSpecificConfig(settingsBackup);
mAgentUnderTest.restoreDeviceSpecificConfig(
settingsBackup,
R.array.restore_blocked_device_specific_settings,
Collections.emptySet());
assertEquals("Not all values were restored.", TEST_VALUES, helper.mWrittenValues);
}
@Test
public void testRoundTripDeviceSpecificSettingsWithBlock() throws IOException {
TestSettingsHelper helper = new TestSettingsHelper(mContext);
mAgentUnderTest.mSettingsHelper = helper;
byte[] settingsBackup = mAgentUnderTest.getDeviceSpecificConfiguration();
assertEquals("Not all values backed up.", TEST_VALUES.keySet(), helper.mReadEntries);
mAgentUnderTest.setBlockedSettings(TEST_VALUES.keySet().toArray(new String[0]));
mAgentUnderTest.restoreDeviceSpecificConfig(
settingsBackup,
R.array.restore_blocked_device_specific_settings,
Collections.emptySet());
assertTrue("Not all values were blocked.", helper.mWrittenValues.isEmpty());
}
@Test
public void testGeneratedHeaderMatchesCurrentDevice() throws IOException {
mAgentUnderTest.mSettingsHelper = new TestSettingsHelper(mContext);
@@ -148,7 +169,10 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
assertFalse(
"Blocking isSourceAcceptable did not stop restore",
mAgentUnderTest.restoreDeviceSpecificConfig(data));
mAgentUnderTest.restoreDeviceSpecificConfig(
data,
R.array.restore_blocked_device_specific_settings,
Collections.emptySet()));
}
private byte[] generateUncorruptedHeader() throws IOException {
@@ -184,14 +208,34 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
}
}
private byte[] generateSingleKeyTestBackupData(String key, String value) throws IOException {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
os.write(SettingsBackupAgent.toByteArray(key));
os.write(SettingsBackupAgent.toByteArray(value));
return os.toByteArray();
}
}
private static class TestFriendlySettingsBackupAgent extends SettingsBackupAgent {
private Boolean mForcedDeviceInfoRestoreAcceptability = null;
private String[] mBlockedSettings = null;
void setForcedDeviceInfoRestoreAcceptability(boolean value) {
mForcedDeviceInfoRestoreAcceptability = value;
}
@VisibleForTesting
void setBlockedSettings(String... blockedSettings) {
mBlockedSettings = blockedSettings;
}
@Override
protected Set<String> getBlockedSettings(int blockedSettingsArrayId) {
return mBlockedSettings == null
? super.getBlockedSettings(blockedSettingsArrayId)
: new HashSet<>(Arrays.asList(mBlockedSettings));
}
@Override
boolean isSourceAcceptable(byte[] data, AtomicInteger pos) {
return mForcedDeviceInfoRestoreAcceptability == null
? super.isSourceAcceptable(data, pos)