1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.providers.settings; 18 19 import static junit.framework.Assert.assertEquals; 20 import static junit.framework.Assert.assertFalse; 21 import static junit.framework.Assert.assertTrue; 22 23 import static org.junit.Assert.assertArrayEquals; 24 25 import android.content.ContentResolver; 26 import android.content.ContentValues; 27 import android.content.Context; 28 import android.content.ContextWrapper; 29 import android.database.Cursor; 30 import android.database.MatrixCursor; 31 import android.net.Uri; 32 import android.os.Build; 33 import android.os.Bundle; 34 import android.provider.Settings; 35 import android.provider.settings.validators.SettingsValidators; 36 import android.provider.settings.validators.Validator; 37 import android.test.mock.MockContentProvider; 38 import android.test.mock.MockContentResolver; 39 40 import androidx.test.runner.AndroidJUnit4; 41 42 import org.junit.Before; 43 import org.junit.Test; 44 import org.junit.runner.RunWith; 45 46 import java.io.ByteArrayOutputStream; 47 import java.io.IOException; 48 import java.nio.ByteBuffer; 49 import java.util.Arrays; 50 import java.util.Collections; 51 import java.util.HashMap; 52 import java.util.HashSet; 53 import java.util.Map; 54 import java.util.Objects; 55 import java.util.Set; 56 import java.util.concurrent.atomic.AtomicInteger; 57 58 /** Tests for the SettingsHelperTest */ 59 @RunWith(AndroidJUnit4.class) 60 public class SettingsBackupAgentTest extends BaseSettingsProviderTest { 61 private static final Uri TEST_URI = Uri.EMPTY; 62 private static final String TEST_DISPLAY_DENSITY_FORCED = "123"; 63 private static final String OVERRIDDEN_TEST_SETTING = "overridden_setting"; 64 private static final String PRESERVED_TEST_SETTING = "preserved_setting"; 65 private static final Map<String, String> DEVICE_SPECIFIC_TEST_VALUES = new HashMap<>(); 66 private static final Map<String, String> TEST_VALUES = new HashMap<>(); 67 private static final Map<String, Validator> TEST_VALUES_VALIDATORS = new HashMap<>(); 68 69 static { DEVICE_SPECIFIC_TEST_VALUES.put(Settings.Secure.DISPLAY_DENSITY_FORCED, TEST_DISPLAY_DENSITY_FORCED)70 DEVICE_SPECIFIC_TEST_VALUES.put(Settings.Secure.DISPLAY_DENSITY_FORCED, 71 TEST_DISPLAY_DENSITY_FORCED); 72 TEST_VALUES.put(OVERRIDDEN_TEST_SETTING, R)73 TEST_VALUES.put(OVERRIDDEN_TEST_SETTING, "123"); TEST_VALUES.put(PRESERVED_TEST_SETTING, R)74 TEST_VALUES.put(PRESERVED_TEST_SETTING, "124"); 75 TEST_VALUES_VALIDATORS.put(OVERRIDDEN_TEST_SETTING, SettingsValidators.ANY_STRING_VALIDATOR)76 TEST_VALUES_VALIDATORS.put(OVERRIDDEN_TEST_SETTING, 77 SettingsValidators.ANY_STRING_VALIDATOR); TEST_VALUES_VALIDATORS.put(PRESERVED_TEST_SETTING, SettingsValidators.ANY_STRING_VALIDATOR)78 TEST_VALUES_VALIDATORS.put(PRESERVED_TEST_SETTING, SettingsValidators.ANY_STRING_VALIDATOR); 79 } 80 81 private TestFriendlySettingsBackupAgent mAgentUnderTest; 82 private Context mContext; 83 84 @Override 85 @Before setUp()86 public void setUp() { 87 super.setUp(); 88 mContext = new ContextWithMockContentResolver(getContext()); 89 90 mAgentUnderTest = new TestFriendlySettingsBackupAgent(); 91 mAgentUnderTest.attach(mContext); 92 } 93 94 @Test testRoundTripDeviceSpecificSettings()95 public void testRoundTripDeviceSpecificSettings() throws IOException { 96 TestSettingsHelper helper = new TestSettingsHelper(mContext); 97 mAgentUnderTest.mSettingsHelper = helper; 98 99 byte[] settingsBackup = mAgentUnderTest.getDeviceSpecificConfiguration(); 100 101 assertEquals("Not all values backed up.", DEVICE_SPECIFIC_TEST_VALUES.keySet(), helper.mReadEntries); 102 103 mAgentUnderTest.restoreDeviceSpecificConfig( 104 settingsBackup, 105 R.array.restore_blocked_device_specific_settings, 106 Collections.emptySet(), 107 Collections.emptySet()); 108 109 assertEquals("Not all values were restored.", DEVICE_SPECIFIC_TEST_VALUES, helper.mWrittenValues); 110 } 111 112 @Test testRoundTripDeviceSpecificSettingsWithBlock()113 public void testRoundTripDeviceSpecificSettingsWithBlock() throws IOException { 114 TestSettingsHelper helper = new TestSettingsHelper(mContext); 115 mAgentUnderTest.mSettingsHelper = helper; 116 117 byte[] settingsBackup = mAgentUnderTest.getDeviceSpecificConfiguration(); 118 119 assertEquals("Not all values backed up.", DEVICE_SPECIFIC_TEST_VALUES.keySet(), helper.mReadEntries); 120 mAgentUnderTest.setBlockedSettings(DEVICE_SPECIFIC_TEST_VALUES.keySet().toArray(new String[0])); 121 122 mAgentUnderTest.restoreDeviceSpecificConfig( 123 settingsBackup, 124 R.array.restore_blocked_device_specific_settings, 125 Collections.emptySet(), 126 Collections.emptySet()); 127 128 assertTrue("Not all values were blocked.", helper.mWrittenValues.isEmpty()); 129 } 130 131 @Test testGeneratedHeaderMatchesCurrentDevice()132 public void testGeneratedHeaderMatchesCurrentDevice() throws IOException { 133 mAgentUnderTest.mSettingsHelper = new TestSettingsHelper(mContext); 134 135 byte[] header = generateUncorruptedHeader(); 136 137 AtomicInteger pos = new AtomicInteger(0); 138 assertTrue( 139 "Generated header is not correct for device.", 140 mAgentUnderTest.isSourceAcceptable(header, pos)); 141 } 142 143 @Test testTestHeaderGeneratorIsAccurate()144 public void testTestHeaderGeneratorIsAccurate() throws IOException { 145 byte[] classGeneratedHeader = generateUncorruptedHeader(); 146 byte[] testGeneratedHeader = generateCorruptedHeader(false, false, false); 147 148 assertArrayEquals( 149 "Difference in header generation", classGeneratedHeader, testGeneratedHeader); 150 } 151 152 @Test testNewerHeaderVersionFailsMatch()153 public void testNewerHeaderVersionFailsMatch() throws IOException { 154 byte[] header = generateCorruptedHeader(true, false, false); 155 156 AtomicInteger pos = new AtomicInteger(0); 157 assertFalse( 158 "Newer header does not fail match", 159 mAgentUnderTest.isSourceAcceptable(header, pos)); 160 } 161 162 @Test testWrongManufacturerFailsMatch()163 public void testWrongManufacturerFailsMatch() throws IOException { 164 byte[] header = generateCorruptedHeader(false, true, false); 165 166 AtomicInteger pos = new AtomicInteger(0); 167 assertFalse( 168 "Wrong manufacturer does not fail match", 169 mAgentUnderTest.isSourceAcceptable(header, pos)); 170 } 171 172 @Test testWrongProductFailsMatch()173 public void testWrongProductFailsMatch() throws IOException { 174 byte[] header = generateCorruptedHeader(false, false, true); 175 176 AtomicInteger pos = new AtomicInteger(0); 177 assertFalse( 178 "Wrong product does not fail match", 179 mAgentUnderTest.isSourceAcceptable(header, pos)); 180 } 181 182 @Test checkAcceptTestFailingBlockRestore()183 public void checkAcceptTestFailingBlockRestore() { 184 mAgentUnderTest.setForcedDeviceInfoRestoreAcceptability(false); 185 byte[] data = new byte[0]; 186 187 assertFalse( 188 "Blocking isSourceAcceptable did not stop restore", 189 mAgentUnderTest.restoreDeviceSpecificConfig( 190 data, 191 R.array.restore_blocked_device_specific_settings, 192 Collections.emptySet(), 193 Collections.emptySet())); 194 } 195 196 @Test testOnRestore_preservedSettingsAreNotRestored()197 public void testOnRestore_preservedSettingsAreNotRestored() { 198 SettingsBackupAgent.SettingsBackupWhitelist whitelist = 199 new SettingsBackupAgent.SettingsBackupWhitelist( 200 new String[] { OVERRIDDEN_TEST_SETTING, PRESERVED_TEST_SETTING }, 201 TEST_VALUES_VALIDATORS); 202 mAgentUnderTest.setSettingsWhitelist(whitelist); 203 mAgentUnderTest.setBlockedSettings(); 204 TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext); 205 mAgentUnderTest.mSettingsHelper = settingsHelper; 206 207 byte[] backupData = generateBackupData(TEST_VALUES); 208 mAgentUnderTest.restoreSettings(backupData, /* pos */ 0, backupData.length, TEST_URI, 209 null, null, null, /* blockedSettingsArrayId */ 0, Collections.emptySet(), 210 new HashSet<>(Collections.singletonList(SettingsBackupAgent.getQualifiedKeyForSetting(PRESERVED_TEST_SETTING, TEST_URI)))); 211 212 assertTrue(settingsHelper.mWrittenValues.containsKey(OVERRIDDEN_TEST_SETTING)); 213 assertFalse(settingsHelper.mWrittenValues.containsKey(PRESERVED_TEST_SETTING)); 214 } 215 generateBackupData(Map<String, String> keyValueData)216 private byte[] generateBackupData(Map<String, String> keyValueData) { 217 int totalBytes = 0; 218 for (String key : keyValueData.keySet()) { 219 totalBytes += 2 * Integer.BYTES + key.getBytes().length 220 + keyValueData.get(key).getBytes().length; 221 } 222 223 ByteBuffer buffer = ByteBuffer.allocate(totalBytes); 224 for (String key : keyValueData.keySet()) { 225 byte[] keyBytes = key.getBytes(); 226 byte[] valueBytes = keyValueData.get(key).getBytes(); 227 buffer.putInt(keyBytes.length); 228 buffer.put(keyBytes); 229 buffer.putInt(valueBytes.length); 230 buffer.put(valueBytes); 231 } 232 233 return buffer.array(); 234 } 235 generateUncorruptedHeader()236 private byte[] generateUncorruptedHeader() throws IOException { 237 try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { 238 mAgentUnderTest.writeHeader(os); 239 return os.toByteArray(); 240 } 241 } 242 generateCorruptedHeader( boolean corruptVersion, boolean corruptManufacturer, boolean corruptProduct)243 private byte[] generateCorruptedHeader( 244 boolean corruptVersion, boolean corruptManufacturer, boolean corruptProduct) 245 throws IOException { 246 try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { 247 int version = SettingsBackupAgent.DEVICE_SPECIFIC_VERSION; 248 if (corruptVersion) { 249 version++; 250 } 251 os.write(SettingsBackupAgent.toByteArray(version)); 252 253 String manufacturer = Build.MANUFACTURER; 254 if (corruptManufacturer) { 255 manufacturer = manufacturer == null ? "X" : manufacturer + "X"; 256 } 257 os.write(SettingsBackupAgent.toByteArray(manufacturer)); 258 259 String product = Build.PRODUCT; 260 if (corruptProduct) { 261 product = product == null ? "X" : product + "X"; 262 } 263 os.write(SettingsBackupAgent.toByteArray(product)); 264 265 return os.toByteArray(); 266 } 267 } 268 generateSingleKeyTestBackupData(String key, String value)269 private byte[] generateSingleKeyTestBackupData(String key, String value) throws IOException { 270 try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { 271 os.write(SettingsBackupAgent.toByteArray(key)); 272 os.write(SettingsBackupAgent.toByteArray(value)); 273 return os.toByteArray(); 274 } 275 } 276 277 private static class TestFriendlySettingsBackupAgent extends SettingsBackupAgent { 278 private Boolean mForcedDeviceInfoRestoreAcceptability = null; 279 private String[] mBlockedSettings = null; 280 private SettingsBackupWhitelist mSettingsWhitelist = null; 281 setForcedDeviceInfoRestoreAcceptability(boolean value)282 void setForcedDeviceInfoRestoreAcceptability(boolean value) { 283 mForcedDeviceInfoRestoreAcceptability = value; 284 } 285 setBlockedSettings(String... blockedSettings)286 void setBlockedSettings(String... blockedSettings) { 287 mBlockedSettings = blockedSettings; 288 } 289 setSettingsWhitelist(SettingsBackupWhitelist settingsWhitelist)290 void setSettingsWhitelist(SettingsBackupWhitelist settingsWhitelist) { 291 mSettingsWhitelist = settingsWhitelist; 292 } 293 294 @Override getBlockedSettings(int blockedSettingsArrayId)295 protected Set<String> getBlockedSettings(int blockedSettingsArrayId) { 296 return mBlockedSettings == null 297 ? super.getBlockedSettings(blockedSettingsArrayId) 298 : new HashSet<>(Arrays.asList(mBlockedSettings)); 299 } 300 301 @Override isSourceAcceptable(byte[] data, AtomicInteger pos)302 boolean isSourceAcceptable(byte[] data, AtomicInteger pos) { 303 return mForcedDeviceInfoRestoreAcceptability == null 304 ? super.isSourceAcceptable(data, pos) 305 : mForcedDeviceInfoRestoreAcceptability; 306 } 307 308 @Override getBackupWhitelist(Uri contentUri)309 SettingsBackupWhitelist getBackupWhitelist(Uri contentUri) { 310 if (mSettingsWhitelist == null) { 311 return super.getBackupWhitelist(contentUri); 312 } 313 314 return mSettingsWhitelist; 315 } 316 } 317 318 /** The TestSettingsHelper tracks which values have been backed up and/or restored. */ 319 private static class TestSettingsHelper extends SettingsHelper { 320 private Set<String> mReadEntries; 321 private Map<String, String> mWrittenValues; 322 TestSettingsHelper(Context context)323 TestSettingsHelper(Context context) { 324 super(context); 325 mReadEntries = new HashSet<>(); 326 mWrittenValues = new HashMap<>(); 327 } 328 329 @Override onBackupValue(String key, String value)330 public String onBackupValue(String key, String value) { 331 mReadEntries.add(key); 332 String readValue = DEVICE_SPECIFIC_TEST_VALUES.get(key); 333 assert readValue != null; 334 return readValue; 335 } 336 337 @Override restoreValue( Context context, ContentResolver cr, ContentValues contentValues, Uri destination, String name, String value, int restoredFromSdkInt)338 public void restoreValue( 339 Context context, 340 ContentResolver cr, 341 ContentValues contentValues, 342 Uri destination, 343 String name, 344 String value, 345 int restoredFromSdkInt) { 346 mWrittenValues.put(name, value); 347 } 348 } 349 350 /** 351 * ContextWrapper which allows us to return a MockContentResolver to code which uses it to 352 * access settings. This allows us to override the ContentProvider for the Settings URIs to 353 * return known values. 354 */ 355 private static class ContextWithMockContentResolver extends ContextWrapper { 356 private MockContentResolver mContentResolver; 357 ContextWithMockContentResolver(Context targetContext)358 ContextWithMockContentResolver(Context targetContext) { 359 super(targetContext); 360 361 mContentResolver = new MockContentResolver(); 362 mContentResolver.addProvider( 363 Settings.AUTHORITY, new DeviceSpecificInfoMockContentProvider()); 364 } 365 366 @Override getContentResolver()367 public ContentResolver getContentResolver() { 368 return mContentResolver; 369 } 370 } 371 372 /** ContentProvider which returns a set of known test values. */ 373 private static class DeviceSpecificInfoMockContentProvider extends MockContentProvider { 374 private static final Object[][] RESULT_ROWS = { 375 {Settings.Secure.DISPLAY_DENSITY_FORCED, TEST_DISPLAY_DENSITY_FORCED}, 376 }; 377 378 @Override query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)379 public Cursor query( 380 Uri uri, 381 String[] projection, 382 String selection, 383 String[] selectionArgs, 384 String sortOrder) { 385 MatrixCursor result = new MatrixCursor(SettingsBackupAgent.PROJECTION); 386 for (Object[] resultRow : RESULT_ROWS) { 387 result.addRow(resultRow); 388 } 389 return result; 390 } 391 392 @Override call(String method, String request, Bundle args)393 public Bundle call(String method, String request, Bundle args) { 394 for (Object[] resultRow : RESULT_ROWS) { 395 if (Objects.equals(request, resultRow[0])) { 396 final Bundle res = new Bundle(); 397 res.putString("value", String.valueOf(resultRow[1])); 398 return res; 399 } 400 } 401 return Bundle.EMPTY; 402 } 403 } 404 } 405