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.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 
     64     InputLogicHandler() {
     65         mNonUIThreadHandler = null;
     66         mLatinIME = null;
     67         mInputLogic = null;
     68     }
     69 
     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 
     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.
     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
     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.
    106     public void onStartBatchInput() {
    107         synchronized (mLock) {
    108             mInBatchInput = true;
    109         }
    110     }
    111 
    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.
    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 
    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.
    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.
    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.
    211     public void updateTailBatchInput(final InputPointers batchPointers,
    212             final int sequenceNumber) {
    213         updateBatchInput(batchPointers, sequenceNumber, true /* isTailBatchInput */);
    214     }
    215 
    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