1 /*
2  * Copyright (C) 2013 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.inputmethod.keyboard.internal;
18 
19 import android.os.Message;
20 import android.os.SystemClock;
21 import android.view.ViewConfiguration;
22 
23 import com.android.inputmethod.keyboard.Key;
24 import com.android.inputmethod.keyboard.PointerTracker;
25 import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
26 import com.android.inputmethod.keyboard.internal.TimerHandler.Callbacks;
27 import com.android.inputmethod.latin.Constants;
28 import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
29 
30 // TODO: Separate this class into KeyTimerHandler and BatchInputTimerHandler or so.
31 public final class TimerHandler extends LeakGuardHandlerWrapper<Callbacks> implements TimerProxy {
32     public interface Callbacks {
startWhileTypingFadeinAnimation()33         public void startWhileTypingFadeinAnimation();
startWhileTypingFadeoutAnimation()34         public void startWhileTypingFadeoutAnimation();
onLongPress(PointerTracker tracker)35         public void onLongPress(PointerTracker tracker);
36     }
37 
38     private static final int MSG_TYPING_STATE_EXPIRED = 0;
39     private static final int MSG_REPEAT_KEY = 1;
40     private static final int MSG_LONGPRESS_KEY = 2;
41     private static final int MSG_LONGPRESS_SHIFT_KEY = 3;
42     private static final int MSG_DOUBLE_TAP_SHIFT_KEY = 4;
43     private static final int MSG_UPDATE_BATCH_INPUT = 5;
44 
45     private final int mIgnoreAltCodeKeyTimeout;
46     private final int mGestureRecognitionUpdateTime;
47 
TimerHandler(final Callbacks ownerInstance, final int ignoreAltCodeKeyTimeout, final int gestureRecognitionUpdateTime)48     public TimerHandler(final Callbacks ownerInstance, final int ignoreAltCodeKeyTimeout,
49             final int gestureRecognitionUpdateTime) {
50         super(ownerInstance);
51         mIgnoreAltCodeKeyTimeout = ignoreAltCodeKeyTimeout;
52         mGestureRecognitionUpdateTime = gestureRecognitionUpdateTime;
53     }
54 
55     @Override
handleMessage(final Message msg)56     public void handleMessage(final Message msg) {
57         final Callbacks callbacks = getOwnerInstance();
58         if (callbacks == null) {
59             return;
60         }
61         final PointerTracker tracker = (PointerTracker) msg.obj;
62         switch (msg.what) {
63         case MSG_TYPING_STATE_EXPIRED:
64             callbacks.startWhileTypingFadeinAnimation();
65             break;
66         case MSG_REPEAT_KEY:
67             tracker.onKeyRepeat(msg.arg1 /* code */, msg.arg2 /* repeatCount */);
68             break;
69         case MSG_LONGPRESS_KEY:
70         case MSG_LONGPRESS_SHIFT_KEY:
71             cancelLongPressTimers();
72             callbacks.onLongPress(tracker);
73             break;
74         case MSG_UPDATE_BATCH_INPUT:
75             tracker.updateBatchInputByTimer(SystemClock.uptimeMillis());
76             startUpdateBatchInputTimer(tracker);
77             break;
78         }
79     }
80 
81     @Override
startKeyRepeatTimerOf(final PointerTracker tracker, final int repeatCount, final int delay)82     public void startKeyRepeatTimerOf(final PointerTracker tracker, final int repeatCount,
83             final int delay) {
84         final Key key = tracker.getKey();
85         if (key == null || delay == 0) {
86             return;
87         }
88         sendMessageDelayed(
89                 obtainMessage(MSG_REPEAT_KEY, key.getCode(), repeatCount, tracker), delay);
90     }
91 
cancelKeyRepeatTimerOf(final PointerTracker tracker)92     private void cancelKeyRepeatTimerOf(final PointerTracker tracker) {
93         removeMessages(MSG_REPEAT_KEY, tracker);
94     }
95 
cancelKeyRepeatTimers()96     public void cancelKeyRepeatTimers() {
97         removeMessages(MSG_REPEAT_KEY);
98     }
99 
100     // TODO: Suppress layout changes in key repeat mode
isInKeyRepeat()101     public boolean isInKeyRepeat() {
102         return hasMessages(MSG_REPEAT_KEY);
103     }
104 
105     @Override
startLongPressTimerOf(final PointerTracker tracker, final int delay)106     public void startLongPressTimerOf(final PointerTracker tracker, final int delay) {
107         final Key key = tracker.getKey();
108         if (key == null) {
109             return;
110         }
111         // Use a separate message id for long pressing shift key, because long press shift key
112         // timers should be canceled when other key is pressed.
113         final int messageId = (key.getCode() == Constants.CODE_SHIFT)
114                 ? MSG_LONGPRESS_SHIFT_KEY : MSG_LONGPRESS_KEY;
115         sendMessageDelayed(obtainMessage(messageId, tracker), delay);
116     }
117 
118     @Override
cancelLongPressTimerOf(final PointerTracker tracker)119     public void cancelLongPressTimerOf(final PointerTracker tracker) {
120         removeMessages(MSG_LONGPRESS_KEY, tracker);
121         removeMessages(MSG_LONGPRESS_SHIFT_KEY, tracker);
122     }
123 
124     @Override
cancelLongPressShiftKeyTimers()125     public void cancelLongPressShiftKeyTimers() {
126         removeMessages(MSG_LONGPRESS_SHIFT_KEY);
127     }
128 
cancelLongPressTimers()129     public void cancelLongPressTimers() {
130         removeMessages(MSG_LONGPRESS_KEY);
131         removeMessages(MSG_LONGPRESS_SHIFT_KEY);
132     }
133 
134     @Override
startTypingStateTimer(final Key typedKey)135     public void startTypingStateTimer(final Key typedKey) {
136         if (typedKey.isModifier() || typedKey.altCodeWhileTyping()) {
137             return;
138         }
139 
140         final boolean isTyping = isTypingState();
141         removeMessages(MSG_TYPING_STATE_EXPIRED);
142         final Callbacks callbacks = getOwnerInstance();
143         if (callbacks == null) {
144             return;
145         }
146 
147         // When user hits the space or the enter key, just cancel the while-typing timer.
148         final int typedCode = typedKey.getCode();
149         if (typedCode == Constants.CODE_SPACE || typedCode == Constants.CODE_ENTER) {
150             if (isTyping) {
151                 callbacks.startWhileTypingFadeinAnimation();
152             }
153             return;
154         }
155 
156         sendMessageDelayed(
157                 obtainMessage(MSG_TYPING_STATE_EXPIRED), mIgnoreAltCodeKeyTimeout);
158         if (isTyping) {
159             return;
160         }
161         callbacks.startWhileTypingFadeoutAnimation();
162     }
163 
164     @Override
isTypingState()165     public boolean isTypingState() {
166         return hasMessages(MSG_TYPING_STATE_EXPIRED);
167     }
168 
169     @Override
startDoubleTapShiftKeyTimer()170     public void startDoubleTapShiftKeyTimer() {
171         sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP_SHIFT_KEY),
172                 ViewConfiguration.getDoubleTapTimeout());
173     }
174 
175     @Override
cancelDoubleTapShiftKeyTimer()176     public void cancelDoubleTapShiftKeyTimer() {
177         removeMessages(MSG_DOUBLE_TAP_SHIFT_KEY);
178     }
179 
180     @Override
isInDoubleTapShiftKeyTimeout()181     public boolean isInDoubleTapShiftKeyTimeout() {
182         return hasMessages(MSG_DOUBLE_TAP_SHIFT_KEY);
183     }
184 
185     @Override
cancelKeyTimersOf(final PointerTracker tracker)186     public void cancelKeyTimersOf(final PointerTracker tracker) {
187         cancelKeyRepeatTimerOf(tracker);
188         cancelLongPressTimerOf(tracker);
189     }
190 
cancelAllKeyTimers()191     public void cancelAllKeyTimers() {
192         cancelKeyRepeatTimers();
193         cancelLongPressTimers();
194     }
195 
196     @Override
startUpdateBatchInputTimer(final PointerTracker tracker)197     public void startUpdateBatchInputTimer(final PointerTracker tracker) {
198         if (mGestureRecognitionUpdateTime <= 0) {
199             return;
200         }
201         removeMessages(MSG_UPDATE_BATCH_INPUT, tracker);
202         sendMessageDelayed(obtainMessage(MSG_UPDATE_BATCH_INPUT, tracker),
203                 mGestureRecognitionUpdateTime);
204     }
205 
206     @Override
cancelUpdateBatchInputTimer(final PointerTracker tracker)207     public void cancelUpdateBatchInputTimer(final PointerTracker tracker) {
208         removeMessages(MSG_UPDATE_BATCH_INPUT, tracker);
209     }
210 
211     @Override
cancelAllUpdateBatchInputTimers()212     public void cancelAllUpdateBatchInputTimers() {
213         removeMessages(MSG_UPDATE_BATCH_INPUT);
214     }
215 
cancelAllMessages()216     public void cancelAllMessages() {
217         cancelAllKeyTimers();
218         cancelAllUpdateBatchInputTimers();
219     }
220 }
221