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