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