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:
32
samples/MultiClientInputMethod/Android.mk
Executable file
32
samples/MultiClientInputMethod/Android.mk
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
LOCAL_PATH:= $(call my-dir)
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
LOCAL_MODULE_TAGS := samples
|
||||||
|
|
||||||
|
LOCAL_SRC_FILES := $(call all-subdir-java-files)
|
||||||
|
|
||||||
|
LOCAL_PACKAGE_NAME := MultiClientInputMethod
|
||||||
|
|
||||||
|
LOCAL_PRIVATE_PLATFORM_APIS := true
|
||||||
|
LOCAL_CERTIFICATE := platform
|
||||||
|
LOCAL_PRIVILEGED_MODULE := true
|
||||||
|
|
||||||
|
LOCAL_DEX_PREOPT := false
|
||||||
|
|
||||||
|
include $(BUILD_PACKAGE)
|
||||||
30
samples/MultiClientInputMethod/AndroidManifest.xml
Executable file
30
samples/MultiClientInputMethod/AndroidManifest.xml
Executable file
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.android.multiclientinputmethod">
|
||||||
|
|
||||||
|
<application android:label="MultiClientInputMethod">
|
||||||
|
<service android:name=".MultiClientInputMethod"
|
||||||
|
android:label="MultiClientInputMethod"
|
||||||
|
android:permission="android.permission.BIND_INPUT_METHOD">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.inputmethodservice.MultiClientInputMethodService"/>
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data android:name="android.view.im" android:resource="@xml/method"/>
|
||||||
|
</service>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
BIN
samples/MultiClientInputMethod/res/drawable/sym_keyboard_delete.png
Executable file
BIN
samples/MultiClientInputMethod/res/drawable/sym_keyboard_delete.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 885 B |
BIN
samples/MultiClientInputMethod/res/drawable/sym_keyboard_done.png
Executable file
BIN
samples/MultiClientInputMethod/res/drawable/sym_keyboard_done.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
BIN
samples/MultiClientInputMethod/res/drawable/sym_keyboard_return.png
Executable file
BIN
samples/MultiClientInputMethod/res/drawable/sym_keyboard_return.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 536 B |
BIN
samples/MultiClientInputMethod/res/drawable/sym_keyboard_search.png
Executable file
BIN
samples/MultiClientInputMethod/res/drawable/sym_keyboard_search.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
BIN
samples/MultiClientInputMethod/res/drawable/sym_keyboard_shift.png
Executable file
BIN
samples/MultiClientInputMethod/res/drawable/sym_keyboard_shift.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
BIN
samples/MultiClientInputMethod/res/drawable/sym_keyboard_space.png
Executable file
BIN
samples/MultiClientInputMethod/res/drawable/sym_keyboard_space.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 859 B |
25
samples/MultiClientInputMethod/res/layout/input.xml
Executable file
25
samples/MultiClientInputMethod/res/layout/input.xml
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
<android.inputmethodservice.KeyboardView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/keyboard"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
/>
|
||||||
17
samples/MultiClientInputMethod/res/xml/method.xml
Normal file
17
samples/MultiClientInputMethod/res/xml/method.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<input-method xmlns:android="http://schemas.android.com/apk/res/android" />
|
||||||
79
samples/MultiClientInputMethod/res/xml/qwerty.xml
Executable file
79
samples/MultiClientInputMethod/res/xml/qwerty.xml
Executable file
@@ -0,0 +1,79 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:keyWidth="10%p"
|
||||||
|
android:horizontalGap="0px"
|
||||||
|
android:verticalGap="0px"
|
||||||
|
android:keyHeight="50dip"
|
||||||
|
>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Key android:codes="113" android:keyLabel="q" android:keyEdgeFlags="left"/>
|
||||||
|
<Key android:codes="119" android:keyLabel="w"/>
|
||||||
|
<Key android:codes="101" android:keyLabel="e"/>
|
||||||
|
<Key android:codes="114" android:keyLabel="r"/>
|
||||||
|
<Key android:codes="116" android:keyLabel="t"/>
|
||||||
|
<Key android:codes="121" android:keyLabel="y"/>
|
||||||
|
<Key android:codes="117" android:keyLabel="u"/>
|
||||||
|
<Key android:codes="105" android:keyLabel="i"/>
|
||||||
|
<Key android:codes="111" android:keyLabel="o"/>
|
||||||
|
<Key android:codes="112" android:keyLabel="p" android:keyEdgeFlags="right"/>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Key android:codes="97" android:keyLabel="a" android:horizontalGap="5%p"
|
||||||
|
android:keyEdgeFlags="left"/>
|
||||||
|
<Key android:codes="115" android:keyLabel="s"/>
|
||||||
|
<Key android:codes="100" android:keyLabel="d"/>
|
||||||
|
<Key android:codes="102" android:keyLabel="f"/>
|
||||||
|
<Key android:codes="103" android:keyLabel="g"/>
|
||||||
|
<Key android:codes="104" android:keyLabel="h"/>
|
||||||
|
<Key android:codes="106" android:keyLabel="j"/>
|
||||||
|
<Key android:codes="107" android:keyLabel="k"/>
|
||||||
|
<Key android:codes="108" android:keyLabel="l" android:keyEdgeFlags="right"/>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift"
|
||||||
|
android:keyWidth="15%p" android:isModifier="true"
|
||||||
|
android:isSticky="true" android:keyEdgeFlags="left"/>
|
||||||
|
<Key android:codes="122" android:keyLabel="z"/>
|
||||||
|
<Key android:codes="120" android:keyLabel="x"/>
|
||||||
|
<Key android:codes="99" android:keyLabel="c"/>
|
||||||
|
<Key android:codes="118" android:keyLabel="v"/>
|
||||||
|
<Key android:codes="98" android:keyLabel="b"/>
|
||||||
|
<Key android:codes="110" android:keyLabel="n"/>
|
||||||
|
<Key android:codes="109" android:keyLabel="m"/>
|
||||||
|
<Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
|
||||||
|
android:keyWidth="15%p" android:keyEdgeFlags="right"
|
||||||
|
android:isRepeatable="true"/>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row android:rowEdgeFlags="bottom">
|
||||||
|
<Key android:codes="-3" android:keyIcon="@drawable/sym_keyboard_done"
|
||||||
|
android:keyWidth="20%p" android:keyEdgeFlags="left"/>
|
||||||
|
<Key android:codes="-2" android:keyLabel="123" android:keyWidth="10%p"/>
|
||||||
|
<Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
|
||||||
|
android:keyWidth="40%p" android:isRepeatable="true"/>
|
||||||
|
<Key android:codes="46,44" android:keyLabel=". ,"
|
||||||
|
android:keyWidth="20%p"/>
|
||||||
|
<Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
|
||||||
|
android:keyWidth="20%p" android:keyEdgeFlags="right"/>
|
||||||
|
</Row>
|
||||||
|
</Keyboard>
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user