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