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