Android - Dialog via Service context

    Android's UI Toolkit requires a valid context to be created. A valid context isn't necessarily an alive context instead one which has a valid window token associated with it. So an attempt to create and add a view in a service context, application context, broadcast receiver context will fail with a android.view.WindowManager$BadTokenException much like adding a view via context of a destroyed activity (although this would be a typical bug of holding active references to context even after the user is finished with it, like a internal non-static Handler implementation in an Activity).

     However, the InputMethod views are always shown via InputMethodService which is a more specific implementation of a Service. For a developer, InputMethodService offers getLayoutInflater() and helper callbacks like onCreateInputView(), onCreateCandidatesView() etc to display the soft keyboard. So how does this work only for this type of service? what context is exactly being used here? Is this some hidden feature that could be used for other purposes like creating a UI element outside an activity's window?

      Under the hood, InputMethodService uses SoftInputWindow which is an extension of Dialog to show the soft keyboard. But even a Dialog creation needs a valid context. An AlertDialog is usually created with an Activity's context and this context is used to create the window and for the decor view to be added to the same window upon invocation of Dialog.show() by the application.

Window w = PolicyManager.makeNewWindow(mContext);
mDecor = mWindow.getDecorView();
mWindowManager.addView(mDecor, l);

    But again, a service can't really create and show a dialog as its context isn't associated with a valid window token. So whats different for an InputMethodService? It works due to a smart manipulation of the token by the android framework.

   When a user clicks on a text view, the view requests to show a soft input and most android versions have a default IME. Android Framework's InputMethodManagerService binds to this IME's InputMethodService. All types of InputMethodService (including custom ones) have this final implementation for onBind()

AbstractInputMethodService

    @Override
    final public IBinder onBind(Intent intent) {
        if (mInputMethod == null) {
            mInputMethod = onCreateInputMethodInterface();
        }
        return new IInputMethodWrapper(this, mInputMethod);
    }

  The InputMethod interface has an API attachToken(). The default implementation of onCreateInputMethodInterface() returns as instance of InputMethodImpl.

InputMethodService

    @Override
    public AbstractInputMethodImpl onCreateInputMethodInterface() {
        return new InputMethodImpl();
    }

InputMethodImpl basically receives a system generated token and sets it on the window associated with the Soft Keyboard (SoftInputWindow)

InputMethodImpl

        public void attachToken(IBinder token) {
            if (mToken == null) {
                mToken = token;
                mWindow.setToken(token);
            }
        }

SoftInputWindow

    public void setToken(IBinder token) {
        WindowManager.LayoutParams lp = getWindow().getAttributes();
        lp.token = token;
        getWindow().setAttributes(lp);
    }

  So this is how a service context's token is replaced by a system generated token. This token is created by InputMethodManagerService after a successful connection to the IME's InputMethodService and is added to the window manager as a type of token meant for Input methods.

InputMethodManagerService

            mCurToken = new Binder();
            try {
                if (true || DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
                mIWindowManager.addWindowToken(mCurToken,
                        WindowManager.LayoutParams.TYPE_INPUT_METHOD);
            } catch (RemoteException e) {
            }

    From here on, the inflated views from the application are just added as the child views of the DecorView corresponding to this window. This also explains as to why the underlying activity isn't paused or stopped even when the soft keyboard is even in the full screen mode.

     So how about we try using this system generated token to display other UI elements? The token is delivered only to an application hosting a soft keyboard and framework supports invisible keyboards for voice based inputs. We just to have to override onCreateInputMethodInterface to return our custom InputMethod and onCreateInputView() to return null. The token by itself isn't good enough as the windows created by apps (via Dialog) are of application types and the token is basically useless for these types of windows (FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW). So its important to change the type of the window to WindowManager.LayoutParams.TYPE_INPUT_METHOD. If the window type isn't changed, the following code would fail with a BadTokenException, Attempted to add window with non-application token. 

    @Override
    public View onCreateInputView() {
        return null;
    }

   @Override
    public AbstractInputMethodImpl onCreateInputMethodInterface() {
        return new CustomInputMethodImpl();
    }

    private class CustomInputMethodImpl extends InputMethodImpl {

        @Override
        public void attachToken(final IBinder token) {
            super.attachToken(token);

            new Handler(Looper.getMainLooper()).post(new Runnable() {

                @Override
                public void run() {
                    AlertDialog.Builder builder = new AlertDialog.Builder(SoftKeyboardService.this);
                    builder.setMessage("It work !!!");
                    Dialog dialog = builder.create();
                    WindowManager.LayoutParams lp = dialog.getWindow().getAttributes();
                    lp.token = token;
                    lp.type = WindowManager.LayoutParams.TYPE_INPUT_METHOD;
                    dialog.getWindow().setAttributes(lp);
                    dialog.show();
                }
            });
        }

    }

   
    And we have a dialog shown just using a InputMethodService context (instead of an activity context). The above code is just for illustration and the dialog is shown as soon as the framework binds to the keyboard's service. The token could very well be saved and used when needed. The only down side to TYPE_INPUT_METHOD is the location of the dialog, its bottom aligned.



No comments: