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