Home | History | Annotate | Download | only in core
      1 /*
      2  * Copyright (C) 2012 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.uiautomator.core;
     18 
     19 import android.accessibilityservice.AccessibilityService;
     20 import android.app.UiAutomation;
     21 import android.app.UiAutomation.AccessibilityEventFilter;
     22 import android.graphics.Point;
     23 import android.os.RemoteException;
     24 import android.os.SystemClock;
     25 import android.util.Log;
     26 import android.view.InputDevice;
     27 import android.view.InputEvent;
     28 import android.view.KeyCharacterMap;
     29 import android.view.KeyEvent;
     30 import android.view.MotionEvent;
     31 import android.view.MotionEvent.PointerCoords;
     32 import android.view.MotionEvent.PointerProperties;
     33 import android.view.accessibility.AccessibilityEvent;
     34 
     35 import com.android.internal.util.Predicate;
     36 
     37 import java.util.ArrayList;
     38 import java.util.List;
     39 import java.util.concurrent.TimeoutException;
     40 
     41 /**
     42  * The InteractionProvider is responsible for injecting user events such as touch events
     43  * (includes swipes) and text key events into the system. To do so, all it needs to know about
     44  * are coordinates of the touch events and text for the text input events.
     45  * The InteractionController performs no synchronization. It will fire touch and text input events
     46  * as fast as it receives them. All idle synchronization is performed prior to querying the
     47  * hierarchy. See {@link QueryController}
     48  */
     49 class InteractionController {
     50 
     51     private static final String LOG_TAG = InteractionController.class.getSimpleName();
     52 
     53     private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
     54 
     55     private final KeyCharacterMap mKeyCharacterMap =
     56             KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
     57 
     58     private final UiAutomatorBridge mUiAutomatorBridge;
     59 
     60     private static final long REGULAR_CLICK_LENGTH = 100;
     61 
     62     private long mDownTime;
     63 
     64     // Inserted after each motion event injection.
     65     private static final int MOTION_EVENT_INJECTION_DELAY_MILLIS = 5;
     66 
     67     public InteractionController(UiAutomatorBridge bridge) {
     68         mUiAutomatorBridge = bridge;
     69     }
     70 
     71     /**
     72      * Predicate for waiting for any of the events specified in the mask
     73      */
     74     class WaitForAnyEventPredicate implements AccessibilityEventFilter {
     75         int mMask;
     76         WaitForAnyEventPredicate(int mask) {
     77             mMask = mask;
     78         }
     79         @Override
     80         public boolean accept(AccessibilityEvent t) {
     81             // check current event in the list
     82             if ((t.getEventType() & mMask) != 0) {
     83                 return true;
     84             }
     85 
     86             // no match yet
     87             return false;
     88         }
     89     }
     90 
     91     /**
     92      * Predicate for waiting for all the events specified in the mask and populating
     93      * a ctor passed list with matching events. User of this Predicate must recycle
     94      * all populated events in the events list.
     95      */
     96     class EventCollectingPredicate implements AccessibilityEventFilter {
     97         int mMask;
     98         List<AccessibilityEvent> mEventsList;
     99 
    100         EventCollectingPredicate(int mask, List<AccessibilityEvent> events) {
    101             mMask = mask;
    102             mEventsList = events;
    103         }
    104 
    105         @Override
    106         public boolean accept(AccessibilityEvent t) {
    107             // check current event in the list
    108             if ((t.getEventType() & mMask) != 0) {
    109                 // For the events you need, always store a copy when returning false from
    110                 // predicates since the original will automatically be recycled after the call.
    111                 mEventsList.add(AccessibilityEvent.obtain(t));
    112             }
    113 
    114             // get more
    115             return false;
    116         }
    117     }
    118 
    119     /**
    120      * Predicate for waiting for every event specified in the mask to be matched at least once
    121      */
    122     class WaitForAllEventPredicate implements AccessibilityEventFilter {
    123         int mMask;
    124         WaitForAllEventPredicate(int mask) {
    125             mMask = mask;
    126         }
    127 
    128         @Override
    129         public boolean accept(AccessibilityEvent t) {
    130             // check current event in the list
    131             if ((t.getEventType() & mMask) != 0) {
    132                 // remove from mask since this condition is satisfied
    133                 mMask &= ~t.getEventType();
    134 
    135                 // Since we're waiting for all events to be matched at least once
    136                 if (mMask != 0)
    137                     return false;
    138 
    139                 // all matched
    140                 return true;
    141             }
    142 
    143             // no match yet
    144             return false;
    145         }
    146     }
    147 
    148     /**
    149      * Helper used by methods to perform actions and wait for any accessibility events and return
    150      * predicated on predefined filter.
    151      *
    152      * @param command
    153      * @param filter
    154      * @param timeout
    155      * @return
    156      */
    157     private AccessibilityEvent runAndWaitForEvents(Runnable command,
    158             AccessibilityEventFilter filter, long timeout) {
    159 
    160         try {
    161             return mUiAutomatorBridge.executeCommandAndWaitForAccessibilityEvent(command, filter,
    162                     timeout);
    163         } catch (TimeoutException e) {
    164             Log.w(LOG_TAG, "runAndwaitForEvent timedout waiting for events");
    165             return null;
    166         } catch (Exception e) {
    167             Log.e(LOG_TAG, "exception from executeCommandAndWaitForAccessibilityEvent", e);
    168             return null;
    169         }
    170     }
    171 
    172     /**
    173      * Send keys and blocks until the first specified accessibility event.
    174      *
    175      * Most key presses will cause some UI change to occur. If the device is busy, this will
    176      * block until the device begins to process the key press at which point the call returns
    177      * and normal wait for idle processing may begin. If no events are detected for the
    178      * timeout period specified, the call will return anyway with false.
    179      *
    180      * @param keyCode
    181      * @param metaState
    182      * @param eventType
    183      * @param timeout
    184      * @return true if events is received, otherwise false.
    185      */
    186     public boolean sendKeyAndWaitForEvent(final int keyCode, final int metaState,
    187             final int eventType, long timeout) {
    188         Runnable command = new Runnable() {
    189             @Override
    190             public void run() {
    191                 final long eventTime = SystemClock.uptimeMillis();
    192                 KeyEvent downEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN,
    193                         keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
    194                         InputDevice.SOURCE_KEYBOARD);
    195                 if (injectEventSync(downEvent)) {
    196                     KeyEvent upEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP,
    197                             keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
    198                             InputDevice.SOURCE_KEYBOARD);
    199                     injectEventSync(upEvent);
    200                 }
    201             }
    202         };
    203 
    204         return runAndWaitForEvents(command, new WaitForAnyEventPredicate(eventType), timeout)
    205                 != null;
    206     }
    207 
    208     /**
    209      * Clicks at coordinates without waiting for device idle. This may be used for operations
    210      * that require stressing the target.
    211      * @param x
    212      * @param y
    213      * @return true if the click executed successfully
    214      */
    215     public boolean clickNoSync(int x, int y) {
    216         Log.d(LOG_TAG, "clickNoSync (" + x + ", " + y + ")");
    217 
    218         if (touchDown(x, y)) {
    219             SystemClock.sleep(REGULAR_CLICK_LENGTH);
    220             if (touchUp(x, y))
    221                 return true;
    222         }
    223         return false;
    224     }
    225 
    226     /**
    227      * Click at coordinates and blocks until either accessibility event TYPE_WINDOW_CONTENT_CHANGED
    228      * or TYPE_VIEW_SELECTED are received.
    229      *
    230      * @param x
    231      * @param y
    232      * @param timeout waiting for event
    233      * @return true if events are received, else false if timeout.
    234      */
    235     public boolean clickAndSync(final int x, final int y, long timeout) {
    236 
    237         String logString = String.format("clickAndSync(%d, %d)", x, y);
    238         Log.d(LOG_TAG, logString);
    239 
    240         return runAndWaitForEvents(clickRunnable(x, y), new WaitForAnyEventPredicate(
    241                 AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED |
    242                 AccessibilityEvent.TYPE_VIEW_SELECTED), timeout) != null;
    243     }
    244 
    245     /**
    246      * Clicks at coordinates and waits for for a TYPE_WINDOW_STATE_CHANGED event followed
    247      * by TYPE_WINDOW_CONTENT_CHANGED. If timeout occurs waiting for TYPE_WINDOW_STATE_CHANGED,
    248      * no further waits will be performed and the function returns.
    249      * @param x
    250      * @param y
    251      * @param timeout waiting for event
    252      * @return true if both events occurred in the expected order
    253      */
    254     public boolean clickAndWaitForNewWindow(final int x, final int y, long timeout) {
    255         String logString = String.format("clickAndWaitForNewWindow(%d, %d)", x, y);
    256         Log.d(LOG_TAG, logString);
    257 
    258         return runAndWaitForEvents(clickRunnable(x, y), new WaitForAllEventPredicate(
    259                 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED |
    260                 AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED), timeout) != null;
    261     }
    262 
    263     /**
    264      * Returns a Runnable for use in {@link #runAndWaitForEvents(Runnable, Predicate, long) to
    265      * perform a click.
    266      *
    267      * @param x coordinate
    268      * @param y coordinate
    269      * @return Runnable
    270      */
    271     private Runnable clickRunnable(final int x, final int y) {
    272         return new Runnable() {
    273             @Override
    274             public void run() {
    275                 if(touchDown(x, y)) {
    276                     SystemClock.sleep(REGULAR_CLICK_LENGTH);
    277                     touchUp(x, y);
    278                 }
    279             }
    280         };
    281     }
    282 
    283     /**
    284      * Touches down for a long press at the specified coordinates.
    285      *
    286      * @param x
    287      * @param y
    288      * @return true if successful.
    289      */
    290     public boolean longTapNoSync(int x, int y) {
    291         if (DEBUG) {
    292             Log.d(LOG_TAG, "longTapNoSync (" + x + ", " + y + ")");
    293         }
    294 
    295         if (touchDown(x, y)) {
    296             SystemClock.sleep(mUiAutomatorBridge.getSystemLongPressTime());
    297             if(touchUp(x, y)) {
    298                 return true;
    299             }
    300         }
    301         return false;
    302     }
    303 
    304     private boolean touchDown(int x, int y) {
    305         if (DEBUG) {
    306             Log.d(LOG_TAG, "touchDown (" + x + ", " + y + ")");
    307         }
    308         mDownTime = SystemClock.uptimeMillis();
    309         MotionEvent event = MotionEvent.obtain(
    310                 mDownTime, mDownTime, MotionEvent.ACTION_DOWN, x, y, 1);
    311         event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
    312         return injectEventSync(event);
    313     }
    314 
    315     private boolean touchUp(int x, int y) {
    316         if (DEBUG) {
    317             Log.d(LOG_TAG, "touchUp (" + x + ", " + y + ")");
    318         }
    319         final long eventTime = SystemClock.uptimeMillis();
    320         MotionEvent event = MotionEvent.obtain(
    321                 mDownTime, eventTime, MotionEvent.ACTION_UP, x, y, 1);
    322         event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
    323         mDownTime = 0;
    324         return injectEventSync(event);
    325     }
    326 
    327     private boolean touchMove(int x, int y) {
    328         if (DEBUG) {
    329             Log.d(LOG_TAG, "touchMove (" + x + ", " + y + ")");
    330         }
    331         final long eventTime = SystemClock.uptimeMillis();
    332         MotionEvent event = MotionEvent.obtain(
    333                 mDownTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 1);
    334         event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
    335         return injectEventSync(event);
    336     }
    337 
    338     /**
    339      * Handle swipes in any direction where the result is a scroll event. This call blocks
    340      * until the UI has fired a scroll event or timeout.
    341      * @param downX
    342      * @param downY
    343      * @param upX
    344      * @param upY
    345      * @param steps
    346      * @return true if we are not at the beginning or end of the scrollable view.
    347      */
    348     public boolean scrollSwipe(final int downX, final int downY, final int upX, final int upY,
    349             final int steps) {
    350         Log.d(LOG_TAG, "scrollSwipe (" +  downX + ", " + downY + ", " + upX + ", "
    351                 + upY + ", " + steps +")");
    352 
    353         Runnable command = new Runnable() {
    354             @Override
    355             public void run() {
    356                 swipe(downX, downY, upX, upY, steps);
    357             }
    358         };
    359 
    360         // Collect all accessibility events generated during the swipe command and get the
    361         // last event
    362         ArrayList<AccessibilityEvent> events = new ArrayList<AccessibilityEvent>();
    363         runAndWaitForEvents(command,
    364                 new EventCollectingPredicate(AccessibilityEvent.TYPE_VIEW_SCROLLED, events),
    365                 Configurator.getInstance().getScrollAcknowledgmentTimeout());
    366 
    367         AccessibilityEvent event = getLastMatchingEvent(events,
    368                 AccessibilityEvent.TYPE_VIEW_SCROLLED);
    369 
    370         if (event == null) {
    371             // end of scroll since no new scroll events received
    372             recycleAccessibilityEvents(events);
    373             return false;
    374         }
    375 
    376         // AdapterViews have indices we can use to check for the beginning.
    377         boolean foundEnd = false;
    378         if (event.getFromIndex() != -1 && event.getToIndex() != -1 && event.getItemCount() != -1) {
    379             foundEnd = event.getFromIndex() == 0 ||
    380                     (event.getItemCount() - 1) == event.getToIndex();
    381             Log.d(LOG_TAG, "scrollSwipe reached scroll end: " + foundEnd);
    382         } else if (event.getScrollX() != -1 && event.getScrollY() != -1) {
    383             // Determine if we are scrolling vertically or horizontally.
    384             if (downX == upX) {
    385                 // Vertical
    386                 foundEnd = event.getScrollY() == 0 ||
    387                         event.getScrollY() == event.getMaxScrollY();
    388                 Log.d(LOG_TAG, "Vertical scrollSwipe reached scroll end: " + foundEnd);
    389             } else if (downY == upY) {
    390                 // Horizontal
    391                 foundEnd = event.getScrollX() == 0 ||
    392                         event.getScrollX() == event.getMaxScrollX();
    393                 Log.d(LOG_TAG, "Horizontal scrollSwipe reached scroll end: " + foundEnd);
    394             }
    395         }
    396         recycleAccessibilityEvents(events);
    397         return !foundEnd;
    398     }
    399 
    400     private AccessibilityEvent getLastMatchingEvent(List<AccessibilityEvent> events, int type) {
    401         for (int x = events.size(); x > 0; x--) {
    402             AccessibilityEvent event = events.get(x - 1);
    403             if (event.getEventType() == type)
    404                 return event;
    405         }
    406         return null;
    407     }
    408 
    409     private void recycleAccessibilityEvents(List<AccessibilityEvent> events) {
    410         for (AccessibilityEvent event : events)
    411             event.recycle();
    412         events.clear();
    413     }
    414 
    415     /**
    416      * Handle swipes in any direction.
    417      * @param downX
    418      * @param downY
    419      * @param upX
    420      * @param upY
    421      * @param steps
    422      * @return true if the swipe executed successfully
    423      */
    424     public boolean swipe(int downX, int downY, int upX, int upY, int steps) {
    425         return swipe(downX, downY, upX, upY, steps, false /*drag*/);
    426     }
    427 
    428     /**
    429      * Handle swipes/drags in any direction.
    430      * @param downX
    431      * @param downY
    432      * @param upX
    433      * @param upY
    434      * @param steps
    435      * @param drag when true, the swipe becomes a drag swipe
    436      * @return true if the swipe executed successfully
    437      */
    438     public boolean swipe(int downX, int downY, int upX, int upY, int steps, boolean drag) {
    439         boolean ret = false;
    440         int swipeSteps = steps;
    441         double xStep = 0;
    442         double yStep = 0;
    443 
    444         // avoid a divide by zero
    445         if(swipeSteps == 0)
    446             swipeSteps = 1;
    447 
    448         xStep = ((double)(upX - downX)) / swipeSteps;
    449         yStep = ((double)(upY - downY)) / swipeSteps;
    450 
    451         // first touch starts exactly at the point requested
    452         ret = touchDown(downX, downY);
    453         if (drag)
    454             SystemClock.sleep(mUiAutomatorBridge.getSystemLongPressTime());
    455         for(int i = 1; i < swipeSteps; i++) {
    456             ret &= touchMove(downX + (int)(xStep * i), downY + (int)(yStep * i));
    457             if(ret == false)
    458                 break;
    459             // set some known constant delay between steps as without it this
    460             // become completely dependent on the speed of the system and results
    461             // may vary on different devices. This guarantees at minimum we have
    462             // a preset delay.
    463             SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
    464         }
    465         if (drag)
    466             SystemClock.sleep(REGULAR_CLICK_LENGTH);
    467         ret &= touchUp(upX, upY);
    468         return(ret);
    469     }
    470 
    471     /**
    472      * Performs a swipe between points in the Point array.
    473      * @param segments is Point array containing at least one Point object
    474      * @param segmentSteps steps to inject between two Points
    475      * @return true on success
    476      */
    477     public boolean swipe(Point[] segments, int segmentSteps) {
    478         boolean ret = false;
    479         int swipeSteps = segmentSteps;
    480         double xStep = 0;
    481         double yStep = 0;
    482 
    483         // avoid a divide by zero
    484         if(segmentSteps == 0)
    485             segmentSteps = 1;
    486 
    487         // must have some points
    488         if(segments.length == 0)
    489             return false;
    490 
    491         // first touch starts exactly at the point requested
    492         ret = touchDown(segments[0].x, segments[0].y);
    493         for(int seg = 0; seg < segments.length; seg++) {
    494             if(seg + 1 < segments.length) {
    495 
    496                 xStep = ((double)(segments[seg+1].x - segments[seg].x)) / segmentSteps;
    497                 yStep = ((double)(segments[seg+1].y - segments[seg].y)) / segmentSteps;
    498 
    499                 for(int i = 1; i < swipeSteps; i++) {
    500                     ret &= touchMove(segments[seg].x + (int)(xStep * i),
    501                             segments[seg].y + (int)(yStep * i));
    502                     if(ret == false)
    503                         break;
    504                     // set some known constant delay between steps as without it this
    505                     // become completely dependent on the speed of the system and results
    506                     // may vary on different devices. This guarantees at minimum we have
    507                     // a preset delay.
    508                     SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
    509                 }
    510             }
    511         }
    512         ret &= touchUp(segments[segments.length - 1].x, segments[segments.length -1].y);
    513         return(ret);
    514     }
    515 
    516 
    517     public boolean sendText(String text) {
    518         if (DEBUG) {
    519             Log.d(LOG_TAG, "sendText (" + text + ")");
    520         }
    521 
    522         KeyEvent[] events = mKeyCharacterMap.getEvents(text.toCharArray());
    523 
    524         if (events != null) {
    525             long keyDelay = Configurator.getInstance().getKeyInjectionDelay();
    526             for (KeyEvent event2 : events) {
    527                 // We have to change the time of an event before injecting it because
    528                 // all KeyEvents returned by KeyCharacterMap.getEvents() have the same
    529                 // time stamp and the system rejects too old events. Hence, it is
    530                 // possible for an event to become stale before it is injected if it
    531                 // takes too long to inject the preceding ones.
    532                 KeyEvent event = KeyEvent.changeTimeRepeat(event2,
    533                         SystemClock.uptimeMillis(), 0);
    534                 if (!injectEventSync(event)) {
    535                     return false;
    536                 }
    537                 SystemClock.sleep(keyDelay);
    538             }
    539         }
    540         return true;
    541     }
    542 
    543     public boolean sendKey(int keyCode, int metaState) {
    544         if (DEBUG) {
    545             Log.d(LOG_TAG, "sendKey (" + keyCode + ", " + metaState + ")");
    546         }
    547 
    548         final long eventTime = SystemClock.uptimeMillis();
    549         KeyEvent downEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN,
    550                 keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
    551                 InputDevice.SOURCE_KEYBOARD);
    552         if (injectEventSync(downEvent)) {
    553             KeyEvent upEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP,
    554                     keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
    555                     InputDevice.SOURCE_KEYBOARD);
    556             if(injectEventSync(upEvent)) {
    557                 return true;
    558             }
    559         }
    560         return false;
    561     }
    562 
    563     /**
    564      * Rotates right and also freezes rotation in that position by
    565      * disabling the sensors. If you want to un-freeze the rotation
    566      * and re-enable the sensors see {@link #unfreezeRotation()}. Note
    567      * that doing so may cause the screen contents to rotate
    568      * depending on the current physical position of the test device.
    569      * @throws RemoteException
    570      */
    571     public void setRotationRight() {
    572         mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_270);
    573     }
    574 
    575     /**
    576      * Rotates left and also freezes rotation in that position by
    577      * disabling the sensors. If you want to un-freeze the rotation
    578      * and re-enable the sensors see {@link #unfreezeRotation()}. Note
    579      * that doing so may cause the screen contents to rotate
    580      * depending on the current physical position of the test device.
    581      * @throws RemoteException
    582      */
    583     public void setRotationLeft() {
    584         mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_90);
    585     }
    586 
    587     /**
    588      * Rotates up and also freezes rotation in that position by
    589      * disabling the sensors. If you want to un-freeze the rotation
    590      * and re-enable the sensors see {@link #unfreezeRotation()}. Note
    591      * that doing so may cause the screen contents to rotate
    592      * depending on the current physical position of the test device.
    593      * @throws RemoteException
    594      */
    595     public void setRotationNatural() {
    596         mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_0);
    597     }
    598 
    599     /**
    600      * Disables the sensors and freezes the device rotation at its
    601      * current rotation state.
    602      * @throws RemoteException
    603      */
    604     public void freezeRotation() {
    605         mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_CURRENT);
    606     }
    607 
    608     /**
    609      * Re-enables the sensors and un-freezes the device rotation
    610      * allowing its contents to rotate with the device physical rotation.
    611      * @throws RemoteException
    612      */
    613     public void unfreezeRotation() {
    614         mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_UNFREEZE);
    615     }
    616 
    617     /**
    618      * This method simply presses the power button if the screen is OFF else
    619      * it does nothing if the screen is already ON.
    620      * @return true if the device was asleep else false
    621      * @throws RemoteException
    622      */
    623     public boolean wakeDevice() throws RemoteException {
    624         if(!isScreenOn()) {
    625             sendKey(KeyEvent.KEYCODE_POWER, 0);
    626             return true;
    627         }
    628         return false;
    629     }
    630 
    631     /**
    632      * This method simply presses the power button if the screen is ON else
    633      * it does nothing if the screen is already OFF.
    634      * @return true if the device was awake else false
    635      * @throws RemoteException
    636      */
    637     public boolean sleepDevice() throws RemoteException {
    638         if(isScreenOn()) {
    639             this.sendKey(KeyEvent.KEYCODE_POWER, 0);
    640             return true;
    641         }
    642         return false;
    643     }
    644 
    645     /**
    646      * Checks the power manager if the screen is ON
    647      * @return true if the screen is ON else false
    648      * @throws RemoteException
    649      */
    650     public boolean isScreenOn() throws RemoteException {
    651         return mUiAutomatorBridge.isScreenOn();
    652     }
    653 
    654     private boolean injectEventSync(InputEvent event) {
    655         return mUiAutomatorBridge.injectInputEvent(event, true);
    656     }
    657 
    658     private int getPointerAction(int motionEnvent, int index) {
    659         return motionEnvent + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
    660     }
    661 
    662     /**
    663      * Performs a multi-touch gesture
    664      *
    665      * Takes a series of touch coordinates for at least 2 pointers. Each pointer must have
    666      * all of its touch steps defined in an array of {@link PointerCoords}. By having the ability
    667      * to specify the touch points along the path of a pointer, the caller is able to specify
    668      * complex gestures like circles, irregular shapes etc, where each pointer may take a
    669      * different path.
    670      *
    671      * To create a single point on a pointer's touch path
    672      * <code>
    673      *       PointerCoords p = new PointerCoords();
    674      *       p.x = stepX;
    675      *       p.y = stepY;
    676      *       p.pressure = 1;
    677      *       p.size = 1;
    678      * </code>
    679      * @param touches each array of {@link PointerCoords} constitute a single pointer's touch path.
    680      *        Multiple {@link PointerCoords} arrays constitute multiple pointers, each with its own
    681      *        path. Each {@link PointerCoords} in an array constitute a point on a pointer's path.
    682      * @return <code>true</code> if all points on all paths are injected successfully, <code>false
    683      *        </code>otherwise
    684      * @since API Level 18
    685      */
    686     public boolean performMultiPointerGesture(PointerCoords[] ... touches) {
    687         boolean ret = true;
    688         if (touches.length < 2) {
    689             throw new IllegalArgumentException("Must provide coordinates for at least 2 pointers");
    690         }
    691 
    692         // Get the pointer with the max steps to inject.
    693         int maxSteps = 0;
    694         for (int x = 0; x < touches.length; x++)
    695             maxSteps = (maxSteps < touches[x].length) ? touches[x].length : maxSteps;
    696 
    697         // specify the properties for each pointer as finger touch
    698         PointerProperties[] properties = new PointerProperties[touches.length];
    699         PointerCoords[] pointerCoords = new PointerCoords[touches.length];
    700         for (int x = 0; x < touches.length; x++) {
    701             PointerProperties prop = new PointerProperties();
    702             prop.id = x;
    703             prop.toolType = MotionEvent.TOOL_TYPE_FINGER;
    704             properties[x] = prop;
    705 
    706             // for each pointer set the first coordinates for touch down
    707             pointerCoords[x] = touches[x][0];
    708         }
    709 
    710         // Touch down all pointers
    711         long downTime = SystemClock.uptimeMillis();
    712         MotionEvent event;
    713         event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 1,
    714                 properties, pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
    715         ret &= injectEventSync(event);
    716 
    717         for (int x = 1; x < touches.length; x++) {
    718             event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
    719                     getPointerAction(MotionEvent.ACTION_POINTER_DOWN, x), x + 1, properties,
    720                     pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
    721             ret &= injectEventSync(event);
    722         }
    723 
    724         // Move all pointers
    725         for (int i = 1; i < maxSteps - 1; i++) {
    726             // for each pointer
    727             for (int x = 0; x < touches.length; x++) {
    728                 // check if it has coordinates to move
    729                 if (touches[x].length > i)
    730                     pointerCoords[x] = touches[x][i];
    731                 else
    732                     pointerCoords[x] = touches[x][touches[x].length - 1];
    733             }
    734 
    735             event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
    736                     MotionEvent.ACTION_MOVE, touches.length, properties, pointerCoords, 0, 0, 1, 1,
    737                     0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
    738 
    739             ret &= injectEventSync(event);
    740             SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
    741         }
    742 
    743         // For each pointer get the last coordinates
    744         for (int x = 0; x < touches.length; x++)
    745             pointerCoords[x] = touches[x][touches[x].length - 1];
    746 
    747         // touch up
    748         for (int x = 1; x < touches.length; x++) {
    749             event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
    750                     getPointerAction(MotionEvent.ACTION_POINTER_UP, x), x + 1, properties,
    751                     pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
    752             ret &= injectEventSync(event);
    753         }
    754 
    755         Log.i(LOG_TAG, "x " + pointerCoords[0].x);
    756         // first to touch down is last up
    757         event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 1,
    758                 properties, pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
    759         ret &= injectEventSync(event);
    760         return ret;
    761     }
    762 
    763     /**
    764      * Simulates a short press on the Recent Apps button.
    765      *
    766      * @return true if successful, else return false
    767      * @since API Level 18
    768      */
    769     public boolean toggleRecentApps() {
    770         return mUiAutomatorBridge.performGlobalAction(
    771                 AccessibilityService.GLOBAL_ACTION_RECENTS);
    772     }
    773 
    774     /**
    775      * Opens the notification shade
    776      *
    777      * @return true if successful, else return false
    778      * @since API Level 18
    779      */
    780     public boolean openNotification() {
    781         return mUiAutomatorBridge.performGlobalAction(
    782                 AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS);
    783     }
    784 
    785     /**
    786      * Opens the quick settings shade
    787      *
    788      * @return true if successful, else return false
    789      * @since API Level 18
    790      */
    791     public boolean openQuickSettings() {
    792         return mUiAutomatorBridge.performGlobalAction(
    793                 AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS);
    794     }
    795 }
    796