OSDN Git Service

am ad17523d: add warning for Windows users and fix typos
[android-x86/frameworks-base.git] / core / java / android / view / SimulatedDpad.java
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 android.view;
18
19 import android.app.SearchManager;
20 import android.content.ActivityNotFoundException;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.os.Handler;
24 import android.os.Message;
25 import android.os.SystemClock;
26 import android.os.SystemProperties;
27 import android.os.UserHandle;
28 import android.util.Log;
29
30 /**
31  * This class creates DPAD events from touchpad events.
32  * 
33  * @see ViewRootImpl
34  */
35
36 //TODO: Make this class an internal class of ViewRootImpl.java
37 class SimulatedDpad {
38
39     private static final String TAG = "SimulatedDpad";
40
41     // 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;
45     // Where the cutoff is for determining an edge swipe
46     private static final float EDGE_SWIPE_THRESHOLD = 0.9f;
47     private static final int MSG_FLICK = 313;
48     // TODO: Pass touch slop from the input device
49     private static final int TOUCH_SLOP = 30;
50     // The position of the previous touchpad event
51     private float mLastTouchpadXPosition;
52     private float mLastTouchpadYPosition;
53     // Where the touchpad was initially pressed
54     private float mTouchpadEnterXPosition;
55     private float mTouchpadEnterYPosition;
56     // When the most recent ACTION_HOVER_ENTER occurred
57     private long mLastTouchPadStartTimeMs = 0;
58     // 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;
62     // Did the swipe begin in a valid region
63     private boolean mEdgeSwipePossible;
64
65     private final Context mContext;
66
67     // 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
78     private float mTouchSlop;
79     private float mTouchSlopSquared;
80     // Has the TouchSlop constraint been invalidated
81     private boolean mAlwaysInTapRegion = true;
82
83     // 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;
88
89     // 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
100     public SimulatedDpad(Context context) {
101         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"));
108         mTouchSlop = TOUCH_SLOP;
109         mTouchSlopSquared = mTouchSlop * mTouchSlop;
110
111         mContext = context;
112     }
113
114     private final Handler mHandler = new Handler(true /*async*/) {
115             @Override
116         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));
128
129                     // 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                 }
139             }
140         }
141     };
142
143     public void updateTouchPad(ViewRootImpl viewroot, MotionEvent event,
144             boolean synthesizeNewKeys) {
145         if (!synthesizeNewKeys) {
146             mHandler.removeMessages(MSG_FLICK);
147         }
148         InputDevice device = event.getDevice();
149         if (device == null) {
150             return;
151         }
152         // Store what time the touchpad event occurred
153         final long time = SystemClock.uptimeMillis();
154         switch (event.getAction()) {
155             case MotionEvent.ACTION_DOWN:
156                 mLastTouchPadStartTimeMs = time;
157                 mAlwaysInTapRegion = true;
158                 mTouchpadEnterXPosition = event.getX();
159                 mTouchpadEnterYPosition = event.getY();
160                 mAccumulatedX = 0;
161                 mAccumulatedY = 0;
162                 mLastMoveX = 0;
163                 mLastMoveY = 0;
164                 if (device.getMotionRange(MotionEvent.AXIS_Y).getMax()
165                         * EDGE_SWIPE_THRESHOLD < event.getY()) {
166                     // Did the swipe begin in a valid region
167                     mEdgeSwipePossible = true;
168                 }
169                 // Clear any flings
170                 if (synthesizeNewKeys) {
171                     mHandler.removeMessages(MSG_FLICK);
172                 }
173                 break;
174             case MotionEvent.ACTION_MOVE:
175                 // 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) {
179                     mAlwaysInTapRegion = false;
180                 }
181                 // Checks if the swipe has crossed the midpoint
182                 // and if our swipe gesture is complete
183                 if (event.getY() < (device.getMotionRange(MotionEvent.AXIS_Y).getMax()
184                         * .5) && mEdgeSwipePossible) {
185                     mEdgeSwipePossible = false;
186
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");
199                     }
200                 }
201                 // 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;
238                         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                         }
248                     }
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                 }
257                 break;
258             case MotionEvent.ACTION_UP:
259                 if (time - mLastTouchPadStartTimeMs < MAX_TAP_TIME && mAlwaysInTapRegion) {
260                     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                     }
270                 } 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) {
277                         mLastDeviceId = event.getDeviceId();
278                         mLastSource = event.getSource();
279                         mLastMetaState = event.getMetaState();
280
281                         if (synthesizeNewKeys) {
282                             Message message = Message.obtain(mHandler, MSG_FLICK,
283                                     mKeySendRateMs, mLastKeySent, viewroot);
284                             message.setAsynchronous(true);
285                             mHandler.sendMessageDelayed(message, mKeySendRateMs);
286                         }
287                     }
288                 }
289                 mEdgeSwipePossible = false;
290                 break;
291         }
292
293         // Store touch event position and time
294         mLastTouchPadEventTimeMs = time;
295         mLastTouchpadXPosition = event.getX();
296         mLastTouchpadYPosition = event.getY();
297     }
298 }