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,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);
}
});
}
}