Add a sample multi-client IME

This CL adds a sample multi-client IME.  Ahthough there are still many
limitations and missing features in this sample IME, this CL
demonstrates how a multi-client IME can be implemented.

Fix: 115784148
Test: prebuilts/checkstyle/checkstyle.py -f \
      development/samples/MultiClientInputMethod/
Test: Manually verified as follows:
  1. make -j MultiClientInputMethod
  2. adb install -r $OUT/system/priv-app/MultiClientInputMethod/MultiClientInputMethod.apk
  3. adb root
  4. adb shell setprop persist.debug.multi_client_ime \
       com.example.android.multiclientinputmethod/.MultiClientInputMethod
  5. adb reboot
  6. Try multiple text input scenario
Change-Id: Ide43e16448fa00355a2c08bc45ae94d98553da50
This commit is contained in:
Yohei Yukawa
2018-11-12 15:09:07 -08:00
parent ceb1c20000
commit eb0868906c
17 changed files with 926 additions and 0 deletions

View File

@@ -0,0 +1,258 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.multiclientinputmethod;
import android.inputmethodservice.MultiClientInputMethodServiceDelegate;
import android.os.Bundle;
import android.os.Looper;
import android.os.ResultReceiver;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
final class ClientCallbackImpl implements MultiClientInputMethodServiceDelegate.ClientCallback {
private static final String TAG = "ClientCallbackImpl";
private static final boolean DEBUG = false;
private final MultiClientInputMethodServiceDelegate mDelegate;
private final SoftInputWindowManager mSoftInputWindowManager;
private final int mClientId;
private final int mUid;
private final int mPid;
private final int mSelfReportedDisplayId;
private final KeyEvent.DispatcherState mDispatcherState;
private final Looper mLooper;
ClientCallbackImpl(MultiClientInputMethodServiceDelegate delegate,
SoftInputWindowManager softInputWindowManager, int clientId, int uid, int pid,
int selfReportedDisplayId) {
mDelegate = delegate;
mSoftInputWindowManager = softInputWindowManager;
mClientId = clientId;
mUid = uid;
mPid = pid;
mSelfReportedDisplayId = selfReportedDisplayId;
mDispatcherState = new KeyEvent.DispatcherState();
// For simplicity, we use the main looper for this sample.
// To use other looper thread, make sure that the IME Window also runs on the same looper.
mLooper = Looper.getMainLooper();
}
KeyEvent.DispatcherState getDispatcherState() {
return mDispatcherState;
}
Looper getLooper() {
return mLooper;
}
@Override
public void onAppPrivateCommand(String action, Bundle data) {
}
@Override
public void onDisplayCompletions(CompletionInfo[] completions) {
}
@Override
public void onFinishSession() {
if (DEBUG) {
Log.v(TAG, "onFinishSession clientId=" + mClientId);
}
final SoftInputWindow window =
mSoftInputWindowManager.getSoftInputWindow(mSelfReportedDisplayId);
if (window == null) {
return;
}
// SoftInputWindow also needs to be cleaned up when this IME client is still associated with
// it.
if (mClientId == window.getClientId()) {
window.onFinishClient();
}
}
@Override
public void onHideSoftInput(int flags, ResultReceiver resultReceiver) {
if (DEBUG) {
Log.v(TAG, "onHideSoftInput clientId=" + mClientId + " flags=" + flags);
}
final SoftInputWindow window =
mSoftInputWindowManager.getSoftInputWindow(mSelfReportedDisplayId);
if (window == null) {
return;
}
// Seems that the Launcher3 has a bug to call onHideSoftInput() too early so we cannot
// enforce clientId check yet.
// TODO: Check clientId like we do so for onShowSoftInput().
window.hide();
}
@Override
public void onShowSoftInput(int flags, ResultReceiver resultReceiver) {
if (DEBUG) {
Log.v(TAG, "onShowSoftInput clientId=" + mClientId + " flags=" + flags);
}
final SoftInputWindow window =
mSoftInputWindowManager.getSoftInputWindow(mSelfReportedDisplayId);
if (window == null) {
return;
}
if (mClientId != window.getClientId()) {
Log.w(TAG, "onShowSoftInput() from a background client is ignored."
+ " windowClientId=" + window.getClientId()
+ " clientId=" + mClientId);
return;
}
window.show();
}
@Override
public void onStartInputOrWindowGainedFocus(InputConnection inputConnection,
EditorInfo editorInfo, int startInputFlags, int softInputMode, int targetWindowHandle) {
if (DEBUG) {
Log.v(TAG, "onStartInputOrWindowGainedFocus clientId=" + mClientId
+ " editorInfo=" + editorInfo
+ " startInputFlags="
+ InputMethodDebug.startInputFlagsToString(startInputFlags)
+ " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode)
+ " targetWindowHandle=" + targetWindowHandle);
}
final int state = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
final boolean forwardNavigation =
(softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0;
final SoftInputWindow window =
mSoftInputWindowManager.getOrCreateSoftInputWindow(mSelfReportedDisplayId);
if (window == null) {
return;
}
if (window.getTargetWindowHandle() != targetWindowHandle) {
// Target window has changed. Report new IME target window to the system.
mDelegate.reportImeWindowTarget(
mClientId, targetWindowHandle, window.getWindow().getAttributes().token);
}
if (inputConnection == null || editorInfo == null) {
// Dummy InputConnection case.
if (window.getClientId() == mClientId) {
// Special hack for temporary focus changes (e.g. notification shade).
// If we have already established a connection to this client, and if a dummy
// InputConnection is notified, just ignore this event.
} else {
window.onDummyStartInput(mClientId, targetWindowHandle);
}
} else {
window.onStartInput(mClientId, targetWindowHandle, inputConnection);
}
switch (state) {
case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
if (forwardNavigation) {
window.show();
}
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
window.show();
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
if (forwardNavigation) {
window.hide();
}
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
window.hide();
break;
}
}
@Override
public void onToggleSoftInput(int showFlags, int hideFlags) {
// TODO: Implement
Log.w(TAG, "onToggleSoftInput is not yet implemented. clientId=" + mClientId
+ " showFlags=" + showFlags + " hideFlags=" + hideFlags);
}
@Override
public void onUpdateCursorAnchorInfo(CursorAnchorInfo info) {
}
@Override
public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd,
int candidatesStart, int candidatesEnd) {
}
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
return false;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (DEBUG) {
Log.v(TAG, "onKeyDown clientId=" + mClientId + " keyCode=" + keyCode
+ " event=" + event);
}
if (keyCode == KeyEvent.KEYCODE_BACK) {
final SoftInputWindow window =
mSoftInputWindowManager.getSoftInputWindow(mSelfReportedDisplayId);
if (window != null && window.isShowing()) {
event.startTracking();
return true;
}
}
return false;
}
@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
return false;
}
@Override
public boolean onKeyMultiple(int keyCode, KeyEvent event) {
return false;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (DEBUG) {
Log.v(TAG, "onKeyUp clientId=" + mClientId + "keyCode=" + keyCode
+ " event=" + event);
}
if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() && !event.isCanceled()) {
final SoftInputWindow window =
mSoftInputWindowManager.getSoftInputWindow(mSelfReportedDisplayId);
if (window != null && window.isShowing()) {
window.hide();
return true;
}
}
return false;
}
@Override
public boolean onTrackballEvent(MotionEvent event) {
return false;
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.multiclientinputmethod;
import android.view.WindowManager;
import com.android.internal.inputmethod.StartInputFlags;
import java.util.StringJoiner;
/**
* Provides useful methods for debugging.
*/
final class InputMethodDebug {
/**
* Not intended to be instantiated.
*/
private InputMethodDebug() {
}
/**
* Converts soft input flags to {@link String} for debug logging.
*
* @param softInputMode integer constant for soft input flags.
* @return {@link String} message corresponds for the given {@code softInputMode}.
*/
public static String softInputModeToString(int softInputMode) {
final StringJoiner joiner = new StringJoiner("|");
final int state = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
final int adjust = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
final boolean isForwardNav =
(softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0;
switch (state) {
case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
joiner.add("STATE_UNSPECIFIED");
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
joiner.add("STATE_UNCHANGED");
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
joiner.add("STATE_HIDDEN");
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
joiner.add("STATE_ALWAYS_HIDDEN");
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
joiner.add("STATE_VISIBLE");
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
joiner.add("STATE_ALWAYS_VISIBLE");
break;
default:
joiner.add("STATE_UNKNOWN(" + state + ")");
break;
}
switch (adjust) {
case WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED:
joiner.add("ADJUST_UNSPECIFIED");
break;
case WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE:
joiner.add("ADJUST_RESIZE");
break;
case WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN:
joiner.add("ADJUST_PAN");
break;
case WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING:
joiner.add("ADJUST_NOTHING");
break;
default:
joiner.add("ADJUST_UNKNOWN(" + adjust + ")");
break;
}
if (isForwardNav) {
// This is a special bit that is set by the system only during the window navigation.
joiner.add("IS_FORWARD_NAVIGATION");
}
return joiner.setEmptyValue("(none)").toString();
}
/**
* Converts start input flags to {@link String} for debug logging.
*
* @param startInputFlags integer constant for start input flags.
* @return {@link String} message corresponds for the given {@code startInputFlags}.
*/
public static String startInputFlagsToString(int startInputFlags) {
final StringJoiner joiner = new StringJoiner("|");
if ((startInputFlags & StartInputFlags.VIEW_HAS_FOCUS) != 0) {
joiner.add("VIEW_HAS_FOCUS");
}
if ((startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0) {
joiner.add("IS_TEXT_EDITOR");
}
if ((startInputFlags & StartInputFlags.FIRST_WINDOW_FOCUS_GAIN) != 0) {
joiner.add("FIRST_WINDOW_FOCUS_GAIN");
}
if ((startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0) {
joiner.add("INITIAL_CONNECTION");
}
return joiner.setEmptyValue("(none)").toString();
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.multiclientinputmethod;
import android.app.Service;
import android.content.Intent;
import android.inputmethodservice.MultiClientInputMethodServiceDelegate;
import android.os.IBinder;
import android.util.Log;
/**
* A {@link Service} that implements multi-client IME protocol.
*/
public final class MultiClientInputMethod extends Service {
private static final String TAG = "MultiClientInputMethod";
private static final boolean DEBUG = false;
SoftInputWindowManager mSoftInputWindowManager;
MultiClientInputMethodServiceDelegate mDelegate;
@Override
public void onCreate() {
if (DEBUG) {
Log.v(TAG, "onCreate");
}
mDelegate = MultiClientInputMethodServiceDelegate.create(this,
new MultiClientInputMethodServiceDelegate.ServiceCallback() {
@Override
public void initialized() {
if (DEBUG) {
Log.i(TAG, "initialized");
}
}
@Override
public void addClient(int clientId, int uid, int pid,
int selfReportedDisplayId) {
final ClientCallbackImpl callback = new ClientCallbackImpl(mDelegate,
mSoftInputWindowManager, clientId, uid, pid, selfReportedDisplayId);
if (DEBUG) {
Log.v(TAG, "addClient clientId=" + clientId + " uid=" + uid
+ " pid=" + pid + " displayId=" + selfReportedDisplayId);
}
mDelegate.acceptClient(clientId, callback, callback.getDispatcherState(),
callback.getLooper());
}
@Override
public void removeClient(int clientId) {
if (DEBUG) {
Log.v(TAG, "removeClient clientId=" + clientId);
}
}
});
mSoftInputWindowManager = new SoftInputWindowManager(this, mDelegate);
}
@Override
public IBinder onBind(Intent intent) {
if (DEBUG) {
Log.v(TAG, "onBind intent=" + intent);
}
return mDelegate.onBind(intent);
}
@Override
public boolean onUnbind(Intent intent) {
if (DEBUG) {
Log.v(TAG, "onUnbind intent=" + intent);
}
return mDelegate.onUnbind(intent);
}
@Override
public void onDestroy() {
if (DEBUG) {
Log.v(TAG, "onDestroy");
}
mDelegate.onDestroy();
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.multiclientinputmethod;
import android.inputmethodservice.KeyboardView;
/**
* Provides the no-op implementation of {@link KeyboardView.OnKeyboardActionListener}
*/
class NoopKeyboardActionListener implements KeyboardView.OnKeyboardActionListener {
@Override
public void onPress(int primaryCode) {
}
@Override
public void onRelease(int primaryCode) {
}
@Override
public void onKey(int primaryCode, int[] keyCodes) {
}
@Override
public void onText(CharSequence text) {
}
@Override
public void swipeLeft() {
}
@Override
public void swipeRight() {
}
@Override
public void swipeDown() {
}
@Override
public void swipeUp() {
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.multiclientinputmethod;
import android.app.Dialog;
import android.content.Context;
import android.inputmethodservice.Keyboard;
import android.inputmethodservice.KeyboardView;
import android.inputmethodservice.MultiClientInputMethodServiceDelegate;
import android.os.IBinder;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.ViewGroup;
import android.view.WindowManager.LayoutParams;
import android.view.inputmethod.InputConnection;
import android.widget.LinearLayout;
import java.util.Arrays;
final class SoftInputWindow extends Dialog {
private static final String TAG = "SoftInputWindow";
private static final boolean DEBUG = false;
private final KeyboardView mQwerty;
private int mClientId = MultiClientInputMethodServiceDelegate.INVALID_CLIENT_ID;
private int mTargetWindowHandle = MultiClientInputMethodServiceDelegate.INVALID_WINDOW_HANDLE;
private static final KeyboardView.OnKeyboardActionListener sNoopListener =
new NoopKeyboardActionListener();
SoftInputWindow(Context context, IBinder token) {
super(context, android.R.style.Theme_DeviceDefault_InputMethod);
final LayoutParams lp = getWindow().getAttributes();
lp.type = LayoutParams.TYPE_INPUT_METHOD;
lp.setTitle("InputMethod");
lp.gravity = Gravity.BOTTOM;
lp.width = LayoutParams.MATCH_PARENT;
lp.height = LayoutParams.WRAP_CONTENT;
lp.token = token;
getWindow().setAttributes(lp);
final int windowSetFlags = LayoutParams.FLAG_LAYOUT_IN_SCREEN
| LayoutParams.FLAG_NOT_FOCUSABLE
| LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
final int windowModFlags = LayoutParams.FLAG_LAYOUT_IN_SCREEN
| LayoutParams.FLAG_NOT_FOCUSABLE
| LayoutParams.FLAG_DIM_BEHIND
| LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
getWindow().setFlags(windowSetFlags, windowModFlags);
final LinearLayout layout = new LinearLayout(context);
layout.setOrientation(LinearLayout.VERTICAL);
mQwerty = (KeyboardView) getLayoutInflater().inflate(R.layout.input, null);
mQwerty.setKeyboard(new Keyboard(context, R.xml.qwerty));
mQwerty.setOnKeyboardActionListener(sNoopListener);
layout.addView(mQwerty);
setContentView(layout, new ViewGroup.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
// TODO: Check why we need to call this.
getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}
int getClientId() {
return mClientId;
}
int getTargetWindowHandle() {
return mTargetWindowHandle;
}
void onFinishClient() {
mQwerty.setOnKeyboardActionListener(sNoopListener);
mClientId = MultiClientInputMethodServiceDelegate.INVALID_CLIENT_ID;
mTargetWindowHandle = MultiClientInputMethodServiceDelegate.INVALID_WINDOW_HANDLE;
}
void onDummyStartInput(int clientId, int targetWindowHandle) {
if (DEBUG) {
Log.v(TAG, "onDummyStartInput clientId=" + clientId
+ " targetWindowHandle=" + targetWindowHandle);
}
mQwerty.setOnKeyboardActionListener(sNoopListener);
mClientId = clientId;
mTargetWindowHandle = targetWindowHandle;
}
void onStartInput(int clientId, int targetWindowHandle, InputConnection inputConnection) {
if (DEBUG) {
Log.v(TAG, "onStartInput clientId=" + clientId
+ " targetWindowHandle=" + targetWindowHandle);
}
mClientId = clientId;
mTargetWindowHandle = targetWindowHandle;
mQwerty.setOnKeyboardActionListener(new NoopKeyboardActionListener() {
@Override
public void onKey(int primaryCode, int[] keyCodes) {
if (DEBUG) {
Log.v(TAG, "onKey clientId=" + clientId + " primaryCode=" + primaryCode
+ " keyCodes=" + Arrays.toString(keyCodes));
}
switch (primaryCode) {
case Keyboard.KEYCODE_CANCEL:
hide();
break;
case Keyboard.KEYCODE_DELETE:
inputConnection.sendKeyEvent(
new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
inputConnection.sendKeyEvent(
new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
break;
default:
if (Character.isLetter(primaryCode)) {
inputConnection.commitText(String.valueOf((char) primaryCode), 1);
}
break;
}
}
@Override
public void onText(CharSequence text) {
if (DEBUG) {
Log.v(TAG, "onText clientId=" + clientId + " text=" + text);
}
if (inputConnection == null) {
return;
}
inputConnection.commitText(text, 0);
}
});
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.multiclientinputmethod;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.inputmethodservice.MultiClientInputMethodServiceDelegate;
import android.os.IBinder;
import android.util.SparseArray;
import android.view.Display;
final class SoftInputWindowManager {
private final Context mContext;
private final MultiClientInputMethodServiceDelegate mDelegate;
private final SparseArray<SoftInputWindow> mSoftInputWindows = new SparseArray<>();
SoftInputWindowManager(Context context, MultiClientInputMethodServiceDelegate delegate) {
mContext = context;
mDelegate = delegate;
}
SoftInputWindow getOrCreateSoftInputWindow(int displayId) {
final SoftInputWindow existingWindow = mSoftInputWindows.get(displayId);
if (existingWindow != null) {
return existingWindow;
}
final Display display =
mContext.getSystemService(DisplayManager.class).getDisplay(displayId);
if (display == null) {
return null;
}
final IBinder windowToken = mDelegate.createInputMethodWindowToken(displayId);
if (windowToken == null) {
return null;
}
final Context displayContext = mContext.createDisplayContext(display);
final SoftInputWindow newWindow = new SoftInputWindow(displayContext, windowToken);
mSoftInputWindows.put(displayId, newWindow);
return newWindow;
}
SoftInputWindow getSoftInputWindow(int displayId) {
return mSoftInputWindows.get(displayId);
}
}