blob: b03e4c71ec067fe0339fc2b735880d38b35a08d3 [file] [log] [blame]
Ben Pietrzakdc4b5032012-09-10 11:33:19 -07001/*
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 android.view;
18
Ben Pietrzak05cb3632012-12-12 11:31:57 -080019import android.app.SearchManager;
Ben Pietrzak6b1d9122012-11-07 15:07:18 -080020import android.content.ActivityNotFoundException;
Ben Pietrzak05cb3632012-12-12 11:31:57 -080021import android.content.Context;
Ben Pietrzak29880222012-11-01 15:36:05 -070022import android.content.Intent;
Ben Pietrzak6f403a82012-10-03 14:54:07 -070023import android.os.Handler;
Ben Pietrzak6f403a82012-10-03 14:54:07 -070024import android.os.Message;
25import android.os.SystemClock;
26import android.os.SystemProperties;
Ben Pietrzak05cb3632012-12-12 11:31:57 -080027import android.os.UserHandle;
Ben Pietrzak6b1d9122012-11-07 15:07:18 -080028import android.util.Log;
Ben Pietrzak6f403a82012-10-03 14:54:07 -070029
Ben Pietrzakdc4b5032012-09-10 11:33:19 -070030/**
Dake Guf8739b92013-01-17 13:39:01 -080031 * This class creates DPAD events from touchpad events.
Ben Pietrzak6f403a82012-10-03 14:54:07 -070032 *
Ben Pietrzakdc4b5032012-09-10 11:33:19 -070033 * @see ViewRootImpl
34 */
Ben Pietrzak05cb3632012-12-12 11:31:57 -080035
36//TODO: Make this class an internal class of ViewRootImpl.java
Dake Guf8739b92013-01-17 13:39:01 -080037class SimulatedDpad {
Ben Pietrzakdc4b5032012-09-10 11:33:19 -070038
Dake Guf8739b92013-01-17 13:39:01 -080039 private static final String TAG = "SimulatedDpad";
Ben Pietrzak6b1d9122012-11-07 15:07:18 -080040
Ben Pietrzak6f403a82012-10-03 14:54:07 -070041 // Maximum difference in milliseconds between the down and up of a touch
42 // event for it to be considered a tap
43 // TODO:Read this value from a configuration file
44 private static final int MAX_TAP_TIME = 250;
Ben Pietrzak29880222012-11-01 15:36:05 -070045 // Where the cutoff is for determining an edge swipe
46 private static final float EDGE_SWIPE_THRESHOLD = 0.9f;
Mita Yuned218c72012-12-06 17:18:25 -080047 private static final int MSG_FLICK = 313;
Ben Pietrzakffb5bde2012-11-12 10:11:52 -080048 // TODO: Pass touch slop from the input device
49 private static final int TOUCH_SLOP = 30;
Ben Pietrzak6f403a82012-10-03 14:54:07 -070050 // The position of the previous touchpad event
Ben Pietrzakdc4b5032012-09-10 11:33:19 -070051 private float mLastTouchpadXPosition;
52 private float mLastTouchpadYPosition;
Ben Pietrzak6f403a82012-10-03 14:54:07 -070053 // Where the touchpad was initially pressed
Ben Pietrzak2c914122012-09-26 11:59:29 -070054 private float mTouchpadEnterXPosition;
55 private float mTouchpadEnterYPosition;
Ben Pietrzak6f403a82012-10-03 14:54:07 -070056 // When the most recent ACTION_HOVER_ENTER occurred
Ben Pietrzakdc4b5032012-09-10 11:33:19 -070057 private long mLastTouchPadStartTimeMs = 0;
Ben Pietrzak6f403a82012-10-03 14:54:07 -070058 // When the most recent direction key was sent
59 private long mLastTouchPadKeySendTimeMs = 0;
60 // When the most recent touch event of any type occurred
61 private long mLastTouchPadEventTimeMs = 0;
Ben Pietrzak29880222012-11-01 15:36:05 -070062 // Did the swipe begin in a valid region
63 private boolean mEdgeSwipePossible;
Ben Pietrzakdc4b5032012-09-10 11:33:19 -070064
Ben Pietrzak05cb3632012-12-12 11:31:57 -080065 private final Context mContext;
66
Ben Pietrzak6f403a82012-10-03 14:54:07 -070067 // How quickly keys were sent;
68 private int mKeySendRateMs = 0;
69 private int mLastKeySent;
70 // Last movement in device screen pixels
71 private float mLastMoveX = 0;
72 private float mLastMoveY = 0;
73 // Offset from the initial touch. Gets reset as direction keys are sent.
74 private float mAccumulatedX;
75 private float mAccumulatedY;
76
77 // Change in position allowed during tap events
Ben Pietrzak2c914122012-09-26 11:59:29 -070078 private float mTouchSlop;
79 private float mTouchSlopSquared;
Ben Pietrzak6f403a82012-10-03 14:54:07 -070080 // Has the TouchSlop constraint been invalidated
Ben Pietrzak2c914122012-09-26 11:59:29 -070081 private boolean mAlwaysInTapRegion = true;
82
Mita Yuned218c72012-12-06 17:18:25 -080083 // Information from the most recent event.
84 // Used to determine what device sent the event during a fling.
85 private int mLastSource;
86 private int mLastMetaState;
87 private int mLastDeviceId;
Ben Pietrzak2c914122012-09-26 11:59:29 -070088
Ben Pietrzak6f403a82012-10-03 14:54:07 -070089 // TODO: Currently using screen dimensions tuned to a Galaxy Nexus, need to
90 // read this from a config file instead
91 private int mDistancePerTick;
92 private int mDistancePerTickSquared;
93 // Highest rate that the flinged events can occur at before dying out
94 private int mMaxRepeatDelay;
95 // The square of the minimum distance needed for a flick to register
96 private int mMinFlickDistanceSquared;
97 // How quickly the repeated events die off
98 private float mFlickDecay;
99
Dake Guf8739b92013-01-17 13:39:01 -0800100 public SimulatedDpad(Context context) {
Ben Pietrzak6f403a82012-10-03 14:54:07 -0700101 mDistancePerTick = SystemProperties.getInt("persist.vr_dist_tick", 64);
102 mDistancePerTickSquared = mDistancePerTick * mDistancePerTick;
103 mMaxRepeatDelay = SystemProperties.getInt("persist.vr_repeat_delay", 300);
104 mMinFlickDistanceSquared = SystemProperties.getInt("persist.vr_min_flick", 20);
105 mMinFlickDistanceSquared *= mMinFlickDistanceSquared;
106 mFlickDecay = Float.parseFloat(SystemProperties.get(
107 "persist.sys.vr_flick_decay", "1.3"));
Ben Pietrzakffb5bde2012-11-12 10:11:52 -0800108 mTouchSlop = TOUCH_SLOP;
Ben Pietrzak2c914122012-09-26 11:59:29 -0700109 mTouchSlopSquared = mTouchSlop * mTouchSlop;
Ben Pietrzak05cb3632012-12-12 11:31:57 -0800110
111 mContext = context;
Ben Pietrzak2c914122012-09-26 11:59:29 -0700112 }
113
Mita Yuned218c72012-12-06 17:18:25 -0800114 private final Handler mHandler = new Handler(true /*async*/) {
Ben Pietrzak6f403a82012-10-03 14:54:07 -0700115 @Override
Mita Yuned218c72012-12-06 17:18:25 -0800116 public void handleMessage(Message msg) {
117 switch (msg.what) {
118 case MSG_FLICK: {
119 final long time = SystemClock.uptimeMillis();
120 ViewRootImpl viewroot = (ViewRootImpl) msg.obj;
121 // Send the key
122 viewroot.enqueueInputEvent(new KeyEvent(time, time,
123 KeyEvent.ACTION_DOWN, msg.arg2, 0, mLastMetaState,
124 mLastDeviceId, 0, KeyEvent.FLAG_FALLBACK, mLastSource));
125 viewroot.enqueueInputEvent(new KeyEvent(time, time,
126 KeyEvent.ACTION_UP, msg.arg2, 0, mLastMetaState,
127 mLastDeviceId, 0, KeyEvent.FLAG_FALLBACK, mLastSource));
Ben Pietrzak6f403a82012-10-03 14:54:07 -0700128
Mita Yuned218c72012-12-06 17:18:25 -0800129 // Increase the delay by the decay factor and resend
130 final int delay = (int) Math.ceil(mFlickDecay * msg.arg1);
131 if (delay <= mMaxRepeatDelay) {
132 Message msgCopy = Message.obtain(msg);
133 msgCopy.arg1 = delay;
134 msgCopy.setAsynchronous(true);
135 mHandler.sendMessageDelayed(msgCopy, delay);
136 }
137 break;
138 }
Ben Pietrzak6f403a82012-10-03 14:54:07 -0700139 }
Ben Pietrzak6f403a82012-10-03 14:54:07 -0700140 }
Mita Yuned218c72012-12-06 17:18:25 -0800141 };
Ben Pietrzak6f403a82012-10-03 14:54:07 -0700142
Dake Guf8739b92013-01-17 13:39:01 -0800143 public void updateTouchPad(ViewRootImpl viewroot, MotionEvent event,
144 boolean synthesizeNewKeys) {
145 if (!synthesizeNewKeys) {
146 mHandler.removeMessages(MSG_FLICK);
147 }
Dake Gu9f811fd2013-02-01 15:54:22 -0800148 InputDevice device = event.getDevice();
149 if (device == null) {
150 return;
151 }
Ben Pietrzak6f403a82012-10-03 14:54:07 -0700152 // Store what time the touchpad event occurred
153 final long time = SystemClock.uptimeMillis();
Ben Pietrzakdc4b5032012-09-10 11:33:19 -0700154 switch (event.getAction()) {
Ben Pietrzakad6a9c92012-11-05 13:10:14 -0800155 case MotionEvent.ACTION_DOWN:
Ben Pietrzakdc4b5032012-09-10 11:33:19 -0700156 mLastTouchPadStartTimeMs = time;
Ben Pietrzak2c914122012-09-26 11:59:29 -0700157 mAlwaysInTapRegion = true;
158 mTouchpadEnterXPosition = event.getX();
159 mTouchpadEnterYPosition = event.getY();
Ben Pietrzak6f403a82012-10-03 14:54:07 -0700160 mAccumulatedX = 0;
161 mAccumulatedY = 0;
162 mLastMoveX = 0;
163 mLastMoveY = 0;
Dake Gu9f811fd2013-02-01 15:54:22 -0800164 if (device.getMotionRange(MotionEvent.AXIS_Y).getMax()
Ben Pietrzak29880222012-11-01 15:36:05 -0700165 * EDGE_SWIPE_THRESHOLD < event.getY()) {
166 // Did the swipe begin in a valid region
167 mEdgeSwipePossible = true;
168 }
Ben Pietrzak6f403a82012-10-03 14:54:07 -0700169 // Clear any flings
Dake Guf8739b92013-01-17 13:39:01 -0800170 if (synthesizeNewKeys) {
171 mHandler.removeMessages(MSG_FLICK);
172 }
Ben Pietrzakdc4b5032012-09-10 11:33:19 -0700173 break;
Ben Pietrzakad6a9c92012-11-05 13:10:14 -0800174 case MotionEvent.ACTION_MOVE:
Ben Pietrzak6f403a82012-10-03 14:54:07 -0700175 // Determine whether the move is slop or an intentional move
176 float deltaX = event.getX() - mTouchpadEnterXPosition;
177 float deltaY = event.getY() - mTouchpadEnterYPosition;
178 if (mTouchSlopSquared < deltaX * deltaX + deltaY * deltaY) {
Ben Pietrzak2c914122012-09-26 11:59:29 -0700179 mAlwaysInTapRegion = false;
180 }
Ben Pietrzak29880222012-11-01 15:36:05 -0700181 // Checks if the swipe has crossed the midpoint
182 // and if our swipe gesture is complete
Dake Gu9f811fd2013-02-01 15:54:22 -0800183 if (event.getY() < (device.getMotionRange(MotionEvent.AXIS_Y).getMax()
Ben Pietrzak29880222012-11-01 15:36:05 -0700184 * .5) && mEdgeSwipePossible) {
185 mEdgeSwipePossible = false;
Ben Pietrzak05cb3632012-12-12 11:31:57 -0800186
187 Intent intent =
188 ((SearchManager)mContext.getSystemService(Context.SEARCH_SERVICE))
189 .getAssistIntent(mContext, UserHandle.USER_CURRENT_OR_SELF);
190 if (intent != null) {
191 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
192 try {
193 mContext.startActivity(intent);
194 } catch (ActivityNotFoundException e){
195 Log.e(TAG, "Could not start search activity");
196 }
197 } else {
198 Log.e(TAG, "Could not find a search activity");
Ben Pietrzak6b1d9122012-11-07 15:07:18 -0800199 }
Ben Pietrzak29880222012-11-01 15:36:05 -0700200 }
Ben Pietrzak6f403a82012-10-03 14:54:07 -0700201 // Find the difference in position between the two most recent
202 // touchpad events
203 mLastMoveX = event.getX() - mLastTouchpadXPosition;
204 mLastMoveY = event.getY() - mLastTouchpadYPosition;
205 mAccumulatedX += mLastMoveX;
206 mAccumulatedY += mLastMoveY;
207 float mAccumulatedXSquared = mAccumulatedX * mAccumulatedX;
208 float mAccumulatedYSquared = mAccumulatedY * mAccumulatedY;
209 // Determine if we've moved far enough to send a key press
210 if (mAccumulatedXSquared > mDistancePerTickSquared ||
211 mAccumulatedYSquared > mDistancePerTickSquared) {
212 float dominantAxis;
213 float sign;
214 boolean isXAxis;
215 int key;
216 int repeatCount = 0;
217 // Determine dominant axis
218 if (mAccumulatedXSquared > mAccumulatedYSquared) {
219 dominantAxis = mAccumulatedX;
220 isXAxis = true;
221 } else {
222 dominantAxis = mAccumulatedY;
223 isXAxis = false;
224 }
225 // Determine sign of axis
226 sign = (dominantAxis > 0) ? 1 : -1;
227 // Determine key to send
228 if (isXAxis) {
229 key = (sign == 1) ? KeyEvent.KEYCODE_DPAD_RIGHT :
230 KeyEvent.KEYCODE_DPAD_LEFT;
231 } else {
232 key = (sign == 1) ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP;
233 }
234 // Send key until maximum distance constraint is satisfied
235 while (dominantAxis * dominantAxis > mDistancePerTickSquared) {
236 repeatCount++;
237 dominantAxis -= sign * mDistancePerTick;
Dake Guf8739b92013-01-17 13:39:01 -0800238 if (synthesizeNewKeys) {
239 viewroot.enqueueInputEvent(new KeyEvent(time, time,
240 KeyEvent.ACTION_DOWN, key, 0, event.getMetaState(),
241 event.getDeviceId(), 0, KeyEvent.FLAG_FALLBACK,
242 event.getSource()));
243 viewroot.enqueueInputEvent(new KeyEvent(time, time,
244 KeyEvent.ACTION_UP, key, 0, event.getMetaState(),
245 event.getDeviceId(), 0, KeyEvent.FLAG_FALLBACK,
246 event.getSource()));
247 }
Ben Pietrzak6f403a82012-10-03 14:54:07 -0700248 }
249 // Save new axis values
250 mAccumulatedX = isXAxis ? dominantAxis : 0;
251 mAccumulatedY = isXAxis ? 0 : dominantAxis;
252
253 mLastKeySent = key;
254 mKeySendRateMs = (int) ((time - mLastTouchPadKeySendTimeMs) / repeatCount);
255 mLastTouchPadKeySendTimeMs = time;
256 }
Ben Pietrzakdc4b5032012-09-10 11:33:19 -0700257 break;
Ben Pietrzakad6a9c92012-11-05 13:10:14 -0800258 case MotionEvent.ACTION_UP:
Ben Pietrzak6f403a82012-10-03 14:54:07 -0700259 if (time - mLastTouchPadStartTimeMs < MAX_TAP_TIME && mAlwaysInTapRegion) {
Dake Guf8739b92013-01-17 13:39:01 -0800260 if (synthesizeNewKeys) {
261 viewroot.enqueueInputEvent(new KeyEvent(mLastTouchPadStartTimeMs, time,
262 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, 0,
263 event.getMetaState(), event.getDeviceId(), 0,
264 KeyEvent.FLAG_FALLBACK, event.getSource()));
265 viewroot.enqueueInputEvent(new KeyEvent(mLastTouchPadStartTimeMs, time,
266 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, 0,
267 event.getMetaState(), event.getDeviceId(), 0,
268 KeyEvent.FLAG_FALLBACK, event.getSource()));
269 }
Ben Pietrzak6f403a82012-10-03 14:54:07 -0700270 } else {
271 float xMoveSquared = mLastMoveX * mLastMoveX;
272 float yMoveSquared = mLastMoveY * mLastMoveY;
273 // Determine whether the last gesture was a fling.
274 if (mMinFlickDistanceSquared <= xMoveSquared + yMoveSquared &&
275 time - mLastTouchPadEventTimeMs <= MAX_TAP_TIME &&
276 mKeySendRateMs <= mMaxRepeatDelay && mKeySendRateMs > 0) {
Mita Yuned218c72012-12-06 17:18:25 -0800277 mLastDeviceId = event.getDeviceId();
278 mLastSource = event.getSource();
279 mLastMetaState = event.getMetaState();
280
Dake Guf8739b92013-01-17 13:39:01 -0800281 if (synthesizeNewKeys) {
282 Message message = Message.obtain(mHandler, MSG_FLICK,
283 mKeySendRateMs, mLastKeySent, viewroot);
284 message.setAsynchronous(true);
285 mHandler.sendMessageDelayed(message, mKeySendRateMs);
286 }
Ben Pietrzak6f403a82012-10-03 14:54:07 -0700287 }
Ben Pietrzak2c914122012-09-26 11:59:29 -0700288 }
Ben Pietrzak29880222012-11-01 15:36:05 -0700289 mEdgeSwipePossible = false;
Ben Pietrzakdc4b5032012-09-10 11:33:19 -0700290 break;
291 }
Mita Yuned218c72012-12-06 17:18:25 -0800292
Ben Pietrzak6f403a82012-10-03 14:54:07 -0700293 // Store touch event position and time
294 mLastTouchPadEventTimeMs = time;
Ben Pietrzakdc4b5032012-09-10 11:33:19 -0700295 mLastTouchpadXPosition = event.getX();
296 mLastTouchpadYPosition = event.getY();
297 }
298}