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
17package com.android.uiautomator.core;
18
19import android.app.ActivityManagerNative;
20import android.app.IActivityManager;
21import android.app.IActivityManager.ContentProviderHolder;
22import android.content.Context;
23import android.content.IContentProvider;
24import android.database.Cursor;
25import android.graphics.Point;
26import android.hardware.input.InputManager;
27import android.os.Binder;
28import android.os.IBinder;
29import android.os.IPowerManager;
30import android.os.RemoteException;
31import android.os.ServiceManager;
32import android.os.SystemClock;
33import android.os.UserHandle;
34import android.provider.Settings;
35import android.util.Log;
36import android.view.IWindowManager;
37import android.view.InputDevice;
38import android.view.InputEvent;
39import android.view.KeyCharacterMap;
40import android.view.KeyEvent;
41import android.view.MotionEvent;
42import android.view.Surface;
43import android.view.accessibility.AccessibilityEvent;
44
45import com.android.internal.util.Predicate;
46
47import java.util.concurrent.TimeoutException;
48
49/**
50 * The InteractionProvider is responsible for injecting user events such as touch events
51 * (includes swipes) and text key events into the system. To do so, all it needs to know about
52 * are coordinates of the touch events and text for the text input events.
53 * The InteractionController performs no synchronization. It will fire touch and text input events
54 * as fast as it receives them. All idle synchronization is performed prior to querying the
55 * hierarchy. See {@link QueryController}
56 */
57class InteractionController {
58
59    private static final String LOG_TAG = InteractionController.class.getSimpleName();
60
61    private static final boolean DEBUG = false;
62
63    private static final long DEFAULT_SCROLL_EVENT_TIMEOUT_MILLIS = 500;
64
65    private final KeyCharacterMap mKeyCharacterMap =
66            KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
67
68    private final UiAutomatorBridge mUiAutomatorBridge;
69
70    private final IWindowManager mWindowManager;
71
72    private final long mLongPressTimeout;
73
74    private static final long REGULAR_CLICK_LENGTH = 100;
75
76    private long mDownTime;
77
78    public InteractionController(UiAutomatorBridge bridge) {
79        mUiAutomatorBridge = bridge;
80
81        // Obtain the window manager.
82        mWindowManager = IWindowManager.Stub.asInterface(
83                ServiceManager.getService(Context.WINDOW_SERVICE));
84        if (mWindowManager == null) {
85            throw new RuntimeException("Unable to connect to WindowManager, "
86                    + "is the system running?");
87        }
88
89        // the value returned is on the border of going undetected as used
90        // by this framework during long presses. Adding few extra 100ms
91        // of long press time helps ensure long enough time for a valid
92        // longClick detection.
93        mLongPressTimeout = getSystemLongPressTime() * 2 + 100;
94    }
95
96    /**
97     * Get the system long press time
98     * @return milliseconds
99     */
100    private long getSystemLongPressTime() {
101        // Read the long press timeout setting.
102        long longPressTimeout = 0;
103        try {
104            IContentProvider provider = null;
105            Cursor cursor = null;
106            IActivityManager activityManager = ActivityManagerNative.getDefault();
107            String providerName = Settings.Secure.CONTENT_URI.getAuthority();
108            IBinder token = new Binder();
109            try {
110                ContentProviderHolder holder = activityManager.getContentProviderExternal(
111                        providerName, UserHandle.USER_OWNER, token);
112                if (holder == null) {
113                    throw new IllegalStateException("Could not find provider: " + providerName);
114                }
115                provider = holder.provider;
116                cursor = provider.query(Settings.Secure.CONTENT_URI,
117                        new String[] {Settings.Secure.VALUE}, "name=?",
118                        new String[] {Settings.Secure.LONG_PRESS_TIMEOUT}, null, null);
119                if (cursor.moveToFirst()) {
120                    longPressTimeout = cursor.getInt(0);
121                }
122            } finally {
123                if (cursor != null) {
124                    cursor.close();
125                }
126                if (provider != null) {
127                    activityManager.removeContentProviderExternal(providerName, token);
128                }
129            }
130        } catch (RemoteException e) {
131            String message = "Error reading long press timeout setting.";
132            Log.e(LOG_TAG, message, e);
133            throw new RuntimeException(message, e);
134        }
135        return longPressTimeout;
136    }
137
138    /**
139     * Click at coordinates and blocks until the first specified accessibility event.
140     *
141     * All clicks will cause some UI change to occur. If the device is busy, this will
142     * block until the device begins to process the click at which point the call returns
143     * and normal wait for idle processing may begin. If no evens are detected for the
144     * timeout period specified, the call will return anyway.
145     * @param x
146     * @param y
147     * @param timeout
148     * @param eventType is an {@link AccessibilityEvent} type
149     * @return True if busy state is detected else false for timeout waiting for busy state
150     */
151    public boolean clickAndWaitForEvent(final int x, final int y, long timeout,
152            final int eventType) {
153        return clickAndWaitForEvents(x, y, timeout, false, eventType);
154    }
155
156    /**
157     * Click at coordinates and blocks until the specified accessibility events. It is possible to
158     * set the wait for all events to occur, in no specific order, or to the wait for any.
159     *
160     * @param x
161     * @param y
162     * @param timeout
163     * @param waitForAll boolean to indicate whether to wait for any or all events
164     * @param eventTypes mask
165     * @return
166     */
167    public boolean clickAndWaitForEvents(final int x, final int y, long timeout,
168            boolean waitForAll, int eventTypes) {
169        String logString = String.format("clickAndWaitForEvents(%d, %d, %d, %s, %d)", x, y, timeout,
170                Boolean.toString(waitForAll), eventTypes);
171        Log.d(LOG_TAG, logString);
172
173        mUiAutomatorBridge.setOperationTime();
174        Runnable command = new Runnable() {
175            @Override
176            public void run() {
177                if(touchDown(x, y)) {
178                    SystemClock.sleep(REGULAR_CLICK_LENGTH);
179                    touchUp(x, y);
180                }
181            }
182        };
183        return runAndWaitForEvents(command, timeout, waitForAll, eventTypes);
184    }
185
186    /**
187     * Runs a command and waits for a specific accessibility event.
188     * @param command is a Runnable to execute before waiting for the event.
189     * @param timeout
190     * @param eventType
191     * @return
192     */
193    private boolean runAndWaitForEvent(Runnable command, long timeout, int eventType) {
194        return runAndWaitForEvents(command, timeout, false, eventType);
195    }
196
197    /**
198     * Runs a command and waits for accessibility events. It is possible to set the wait for all
199     * events to occur at least once for each, or wait for any one to occur at least once.
200     *
201     * @param command
202     * @param timeout
203     * @param waitForAll boolean to indicate whether to wait for any or all events
204     * @param eventTypesMask
205     * @return
206     */
207    private boolean runAndWaitForEvents(Runnable command, long timeout, final boolean waitForAll,
208            final int eventTypesMask) {
209
210        if (eventTypesMask == 0)
211            throw new IllegalArgumentException("events mask cannot be zero");
212
213        class EventPredicate implements Predicate<AccessibilityEvent> {
214            int mMask;
215            EventPredicate(int mask) {
216                mMask = mask;
217            }
218            @Override
219            public boolean apply(AccessibilityEvent t) {
220                // check current event in the list
221                if ((t.getEventType() & mMask) != 0) {
222                    if (!waitForAll)
223                        return true;
224
225                    // remove from mask since this condition is satisfied
226                    mMask &= ~t.getEventType();
227
228                    // Since we're waiting for all events to be matched at least once
229                    if (mMask != 0)
230                        return false;
231
232                    // all matched
233                    return true;
234                }
235                // not one of our events
236                return false;
237            }
238        }
239
240        try {
241            mUiAutomatorBridge.executeCommandAndWaitForAccessibilityEvent(command,
242                    new EventPredicate(eventTypesMask), timeout);
243        } catch (TimeoutException e) {
244            Log.w(LOG_TAG, "runAndwaitForEvent timedout waiting for events: " + eventTypesMask);
245            return false;
246        } catch (Exception e) {
247            Log.e(LOG_TAG, "exception from executeCommandAndWaitForAccessibilityEvent", e);
248            return false;
249        }
250        return true;
251    }
252
253    /**
254     * Send keys and blocks until the first specified accessibility event.
255     *
256     * Most key presses will cause some UI change to occur. If the device is busy, this will
257     * block until the device begins to process the key press at which point the call returns
258     * and normal wait for idle processing may begin. If no evens are detected for the
259     * timeout period specified, the call will return anyway with false.
260     *
261     * @param keyCode
262     * @param metaState
263     * @param eventType
264     * @param timeout
265     * @return
266     */
267    public boolean sendKeyAndWaitForEvent(final int keyCode, final int metaState,
268            final int eventType, long timeout) {
269        mUiAutomatorBridge.setOperationTime();
270        Runnable command = new Runnable() {
271            @Override
272            public void run() {
273                final long eventTime = SystemClock.uptimeMillis();
274                KeyEvent downEvent = KeyEvent.obtain(eventTime, eventTime, KeyEvent.ACTION_DOWN,
275                        keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
276                        InputDevice.SOURCE_KEYBOARD, null);
277                if (injectEventSync(downEvent)) {
278                    KeyEvent upEvent = KeyEvent.obtain(eventTime, eventTime, KeyEvent.ACTION_UP,
279                            keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
280                            InputDevice.SOURCE_KEYBOARD, null);
281                    injectEventSync(upEvent);
282                }
283            }
284        };
285
286        return runAndWaitForEvent(command, timeout, eventType);
287    }
288
289    /**
290     * Clicks at coordinates without waiting for device idle. This may be used for operations
291     * that require stressing the target.
292     * @param x
293     * @param y
294     * @return
295     */
296    public boolean click(int x, int y) {
297        Log.d(LOG_TAG, "click (" + x + ", " + y + ")");
298        mUiAutomatorBridge.setOperationTime();
299
300        if (touchDown(x, y)) {
301            SystemClock.sleep(REGULAR_CLICK_LENGTH);
302            if (touchUp(x, y))
303                return true;
304        }
305        return false;
306    }
307
308    /**
309     * Clicks at coordinates and waits for for a TYPE_WINDOW_STATE_CHANGED event followed
310     * by TYPE_WINDOW_CONTENT_CHANGED. If timeout occurs waiting for TYPE_WINDOW_STATE_CHANGED,
311     * no further waits will be performed and the function returns.
312     * @param x
313     * @param y
314     * @param timeout
315     * @return true if both events occurred in the expected order
316     */
317    public boolean clickAndWaitForNewWindow(final int x, final int y, long timeout) {
318        return (clickAndWaitForEvents(x, y, timeout, true,
319                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED +
320                AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED));
321    }
322
323    public boolean longTap(int x, int y) {
324        if (DEBUG) {
325            Log.d(LOG_TAG, "longTap (" + x + ", " + y + ")");
326        }
327
328        mUiAutomatorBridge.setOperationTime();
329        if (touchDown(x, y)) {
330            SystemClock.sleep(mLongPressTimeout);
331            if(touchUp(x, y)) {
332                return true;
333            }
334        }
335        return false;
336    }
337
338    private boolean touchDown(int x, int y) {
339        if (DEBUG) {
340            Log.d(LOG_TAG, "touchDown (" + x + ", " + y + ")");
341        }
342        mDownTime = SystemClock.uptimeMillis();
343        MotionEvent event = MotionEvent.obtain(
344                mDownTime, mDownTime, MotionEvent.ACTION_DOWN, x, y, 1);
345        event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
346        return injectEventSync(event);
347    }
348
349    private boolean touchUp(int x, int y) {
350        if (DEBUG) {
351            Log.d(LOG_TAG, "touchUp (" + x + ", " + y + ")");
352        }
353        final long eventTime = SystemClock.uptimeMillis();
354        MotionEvent event = MotionEvent.obtain(
355                mDownTime, eventTime, MotionEvent.ACTION_UP, x, y, 1);
356        event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
357        mDownTime = 0;
358        return injectEventSync(event);
359    }
360
361    private boolean touchMove(int x, int y) {
362        if (DEBUG) {
363            Log.d(LOG_TAG, "touchMove (" + x + ", " + y + ")");
364        }
365        final long eventTime = SystemClock.uptimeMillis();
366        MotionEvent event = MotionEvent.obtain(
367                mDownTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 1);
368        event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
369        return injectEventSync(event);
370    }
371
372    /**
373     * Handle swipes in any direction where the result is a scroll event. This call blocks
374     * until the UI has fired a scroll event or timeout.
375     * @param downX
376     * @param downY
377     * @param upX
378     * @param upY
379     * @param duration
380     * @return true if the swipe and scrolling have been successfully completed.
381     */
382    public boolean scrollSwipe(final int downX, final int downY, final int upX, final int upY,
383            final int steps) {
384        Log.d(LOG_TAG, "scrollSwipe (" +  downX + ", " + downY + ", " + upX + ", "
385                + upY + ", " + steps +")");
386
387        Runnable command = new Runnable() {
388            @Override
389            public void run() {
390                swipe(downX, downY, upX, upY, steps);
391            }
392        };
393
394        return runAndWaitForEvent(command, DEFAULT_SCROLL_EVENT_TIMEOUT_MILLIS,
395                AccessibilityEvent.TYPE_VIEW_SCROLLED);
396    }
397
398    /**
399     * Handle swipes in any direction.
400     * @param downX
401     * @param downY
402     * @param upX
403     * @param upY
404     * @param duration
405     * @return
406     */
407    public boolean swipe(int downX, int downY, int upX, int upY, int steps) {
408        boolean ret = false;
409        int swipeSteps = steps;
410        double xStep = 0;
411        double yStep = 0;
412
413        // avoid a divide by zero
414        if(swipeSteps == 0)
415            swipeSteps = 1;
416
417        xStep = ((double)(upX - downX)) / swipeSteps;
418        yStep = ((double)(upY - downY)) / swipeSteps;
419
420        // first touch starts exactly at the point requested
421        ret = touchDown(downX, downY);
422        for(int i = 1; i < swipeSteps; i++) {
423            ret &= touchMove(downX + (int)(xStep * i), downY + (int)(yStep * i));
424            if(ret == false)
425                break;
426            // set some known constant delay between steps as without it this
427            // become completely dependent on the speed of the system and results
428            // may vary on different devices. This guarantees at minimum we have
429            // a preset delay.
430            SystemClock.sleep(5);
431        }
432        ret &= touchUp(upX, upY);
433        return(ret);
434    }
435
436    /**
437     * Performs a swipe between points in the Point array.
438     * @param segments is Point array containing at least one Point object
439     * @param segmentSteps steps to inject between two Points
440     * @return true on success
441     */
442    public boolean swipe(Point[] segments, int segmentSteps) {
443        boolean ret = false;
444        int swipeSteps = segmentSteps;
445        double xStep = 0;
446        double yStep = 0;
447
448        // avoid a divide by zero
449        if(segmentSteps == 0)
450            segmentSteps = 1;
451
452        // must have some points
453        if(segments.length == 0)
454            return false;
455
456        // first touch starts exactly at the point requested
457        ret = touchDown(segments[0].x, segments[0].y);
458        for(int seg = 0; seg < segments.length; seg++) {
459            if(seg + 1 < segments.length) {
460
461                xStep = ((double)(segments[seg+1].x - segments[seg].x)) / segmentSteps;
462                yStep = ((double)(segments[seg+1].y - segments[seg].y)) / segmentSteps;
463
464                for(int i = 1; i < swipeSteps; i++) {
465                    ret &= touchMove(segments[seg].x + (int)(xStep * i),
466                            segments[seg].y + (int)(yStep * i));
467                    if(ret == false)
468                        break;
469                    // set some known constant delay between steps as without it this
470                    // become completely dependent on the speed of the system and results
471                    // may vary on different devices. This guarantees at minimum we have
472                    // a preset delay.
473                    SystemClock.sleep(5);
474                }
475            }
476        }
477        ret &= touchUp(segments[segments.length - 1].x, segments[segments.length -1].y);
478        return(ret);
479    }
480
481
482    public boolean sendText(String text) {
483        if (DEBUG) {
484            Log.d(LOG_TAG, "sendText (" + text + ")");
485        }
486
487        mUiAutomatorBridge.setOperationTime();
488        KeyEvent[] events = mKeyCharacterMap.getEvents(text.toCharArray());
489        if (events != null) {
490            for (KeyEvent event2 : events) {
491                // We have to change the time of an event before injecting it because
492                // all KeyEvents returned by KeyCharacterMap.getEvents() have the same
493                // time stamp and the system rejects too old events. Hence, it is
494                // possible for an event to become stale before it is injected if it
495                // takes too long to inject the preceding ones.
496                KeyEvent event = KeyEvent.changeTimeRepeat(event2,
497                        SystemClock.uptimeMillis(), 0);
498                if (!injectEventSync(event)) {
499                    return false;
500                }
501            }
502        }
503        return true;
504    }
505
506    public boolean sendKey(int keyCode, int metaState) {
507        if (DEBUG) {
508            Log.d(LOG_TAG, "sendKey (" + keyCode + ", " + metaState + ")");
509        }
510
511        mUiAutomatorBridge.setOperationTime();
512        final long eventTime = SystemClock.uptimeMillis();
513        KeyEvent downEvent = KeyEvent.obtain(eventTime, eventTime, KeyEvent.ACTION_DOWN,
514                keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
515                InputDevice.SOURCE_KEYBOARD, null);
516        if (injectEventSync(downEvent)) {
517            KeyEvent upEvent = KeyEvent.obtain(eventTime, eventTime, KeyEvent.ACTION_UP,
518                    keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
519                    InputDevice.SOURCE_KEYBOARD, null);
520            if(injectEventSync(upEvent)) {
521                return true;
522            }
523        }
524        return false;
525    }
526
527    /**
528     * Check if the device is in its natural orientation. This is determined by
529     * checking whether the orientation is at 0 or 180 degrees.
530     * @return true if it is in natural orientation
531     * @throws RemoteException
532     */
533    public boolean isNaturalRotation() throws RemoteException {
534        return mWindowManager.getRotation() == Surface.ROTATION_0
535                || mWindowManager.getRotation() == Surface.ROTATION_180;
536    }
537
538    /**
539     * Rotates right and also freezes rotation in that position by
540     * disabling the sensors. If you want to un-freeze the rotation
541     * and re-enable the sensors see {@link #unfreezeRotation()}. Note
542     * that doing so may cause the screen contents to rotate
543     * depending on the current physical position of the test device.
544     * @throws RemoteException
545     */
546    public void setRotationRight() throws RemoteException {
547        mWindowManager.freezeRotation(Surface.ROTATION_270);
548    }
549
550    /**
551     * Rotates left and also freezes rotation in that position by
552     * disabling the sensors. If you want to un-freeze the rotation
553     * and re-enable the sensors see {@link #unfreezeRotation()}. Note
554     * that doing so may cause the screen contents to rotate
555     * depending on the current physical position of the test device.
556     * @throws RemoteException
557     */
558    public void setRotationLeft() throws RemoteException {
559        mWindowManager.freezeRotation(Surface.ROTATION_90);
560    }
561
562    /**
563     * Rotates up and also freezes rotation in that position by
564     * disabling the sensors. If you want to un-freeze the rotation
565     * and re-enable the sensors see {@link #unfreezeRotation()}. Note
566     * that doing so may cause the screen contents to rotate
567     * depending on the current physical position of the test device.
568     * @throws RemoteException
569     */
570    public void setRotationNatural() throws RemoteException {
571        mWindowManager.freezeRotation(Surface.ROTATION_0);
572    }
573
574    /**
575     * Disables the sensors and freezes the device rotation at its
576     * current rotation state.
577     * @throws RemoteException
578     */
579    public void freezeRotation() throws RemoteException {
580        mWindowManager.freezeRotation(-1);
581    }
582
583    /**
584     * Re-enables the sensors and un-freezes the device rotation
585     * allowing its contents to rotate with the device physical rotation.
586     * @throws RemoteException
587     */
588    public void unfreezeRotation() throws RemoteException {
589        mWindowManager.thawRotation();
590    }
591
592    /**
593     * This method simply presses the power button if the screen is OFF else
594     * it does nothing if the screen is already ON.
595     * @return true if the device was asleep else false
596     * @throws RemoteException
597     */
598    public boolean wakeDevice() throws RemoteException {
599        if(!isScreenOn()) {
600            sendKey(KeyEvent.KEYCODE_POWER, 0);
601            return true;
602        }
603        return false;
604    }
605
606    /**
607     * This method simply presses the power button if the screen is ON else
608     * it does nothing if the screen is already OFF.
609     * @return true if the device was awake else false
610     * @throws RemoteException
611     */
612    public boolean sleepDevice() throws RemoteException {
613        if(isScreenOn()) {
614            this.sendKey(KeyEvent.KEYCODE_POWER, 0);
615            return true;
616        }
617        return false;
618    }
619
620    /**
621     * Checks the power manager if the screen is ON
622     * @return true if the screen is ON else false
623     * @throws RemoteException
624     */
625    public boolean isScreenOn() throws RemoteException {
626        IPowerManager pm =
627                IPowerManager.Stub.asInterface(ServiceManager.getService(Context.POWER_SERVICE));
628        return pm.isScreenOn();
629    }
630
631    private static boolean injectEventSync(InputEvent event) {
632        return InputManager.getInstance().injectInputEvent(event,
633                InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
634    }
635}
636