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
17package com.android.inputmethod.keyboard.internal;
18
19import android.os.Message;
20import android.os.SystemClock;
21import android.view.ViewConfiguration;
22
23import com.android.inputmethod.keyboard.Key;
24import com.android.inputmethod.keyboard.PointerTracker;
25import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
26import com.android.inputmethod.keyboard.internal.TimerHandler.Callbacks;
27import com.android.inputmethod.latin.Constants;
28import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
29
30// TODO: Separate this class into KeyTimerHandler and BatchInputTimerHandler or so.
31public final class TimerHandler extends LeakGuardHandlerWrapper<Callbacks> implements TimerProxy {
32    public interface Callbacks {
33        public void startWhileTypingFadeinAnimation();
34        public void startWhileTypingFadeoutAnimation();
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
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
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
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
92    private void cancelKeyRepeatTimerOf(final PointerTracker tracker) {
93        removeMessages(MSG_REPEAT_KEY, tracker);
94    }
95
96    public void cancelKeyRepeatTimers() {
97        removeMessages(MSG_REPEAT_KEY);
98    }
99
100    // TODO: Suppress layout changes in key repeat mode
101    public boolean isInKeyRepeat() {
102        return hasMessages(MSG_REPEAT_KEY);
103    }
104
105    @Override
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
119    public void cancelLongPressTimerOf(final PointerTracker tracker) {
120        removeMessages(MSG_LONGPRESS_KEY, tracker);
121        removeMessages(MSG_LONGPRESS_SHIFT_KEY, tracker);
122    }
123
124    @Override
125    public void cancelLongPressShiftKeyTimers() {
126        removeMessages(MSG_LONGPRESS_SHIFT_KEY);
127    }
128
129    public void cancelLongPressTimers() {
130        removeMessages(MSG_LONGPRESS_KEY);
131        removeMessages(MSG_LONGPRESS_SHIFT_KEY);
132    }
133
134    @Override
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
165    public boolean isTypingState() {
166        return hasMessages(MSG_TYPING_STATE_EXPIRED);
167    }
168
169    @Override
170    public void startDoubleTapShiftKeyTimer() {
171        sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP_SHIFT_KEY),
172                ViewConfiguration.getDoubleTapTimeout());
173    }
174
175    @Override
176    public void cancelDoubleTapShiftKeyTimer() {
177        removeMessages(MSG_DOUBLE_TAP_SHIFT_KEY);
178    }
179
180    @Override
181    public boolean isInDoubleTapShiftKeyTimeout() {
182        return hasMessages(MSG_DOUBLE_TAP_SHIFT_KEY);
183    }
184
185    @Override
186    public void cancelKeyTimersOf(final PointerTracker tracker) {
187        cancelKeyRepeatTimerOf(tracker);
188        cancelLongPressTimerOf(tracker);
189    }
190
191    public void cancelAllKeyTimers() {
192        cancelKeyRepeatTimers();
193        cancelLongPressTimers();
194    }
195
196    @Override
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
207    public void cancelUpdateBatchInputTimer(final PointerTracker tracker) {
208        removeMessages(MSG_UPDATE_BATCH_INPUT, tracker);
209    }
210
211    @Override
212    public void cancelAllUpdateBatchInputTimers() {
213        removeMessages(MSG_UPDATE_BATCH_INPUT);
214    }
215
216    public void cancelAllMessages() {
217        cancelAllKeyTimers();
218        cancelAllUpdateBatchInputTimers();
219    }
220}
221