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