Remove sample multi-client IME

We have CL[1] to remove the multi-client IME mechanism,
remove the corresponding sample app as well.

[1]: I0bdc8fe3d32ccabc8ea7996fc689543c3f99331a

Bug: 173341412
Test: Build
Change-Id: I355d4309dca1ee712de729921a184f5e40e1730c
This commit is contained in:
Wilson Wu
2020-11-13 19:05:55 +08:00
parent 92a8bdb8ca
commit a2c938f437
20 changed files with 0 additions and 1260 deletions

View File

@@ -1,31 +0,0 @@
//
// 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 {
// See: http://go/android-license-faq
default_applicable_licenses: ["Android-Apache-2.0"],
}
android_app {
name: "MultiClientInputMethod",
srcs: ["**/*.java"],
platform_apis: true,
certificate: "platform",
privileged: true,
dex_preopt: {
enabled: false,
},
}

View File

@@ -1,30 +0,0 @@
<?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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 536 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 859 B

View File

@@ -1,25 +0,0 @@
<?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"
/>

View File

@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
** Copyright 2019, 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.
*/
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string-array name="config_inputDisplayToImeDisplay">
<!--
The MultiClientInputMethod will use the same display for the IME window by default.
But, if you want to use the separate display for the IME window, consider to define item of
'config_inputDisplayToImeDisplay'. The each item is a slash-separated (/) pair of the display
the uniqueIds. The first is the uniqueId of the display where the input happens and the second
is the unqiueId of the display where the IME window will be shown.
FYI, you can find the uniqueId of displays in "dumpsys display".
E.g. If you have two displays 19261083906282752, local:19260422155234049 and you want to use
local:19260422155234049 as the IME window for the input at the display local:19261083906282752,
then the config item will be:
<item>local:19261083906282752/local:19260422155234049</item>
E.g. The display of ActivityView has the unique id of the form of
'virtual:' + package_name + ',' + ownerUid + ',' + 'ActivityViewVirtualDisplay@'
+ hashCode + ',' + displayIndex.
We can use the following regular expression to match it:
<item>virtual:com.android.car.carlauncher,\\d+,ActivityViewVirtualDisplay@\\d+,\\d+/local:19260422155234049</item>
-->
</string-array>
</resources>

View File

@@ -1,17 +0,0 @@
<?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" />

View File

@@ -1,79 +0,0 @@
<?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="15%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="15%p"/>
<Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
android:keyWidth="20%p" android:keyEdgeFlags="right"/>
</Row>
</Keyboard>

View File

@@ -1,79 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2019 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="49" android:keyLabel="1" android:keyEdgeFlags="left"/>
<Key android:codes="50" android:keyLabel="2"/>
<Key android:codes="51" android:keyLabel="3"/>
<Key android:codes="52" android:keyLabel="4"/>
<Key android:codes="53" android:keyLabel="5"/>
<Key android:codes="54" android:keyLabel="6"/>
<Key android:codes="55" android:keyLabel="7"/>
<Key android:codes="56" android:keyLabel="8"/>
<Key android:codes="57" android:keyLabel="9"/>
<Key android:codes="48" android:keyLabel="0" android:keyEdgeFlags="right"/>
</Row>
<Row>
<Key android:codes="64" android:keyLabel="\@" android:keyEdgeFlags="left"/>
<Key android:codes="35" android:keyLabel="\#"/>
<Key android:codes="36" android:keyLabel="$"/>
<Key android:codes="37" android:keyLabel="%"/>
<Key android:codes="38" android:keyLabel="&amp;"/>
<Key android:codes="42" android:keyLabel="*"/>
<Key android:codes="45" android:keyLabel="-"/>
<Key android:codes="61" android:keyLabel="="/>
<Key android:codes="40" android:keyLabel="("/>
<Key android:codes="41" android:keyLabel=")" 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="33" android:keyLabel="!" />
<Key android:codes="34" android:keyLabel="&quot;"/>
<Key android:codes="39" android:keyLabel="\'"/>
<Key android:codes="58" android:keyLabel=":"/>
<Key android:codes="59" android:keyLabel=";"/>
<Key android:codes="47" android:keyLabel="/" />
<Key android:codes="63" android:keyLabel="\?"/>
<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="15%p" android:keyEdgeFlags="left"/>
<Key android:codes="-2" android:keyLabel="ABC" 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="15%p"/>
<Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
android:keyWidth="20%p" android:keyEdgeFlags="right"/>
</Row>
</Keyboard>

View File

@@ -1,79 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2019 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="126" android:keyLabel="~" android:keyEdgeFlags="left"/>
<Key android:codes="177" android:keyLabel="±"/>
<Key android:codes="215" android:keyLabel="×"/>
<Key android:codes="247" android:keyLabel="÷"/>
<Key android:codes="8226" android:keyLabel="•"/>
<Key android:codes="176" android:keyLabel="°"/>
<Key android:codes="96" android:keyLabel="`"/>
<Key android:codes="180" android:keyLabel="´"/>
<Key android:codes="123" android:keyLabel="{"/>
<Key android:codes="125" android:keyLabel="}" android:keyEdgeFlags="right"/>
</Row>
<Row>
<Key android:codes="169" android:keyLabel="©" android:keyEdgeFlags="left"/>
<Key android:codes="163" android:keyLabel="£"/>
<Key android:codes="8364" android:keyLabel="€"/>
<Key android:codes="94" android:keyLabel="^"/>
<Key android:codes="174" android:keyLabel="®"/>
<Key android:codes="165" android:keyLabel="¥"/>
<Key android:codes="95" android:keyLabel="_"/>
<Key android:codes="43" android:keyLabel="+"/>
<Key android:codes="91" android:keyLabel="["/>
<Key android:codes="93" android:keyLabel="]" 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="161" android:keyLabel="¡" />
<Key android:codes="60" android:keyLabel="&lt;"/>
<Key android:codes="62" android:keyLabel="&gt;"/>
<Key android:codes="162" android:keyLabel="¢"/>
<Key android:codes="124" android:keyLabel="|"/>
<Key android:codes="92" android:keyLabel="\\" />
<Key android:codes="191" android:keyLabel="¿"/>
<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="15%p" android:keyEdgeFlags="left"/>
<Key android:codes="-2" android:keyLabel="ABC" 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="15%p"/>
<Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
android:keyWidth="20%p" android:keyEdgeFlags="right"/>
</Row>
</Keyboard>

View File

@@ -1,262 +0,0 @@
/*
* 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;
private final MultiClientInputMethod mInputMethod;
ClientCallbackImpl(MultiClientInputMethod inputMethod,
MultiClientInputMethodServiceDelegate delegate,
SoftInputWindowManager softInputWindowManager, int clientId, int uid, int pid,
int selfReportedDisplayId) {
mInputMethod = inputMethod;
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
// and introduce an appropriate synchronization mechanism instead of directly accessing
// MultiClientInputMethod#mDisplayToLastClientId.
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);
}
final int lastClientId = mInputMethod.mDisplayToLastClientId.get(mSelfReportedDisplayId);
if (lastClientId != mClientId) {
// deactivate previous client and activate current.
mDelegate.setActive(lastClientId, false /* active */);
mDelegate.setActive(mClientId, true /* active */);
}
if (inputConnection == null || editorInfo == null) {
// Placeholder 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 placeholder
// 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;
}
mInputMethod.mDisplayToLastClientId.put(mSelfReportedDisplayId, mClientId);
}
@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

@@ -1,119 +0,0 @@
/*
* 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.INITIAL_CONNECTION) != 0) {
joiner.add("INITIAL_CONNECTION");
}
return joiner.setEmptyValue("(none)").toString();
}
}

View File

@@ -1,170 +0,0 @@
/*
* 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.annotation.NonNull;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.inputmethodservice.MultiClientInputMethodServiceDelegate;
import android.os.IBinder;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.Display;
/**
* A {@link Service} that implements multi-client IME protocol.
*/
public final class MultiClientInputMethod extends Service implements DisplayListener {
private static final String TAG = "MultiClientInputMethod";
private static final boolean DEBUG = false;
// last client that had active InputConnection for a given displayId.
final SparseIntArray mDisplayToLastClientId = new SparseIntArray();
// Mapping table from the display where IME is attached to the display where IME window will be
// shown. Assumes that missing display will use the same display for the IME window.
SparseIntArray mInputDisplayToImeDisplay;
SoftInputWindowManager mSoftInputWindowManager;
MultiClientInputMethodServiceDelegate mDelegate;
private DisplayManager mDisplayManager;
@Override
public void onCreate() {
if (DEBUG) {
Log.v(TAG, "onCreate");
}
mInputDisplayToImeDisplay = buildInputDisplayToImeDisplay();
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) {
int imeDisplayId = mInputDisplayToImeDisplay.get(selfReportedDisplayId,
selfReportedDisplayId);
final ClientCallbackImpl callback = new ClientCallbackImpl(
MultiClientInputMethod.this, mDelegate,
mSoftInputWindowManager, clientId, uid, pid, imeDisplayId);
if (DEBUG) {
Log.v(TAG, "addClient clientId=" + clientId + " uid=" + uid
+ " pid=" + pid + " displayId=" + selfReportedDisplayId
+ " imeDisplayId=" + imeDisplayId);
}
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 void onDisplayAdded(int displayId) {
mInputDisplayToImeDisplay = buildInputDisplayToImeDisplay();
}
@Override
public void onDisplayRemoved(int displayId) {
mDisplayToLastClientId.delete(displayId);
}
@Override
public void onDisplayChanged(int displayId) {
}
@Override
public IBinder onBind(Intent intent) {
if (DEBUG) {
Log.v(TAG, "onBind intent=" + intent);
}
mDisplayManager = getApplicationContext().getSystemService(DisplayManager.class);
mDisplayManager.registerDisplayListener(this, getMainThreadHandler());
return mDelegate.onBind(intent);
}
@Override
public boolean onUnbind(Intent intent) {
if (DEBUG) {
Log.v(TAG, "onUnbind intent=" + intent);
}
if (mDisplayManager != null) {
mDisplayManager.unregisterDisplayListener(this);
}
return mDelegate.onUnbind(intent);
}
@Override
public void onDestroy() {
if (DEBUG) {
Log.v(TAG, "onDestroy");
}
mDelegate.onDestroy();
}
@NonNull
private SparseIntArray buildInputDisplayToImeDisplay() {
Context context = getApplicationContext();
String config[] = context.getResources().getStringArray(
R.array.config_inputDisplayToImeDisplay);
SparseIntArray inputDisplayToImeDisplay = new SparseIntArray();
Display[] displays = context.getSystemService(DisplayManager.class).getDisplays();
for (String item: config) {
String[] pair = item.split("/");
if (pair.length != 2) {
Log.w(TAG, "Skip illegal config: " + item);
continue;
}
int inputDisplay = findDisplayId(displays, pair[0]);
int imeDisplay = findDisplayId(displays, pair[1]);
if (inputDisplay != Display.INVALID_DISPLAY && imeDisplay != Display.INVALID_DISPLAY) {
inputDisplayToImeDisplay.put(inputDisplay, imeDisplay);
}
}
return inputDisplayToImeDisplay;
}
private static int findDisplayId(Display displays[], String regexp) {
for (Display display: displays) {
if (display.getUniqueId().matches(regexp)) {
int displayId = display.getDisplayId();
if (DEBUG) {
Log.v(TAG, regexp + " matches displayId=" + displayId);
}
return displayId;
}
}
Log.w(TAG, "Can't find the display of " + regexp);
return Display.INVALID_DISPLAY;
}
}

View File

@@ -1,56 +0,0 @@
/*
* 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

@@ -1,211 +0,0 @@
/*
* 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 mKeyboardView;
private final Keyboard mQwertygKeyboard;
private final Keyboard mSymbolKeyboard;
private final Keyboard mSymbolShiftKeyboard;
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);
mKeyboardView = (KeyboardView) getLayoutInflater().inflate(R.layout.input, null);
mQwertygKeyboard = new Keyboard(context, R.xml.qwerty);
mSymbolKeyboard = new Keyboard(context, R.xml.symbols);
mSymbolShiftKeyboard = new Keyboard(context, R.xml.symbols_shift);
mKeyboardView.setKeyboard(mQwertygKeyboard);
mKeyboardView.setOnKeyboardActionListener(sNoopListener);
// TODO(b/158663354): Disabling keyboard popped preview key since it is currently broken.
mKeyboardView.setPreviewEnabled(false);
layout.addView(mKeyboardView);
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;
}
boolean isQwertyKeyboard() {
return mKeyboardView.getKeyboard() == mQwertygKeyboard;
}
boolean isSymbolKeyboard() {
Keyboard keyboard = mKeyboardView.getKeyboard();
return keyboard == mSymbolKeyboard || keyboard == mSymbolShiftKeyboard;
}
void onFinishClient() {
mKeyboardView.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);
}
mKeyboardView.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;
mKeyboardView.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));
}
boolean isShifted = isShifted(); // Store the current state before resetting it.
resetShift();
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;
case Keyboard.KEYCODE_MODE_CHANGE:
handleSwitchKeyboard();
break;
case Keyboard.KEYCODE_SHIFT:
handleShift(isShifted);
break;
default:
handleCharacter(inputConnection, primaryCode, isShifted);
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);
}
});
}
void handleSwitchKeyboard() {
if (isQwertyKeyboard()) {
mKeyboardView.setKeyboard(mSymbolKeyboard);
} else {
mKeyboardView.setKeyboard(mQwertygKeyboard);
}
}
boolean isShifted() {
return mKeyboardView.isShifted();
}
void resetShift() {
if (isSymbolKeyboard() && isShifted()) {
mKeyboardView.setKeyboard(mSymbolKeyboard);
}
mKeyboardView.setShifted(false);
}
void handleShift(boolean isShifted) {
if (isSymbolKeyboard()) {
mKeyboardView.setKeyboard(isShifted ? mSymbolKeyboard : mSymbolShiftKeyboard);
}
mKeyboardView.setShifted(!isShifted);
}
void handleCharacter(InputConnection inputConnection, int primaryCode, boolean isShifted) {
if (isQwertyKeyboard() && isShifted) {
primaryCode = Character.toUpperCase(primaryCode);
}
inputConnection.commitText(String.valueOf((char) primaryCode), 1);
}
}

View File

@@ -1,61 +0,0 @@
/*
* 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);
}
}