Home | History | Annotate | Download | only in inputmethodservice
      1 /*
      2  * Copyright (C) 2008 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 android.inputmethodservice;
     18 
     19 import android.annotation.BinderThread;
     20 import android.annotation.MainThread;
     21 import android.content.Context;
     22 import android.content.pm.PackageManager;
     23 import android.os.Binder;
     24 import android.os.IBinder;
     25 import android.os.Message;
     26 import android.os.RemoteException;
     27 import android.os.ResultReceiver;
     28 import android.util.Log;
     29 import android.view.InputChannel;
     30 import android.view.inputmethod.EditorInfo;
     31 import android.view.inputmethod.InputBinding;
     32 import android.view.inputmethod.InputConnection;
     33 import android.view.inputmethod.InputConnectionInspector;
     34 import android.view.inputmethod.InputMethod;
     35 import android.view.inputmethod.InputMethodSession;
     36 import android.view.inputmethod.InputMethodSubtype;
     37 
     38 import com.android.internal.os.HandlerCaller;
     39 import com.android.internal.os.SomeArgs;
     40 import com.android.internal.view.IInputContext;
     41 import com.android.internal.view.IInputMethod;
     42 import com.android.internal.view.IInputMethodSession;
     43 import com.android.internal.view.IInputSessionCallback;
     44 import com.android.internal.view.InputConnectionWrapper;
     45 
     46 import java.io.FileDescriptor;
     47 import java.io.PrintWriter;
     48 import java.lang.ref.WeakReference;
     49 import java.util.concurrent.CountDownLatch;
     50 import java.util.concurrent.TimeUnit;
     51 import java.util.concurrent.atomic.AtomicBoolean;
     52 
     53 /**
     54  * Implements the internal IInputMethod interface to convert incoming calls
     55  * on to it back to calls on the public InputMethod interface, scheduling
     56  * them on the main thread of the process.
     57  */
     58 class IInputMethodWrapper extends IInputMethod.Stub
     59         implements HandlerCaller.Callback {
     60     private static final String TAG = "InputMethodWrapper";
     61 
     62     private static final int DO_DUMP = 1;
     63     private static final int DO_ATTACH_TOKEN = 10;
     64     private static final int DO_SET_INPUT_CONTEXT = 20;
     65     private static final int DO_UNSET_INPUT_CONTEXT = 30;
     66     private static final int DO_START_INPUT = 32;
     67     private static final int DO_CREATE_SESSION = 40;
     68     private static final int DO_SET_SESSION_ENABLED = 45;
     69     private static final int DO_REVOKE_SESSION = 50;
     70     private static final int DO_SHOW_SOFT_INPUT = 60;
     71     private static final int DO_HIDE_SOFT_INPUT = 70;
     72     private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80;
     73 
     74     final WeakReference<AbstractInputMethodService> mTarget;
     75     final Context mContext;
     76     final HandlerCaller mCaller;
     77     final WeakReference<InputMethod> mInputMethod;
     78     final int mTargetSdkVersion;
     79 
     80     /**
     81      * This is not {@null} only between {@link #bindInput(InputBinding)} and {@link #unbindInput()}
     82      * so that {@link InputConnectionWrapper} can query if {@link #unbindInput()} has already been
     83      * called or not, mainly to avoid unnecessary blocking operations.
     84      *
     85      * <p>This field must be set and cleared only from the binder thread(s), where the system
     86      * guarantees that {@link #bindInput(InputBinding)},
     87      * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean)}, and
     88      * {@link #unbindInput()} are called with the same order as the original calls
     89      * in {@link com.android.server.InputMethodManagerService}.  See {@link IBinder#FLAG_ONEWAY}
     90      * for detailed semantics.</p>
     91      */
     92     AtomicBoolean mIsUnbindIssued = null;
     93 
     94     // NOTE: we should have a cache of these.
     95     static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
     96         final Context mContext;
     97         final InputChannel mChannel;
     98         final IInputSessionCallback mCb;
     99 
    100         InputMethodSessionCallbackWrapper(Context context, InputChannel channel,
    101                 IInputSessionCallback cb) {
    102             mContext = context;
    103             mChannel = channel;
    104             mCb = cb;
    105         }
    106 
    107         @Override
    108         public void sessionCreated(InputMethodSession session) {
    109             try {
    110                 if (session != null) {
    111                     IInputMethodSessionWrapper wrap =
    112                             new IInputMethodSessionWrapper(mContext, session, mChannel);
    113                     mCb.sessionCreated(wrap);
    114                 } else {
    115                     if (mChannel != null) {
    116                         mChannel.dispose();
    117                     }
    118                     mCb.sessionCreated(null);
    119                 }
    120             } catch (RemoteException e) {
    121             }
    122         }
    123     }
    124 
    125     public IInputMethodWrapper(AbstractInputMethodService context, InputMethod inputMethod) {
    126         mTarget = new WeakReference<>(context);
    127         mContext = context.getApplicationContext();
    128         mCaller = new HandlerCaller(mContext, null, this, true /*asyncHandler*/);
    129         mInputMethod = new WeakReference<>(inputMethod);
    130         mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
    131     }
    132 
    133     @MainThread
    134     @Override
    135     public void executeMessage(Message msg) {
    136         InputMethod inputMethod = mInputMethod.get();
    137         // Need a valid reference to the inputMethod for everything except a dump.
    138         if (inputMethod == null && msg.what != DO_DUMP) {
    139             Log.w(TAG, "Input method reference was null, ignoring message: " + msg.what);
    140             return;
    141         }
    142 
    143         switch (msg.what) {
    144             case DO_DUMP: {
    145                 AbstractInputMethodService target = mTarget.get();
    146                 if (target == null) {
    147                     return;
    148                 }
    149                 SomeArgs args = (SomeArgs)msg.obj;
    150                 try {
    151                     target.dump((FileDescriptor)args.arg1,
    152                             (PrintWriter)args.arg2, (String[])args.arg3);
    153                 } catch (RuntimeException e) {
    154                     ((PrintWriter)args.arg2).println("Exception: " + e);
    155                 }
    156                 synchronized (args.arg4) {
    157                     ((CountDownLatch)args.arg4).countDown();
    158                 }
    159                 args.recycle();
    160                 return;
    161             }
    162 
    163             case DO_ATTACH_TOKEN: {
    164                 inputMethod.attachToken((IBinder)msg.obj);
    165                 return;
    166             }
    167             case DO_SET_INPUT_CONTEXT: {
    168                 inputMethod.bindInput((InputBinding)msg.obj);
    169                 return;
    170             }
    171             case DO_UNSET_INPUT_CONTEXT:
    172                 inputMethod.unbindInput();
    173                 return;
    174             case DO_START_INPUT: {
    175                 final SomeArgs args = (SomeArgs) msg.obj;
    176                 final int missingMethods = msg.arg1;
    177                 final boolean restarting = msg.arg2 != 0;
    178                 final IBinder startInputToken = (IBinder) args.arg1;
    179                 final IInputContext inputContext = (IInputContext) args.arg2;
    180                 final EditorInfo info = (EditorInfo) args.arg3;
    181                 final AtomicBoolean isUnbindIssued = (AtomicBoolean) args.arg4;
    182                 final InputConnection ic = inputContext != null
    183                         ? new InputConnectionWrapper(
    184                                 mTarget, inputContext, missingMethods, isUnbindIssued) : null;
    185                 info.makeCompatible(mTargetSdkVersion);
    186                 inputMethod.dispatchStartInputWithToken(ic, info, restarting /* restarting */,
    187                         startInputToken);
    188                 args.recycle();
    189                 return;
    190             }
    191             case DO_CREATE_SESSION: {
    192                 SomeArgs args = (SomeArgs)msg.obj;
    193                 inputMethod.createSession(new InputMethodSessionCallbackWrapper(
    194                         mContext, (InputChannel)args.arg1,
    195                         (IInputSessionCallback)args.arg2));
    196                 args.recycle();
    197                 return;
    198             }
    199             case DO_SET_SESSION_ENABLED:
    200                 inputMethod.setSessionEnabled((InputMethodSession)msg.obj,
    201                         msg.arg1 != 0);
    202                 return;
    203             case DO_REVOKE_SESSION:
    204                 inputMethod.revokeSession((InputMethodSession)msg.obj);
    205                 return;
    206             case DO_SHOW_SOFT_INPUT:
    207                 inputMethod.showSoftInput(msg.arg1, (ResultReceiver)msg.obj);
    208                 return;
    209             case DO_HIDE_SOFT_INPUT:
    210                 inputMethod.hideSoftInput(msg.arg1, (ResultReceiver)msg.obj);
    211                 return;
    212             case DO_CHANGE_INPUTMETHOD_SUBTYPE:
    213                 inputMethod.changeInputMethodSubtype((InputMethodSubtype)msg.obj);
    214                 return;
    215         }
    216         Log.w(TAG, "Unhandled message code: " + msg.what);
    217     }
    218 
    219     @BinderThread
    220     @Override
    221     protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
    222         AbstractInputMethodService target = mTarget.get();
    223         if (target == null) {
    224             return;
    225         }
    226         if (target.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
    227                 != PackageManager.PERMISSION_GRANTED) {
    228 
    229             fout.println("Permission Denial: can't dump InputMethodManager from from pid="
    230                     + Binder.getCallingPid()
    231                     + ", uid=" + Binder.getCallingUid());
    232             return;
    233         }
    234 
    235         CountDownLatch latch = new CountDownLatch(1);
    236         mCaller.executeOrSendMessage(mCaller.obtainMessageOOOO(DO_DUMP,
    237                 fd, fout, args, latch));
    238         try {
    239             if (!latch.await(5, TimeUnit.SECONDS)) {
    240                 fout.println("Timeout waiting for dump");
    241             }
    242         } catch (InterruptedException e) {
    243             fout.println("Interrupted waiting for dump");
    244         }
    245     }
    246 
    247     @BinderThread
    248     @Override
    249     public void attachToken(IBinder token) {
    250         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_ATTACH_TOKEN, token));
    251     }
    252 
    253     @BinderThread
    254     @Override
    255     public void bindInput(InputBinding binding) {
    256         if (mIsUnbindIssued != null) {
    257             Log.e(TAG, "bindInput must be paired with unbindInput.");
    258         }
    259         mIsUnbindIssued = new AtomicBoolean();
    260         // This IInputContext is guaranteed to implement all the methods.
    261         final int missingMethodFlags = 0;
    262         InputConnection ic = new InputConnectionWrapper(mTarget,
    263                 IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags,
    264                 mIsUnbindIssued);
    265         InputBinding nu = new InputBinding(ic, binding);
    266         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
    267     }
    268 
    269     @BinderThread
    270     @Override
    271     public void unbindInput() {
    272         if (mIsUnbindIssued != null) {
    273             // Signal the flag then forget it.
    274             mIsUnbindIssued.set(true);
    275             mIsUnbindIssued = null;
    276         } else {
    277             Log.e(TAG, "unbindInput must be paired with bindInput.");
    278         }
    279         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_UNSET_INPUT_CONTEXT));
    280     }
    281 
    282     @BinderThread
    283     @Override
    284     public void startInput(IBinder startInputToken, IInputContext inputContext,
    285             @InputConnectionInspector.MissingMethodFlags final int missingMethods,
    286             EditorInfo attribute, boolean restarting) {
    287         if (mIsUnbindIssued == null) {
    288             Log.e(TAG, "startInput must be called after bindInput.");
    289             mIsUnbindIssued = new AtomicBoolean();
    290         }
    291         mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOOO(DO_START_INPUT,
    292                 missingMethods, restarting ? 1 : 0, startInputToken, inputContext, attribute,
    293                 mIsUnbindIssued));
    294     }
    295 
    296     @BinderThread
    297     @Override
    298     public void createSession(InputChannel channel, IInputSessionCallback callback) {
    299         mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_SESSION,
    300                 channel, callback));
    301     }
    302 
    303     @BinderThread
    304     @Override
    305     public void setSessionEnabled(IInputMethodSession session, boolean enabled) {
    306         try {
    307             InputMethodSession ls = ((IInputMethodSessionWrapper)
    308                     session).getInternalInputMethodSession();
    309             if (ls == null) {
    310                 Log.w(TAG, "Session is already finished: " + session);
    311                 return;
    312             }
    313             mCaller.executeOrSendMessage(mCaller.obtainMessageIO(
    314                     DO_SET_SESSION_ENABLED, enabled ? 1 : 0, ls));
    315         } catch (ClassCastException e) {
    316             Log.w(TAG, "Incoming session not of correct type: " + session, e);
    317         }
    318     }
    319 
    320     @BinderThread
    321     @Override
    322     public void revokeSession(IInputMethodSession session) {
    323         try {
    324             InputMethodSession ls = ((IInputMethodSessionWrapper)
    325                     session).getInternalInputMethodSession();
    326             if (ls == null) {
    327                 Log.w(TAG, "Session is already finished: " + session);
    328                 return;
    329             }
    330             mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_REVOKE_SESSION, ls));
    331         } catch (ClassCastException e) {
    332             Log.w(TAG, "Incoming session not of correct type: " + session, e);
    333         }
    334     }
    335 
    336     @BinderThread
    337     @Override
    338     public void showSoftInput(int flags, ResultReceiver resultReceiver) {
    339         mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_SHOW_SOFT_INPUT,
    340                 flags, resultReceiver));
    341     }
    342 
    343     @BinderThread
    344     @Override
    345     public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
    346         mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_HIDE_SOFT_INPUT,
    347                 flags, resultReceiver));
    348     }
    349 
    350     @BinderThread
    351     @Override
    352     public void changeInputMethodSubtype(InputMethodSubtype subtype) {
    353         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CHANGE_INPUTMETHOD_SUBTYPE,
    354                 subtype));
    355     }
    356 }
    357