Home | History | Annotate | Download | only in helpers
      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.platform.test.helpers;
     18 
     19 import android.app.Instrumentation;
     20 import android.content.pm.PackageManager.NameNotFoundException;
     21 import android.os.SystemClock;
     22 import android.platform.test.helpers.exceptions.UnknownUiException;
     23 import android.support.test.uiautomator.By;
     24 import android.support.test.uiautomator.BySelector;
     25 import android.support.test.uiautomator.Direction;
     26 import android.support.test.uiautomator.Until;
     27 import android.support.test.uiautomator.UiDevice;
     28 import android.support.test.uiautomator.UiObject2;
     29 import android.util.Log;
     30 
     31 import java.util.List;
     32 import java.util.regex.Pattern;
     33 
     34 import junit.framework.Assert;
     35 
     36 
     37 import android.os.Environment;
     38 import java.io.File;
     39 import java.io.IOException;
     40 
     41 public class PhotosHelperImpl extends AbstractPhotosHelper {
     42     private static final String LOG_TAG = PhotosHelperImpl.class.getSimpleName();
     43 
     44     private static final long APP_LOAD_WAIT = 7500;
     45     private static final long HACKY_WAIT = 2500;
     46     private static final long PICTURE_LOAD_WAIT = 20000;
     47     private static final long UI_NAVIGATION_WAIT = 5000;
     48 
     49     private static final Pattern UI_PHOTO_DESC = Pattern.compile("^Photo.*");
     50 
     51     private static final String UI_DONE_BUTTON_ID = "done_button";
     52     private static final String UI_GET_STARTED_CONTAINER = "get_started_container";
     53     private static final String UI_GET_STARTED_ID = "get_started";
     54     private static final String UI_LOADING_ICON_ID = "list_empty_progress_bar";
     55     private static final String UI_NEXT_BUTTON_ID = "next_button";
     56     private static final String UI_PACKAGE_NAME = "com.google.android.apps.photos";
     57     private static final String UI_PHOTO_TAB_ID = "tab_photos";
     58     private static final String UI_DEVICE_FOLDER_TEXT = "Device folders";
     59     private static final String UI_PHOTO_VIEW_PAGER_ID = "photo_view_pager";
     60     private static final String UI_PHOTO_SCROLL_VIEW_ID = "recycler_view";
     61     private static final String UI_NAVIGATION_LIST_ID = "navigation_list";
     62     private static final int MAX_UI_SCROLL_COUNT = 20;
     63     private static final int MAX_DISMISS_INIT_DIALOG_RETRY = 20;
     64 
     65     public PhotosHelperImpl(Instrumentation instr) {
     66         super(instr);
     67     }
     68 
     69     /**
     70      * {@inheritDoc}
     71      */
     72     @Override
     73     public String getPackage() {
     74         return "com.google.android.apps.photos";
     75     }
     76 
     77 
     78     /**
     79      * {@inheritDoc}
     80      */
     81     @Override
     82     public String getLauncherName() {
     83         return "Photos";
     84     }
     85 
     86     /**
     87      * {@inheritDoc}
     88      */
     89     @Override
     90     public void dismissInitialDialogs() {
     91         // Target Photos version 1.18.0.119671374
     92         SystemClock.sleep(APP_LOAD_WAIT);
     93 
     94         if (isOnInitialDialogScreen()) {
     95             UiObject2 getStartedButton = mDevice.wait(
     96                     Until.findObject(By.res(UI_PACKAGE_NAME, UI_GET_STARTED_ID)), APP_LOAD_WAIT);
     97             int retryCount = 0;
     98             while ((retryCount < MAX_DISMISS_INIT_DIALOG_RETRY) &&
     99                    (getStartedButton == null)) {
    100                 /*
    101                   The UiAutomator sometimes cannot find GET STARTED button even though
    102                   it is seen on the screen.
    103                   The reason is because the initial "spinner" animation screen updates
    104                   views too quickly for UiAutomator to catch the change.
    105 
    106                   The following hack is used to reload the init dialog for UiAutomator to
    107                   retry catching the GET STARTED button.
    108                 */
    109 
    110                 mDevice.pressBack();
    111                 mDevice.waitForIdle();
    112                 mDevice.pressHome();
    113                 mDevice.waitForIdle();
    114                 open();
    115 
    116                 getStartedButton = mDevice.wait(
    117                         Until.findObject(By.res(UI_PACKAGE_NAME, UI_GET_STARTED_ID)),
    118                         APP_LOAD_WAIT);
    119                 retryCount += 1;
    120 
    121                 if (!isOnInitialDialogScreen()) {
    122                     break;
    123                 }
    124             }
    125 
    126             if (isOnInitialDialogScreen() && (getStartedButton == null)) {
    127                 throw new IllegalStateException("UiAutomator cannot catch GET STARTED button");
    128             }
    129             else {
    130                 if (getStartedButton != null) {
    131                     getStartedButton.click();
    132                 }
    133             }
    134         }
    135         else {
    136             Log.e(LOG_TAG, "Didn't find GET STARTED button.");
    137         }
    138 
    139         // Address dialogs with an account vs. without an account
    140         Pattern signInWords = Pattern.compile("Sign in", Pattern.CASE_INSENSITIVE);
    141         boolean hasAccount = !mDevice.hasObject(By.text(signInWords));
    142         if (!hasAccount) {
    143             // Select 'NO THANKS' if no account exists
    144             Pattern noThanksWords = Pattern.compile("No thanks", Pattern.CASE_INSENSITIVE);
    145             UiObject2 noThanksButton = mDevice.findObject(By.text(noThanksWords));
    146             if (noThanksButton != null) {
    147                 noThanksButton.click();
    148                 mDevice.waitForIdle();
    149             } else {
    150                 Log.e(LOG_TAG, "Unable to find NO THANKS button.");
    151             }
    152         } else {
    153             UiObject2 doneButton = mDevice.wait(Until.findObject(
    154                     By.res(UI_PACKAGE_NAME, UI_DONE_BUTTON_ID)), 5000);
    155             if (doneButton != null) {
    156                 doneButton.click();
    157                 mDevice.waitForIdle();
    158             }
    159             else {
    160                 Log.e(LOG_TAG, "Didn't find DONE button.");
    161             }
    162 
    163             // Press the next button (arrow and check mark) four consecutive times
    164             for (int repeat = 0; repeat < 4; repeat++) {
    165                 UiObject2 nextButton = mDevice.findObject(
    166                         By.res(UI_PACKAGE_NAME, UI_NEXT_BUTTON_ID));
    167                 if (nextButton != null) {
    168                     nextButton.click();
    169                     mDevice.waitForIdle();
    170                 } else {
    171                     Log.e(LOG_TAG, "Unable to find arrow or check mark buttons.");
    172                 }
    173             }
    174 
    175             mDevice.wait(Until.gone(
    176                          By.res(UI_PACKAGE_NAME, UI_LOADING_ICON_ID)), PICTURE_LOAD_WAIT);
    177         }
    178 
    179         mDevice.waitForIdle();
    180     }
    181 
    182     /**
    183      * {@inheritDoc}
    184      */
    185     @Override
    186     public void openFirstClip() {
    187         if (searchForVideoClip()) {
    188             UiObject2 clip = getFirstClip();
    189             if (clip != null) {
    190                 clip.click();
    191                 mDevice.wait(Until.findObject(
    192                         By.res(UI_PACKAGE_NAME, "photos_videoplayer_play_button_holder")), 2000);
    193             }
    194             else {
    195                 throw new IllegalStateException("Cannot play a video after finding video clips");
    196             }
    197         }
    198         else {
    199             throw new UnsupportedOperationException("Cannot find a video clip");
    200         }
    201     }
    202 
    203     /**
    204      * {@inheritDoc}
    205      */
    206     @Override
    207     public void pauseClip() {
    208         UiObject2 holder = mDevice.findObject(
    209                 By.res(UI_PACKAGE_NAME, "photos_videoplayer_play_button_holder"));
    210         if (holder != null) {
    211             holder.click();
    212         } else {
    213             throw new UnknownUiException("Unable to find pause button holder.");
    214         }
    215 
    216         UiObject2 pause = mDevice.wait(Until.findObject(
    217                 By.res(UI_PACKAGE_NAME, "photos_videoplayer_pause_button")), 2500);
    218         if (pause != null) {
    219             pause.click();
    220             mDevice.wait(Until.findObject(By.desc("Play video")), 2500);
    221         } else {
    222             throw new UnknownUiException("Unable to find pause button.");
    223         }
    224     }
    225 
    226     /**
    227      * {@inheritDoc}
    228      */
    229     @Override
    230     public void playClip() {
    231         UiObject2 play = mDevice.findObject(By.desc("Play video"));
    232         if (play != null) {
    233             play.click();
    234             mDevice.wait(Until.findObject(
    235                     By.res(UI_PACKAGE_NAME, "photos_videoplayer_pause_button")), 2500);
    236         } else {
    237             throw new UnknownUiException("Unable to find play button");
    238         }
    239     }
    240 
    241     /**
    242      * {@inheritDoc}
    243      */
    244     @Override
    245     public void goToMainScreen() {
    246         for (int retriesRemaining = 5; retriesRemaining > 0 && !isOnMainScreen();
    247                 --retriesRemaining) {
    248             // check if we see the Photos tab at the bottom of the screen
    249             // If we do, clicking on the tab should go to home screen.
    250             UiObject2 photosButton = mDevice.findObject(
    251                     By.res(UI_PACKAGE_NAME, UI_PHOTO_TAB_ID));
    252             if (photosButton != null) {
    253                 photosButton.click();
    254             }
    255             else {
    256                 mDevice.pressBack();
    257             }
    258             mDevice.waitForIdle();
    259         }
    260 
    261         if (!isOnMainScreen()) {
    262             throw new IllegalStateException("Cannot go to main screen");
    263         }
    264     }
    265 
    266     /**
    267      * {@inheritDoc}
    268      */
    269     @Override
    270     public void openPicture(int index) {
    271 
    272         mDevice.waitForIdle();
    273         List<UiObject2> photos = mDevice.findObjects(By.pkg(UI_PACKAGE_NAME).desc(UI_PHOTO_DESC));
    274 
    275         if (photos == null) {
    276             throw new IllegalStateException("Cannot find photos on current view screen");
    277         }
    278 
    279         if ((index < 0) || (index >= photos.size())) {
    280             String errMsg = String.format("Photo index (%d) out of bound (0..%d)",
    281                                           index, photos.size());
    282             throw new IllegalArgumentException(errMsg);
    283         }
    284 
    285         UiObject2 photo = photos.get(index);
    286         photo.click();
    287         if (!mDevice.wait(Until.hasObject(By.res(UI_PACKAGE_NAME, UI_PHOTO_VIEW_PAGER_ID)),
    288                 UI_NAVIGATION_WAIT)) {
    289             throw new IllegalStateException("Cannot display photo on screen");
    290         }
    291     }
    292 
    293     /**
    294      * {@inheritDoc}
    295      */
    296     @Override
    297     public void scrollAlbum(Direction direction) {
    298         if (!(Direction.LEFT.equals(direction) || Direction.RIGHT.equals(direction))) {
    299             throw new IllegalArgumentException("Scroll direction must be LEFT or RIGHT");
    300         }
    301 
    302         UiObject2 scrollContainer = mDevice.findObject(
    303                 By.res(UI_PACKAGE_NAME, UI_PHOTO_VIEW_PAGER_ID));
    304 
    305         if (scrollContainer == null) {
    306             throw new UnknownUiException("Cannot find scroll container");
    307         }
    308 
    309         scrollContainer.scroll(direction, 1.0f);
    310     }
    311 
    312     /**
    313      * {@inheritDoc}
    314      */
    315     @Override
    316     public void goToDeviceFolderScreen() {
    317         if (!isOnDeviceFolderScreen()) {
    318 
    319             if (!isOnMainScreen()) {
    320                 goToMainScreen();
    321             }
    322 
    323             openNavigationDrawer();
    324 
    325             UiObject2 deviceFolderButton = mDevice.wait(Until.findObject(
    326                                                By.text(UI_DEVICE_FOLDER_TEXT)), UI_NAVIGATION_WAIT);
    327             if (deviceFolderButton != null) {
    328                 deviceFolderButton.click();
    329             }
    330             else {
    331                 UiObject2 photosButton = mDevice.wait(Until.findObject(By.text("Photos")),
    332                                                       UI_NAVIGATION_WAIT);
    333                 if (photosButton != null) {
    334                     photosButton.click();
    335                 }
    336                 else {
    337                     throw new IllegalStateException("No device folder in navigation drawer");
    338                 }
    339             }
    340         }
    341 
    342         if (!isOnDeviceFolderScreen()) {
    343             throw new UnknownUiException("Can not go to device folder screen");
    344         }
    345     }
    346 
    347     /**
    348      * {@inheritDoc}
    349      */
    350     @Override
    351     public boolean searchForDeviceFolder(String folderName) {
    352         boolean foundFolder = false;
    353         int scrollCount = 0;
    354         while (!foundFolder && (scrollCount < MAX_UI_SCROLL_COUNT)) {
    355             foundFolder = mDevice.wait(Until.hasObject(By.text(folderName)), 2000);
    356             if (!foundFolder) {
    357                 if (!scrollView(Direction.DOWN)) {
    358                     break;
    359                 }
    360             }
    361             scrollCount += 1;
    362         }
    363 
    364         if (!foundFolder) {
    365             foundFolder = mDevice.wait(Until.hasObject(By.text(folderName)), 2000);
    366         }
    367 
    368         return foundFolder;
    369     }
    370 
    371     /**
    372      * {@inheritDoc}
    373      */
    374     @Override
    375     public boolean searchForVideoClip() {
    376         boolean foundVideoClip = false;
    377         int scrollCount = 0;
    378         while (!foundVideoClip && (scrollCount < MAX_UI_SCROLL_COUNT)) {
    379             foundVideoClip = (getFirstClip() != null);
    380             if (!foundVideoClip) {
    381                 if (!scrollView(Direction.DOWN)) {
    382                     break;
    383                 }
    384             }
    385             scrollCount += 1;
    386         }
    387         return foundVideoClip;
    388     }
    389 
    390     /**
    391      * {@inheritDoc}
    392      */
    393     @Override
    394     public boolean searchForPicture() {
    395         boolean foundPicture = false;
    396         int scrollCount = 0;
    397         while (!foundPicture && (scrollCount < MAX_UI_SCROLL_COUNT)) {
    398             foundPicture = mDevice.wait(Until.hasObject(By.descStartsWith("Photo")), 2000);
    399             if (!foundPicture) {
    400                 if (!scrollView(Direction.DOWN)) {
    401                     break;
    402                 }
    403             }
    404             scrollCount += 1;
    405         }
    406         return foundPicture;
    407     }
    408 
    409     /**
    410      * {@inheritDoc}
    411      */
    412     @Override
    413     public void openDeviceFolder(String folderName) {
    414         UiObject2 deviceFolder = mDevice.wait(Until.findObject(By.text(folderName)),
    415                                               UI_NAVIGATION_WAIT);
    416         if (deviceFolder != null) {
    417             deviceFolder.click();
    418         }
    419         else {
    420             throw new IllegalArgumentException(String.format("Cannot open device folder %s",
    421                                                              folderName));
    422         }
    423     }
    424 
    425     private UiObject2 getFirstClip() {
    426         return mDevice.wait(Until.findObject(By.descStartsWith("Video")), 2000);
    427     }
    428 
    429     /**
    430      *  This function returns true if Photos is currently on the first-use
    431      *  initial dialog screen, with "Get Started" button displayed on screen
    432      *
    433      * @return Returns true if app is on the initial dialog screen, false otherwise
    434      */
    435     private boolean isOnInitialDialogScreen() {
    436         return mDevice.hasObject(By.res(UI_PACKAGE_NAME, UI_GET_STARTED_CONTAINER));
    437     }
    438 
    439     private boolean isOnMainScreen() {
    440         return mDevice.hasObject(By.descContains("Photos, selected"));
    441     }
    442 
    443     /**
    444      *  This function returns true if Photos is currently in the
    445      *  photo-viewing screen, displaying either one photo
    446      *  or video on the screen.
    447      *
    448      * @return Returns true if one photo or video is displayed on the screen,
    449      *         false otherwise.
    450      */
    451     private boolean isOnPhotoViewingScreen() {
    452         return mDevice.hasObject(By.res(UI_PACKAGE_NAME, UI_PHOTO_VIEW_PAGER_ID));
    453     }
    454 
    455     private boolean isOnDeviceFolderScreen() {
    456 
    457         if (mDevice.hasObject(By.pkg(UI_PACKAGE_NAME).text(UI_DEVICE_FOLDER_TEXT))) {
    458             return true;
    459         }
    460 
    461         // sometimes the "Device Folder" tab is hidden.
    462         // scroll down once to make sure the tab is visible
    463         UiObject2 scrollContainer = mDevice.findObject(
    464                                         By.res(UI_PACKAGE_NAME, UI_PHOTO_SCROLL_VIEW_ID));
    465         if (scrollContainer != null) {
    466             scrollContainer.scroll(Direction.DOWN, 1.0f);
    467             return mDevice.hasObject(By.pkg(UI_PACKAGE_NAME).text(UI_DEVICE_FOLDER_TEXT));
    468         }
    469         else {
    470             return false;
    471         }
    472     }
    473 
    474     /**
    475      * This function performs one scroll on the current screen, in the direction
    476      * specified by input argument.
    477      *
    478      * @param dir The direction of the scroll
    479      * @return Returns whether the object can still scroll in the given direction
    480      */
    481    private boolean scrollView(Direction dir) {
    482         UiObject2 scrollContainer = mDevice.findObject(By.res(UI_PACKAGE_NAME,
    483                                                               UI_PHOTO_SCROLL_VIEW_ID));
    484         if (scrollContainer == null) {
    485             return false;
    486         }
    487 
    488         return scrollContainer.scroll(dir, 1.0f);
    489     }
    490 
    491     private void openNavigationDrawer() {
    492         UiObject2 navigationDrawer = mDevice.findObject(By.desc("Show Navigation Drawer"));
    493         if (navigationDrawer == null) {
    494             mDevice.pressBack();
    495             navigationDrawer = mDevice.wait(Until.findObject(By.desc("Show Navigation Drawer")),
    496                                             UI_NAVIGATION_WAIT);
    497         }
    498 
    499         if (navigationDrawer == null) {
    500             throw new UnknownUiException("Cannot find navigation drawer");
    501         }
    502 
    503         navigationDrawer.click();
    504 
    505         if (!mDevice.hasObject(By.res(UI_PACKAGE_NAME, UI_NAVIGATION_LIST_ID))) {
    506             throw new UnknownUiException("Cannot open navigation drawer");
    507         }
    508     }
    509 }
    510