1 /* 2 * Copyright (C) 2016 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 android.system.helpers; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothManager; 21 import android.content.ComponentName; 22 import android.content.ContentResolver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.net.wifi.WifiManager; 26 import android.provider.Settings; 27 import android.provider.Settings.SettingNotFoundException; 28 import android.support.test.InstrumentationRegistry; 29 import android.support.test.uiautomator.By; 30 import android.support.test.uiautomator.BySelector; 31 import android.support.test.uiautomator.Direction; 32 import android.support.test.uiautomator.UiDevice; 33 import android.support.test.uiautomator.UiObject; 34 import android.support.test.uiautomator.UiObject2; 35 import android.support.test.uiautomator.UiObjectNotFoundException; 36 import android.support.test.uiautomator.UiScrollable; 37 import android.support.test.uiautomator.UiSelector; 38 import android.support.test.uiautomator.Until; 39 import android.util.Log; 40 import android.widget.Switch; 41 import android.widget.TextView; 42 43 import junit.framework.Assert; 44 45 import java.util.regex.Pattern; 46 47 /** 48 * Implement common helper methods for settings. 49 */ 50 public class SettingsHelper { 51 private static final String TAG = SettingsHelper.class.getSimpleName(); 52 private static final String SETTINGS_PACKAGE = "com.android.settings"; 53 private static final String SETTINGS_APP = "Settings"; 54 private static final String SWITCH_WIDGET = "switch_widget"; 55 private static final String WIFI = "Wi-Fi"; 56 private static final String BLUETOOTH = "Bluetooth"; 57 private static final String AIRPLANE = "Airplane mode"; 58 private static final String LOCATION = "Location"; 59 private static final String DND = "Do not disturb"; 60 private static final String ZEN_MODE = "zen_mode"; 61 private static final String FLASHLIGHT = "Flashlight"; 62 private static final String AUTO_ROTATE_SCREEN = "Auto-rotate screen"; 63 private static final BySelector SETTINGS_DASHBOARD = By.res(SETTINGS_PACKAGE, 64 "dashboard_container"); 65 private static final UiSelector LIST_ITEM_VALUE = 66 new UiSelector().className(TextView.class); 67 public static final int TIMEOUT = 2000; 68 private static SettingsHelper sInstance = null; 69 private ActivityHelper mActivityHelper = null; 70 private ContentResolver mResolver = null; 71 private Context mContext = null; 72 private UiDevice mDevice = null; 73 74 public SettingsHelper() { 75 mContext = InstrumentationRegistry.getTargetContext(); 76 mResolver = mContext.getContentResolver(); 77 mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 78 mActivityHelper = ActivityHelper.getInstance(); 79 } 80 81 public static SettingsHelper getInstance() { 82 if (sInstance == null) { 83 sInstance = new SettingsHelper(); 84 } 85 return sInstance; 86 } 87 88 public static enum SettingsType { 89 SYSTEM, SECURE, GLOBAL 90 } 91 92 /** 93 * @return Settings package name 94 */ 95 public String getPackage() { 96 return SETTINGS_PACKAGE; 97 } 98 99 /** 100 * @return Settings app name 101 */ 102 public String getLauncherName() { 103 return SETTINGS_APP; 104 } 105 106 /** 107 * Scroll through settings page 108 * @param numberOfFlings 109 * @throws Exception 110 */ 111 public void scrollThroughSettings(int numberOfFlings) throws Exception { 112 UiObject2 settingsList = loadAllSettings(); 113 int count = 0; 114 while (count <= numberOfFlings && settingsList.fling(Direction.DOWN)) { 115 count++; 116 } 117 } 118 119 /** 120 * Move to top of settings page 121 * @throws Exception 122 */ 123 public void flingSettingsToStart() throws Exception { 124 UiObject2 settingsList = loadAllSettings(); 125 while (settingsList.fling(Direction.UP)); 126 } 127 128 /** 129 * Launch specific settings page 130 * @param ctx 131 * @param pageName 132 * @throws Exception 133 */ 134 public static void launchSettingsPage(Context ctx, String pageName) throws Exception { 135 Intent intent = new Intent(pageName); 136 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 137 ctx.startActivity(intent); 138 Thread.sleep(TIMEOUT * 2); 139 } 140 141 /** 142 * Scroll vertically up or down 143 * @param isUp 144 */ 145 public void scrollVert(boolean isUp) { 146 int w = mDevice.getDisplayWidth(); 147 int h = mDevice.getDisplayHeight(); 148 mDevice.swipe(w / 2, h / 2, w / 2, isUp ? h : 0, 2); 149 } 150 151 /** 152 * On N, the settingsDashboard is initially collapsed, and the user can see the "See all" 153 * element. On hitting "See all", the same settings dashboard element is now scrollable. For 154 * pre-N, the settings Dashboard is always scrollable, hence the check in the while loop. All 155 * this method does is expand the Settings list if needed, before returning the element. 156 */ 157 public UiObject2 loadAllSettings() throws Exception { 158 UiObject2 settingsDashboard = mDevice.wait(Until.findObject(SETTINGS_DASHBOARD), 159 TIMEOUT * 2); 160 Assert.assertNotNull("Could not find the settings dashboard object.", settingsDashboard); 161 int count = 0; 162 while (!settingsDashboard.isScrollable() && count <= 2) { 163 mDevice.wait(Until.findObject(By.text("SEE ALL")), TIMEOUT * 2).click(); 164 settingsDashboard = mDevice.wait(Until.findObject(SETTINGS_DASHBOARD), 165 TIMEOUT * 2); 166 count++; 167 } 168 return settingsDashboard; 169 } 170 171 /** 172 * Performs click action on a setting when setting name is provided as exact string 173 * @param settingName 174 * @throws InterruptedException 175 */ 176 public void clickSetting(String settingName) throws InterruptedException { 177 int count = 5; 178 while (count > 0 && mDevice.wait(Until.findObject(By.text(settingName)), TIMEOUT) == null) { 179 scrollVert(false); 180 count--; 181 } 182 mDevice.wait(Until.findObject(By.text(settingName)), TIMEOUT).click(); 183 Thread.sleep(TIMEOUT); 184 } 185 186 /** 187 * Performs click action on a setting when setting has been found 188 * @param name 189 * @throws InterruptedException,UiObjectNotFoundException 190 */ 191 public boolean selectSettingFor(String settingName) 192 throws InterruptedException, UiObjectNotFoundException { 193 UiScrollable settingsList = new UiScrollable( 194 new UiSelector().resourceId("android:id/content")); 195 UiObject appSettings = settingsList.getChildByText(LIST_ITEM_VALUE, settingName); 196 if (appSettings != null) { 197 return appSettings.click(); 198 } 199 return false; 200 } 201 202 /** 203 * Performs click action on a setting when setting name is provided as pattern 204 * 205 * @param settingName 206 * @throws InterruptedException 207 */ 208 public void clickSetting(Pattern settingName) throws InterruptedException { 209 mDevice.wait(Until.findObject(By.text(settingName)), TIMEOUT).click(); 210 Thread.sleep(400); 211 } 212 213 /** 214 * Gets string value of a setting 215 * @param type 216 * @param sName 217 * @return 218 */ 219 public String getStringSetting(SettingsType type, String sName) { 220 switch (type) { 221 case SYSTEM: 222 return Settings.System.getString(mResolver, sName); 223 case GLOBAL: 224 return Settings.Global.getString(mResolver, sName); 225 case SECURE: 226 return Settings.Secure.getString(mResolver, sName); 227 } 228 return ""; 229 } 230 231 /** 232 * Get int value of a setting 233 * @param type 234 * @param sName 235 * @return 236 * @throws SettingNotFoundException 237 */ 238 public int getIntSetting(SettingsType type, String sName) throws SettingNotFoundException { 239 switch (type) { 240 case SYSTEM: 241 return Settings.System.getInt(mResolver, sName); 242 case GLOBAL: 243 return Settings.Global.getInt(mResolver, sName); 244 case SECURE: 245 return Settings.Secure.getInt(mResolver, sName); 246 } 247 return Integer.MIN_VALUE; 248 } 249 250 /** 251 * Set string value of a setting 252 * @param type 253 * @param sName 254 * @param value 255 */ 256 public void setStringSetting(SettingsType type, String sName, String value) 257 throws InterruptedException { 258 switch (type) { 259 case SYSTEM: 260 Settings.System.putString(mResolver, sName, value); 261 break; 262 case GLOBAL: 263 Settings.Global.putString(mResolver, sName, value); 264 break; 265 case SECURE: 266 Settings.Secure.putString(mResolver, sName, value); 267 break; 268 } 269 Thread.sleep(TIMEOUT); 270 } 271 272 /** 273 * Sets int value of a setting 274 * @param type 275 * @param sName 276 * @param value 277 */ 278 public void setIntSetting(SettingsType type, String sName, int value) 279 throws InterruptedException { 280 switch (type) { 281 case SYSTEM: 282 Settings.System.putInt(mResolver, sName, value); 283 break; 284 case GLOBAL: 285 Settings.Global.putInt(mResolver, sName, value); 286 break; 287 case SECURE: 288 Settings.Secure.putInt(mResolver, sName, value); 289 break; 290 } 291 Thread.sleep(TIMEOUT); 292 } 293 294 /** 295 * Toggles setting and verifies the action, when setting name is passed as string 296 * @param type 297 * @param settingAction 298 * @param settingName 299 * @param internalName 300 * @return 301 * @throws Exception 302 */ 303 public boolean verifyToggleSetting(SettingsType type, String settingAction, 304 String settingName, String internalName) throws Exception { 305 return verifyToggleSetting( 306 type, settingAction, Pattern.compile(settingName), internalName, true); 307 } 308 309 /** 310 * Toggles setting and verifies the action, when setting name is passed as pattern 311 * @param type 312 * @param settingAction 313 * @param settingName 314 * @param internalName 315 * @return 316 * @throws Exception 317 */ 318 public boolean verifyToggleSetting(SettingsType type, String settingAction, 319 Pattern settingName, String internalName) throws Exception { 320 return verifyToggleSetting(type, settingAction, settingName, internalName, true); 321 } 322 323 /** 324 * Toggles setting and verifies the action, when setting name is passed as string 325 * and settings page needs to be launched or not 326 * @param type 327 * @param settingAction 328 * @param settingName 329 * @param internalName 330 * @param doLaunch 331 * @return 332 * @throws Exception 333 */ 334 public boolean verifyToggleSetting(SettingsType type, String settingAction, 335 String settingName, String internalName, boolean doLaunch) throws Exception { 336 return verifyToggleSetting( 337 type, settingAction, Pattern.compile(settingName), internalName, doLaunch); 338 } 339 340 /** 341 * Toggles setting and verifies the action 342 * @param type 343 * @param settingAction 344 * @param settingName 345 * @param internalName 346 * @param doLaunch 347 * @return 348 * @throws Exception 349 */ 350 public boolean verifyToggleSetting(SettingsType type, String settingAction, 351 Pattern settingName, String internalName, boolean doLaunch) throws Exception { 352 String onSettingBaseVal = getStringSetting(type, internalName); 353 if (onSettingBaseVal == null) { 354 // Per bug b/35717943 default for charging sounds is ON 355 // So if null, the value should be set to 1. 356 if (settingName.matcher("Charging sounds").matches()) { 357 onSettingBaseVal = "1"; 358 } 359 else { 360 onSettingBaseVal = "0"; 361 } 362 } 363 int onSetting = Integer.parseInt(onSettingBaseVal); 364 Log.d(TAG, "On Setting value is : " + onSetting); 365 if (doLaunch) { 366 launchSettingsPage(mContext, settingAction); 367 } 368 clickSetting(settingName); 369 Log.d(TAG, "Clicked setting : " + settingName); 370 Thread.sleep(5000); 371 String changedSetting = getStringSetting(type, internalName); 372 Log.d(TAG, "Changed Setting value is : " + changedSetting); 373 if (changedSetting == null) { 374 Log.d(TAG, "Changed Setting value is : NULL"); 375 changedSetting = "0"; 376 } 377 return (1 - onSetting) == Integer.parseInt(changedSetting); 378 } 379 380 /** 381 * @param type 382 * @param settingAction 383 * @param baseName 384 * @param settingName 385 * @param internalName 386 * @param testVal 387 * @return 388 * @throws Exception 389 */ 390 public boolean verifyRadioSetting(SettingsType type, String settingAction, 391 String baseName, String settingName, 392 String internalName, String testVal) throws Exception { 393 if (baseName != null) 394 clickSetting(baseName); 395 clickSetting(settingName); 396 Thread.sleep(500); 397 return getStringSetting(type, internalName).equals(testVal); 398 } 399 400 public void toggleWiFiOnOffAndVerify(boolean verifyOn, boolean isQuickSettings) 401 throws Exception { 402 String switchText = (verifyOn ? "OFF" : "ON"); 403 WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); 404 wifiManager.setWifiEnabled(!verifyOn); 405 Thread.sleep(TIMEOUT * 3); 406 if (isQuickSettings) { 407 launchAndClickSettings(isQuickSettings, null, By.descContains(WIFI) 408 .clazz(Switch.class)); 409 } else { 410 launchAndClickSettings(isQuickSettings, Settings.ACTION_WIFI_SETTINGS, 411 By.res(SETTINGS_PACKAGE, SWITCH_WIDGET).text(switchText)); 412 } 413 Thread.sleep(TIMEOUT * 3); 414 String wifiValue = Settings.Global.getString(mResolver, Settings.Global.WIFI_ON); 415 if (verifyOn) { 416 Assert.assertFalse(wifiValue == "0"); 417 } else { 418 Assert.assertEquals("0", wifiValue); 419 } 420 mDevice.pressHome(); 421 Thread.sleep(TIMEOUT * 3); 422 } 423 424 public void toggleBTOnOffAndVerify(boolean verifyOn, boolean isQuickSettings) 425 throws Exception { 426 String switchText = (verifyOn ? "OFF" : "ON"); 427 BluetoothAdapter bluetoothAdapter = ((BluetoothManager) mContext 428 .getSystemService(Context.BLUETOOTH_SERVICE)).getAdapter(); 429 boolean isEnabled = bluetoothAdapter.isEnabled(); 430 boolean success = (verifyOn ? bluetoothAdapter.disable() : bluetoothAdapter.enable()); 431 Thread.sleep(TIMEOUT * 3); 432 if (isQuickSettings) { 433 launchAndClickSettings(isQuickSettings, null, 434 By.descContains(BLUETOOTH).clazz(Switch.class)); 435 } else { 436 launchAndClickSettings(isQuickSettings, Settings.ACTION_BLUETOOTH_SETTINGS, 437 By.res(SETTINGS_PACKAGE, SWITCH_WIDGET).text(switchText)); 438 } 439 Thread.sleep(TIMEOUT * 3); 440 String bluetoothValue = Settings.Global.getString( 441 mResolver, 442 Settings.Global.BLUETOOTH_ON); 443 Assert.assertEquals((verifyOn ? "1" : "0"), bluetoothValue); 444 if (isEnabled) { 445 bluetoothAdapter.enable(); 446 } else { 447 bluetoothAdapter.disable(); 448 } 449 mDevice.pressHome(); 450 Thread.sleep(TIMEOUT * 3); 451 } 452 453 public void toggleAirplaneModeOnOrOffAndVerify(boolean verifyOn, boolean isQuickSettings) 454 throws Exception { 455 String settingValToPut = (verifyOn ? "0" : "1"); 456 Settings.Global.putString(mResolver, Settings.Global.AIRPLANE_MODE_ON, settingValToPut); 457 if (isQuickSettings) { 458 launchAndClickSettings(isQuickSettings, null, By.descContains(AIRPLANE)); 459 } else { 460 launchAndClickSettings(isQuickSettings, Settings.ACTION_WIRELESS_SETTINGS, 461 By.text(AIRPLANE)); 462 } 463 Thread.sleep(TIMEOUT * 3); 464 String airplaneModeValue = Settings.Global 465 .getString(mResolver, 466 Settings.Global.AIRPLANE_MODE_ON); 467 Assert.assertEquals((verifyOn ? "1" : "0"), airplaneModeValue); 468 mDevice.pressHome(); 469 Thread.sleep(TIMEOUT * 3); 470 } 471 472 public void toggleLocationSettingsOnOrOffAndVerify(boolean verifyOn, boolean isQuickSettings) 473 throws Exception { 474 // Set location flag 475 int settingValToPut = (verifyOn ? Settings.Secure.LOCATION_MODE_OFF 476 : Settings.Secure.LOCATION_MODE_SENSORS_ONLY); 477 Settings.Secure.putInt(mResolver, Settings.Secure.LOCATION_MODE, settingValToPut); 478 // Load location settings 479 if (isQuickSettings) { 480 launchAndClickSettings(isQuickSettings, null, By.descContains(LOCATION)); 481 } else { 482 launchAndClickSettings(isQuickSettings, Settings.ACTION_LOCATION_SOURCE_SETTINGS, 483 By.res(SETTINGS_PACKAGE, SWITCH_WIDGET)); 484 } 485 Thread.sleep(TIMEOUT * 3); 486 // Verify change in setting 487 int locationEnabled = Settings.Secure.getInt(mResolver, 488 Settings.Secure.LOCATION_MODE); 489 if (verifyOn) { 490 Assert.assertFalse("Location not enabled correctly", locationEnabled == 0); 491 } else { 492 Assert.assertEquals("Location not disabled correctly", 0, locationEnabled); 493 } 494 mDevice.pressHome(); 495 Thread.sleep(TIMEOUT * 3); 496 } 497 498 public void launchAndClickSettings(boolean isQuickSettings, String settingsPage, 499 BySelector bySelector) throws Exception { 500 if (isQuickSettings) { 501 launchQuickSettingsAndWait(); 502 UiObject2 qsTile = mDevice.wait(Until.findObject(bySelector), TIMEOUT * 3); 503 qsTile.findObject(By.clazz("android.widget.FrameLayout")).click(); 504 } else { 505 mActivityHelper.launchIntent(settingsPage); 506 mDevice.wait(Until.findObject(bySelector), TIMEOUT * 3).click(); 507 } 508 } 509 510 /** 511 * Verify Quick Setting DND can be toggled DND default value is OFF 512 * @throws Exception 513 */ 514 public void toggleQuickSettingDNDAndVerify() throws Exception { 515 try { 516 int onSetting = Settings.Global.getInt(mResolver, ZEN_MODE); 517 launchQuickSettingsAndWait(); 518 mDevice.wait(Until.findObject(By.descContains(DND)), 519 TIMEOUT * 3).getChildren().get(0).click(); 520 Thread.sleep(TIMEOUT * 3); 521 int changedSetting = Settings.Global.getInt(mResolver, ZEN_MODE); 522 Assert.assertFalse(onSetting == changedSetting); 523 mDevice.pressHome(); 524 Thread.sleep(TIMEOUT * 3); 525 } finally { 526 // change to DND default value 527 int setting = Settings.Global.getInt(mResolver, ZEN_MODE); 528 if (setting > 0) { 529 launchQuickSettingsAndWait(); 530 mDevice.wait(Until.findObject(By.descContains(DND)), 531 TIMEOUT * 3).getChildren().get(0).click(); 532 Thread.sleep(TIMEOUT * 3); 533 } 534 } 535 } 536 537 public void toggleQuickSettingFlashLightAndVerify() throws Exception { 538 String lightOn = "On"; 539 String lightOff = "Off"; 540 boolean verifyOn = false; 541 launchQuickSettingsAndWait(); 542 UiObject2 flashLight = mDevice.wait( 543 Until.findObject(By.desc(FLASHLIGHT)), 544 TIMEOUT * 3); 545 if (flashLight != null && flashLight.getText().equals(lightOn)) { 546 verifyOn = true; 547 } 548 mDevice.wait(Until.findObject(By.desc(FLASHLIGHT)), 549 TIMEOUT * 3).click(); 550 Thread.sleep(TIMEOUT * 3); 551 flashLight = mDevice.wait( 552 Until.findObject(By.desc(FLASHLIGHT)), 553 TIMEOUT * 3); 554 if (flashLight != null) { 555 String txt = flashLight.getText(); 556 if (verifyOn) { 557 Assert.assertTrue(txt.equals(lightOff)); 558 } else { 559 Assert.assertTrue(txt.equals(lightOn)); 560 mDevice.wait(Until.findObject(By.textContains(FLASHLIGHT)), 561 TIMEOUT * 3).click(); 562 } 563 } 564 mDevice.pressHome(); 565 Thread.sleep(TIMEOUT * 3); 566 } 567 568 public void toggleQuickSettingOrientationAndVerify() throws Exception { 569 launchQuickSettingsAndWait(); 570 mDevice.wait(Until.findObject(By.descContains(AUTO_ROTATE_SCREEN)), 571 TIMEOUT * 3).click(); 572 Thread.sleep(TIMEOUT * 3); 573 String rotation = Settings.System.getString(mResolver, 574 Settings.System.ACCELEROMETER_ROTATION); 575 Assert.assertEquals("1", rotation); 576 mDevice.setOrientationNatural(); 577 mDevice.pressHome(); 578 Thread.sleep(TIMEOUT * 3); 579 } 580 581 public void launchQuickSettingsAndWait() throws Exception { 582 mDevice.openQuickSettings(); 583 Thread.sleep(TIMEOUT * 2); 584 } 585 586 public void launchSettingsPageByComponentName(Context ctx, String name) { 587 Intent intent = new Intent(Intent.ACTION_MAIN); 588 ComponentName settingComponent = new ComponentName(SETTINGS_PACKAGE, 589 String.format("%s.%s$%s", SETTINGS_PACKAGE, SETTINGS_APP, name)); 590 intent.setComponent(settingComponent); 591 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 592 ctx.startActivity(intent); 593 } 594 } 595