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