1/*
2 * Copyright (C) 2013 DroidDriver committers
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 io.appium.droiddriver.util;
18
19import android.annotation.TargetApi;
20import android.os.Build;
21import android.os.SystemClock;
22import android.util.Log;
23import android.view.InputDevice;
24import android.view.InputEvent;
25import android.view.KeyEvent;
26import android.view.MotionEvent;
27
28import io.appium.droiddriver.actions.InputInjector;
29import io.appium.droiddriver.exceptions.ActionException;
30
31/**
32 * Helper methods to create InputEvents.
33 */
34public class Events {
35  /**
36   * @return a touch down event at the specified coordinates
37   */
38  @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
39  private static MotionEvent newTouchDownEvent(int x, int y) {
40    long downTime = SystemClock.uptimeMillis();
41    MotionEvent event = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, x, y, 1);
42    // TODO: Fix this if 'source' is required on devices older than HONEYCOMB_MR1.
43    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
44      event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
45    }
46    return event;
47  }
48
49  /**
50   * @return a touch up event at the specified coordinates
51   */
52  @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
53  private static MotionEvent newTouchUpEvent(long downTime, int x, int y) {
54    long eventTime = SystemClock.uptimeMillis();
55    MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 1);
56    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
57      event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
58    }
59    return event;
60  }
61
62  /**
63   * @return a touch move event at the specified coordinates
64   */
65  @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
66  private static MotionEvent newTouchMoveEvent(long downTime, int x, int y) {
67    long eventTime = SystemClock.uptimeMillis();
68    MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 1);
69    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
70      event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
71    }
72    return event;
73  }
74
75  @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
76  private static KeyEvent newKeyEvent(long downTime, long eventTime, int action, int keyCode,
77      int metaState) {
78    KeyEvent event = new KeyEvent(downTime, eventTime, action, keyCode, 0 /* repeat */, metaState);
79    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
80      event.setSource(InputDevice.SOURCE_KEYBOARD);
81    }
82    return event;
83  }
84
85  /**
86   * Injects {@code event}. {@code event} is recycled and should not be used
87   * after.
88   *
89   * @throws ActionException if injection failed
90   */
91  private static void injectEvent(InputInjector injector, InputEvent event) {
92    injectEvent(Log.DEBUG, injector, event);
93  }
94
95  private static void injectEvent(int priority, InputInjector injector, InputEvent event) {
96    Logs.call(priority, injector, "injectInputEvent", event);
97    try {
98      if (!injector.injectInputEvent(event)) {
99        throw new ActionException("Failed to inject " + event);
100      }
101    } finally {
102      if (event instanceof MotionEvent) {
103        ((MotionEvent) event).recycle();
104      }
105    }
106  }
107
108  public static long touchDown(InputInjector injector, int x, int y) {
109    MotionEvent downEvent = newTouchDownEvent(x, y);
110    long downTime = downEvent.getDownTime();
111    injectEvent(injector, downEvent);
112    return downTime;
113  }
114
115  public static void touchUp(InputInjector injector, long downTime, int x, int y) {
116    injectEvent(injector, newTouchUpEvent(downTime, x, y));
117  }
118
119  public static void touchMove(InputInjector injector, long downTime, int x, int y) {
120    injectEvent(Log.VERBOSE, injector, newTouchMoveEvent(downTime, x, y));
121  }
122
123  public static long keyDown(InputInjector injector, int keyCode, int metaState) {
124    long downTime = SystemClock.uptimeMillis();
125    KeyEvent downEvent = newKeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, metaState);
126    injectEvent(injector, downEvent);
127    return downTime;
128  }
129
130  public static void keyUp(InputInjector injector, long downTime, int keyCode, int metaState) {
131    injectEvent(injector,
132        newKeyEvent(downTime, SystemClock.uptimeMillis(), KeyEvent.ACTION_UP, keyCode, metaState));
133  }
134
135  private Events() {}
136}
137