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