Home | History | Annotate | Download | only in helpers
      1 /*
      2  * Copyright (C) 2017 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.app.Instrumentation;
     20 import android.content.ComponentName;
     21 import android.content.Intent;
     22 import android.graphics.Point;
     23 import android.graphics.Rect;
     24 import android.support.test.uiautomator.By;
     25 import android.support.test.uiautomator.UiDevice;
     26 import android.support.test.uiautomator.UiObject2;
     27 import android.support.test.uiautomator.UiObjectNotFoundException;
     28 import android.support.test.uiautomator.UiScrollable;
     29 import android.support.test.uiautomator.UiSelector;
     30 import android.support.test.uiautomator.Until;
     31 import android.util.Log;
     32 
     33 import java.util.List;
     34 
     35 /**
     36  * Implement common helper functions for Accessibility scanner.
     37  */
     38 public class AccessibilityScannerHelper {
     39     public static final String ACCESSIBILITY_SCANNER_PACKAGE
     40             = "com.google.android.apps.accessibility.auditor";
     41     public static final String MAIN_ACTIVITY_CLASS = "%s.ui.MainActivity";
     42     public static final String CHECK_BUTTON_RES_ID = "accessibilibutton";
     43     private static final int SCANNER_WAIT_TIME = 5000;
     44     private static final int SHORT_TIMEOUT = 2000;
     45     private static final String LOG_TAG = AccessibilityScannerHelper.class.getSimpleName();
     46     private static final String RESULT_TAG = "A11Y_SCANNER_RESULT";
     47     public static AccessibilityScannerHelper sInstance = null;
     48     private UiDevice mDevice = null;
     49     private ActivityHelper mActivityHelper = null;
     50     private PackageHelper mPackageHelper = null;
     51     private AccessibilityHelper mAccessibilityHelper = null;
     52 
     53     private AccessibilityScannerHelper(Instrumentation instr) {
     54         mDevice = UiDevice.getInstance(instr);
     55         mActivityHelper = ActivityHelper.getInstance();
     56         mPackageHelper = PackageHelper.getInstance(instr);
     57         mAccessibilityHelper = AccessibilityHelper.getInstance(instr);
     58     }
     59 
     60     public static AccessibilityScannerHelper getInstance(Instrumentation instr) {
     61         if (sInstance == null) {
     62             sInstance = new AccessibilityScannerHelper(instr);
     63         }
     64         return sInstance;
     65     }
     66 
     67     /**
     68      * If accessibility scanner installed.
     69      *
     70      * @return true/false
     71      */
     72     public boolean scannerInstalled() {
     73         return mPackageHelper.isPackageInstalled(ACCESSIBILITY_SCANNER_PACKAGE);
     74     }
     75 
     76     /**
     77      * Click scanner check button and parse and log results.
     78      *
     79      * @param resultPrefix
     80      * @throws Exception
     81      */
     82     public void runScanner(String resultPrefix) throws Exception {
     83         int tries = 3; // retries
     84         while (tries-- > 0) {
     85             try {
     86                 clickScannerCheck();
     87                 logScannerResult(resultPrefix);
     88                 break;
     89             } catch (UiObjectNotFoundException e) {
     90                 continue;
     91             } catch (Exception e) {
     92                 throw e;
     93             }
     94         }
     95     }
     96 
     97     /**
     98      * Click scanner check button and open share app in the share menu.
     99      *
    100      * @param resultPrefix
    101      * @param shareAppTag
    102      * @throws Exception
    103      */
    104     public void runScannerAndOpenShareApp(String resultPrefix, String shareAppTag)
    105             throws Exception {
    106         runScanner(resultPrefix);
    107         UiObject2 shareApp = getShareApp(shareAppTag);
    108         if (shareApp != null) {
    109             shareApp.click();
    110         }
    111     }
    112 
    113     /**
    114      * Set Accessibility Scanner setting ON/OFF.
    115      *
    116      * @throws Exception
    117      */
    118     public void setAccessibilityScannerSetting(AccessibilityHelper.SwitchStatus value)
    119             throws Exception {
    120         if (!scannerInstalled()) {
    121             throw new Exception("Accessibility Scanner not installed.");
    122         }
    123         mAccessibilityHelper.launchSpecificAccessibilitySetting("Accessibility Scanner");
    124         for (int tries = 0; tries < 2; tries++) {
    125             UiObject2 swt = mDevice.wait(Until.findObject(
    126                     By.res(AccessibilityHelper.SETTINGS_PACKAGE, "switch_widget")),
    127                     SHORT_TIMEOUT * 2);
    128             if (swt.getText().equals(value.toString())) {
    129                 break;
    130             } else if (tries == 1) {
    131                 throw new Exception(String.format("Fail to set scanner to: %s.", value.toString()));
    132             } else {
    133                 swt.click();
    134                 UiObject2 okBtn = mDevice.wait(Until.findObject(By.text("OK")), SHORT_TIMEOUT);
    135                 if (okBtn != null) {
    136                     okBtn.click();
    137                 }
    138                 if (initialSetups()) {
    139                     mDevice.pressBack();
    140                 }
    141                 grantPermissions();
    142             }
    143         }
    144     }
    145 
    146     /**
    147      * Click through all permission pop ups for scanner. Grant all necessary permissions.
    148      */
    149     private void grantPermissions() {
    150         UiObject2 auth1 = mDevice.wait(Until.findObject(
    151                 By.text("BEGIN AUTHORIZATION")), SHORT_TIMEOUT);
    152         if (auth1 != null) {
    153             auth1.click();
    154         }
    155         UiObject2 chk = mDevice.wait(Until.findObject(
    156                 By.clazz(AccessibilityHelper.CHECK_BOX)), SHORT_TIMEOUT);
    157         if (chk != null) {
    158             chk.click();
    159             mDevice.findObject(By.text("START NOW")).click();
    160         }
    161         UiObject2 auth2 = mDevice.wait(Until.findObject(
    162                 By.text("BEGIN AUTHORIZATION")), SHORT_TIMEOUT);
    163         if (auth2 != null) {
    164             auth2.click();
    165         }
    166         UiObject2 tapOk = mDevice.wait(Until.findObject(
    167                 By.pkg(ACCESSIBILITY_SCANNER_PACKAGE).text("OK")), SHORT_TIMEOUT);
    168         if (tapOk != null) {
    169             tapOk.click();
    170         }
    171     }
    172 
    173     /**
    174      * Launch accessibility scanner.
    175      *
    176      * @throws UiObjectNotFoundException
    177      */
    178     public void launchScannerApp() throws Exception {
    179         Intent intent = new Intent(Intent.ACTION_MAIN);
    180         ComponentName settingComponent = new ComponentName(ACCESSIBILITY_SCANNER_PACKAGE,
    181                 String.format(MAIN_ACTIVITY_CLASS, ACCESSIBILITY_SCANNER_PACKAGE));
    182         intent.setComponent(settingComponent);
    183         mActivityHelper.launchIntent(intent);
    184         initialSetups();
    185     }
    186 
    187     /**
    188      * Steps for first time launching scanner app.
    189      *
    190      * @return true/false return false immediately, if initial setup screen doesn't show up.
    191      * @throws Exception
    192      */
    193     private boolean initialSetups() throws Exception {
    194         UiObject2 getStartBtn = mDevice.wait(
    195                 Until.findObject(By.text("GET STARTED")), SHORT_TIMEOUT);
    196         if (getStartBtn != null) {
    197             getStartBtn.click();
    198             UiObject2 msg = mDevice.wait(Until.findObject(
    199                     By.text("Turn on Accessibility Scanner")), SHORT_TIMEOUT);
    200             if (msg != null) {
    201                 mDevice.findObject(By.text("OK")).click();
    202                 setAccessibilityScannerSetting(AccessibilityHelper.SwitchStatus.ON);
    203             }
    204             mDevice.wait(Until.findObject(By.text("OK, GOT IT")), SCANNER_WAIT_TIME).click();
    205             mDevice.wait(Until.findObject(By.text("DISMISS")), SHORT_TIMEOUT).click();
    206             return true;
    207         } else {
    208             return false;
    209         }
    210     }
    211 
    212     /**
    213      * Clear history of accessibility scanner.
    214      *
    215      * @throws InterruptedException
    216      */
    217     public void clearHistory() throws Exception {
    218         launchScannerApp();
    219         int maxTry = 20;
    220         while (maxTry > 0) {
    221             List<UiObject2> historyItemList = mDevice.findObjects(
    222                     By.res(ACCESSIBILITY_SCANNER_PACKAGE, "history_item_row"));
    223             if (historyItemList.size() == 0) {
    224                 break;
    225             }
    226             historyItemList.get(0).click();
    227             Thread.sleep(SHORT_TIMEOUT);
    228             deleteHistory();
    229             Thread.sleep(SHORT_TIMEOUT);
    230             maxTry--;
    231         }
    232     }
    233 
    234     /**
    235      * Log results of accessibility scanner.
    236      *
    237      * @param pageName
    238      * @throws Exception
    239      */
    240     public void logScannerResult(String pageName) throws Exception {
    241         int res = getNumberOfSuggestions();
    242         if (res > 0) {
    243             Log.i(RESULT_TAG, String.format("%s: %s suggestions!", pageName, res));
    244         } else if (res == 0) {
    245             Log.i(RESULT_TAG, String.format("%s: Pass.", pageName));
    246         } else {
    247             throw new UiObjectNotFoundException("Fail to get number of suggestions.");
    248         }
    249     }
    250 
    251     /**
    252      * Move scanner button to avoid blocking the object.
    253      *
    254      * @param avoidObj object to move the check button away from
    255      */
    256     public void adjustScannerButton(UiObject2 avoidObj)
    257             throws UiObjectNotFoundException, InterruptedException {
    258         Rect origBounds = getScannerCheckBtn().getVisibleBounds();
    259         Rect avoidBounds = avoidObj.getVisibleBounds();
    260         if (origBounds.intersect(avoidBounds)) {
    261             Point dest = calculateDest(origBounds, avoidBounds);
    262             moveScannerCheckButton(dest.x, dest.y);
    263         }
    264     }
    265 
    266     /**
    267      * Move scanner check button back to the middle of the screen.
    268      */
    269     public void resetScannerCheckButton() throws UiObjectNotFoundException, InterruptedException {
    270         int midY = (int) Math.ceil(mDevice.getDisplayHeight() * 0.5);
    271         int midX = (int) Math.ceil(mDevice.getDisplayWidth() * 0.5);
    272         moveScannerCheckButton(midX, midY);
    273     }
    274 
    275     /**
    276      * Move scanner check button to a target location.
    277      *
    278      * @param locX target location x-axis
    279      * @param locY target location y-axis
    280      * @throws UiObjectNotFoundException
    281      */
    282     public void moveScannerCheckButton(int locX, int locY)
    283             throws UiObjectNotFoundException, InterruptedException {
    284         int tries = 2;
    285         while (tries-- > 0) {
    286             UiObject2 btn = getScannerCheckBtn();
    287             Rect bounds = btn.getVisibleBounds();
    288             int origX = bounds.centerX();
    289             int origY = bounds.centerY();
    290             int buttonWidth = bounds.width();
    291             int buttonHeight = bounds.height();
    292             if (Math.abs(locX - origX) > buttonWidth || Math.abs(locY - origY) > buttonHeight) {
    293                 btn.drag(new Point(locX, locY));
    294             }
    295             Thread.sleep(SCANNER_WAIT_TIME);
    296             // drag cause a click on the scanner button, bring the UI into scanner app
    297             if (getScannerCheckBtn() == null
    298                     && mDevice.findObject(By.pkg(ACCESSIBILITY_SCANNER_PACKAGE)) != null) {
    299                 mDevice.pressBack();
    300             } else {
    301                 break;
    302             }
    303         }
    304     }
    305 
    306     /**
    307      * Calculate the moving destination of check button.
    308      *
    309      * @param origRect original bounds of the check button
    310      * @param avoidRect bounds to move away from
    311      * @return destination of check button center point.
    312      */
    313     private Point calculateDest(Rect origRect, Rect avoidRect) {
    314         int bufferY = (int)Math.ceil(mDevice.getDisplayHeight() * 0.1);
    315         int destY = avoidRect.bottom + bufferY + origRect.height()/2;
    316         if (destY >= mDevice.getDisplayHeight()) {
    317             destY = avoidRect.top - bufferY - origRect.height()/2;
    318         }
    319         return new Point(origRect.centerX(), destY);
    320     }
    321 
    322     /**
    323      * Return scanner check button.
    324      *
    325      * @return UiObject2
    326      */
    327     private UiObject2 getScannerCheckBtn() {
    328         return mDevice.findObject(By.res(ACCESSIBILITY_SCANNER_PACKAGE, CHECK_BUTTON_RES_ID));
    329     }
    330 
    331     private void clickScannerCheck() throws UiObjectNotFoundException, InterruptedException {
    332         UiObject2 accessibilityScannerButton = getScannerCheckBtn();
    333         if (accessibilityScannerButton != null) {
    334             accessibilityScannerButton.click();
    335         } else {
    336             // TODO: check if app crash error, restart scanner service
    337             Log.i(LOG_TAG, "Fail to find accessibility scanner check button.");
    338             throw new UiObjectNotFoundException(
    339                     "Fail to find accessibility scanner check button.");
    340         }
    341         Thread.sleep(SCANNER_WAIT_TIME);
    342     }
    343 
    344     /**
    345      * Check if no suggestion.
    346      * @deprecated Use {@link #getNumberOfSuggestions} instead
    347      */
    348     @Deprecated
    349     private Boolean testPass() throws UiObjectNotFoundException {
    350         UiObject2 txtView = getToolBarTextView();
    351         return txtView.getText().equals("No suggestions");
    352     }
    353 
    354     /**
    355      * Return accessibility scanner tool bar text view.
    356      *
    357      * @return UiObject2
    358      * @throws UiObjectNotFoundException
    359      */
    360     private UiObject2 getToolBarTextView() throws UiObjectNotFoundException {
    361         UiObject2 toolBar = mDevice.wait(Until.findObject(
    362                 By.res(ACCESSIBILITY_SCANNER_PACKAGE, "toolbar")), SHORT_TIMEOUT);
    363         if (toolBar != null) {
    364             return toolBar.findObject(By.clazz(AccessibilityHelper.TEXT_VIEW));
    365         } else {
    366             throw new UiObjectNotFoundException(
    367                     "Failed to find Scanner tool bar. Scanner app might not be active.");
    368         }
    369     }
    370 
    371     /**
    372      * Delete active scanner history.
    373      */
    374     private void deleteHistory() {
    375         UiObject2 moreBtn = mDevice.wait(Until.findObject(By.desc("More options")), SHORT_TIMEOUT);
    376         if (moreBtn != null) {
    377             moreBtn.click();
    378             mDevice.wait(Until.findObject(
    379                     By.clazz(AccessibilityHelper.TEXT_VIEW).text("Delete")), SHORT_TIMEOUT).click();
    380         }
    381     }
    382 
    383     /**
    384      * Return number suggestions.
    385      *
    386      * @return number of suggestions
    387      * @throws UiObjectNotFoundException
    388      */
    389     private int getNumberOfSuggestions() throws UiObjectNotFoundException {
    390         int tries = 2; // retries
    391         while (tries-- > 0) {
    392             UiObject2 txtView = getToolBarTextView();
    393             if (txtView != null) {
    394                 String result = txtView.getText();
    395                 if (result.equals("No suggestions")) {
    396                     return 0;
    397                 } else {
    398                     String str = result.split("\\s+")[0];
    399                     return Integer.parseInt(str);
    400                 }
    401             }
    402         }
    403         Log.i(LOG_TAG, String.format("Error in getting number of suggestions."));
    404         return -1;
    405     }
    406 
    407     /**
    408      * Return share app UiObject2
    409      *
    410      * @param appName
    411      * @return
    412      */
    413     private UiObject2 getShareApp(String appName) throws UiObjectNotFoundException {
    414         UiObject2 shareBtn = mDevice.wait(Until.findObject(By.res(ACCESSIBILITY_SCANNER_PACKAGE,
    415                 "action_share_results")), SHORT_TIMEOUT);
    416         if (shareBtn != null) {
    417             shareBtn.click();
    418             mDevice.wait(Until.hasObject(By.res("android:id/resolver_list")), SHORT_TIMEOUT * 3);
    419             UiScrollable scrollable = new UiScrollable(
    420                     new UiSelector().className("android.widget.ScrollView"));
    421             int tries = 3;
    422             while (!mDevice.hasObject(By.text(appName)) && tries-- > 0) {
    423                 scrollable.scrollForward();
    424             }
    425             return mDevice.findObject(By.text(appName));
    426         }
    427         return null;
    428     }
    429 
    430     /**
    431      * Return if scanner enabled by check if home screen has check button.
    432      *
    433      * @return true/false
    434      */
    435     public boolean ifScannerEnabled() throws InterruptedException {
    436         mDevice.pressHome();
    437         Thread.sleep(SHORT_TIMEOUT);
    438         mDevice.waitForIdle();
    439         return getScannerCheckBtn() != null;
    440     }
    441 }
    442