/* * 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); } }