Home | History | Annotate | Download | only in inputlogic
      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