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