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