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.InputPointers; 25 import com.android.inputmethod.latin.LatinIME; 26 import com.android.inputmethod.latin.Suggest; 27 import com.android.inputmethod.latin.SuggestedWords; 28 import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback; 29 30 /** 31 * A helper to manage deferred tasks for the input logic. 32 */ 33 class 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