InputLogicHandler.java revision b8d764772b174cbd37354ffd0009bda56f223dc4
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.latin.inputlogic;
18
19import android.os.Handler;
20import android.os.HandlerThread;
21import android.os.Message;
22
23import com.android.inputmethod.compat.LooperCompatUtils;
24import com.android.inputmethod.latin.InputPointers;
25import com.android.inputmethod.latin.LatinIME;
26import com.android.inputmethod.latin.Suggest;
27import com.android.inputmethod.latin.SuggestedWords;
28import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
29
30/**
31 * A helper to manage deferred tasks for the input logic.
32 */
33class InputLogicHandler implements Handler.Callback {
34    final Handler mNonUIThreadHandler;
35    // TODO: remove this reference.
36    final LatinIME mLatinIME;
37    final InputLogic mInputLogic;
38    private final Object mLock = new Object();
39    private boolean mInBatchInput; // synchronized using {@link #mLock}.
40
41    private static final int MSG_GET_SUGGESTED_WORDS = 1;
42
43    // A handler that never does anything. This is used for cases where events come before anything
44    // is initialized, though probably only the monkey can actually do this.
45    public static final InputLogicHandler NULL_HANDLER = new InputLogicHandler() {
46        @Override
47        public void reset() {}
48        @Override
49        public boolean handleMessage(final Message msg) { return true; }
50        @Override
51        public void onStartBatchInput() {}
52        @Override
53        public void onUpdateBatchInput(final InputPointers batchPointers,
54                final int sequenceNumber) {}
55        @Override
56        public void onCancelBatchInput() {}
57        @Override
58        public void updateTailBatchInput(final InputPointers batchPointers,
59                final int sequenceNumber) {}
60        @Override
61        public void getSuggestedWords(final int sessionId, final int sequenceNumber,
62                final OnGetSuggestedWordsCallback callback) {}
63    };
64
65    private InputLogicHandler() {
66        mNonUIThreadHandler = null;
67        mLatinIME = null;
68        mInputLogic = null;
69    }
70
71    public InputLogicHandler(final LatinIME latinIME, final InputLogic inputLogic) {
72        final HandlerThread handlerThread = new HandlerThread(
73                InputLogicHandler.class.getSimpleName());
74        handlerThread.start();
75        mNonUIThreadHandler = new Handler(handlerThread.getLooper(), this);
76        mLatinIME = latinIME;
77        mInputLogic = inputLogic;
78    }
79
80    public void reset() {
81        mNonUIThreadHandler.removeCallbacksAndMessages(null);
82    }
83
84    // In unit tests, we create several instances of LatinIME, which results in several instances
85    // of InputLogicHandler. To avoid these handlers lingering, we call this.
86    public void destroy() {
87        LooperCompatUtils.quitSafely(mNonUIThreadHandler.getLooper());
88    }
89
90    /**
91     * Handle a message.
92     * @see android.os.Handler.Callback#handleMessage(android.os.Message)
93     */
94    // Called on the Non-UI handler thread by the Handler code.
95    @Override
96    public boolean handleMessage(final Message msg) {
97        switch (msg.what) {
98            case MSG_GET_SUGGESTED_WORDS:
99                mLatinIME.getSuggestedWords(msg.arg1 /* inputStyle */,
100                        msg.arg2 /* sequenceNumber */, (OnGetSuggestedWordsCallback) msg.obj);
101                break;
102        }
103        return true;
104    }
105
106    // Called on the UI thread by InputLogic.
107    public void onStartBatchInput() {
108        synchronized (mLock) {
109            mInBatchInput = true;
110        }
111    }
112
113    public boolean isInBatchInput() {
114        return mInBatchInput;
115    }
116
117    /**
118     * Fetch suggestions corresponding to an update of a batch input.
119     * @param batchPointers the updated pointers, including the part that was passed last time.
120     * @param sequenceNumber the sequence number associated with this batch input.
121     * @param isTailBatchInput true if this is the end of a batch input, false if it's an update.
122     */
123    // This method can be called from any thread and will see to it that the correct threads
124    // are used for parts that require it. This method will send a message to the Non-UI handler
125    // thread to pull suggestions, and get the inlined callback to get called on the Non-UI
126    // handler thread. If this is the end of a batch input, the callback will then proceed to
127    // send a message to the UI handler in LatinIME so that showing suggestions can be done on
128    // the UI thread.
129    private void updateBatchInput(final InputPointers batchPointers,
130            final int sequenceNumber, final boolean isTailBatchInput) {
131        synchronized (mLock) {
132            if (!mInBatchInput) {
133                // Batch input has ended or canceled while the message was being delivered.
134                return;
135            }
136            mInputLogic.mWordComposer.setBatchInputPointers(batchPointers);
137            getSuggestedWords(isTailBatchInput ? SuggestedWords.INPUT_STYLE_TAIL_BATCH
138                    : SuggestedWords.INPUT_STYLE_UPDATE_BATCH, sequenceNumber,
139                    new OnGetSuggestedWordsCallback() {
140                        @Override
141                        public void onGetSuggestedWords(SuggestedWords suggestedWords) {
142                            // We're now inside the callback. This always runs on the Non-UI thread,
143                            // no matter what thread updateBatchInput was originally called on.
144                            if (suggestedWords.isEmpty()) {
145                                // Use old suggestions if we don't have any new ones.
146                                // Previous suggestions are found in InputLogic#mSuggestedWords.
147                                // Since these are the most recent ones and we just recomputed
148                                // new ones to update them, then the previous ones are there.
149                                suggestedWords = mInputLogic.mSuggestedWords;
150                            }
151                            mLatinIME.mHandler.showGesturePreviewAndSuggestionStrip(suggestedWords,
152                                    isTailBatchInput /* dismissGestureFloatingPreviewText */);
153                            if (isTailBatchInput) {
154                                mInBatchInput = false;
155                                // The following call schedules onEndBatchInputInternal
156                                // to be called on the UI thread.
157                                mLatinIME.mHandler.showTailBatchInputResult(suggestedWords);
158                            }
159                        }
160                    });
161        }
162    }
163
164    /**
165     * Update a batch input.
166     *
167     * This fetches suggestions and updates the suggestion strip and the floating text preview.
168     *
169     * @param batchPointers the updated batch pointers.
170     * @param sequenceNumber the sequence number associated with this batch input.
171     */
172    // Called on the UI thread by InputLogic.
173    public void onUpdateBatchInput(final InputPointers batchPointers,
174            final int sequenceNumber) {
175        updateBatchInput(batchPointers, sequenceNumber, false /* isTailBatchInput */);
176    }
177
178    /**
179     * Cancel a batch input.
180     *
181     * Note that as opposed to updateTailBatchInput, we do the UI side of this immediately on the
182     * same thread, rather than get this to call a method in LatinIME. This is because
183     * canceling a batch input does not necessitate the long operation of pulling suggestions.
184     */
185    // Called on the UI thread by InputLogic.
186    public void onCancelBatchInput() {
187        synchronized (mLock) {
188            mInBatchInput = false;
189        }
190    }
191
192    /**
193     * Trigger an update for a tail batch input.
194     *
195     * A tail batch input is the last update for a gesture, the one that is triggered after the
196     * user lifts their finger. This method schedules fetching suggestions on the non-UI thread,
197     * then when the suggestions are computed it comes back on the UI thread to update the
198     * suggestion strip, commit the first suggestion, and dismiss the floating text preview.
199     *
200     * @param batchPointers the updated batch pointers.
201     * @param sequenceNumber the sequence number associated with this batch input.
202     */
203    // Called on the UI thread by InputLogic.
204    public void updateTailBatchInput(final InputPointers batchPointers,
205            final int sequenceNumber) {
206        updateBatchInput(batchPointers, sequenceNumber, true /* isTailBatchInput */);
207    }
208
209    public void getSuggestedWords(final int inputStyle, final int sequenceNumber,
210            final OnGetSuggestedWordsCallback callback) {
211        mNonUIThreadHandler.obtainMessage(
212                MSG_GET_SUGGESTED_WORDS, inputStyle, sequenceNumber, callback).sendToTarget();
213    }
214}
215