1 /*
2  * Copyright (C) 2021 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.cts.readsettingsfieldsapp;
18 
19 import android.content.ContentResolver;
20 import android.database.Cursor;
21 import android.net.Uri;
22 import android.os.Bundle;
23 import android.provider.Settings;
24 import android.test.AndroidTestCase;
25 import android.util.ArraySet;
26 
27 import java.lang.reflect.Field;
28 import java.lang.reflect.InvocationTargetException;
29 import java.lang.reflect.Method;
30 import java.util.ArrayList;
31 import java.util.Set;
32 import java.util.stream.Collectors;
33 
34 public class ReadSettingsFieldsTest extends AndroidTestCase {
35     /** Test public keys are readable with annotation */
testSecureNonHiddenSettingsKeysAreReadable()36     public void testSecureNonHiddenSettingsKeysAreReadable() {
37         testNonHiddenSettingsKeysAreReadable(Settings.Secure.class);
38     }
39 
testSystemNonHiddenSettingsKeysAreReadable()40     public void testSystemNonHiddenSettingsKeysAreReadable() {
41         testNonHiddenSettingsKeysAreReadable(Settings.System.class);
42     }
43 
testGlobalNonHiddenSettingsKeysAreReadable()44     public void testGlobalNonHiddenSettingsKeysAreReadable() {
45         testNonHiddenSettingsKeysAreReadable(Settings.Global.class);
46     }
47 
testNonHiddenSettingsKeysAreReadable( Class<T> settingsClass)48     private <T extends Settings.NameValueTable> void testNonHiddenSettingsKeysAreReadable(
49             Class<T> settingsClass) {
50         for (String key : getNonHiddenSettingsKeys(settingsClass)) {
51             try {
52                 callGetStringMethod(settingsClass, key);
53             } catch (SecurityException ex) {
54                 if (isSettingsDeprecated(ex) || isAvailableForLowerOrEqualTargetedSDK(ex)) {
55                     continue;
56                 }
57                 fail("Reading non-hidden " + settingsClass.getSimpleName() + " settings key <" + key
58                         + "> should not raise exception! "
59                         + "Did you forget to add @Readable annotation?\n" + ex.getMessage());
60             }
61         }
62     }
63 
callGetStringMethod(Class<T> settingsClass, String key)64     private <T extends Settings.NameValueTable> void callGetStringMethod(Class<T> settingsClass,
65             String key) throws SecurityException {
66         try {
67             Method getStringMethod = settingsClass.getMethod("getString",
68                     ContentResolver.class, String.class);
69             getStringMethod.invoke(null, getContext().getContentResolver(), key);
70         } catch (NoSuchMethodException | IllegalAccessException e) {
71             e.printStackTrace();
72         } catch (InvocationTargetException e) {
73             throw new SecurityException(e.getCause());
74         }
75     }
76 
getNonHiddenSettingsKeys(Class<T> settingsClass)77     private <T> ArraySet<String> getNonHiddenSettingsKeys(Class<T> settingsClass) {
78         final ArraySet<String> publicSettingsKeys = new ArraySet<>();
79         final Field[] allFields = settingsClass.getDeclaredFields();
80         try {
81             for (int i = 0; i < allFields.length; i++) {
82                 final Field field = allFields[i];
83                 if (field.getType().equals(String.class)) {
84                     final Object value = field.get(settingsClass);
85                     if (value.getClass().equals(String.class)) {
86                         publicSettingsKeys.add((String) value);
87                     }
88                 }
89             }
90         } catch (IllegalAccessException ignored) {
91         }
92         return publicSettingsKeys;
93     }
94 
isSettingsDeprecated(SecurityException ex)95     private boolean isSettingsDeprecated(SecurityException ex) {
96         return ex.getMessage().contains("is deprecated and no longer accessible");
97     }
98 
isAvailableForLowerOrEqualTargetedSDK(SecurityException ex)99     private boolean isAvailableForLowerOrEqualTargetedSDK(SecurityException ex) {
100         return ex.getMessage().contains("targetSdkVersion lower than or equal");
101     }
102 
103     /** Test hidden keys are readable with annotation */
testSecureSomeHiddenSettingsKeysAreReadable()104     public void testSecureSomeHiddenSettingsKeysAreReadable() {
105         final ArraySet<String> publicSettingsKeys = getNonHiddenSettingsKeys(Settings.Secure.class);
106         final String[] hiddenSettingsKeys = {"adaptive_sleep", "bugreport_in_power_menu",
107                 "input_methods_subtype_history"};
108         testHiddenSettingsKeysReadable(Settings.Secure.class, publicSettingsKeys,
109                 hiddenSettingsKeys);
110     }
111 
testSystemSomeHiddenSettingsKeysAreReadable()112     public void testSystemSomeHiddenSettingsKeysAreReadable() {
113         final ArraySet<String> publicSettingsKeys = getNonHiddenSettingsKeys(Settings.System.class);
114         final String[] hiddenSettingsKeys = {"advanced_settings", "system_locales",
115                 "display_color_mode", "min_refresh_rate"};
116         testHiddenSettingsKeysReadable(Settings.System.class, publicSettingsKeys,
117                 hiddenSettingsKeys);
118     }
119 
testGlobalSomeHiddenSettingsKeysAreReadable()120     public void testGlobalSomeHiddenSettingsKeysAreReadable() {
121         final ArraySet<String> publicSettingsKeys = getNonHiddenSettingsKeys(Settings.Secure.class);
122         final String[] hiddenSettingsKeys = {"add_users_when_locked",
123                 "enable_accessibility_global_gesture_enabled"};
124         testHiddenSettingsKeysReadable(Settings.Global.class, publicSettingsKeys,
125                 hiddenSettingsKeys);
126     }
127 
testHiddenSettingsKeysReadable( Class<T> settingsClass, ArraySet<String> publicKeys, String[] targetKeys)128     private <T extends Settings.NameValueTable> void testHiddenSettingsKeysReadable(
129             Class<T> settingsClass, ArraySet<String> publicKeys, String[] targetKeys) {
130         for (String key : targetKeys) {
131             // Verify that the hidden keys are not visible to the test app
132             assertFalse("Settings key <" + key + "> should not be visible",
133                     publicKeys.contains(key));
134             try {
135                 // Verify that the hidden keys can still be read
136                 callGetStringMethod(settingsClass, key);
137             } catch (SecurityException ex) {
138                 fail("Reading hidden " + settingsClass.getSimpleName() + " settings key <" + key
139                         + "> should not raise!");
140             }
141         }
142     }
143 
144     /** Test hidden keys are not readable without annotation */
testSecureHiddenSettingsKeysNotReadableWithoutAnnotation()145     public void testSecureHiddenSettingsKeysNotReadableWithoutAnnotation() {
146         final ArraySet<String> publicSettingsKeys = getNonHiddenSettingsKeys(Settings.Secure.class);
147         final String[] hiddenSettingsKeys = {"camera_autorotate",
148                 "location_time_zone_detection_enabled"};
149         testHiddenSettingsKeysNotReadableWithoutAnnotation(Settings.Secure.class,
150                 publicSettingsKeys, hiddenSettingsKeys);
151     }
152 
testSystemHiddenSettingsKeysNotReadableWithoutAnnotation()153     public void testSystemHiddenSettingsKeysNotReadableWithoutAnnotation() {
154         final ArraySet<String> publicSettingsKeys = getNonHiddenSettingsKeys(Settings.System.class);
155         final String[] hiddenSettingsKeys = {"display_color_mode_vendor_hint"};
156         testHiddenSettingsKeysNotReadableWithoutAnnotation(Settings.System.class,
157                 publicSettingsKeys, hiddenSettingsKeys);
158     }
159 
testGlobalHiddenSettingsKeysNotReadableWithoutAnnotation()160     public void testGlobalHiddenSettingsKeysNotReadableWithoutAnnotation() {
161         final ArraySet<String> publicSettingsKeys = getNonHiddenSettingsKeys(Settings.Global.class);
162         final String[] hiddenSettingsKeys = {"restricted_networking_mode",
163                 "people_space_conversation_type"};
164         testHiddenSettingsKeysNotReadableWithoutAnnotation(Settings.Global.class,
165                 publicSettingsKeys, hiddenSettingsKeys);
166     }
167 
168     // test the cases that hidden keys are marked with readable annotation but access should be
169     // protected by additional permission check.
testGlobalHiddenSettingsKeyNotReadableWithoutPermissions()170     public void testGlobalHiddenSettingsKeyNotReadableWithoutPermissions() {
171         final String[] hiddenSettingsKeysRequiresPermissions = {"multi_sim_data_call"};
172         for (String key : hiddenSettingsKeysRequiresPermissions) {
173             try {
174                 // Verify that the hidden keys can't be accessed due to lack of permissions.
175                 callGetStringMethod(Settings.Global.class, key);
176             } catch (SecurityException ex) {
177                 assertTrue(ex.getMessage().contains("permission"));
178                 continue;
179             }
180             fail("Reading hidden " + Settings.Global.class.getSimpleName() + " settings key <" + key
181                     + "> should be protected with permission!");
182         }
183     }
184 
185     private <T extends Settings.NameValueTable>
testHiddenSettingsKeysNotReadableWithoutAnnotation( Class<T> settingsClass, ArraySet<String> publicKeys, String[] targetKeys)186     void testHiddenSettingsKeysNotReadableWithoutAnnotation(
187             Class<T> settingsClass, ArraySet<String> publicKeys, String[] targetKeys) {
188         for (String key : targetKeys) {
189             // Verify that the hidden keys are not visible to the test app
190             assertFalse("Settings key <" + key + "> should not be visible",
191                     publicKeys.contains(key));
192             try {
193                 // Verify that the hidden keys cannot be read
194                 callGetStringMethod(settingsClass, key);
195                 fail("Reading hidden " + settingsClass.getSimpleName() + " settings key <" + key
196                         + "> should raise!");
197             } catch (SecurityException ex) {
198                 assertTrue(ex.getMessage().contains(
199                         "Settings key: <" + key + "> is not readable."));
200             }
201         }
202     }
203 
204     /** Test hidden keys are readable if the app is test only, even without annotation */
testSecureHiddenSettingsKeysReadableWithoutAnnotation()205     public void testSecureHiddenSettingsKeysReadableWithoutAnnotation() {
206         final ArraySet<String> publicSettingsKeys = getNonHiddenSettingsKeys(Settings.Secure.class);
207         final String[] hiddenSettingsKeys = {"camera_autorotate",
208                 "location_time_zone_detection_enabled"};
209         testHiddenSettingsKeysReadable(Settings.Secure.class, publicSettingsKeys,
210                 hiddenSettingsKeys);
211     }
212 
testSystemHiddenSettingsKeysReadableWithoutAnnotation()213     public void testSystemHiddenSettingsKeysReadableWithoutAnnotation() {
214         final ArraySet<String> publicSettingsKeys = getNonHiddenSettingsKeys(Settings.System.class);
215         final String[] hiddenSettingsKeys = {"display_color_mode_vendor_hint"};
216         testHiddenSettingsKeysReadable(Settings.System.class, publicSettingsKeys,
217                 hiddenSettingsKeys);
218     }
219 
testGlobalHiddenSettingsKeysReadableWithoutAnnotation()220     public void testGlobalHiddenSettingsKeysReadableWithoutAnnotation() {
221         final ArraySet<String> publicSettingsKeys = getNonHiddenSettingsKeys(Settings.Global.class);
222         final String[] hiddenSettingsKeys = {"restricted_networking_mode",
223                 "people_space_conversation_type"};
224         testHiddenSettingsKeysReadable(Settings.Global.class, publicSettingsKeys,
225                 hiddenSettingsKeys);
226     }
227 
228 
testSettingsKeysNotReadableForAfterR()229     public void testSettingsKeysNotReadableForAfterR() {
230         final String keyWithTargetSdkR = "media_button_receiver";
231         try {
232             // Verify that the hidden key is not readable because of maxTargetSdk restriction
233             callGetStringMethod(Settings.System.class, keyWithTargetSdkR);
234             fail("Reading hidden settings key <" + keyWithTargetSdkR
235                     + "> should raise!");
236         } catch (SecurityException ex) {
237             assertTrue(ex.getMessage().contains("targetSdkVersion"));
238         }
239     }
240 
testSettingsKeysReadableForRMinus()241     public void testSettingsKeysReadableForRMinus() {
242         final String keyWithTargetSdkR = "media_button_receiver";
243         try {
244             // Verify that the hidden key can still be read
245             callGetStringMethod(Settings.System.class, keyWithTargetSdkR);
246         } catch (SecurityException ex) {
247             fail("Reading hidden settings key <" + keyWithTargetSdkR + "> should not raise!");
248         }
249     }
250 
testQueryGlobalSettingsNoHiddenKeysWithoutAnnotation()251     public void testQueryGlobalSettingsNoHiddenKeysWithoutAnnotation() {
252         checkQueryResults(Settings.Global.CONTENT_URI, Settings.Global.class);
253     }
254 
testQuerySystemSettingsNoHiddenKeysWithoutAnnotation()255     public void testQuerySystemSettingsNoHiddenKeysWithoutAnnotation() {
256         checkQueryResults(Settings.System.CONTENT_URI, Settings.System.class);
257     }
258 
testQuerySecureSettingsNoHiddenKeysWithoutAnnotation()259     public void testQuerySecureSettingsNoHiddenKeysWithoutAnnotation() {
260         checkQueryResults(Settings.Secure.CONTENT_URI, Settings.Secure.class);
261     }
262 
263     /**
264      * Check the result of ContentResolver.query() and make sure all the settings keys in the result
265      * is Readable.
266      */
checkQueryResults(Uri uri, Class<T> settingsClass)267     private <T extends Settings.NameValueTable> void checkQueryResults(Uri uri,
268             Class<T> settingsClass) {
269         try {
270             final ContentResolver resolver = getContext().getContentResolver();
271             final Cursor cursor = resolver.query(uri, new String[]{"name", "value"}, null,
272                     null, null);
273             assertTrue(cursor.getCount() > 0);
274             while (cursor.moveToNext()) {
275                 final String key = cursor.getString(0);
276                 try {
277                     // every key in the result should be readable
278                     callGetStringMethod(settingsClass, key);
279                 } catch (SecurityException ex) {
280                     if (isSettingsDeprecated(ex)) {
281                         continue;
282                     }
283                     fail("Hidden settings key <" + key + "> should not be included in the "
284                             + "query result!");
285                 }
286             }
287             cursor.close();
288         } catch (Exception e) {
289             fail("Failed to query ContentResolver: " + e.getMessage());
290         }
291     }
292 
testListGlobalSettingsNoHiddenKeysWithoutAnnotation()293     public void testListGlobalSettingsNoHiddenKeysWithoutAnnotation() {
294         checkListResults("LIST_global", Settings.Global.class);
295     }
296 
testListSystemSettingsNoHiddenKeysWithoutAnnotation()297     public void testListSystemSettingsNoHiddenKeysWithoutAnnotation() {
298         checkListResults("LIST_system", Settings.System.class);
299     }
300 
testListSecureSettingsNoHiddenKeysWithoutAnnotation()301     public void testListSecureSettingsNoHiddenKeysWithoutAnnotation() {
302         checkListResults("LIST_secure", Settings.Secure.class);
303     }
304 
305     /**
306      * Check the result of ContentResolver.call() and make sure all the settings keys in the result
307      * is Readable.
308      */
checkListResults(String listSettingsType, Class<T> settingsClass)309     private  <T extends Settings.NameValueTable> void checkListResults(String listSettingsType,
310             Class<T> settingsClass) {
311         try {
312             final ContentResolver resolver = getContext().getContentResolver();
313             final Uri uri;
314             switch (listSettingsType) {
315                 case "LIST_global":
316                     uri = Settings.Global.CONTENT_URI;
317                     break;
318                 case "LIST_system":
319                     uri = Settings.System.CONTENT_URI;
320                     break;
321                 case "LIST_secure":
322                     // fall through
323                 default:
324                     uri = Settings.Secure.CONTENT_URI;
325             }
326             final Bundle data = resolver.call(uri, listSettingsType, null, null);
327             final ArrayList<String> result = data.getStringArrayList("result_settings_list");
328             assertTrue(result.size() > 0);
329             Set<String> keysFromResolverCall = result.stream().map(line -> line.split("=")[0])
330                     .collect(Collectors.toSet());
331 
332             // Verify result against resolver.query
333             Cursor cursor = resolver.query(uri, new String[]{"name", "value"}, null, null, null);
334             Set<String> keysFromResolverQuery = new ArraySet<>();
335             while (cursor.moveToNext()) {
336                 keysFromResolverQuery.add(cursor.getString(0));
337             }
338             cursor.close();
339             assertEquals(keysFromResolverQuery, keysFromResolverCall);
340 
341             for (String key : keysFromResolverCall) {
342                 try {
343                     // every key in the result should be readable
344                     callGetStringMethod(settingsClass, key);
345                 } catch (SecurityException ex) {
346                     if (isSettingsDeprecated(ex)) {
347                         continue;
348                     }
349                     fail("Non-readable settings key <" + key + "> should not be included in the "
350                             + "query or call result!");
351                 }
352             }
353         } catch (Exception e) {
354             fail("Failed to query ContentResolver: " + e.getMessage());
355         }
356     }
357 }
358 
359