1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.multiclientinputmethod; 18 19 import android.app.Dialog; 20 import android.content.Context; 21 import android.inputmethodservice.Keyboard; 22 import android.inputmethodservice.KeyboardView; 23 import android.inputmethodservice.MultiClientInputMethodServiceDelegate; 24 import android.os.IBinder; 25 import android.util.Log; 26 import android.view.Gravity; 27 import android.view.KeyEvent; 28 import android.view.ViewGroup; 29 import android.view.WindowManager.LayoutParams; 30 import android.view.inputmethod.InputConnection; 31 import android.widget.LinearLayout; 32 33 import java.util.Arrays; 34 35 final class SoftInputWindow extends Dialog { 36 private static final String TAG = "SoftInputWindow"; 37 private static final boolean DEBUG = false; 38 39 private final KeyboardView mKeyboardView; 40 41 private final Keyboard mQwertygKeyboard; 42 private final Keyboard mSymbolKeyboard; 43 private final Keyboard mSymbolShiftKeyboard; 44 45 private int mClientId = MultiClientInputMethodServiceDelegate.INVALID_CLIENT_ID; 46 private int mTargetWindowHandle = MultiClientInputMethodServiceDelegate.INVALID_WINDOW_HANDLE; 47 48 private static final KeyboardView.OnKeyboardActionListener sNoopListener = 49 new NoopKeyboardActionListener(); 50 SoftInputWindow(Context context, IBinder token)51 SoftInputWindow(Context context, IBinder token) { 52 super(context, android.R.style.Theme_DeviceDefault_InputMethod); 53 54 final LayoutParams lp = getWindow().getAttributes(); 55 lp.type = LayoutParams.TYPE_INPUT_METHOD; 56 lp.setTitle("InputMethod"); 57 lp.gravity = Gravity.BOTTOM; 58 lp.width = LayoutParams.MATCH_PARENT; 59 lp.height = LayoutParams.WRAP_CONTENT; 60 lp.token = token; 61 getWindow().setAttributes(lp); 62 63 final int windowSetFlags = LayoutParams.FLAG_LAYOUT_IN_SCREEN 64 | LayoutParams.FLAG_NOT_FOCUSABLE 65 | LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; 66 final int windowModFlags = LayoutParams.FLAG_LAYOUT_IN_SCREEN 67 | LayoutParams.FLAG_NOT_FOCUSABLE 68 | LayoutParams.FLAG_DIM_BEHIND 69 | LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; 70 getWindow().setFlags(windowSetFlags, windowModFlags); 71 72 final LinearLayout layout = new LinearLayout(context); 73 layout.setOrientation(LinearLayout.VERTICAL); 74 75 mKeyboardView = (KeyboardView) getLayoutInflater().inflate(R.layout.input, null); 76 mQwertygKeyboard = new Keyboard(context, R.xml.qwerty); 77 mSymbolKeyboard = new Keyboard(context, R.xml.symbols); 78 mSymbolShiftKeyboard = new Keyboard(context, R.xml.symbols_shift); 79 mKeyboardView.setKeyboard(mQwertygKeyboard); 80 mKeyboardView.setOnKeyboardActionListener(sNoopListener); 81 82 // TODO(b/158663354): Disabling keyboard popped preview key since it is currently broken. 83 mKeyboardView.setPreviewEnabled(false); 84 85 layout.addView(mKeyboardView); 86 87 setContentView(layout, new ViewGroup.LayoutParams( 88 LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); 89 90 // TODO: Check why we need to call this. 91 getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 92 } 93 getClientId()94 int getClientId() { 95 return mClientId; 96 } 97 getTargetWindowHandle()98 int getTargetWindowHandle() { 99 return mTargetWindowHandle; 100 } 101 isQwertyKeyboard()102 boolean isQwertyKeyboard() { 103 return mKeyboardView.getKeyboard() == mQwertygKeyboard; 104 } 105 isSymbolKeyboard()106 boolean isSymbolKeyboard() { 107 Keyboard keyboard = mKeyboardView.getKeyboard(); 108 return keyboard == mSymbolKeyboard || keyboard == mSymbolShiftKeyboard; 109 } 110 onFinishClient()111 void onFinishClient() { 112 mKeyboardView.setOnKeyboardActionListener(sNoopListener); 113 mClientId = MultiClientInputMethodServiceDelegate.INVALID_CLIENT_ID; 114 mTargetWindowHandle = MultiClientInputMethodServiceDelegate.INVALID_WINDOW_HANDLE; 115 } 116 onDummyStartInput(int clientId, int targetWindowHandle)117 void onDummyStartInput(int clientId, int targetWindowHandle) { 118 if (DEBUG) { 119 Log.v(TAG, "onDummyStartInput clientId=" + clientId 120 + " targetWindowHandle=" + targetWindowHandle); 121 } 122 mKeyboardView.setOnKeyboardActionListener(sNoopListener); 123 mClientId = clientId; 124 mTargetWindowHandle = targetWindowHandle; 125 } 126 onStartInput(int clientId, int targetWindowHandle, InputConnection inputConnection)127 void onStartInput(int clientId, int targetWindowHandle, InputConnection inputConnection) { 128 if (DEBUG) { 129 Log.v(TAG, "onStartInput clientId=" + clientId 130 + " targetWindowHandle=" + targetWindowHandle); 131 } 132 mClientId = clientId; 133 mTargetWindowHandle = targetWindowHandle; 134 mKeyboardView.setOnKeyboardActionListener(new NoopKeyboardActionListener() { 135 @Override 136 public void onKey(int primaryCode, int[] keyCodes) { 137 if (DEBUG) { 138 Log.v(TAG, "onKey clientId=" + clientId + " primaryCode=" + primaryCode 139 + " keyCodes=" + Arrays.toString(keyCodes)); 140 } 141 boolean isShifted = isShifted(); // Store the current state before resetting it. 142 resetShift(); 143 switch (primaryCode) { 144 case Keyboard.KEYCODE_CANCEL: 145 hide(); 146 break; 147 case Keyboard.KEYCODE_DELETE: 148 inputConnection.sendKeyEvent( 149 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); 150 inputConnection.sendKeyEvent( 151 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL)); 152 break; 153 case Keyboard.KEYCODE_MODE_CHANGE: 154 handleSwitchKeyboard(); 155 break; 156 case Keyboard.KEYCODE_SHIFT: 157 handleShift(isShifted); 158 break; 159 default: 160 handleCharacter(inputConnection, primaryCode, isShifted); 161 break; 162 } 163 } 164 165 @Override 166 public void onText(CharSequence text) { 167 if (DEBUG) { 168 Log.v(TAG, "onText clientId=" + clientId + " text=" + text); 169 } 170 if (inputConnection == null) { 171 return; 172 } 173 inputConnection.commitText(text, 0); 174 } 175 }); 176 } 177 handleSwitchKeyboard()178 void handleSwitchKeyboard() { 179 if (isQwertyKeyboard()) { 180 mKeyboardView.setKeyboard(mSymbolKeyboard); 181 } else { 182 mKeyboardView.setKeyboard(mQwertygKeyboard); 183 } 184 185 } 186 isShifted()187 boolean isShifted() { 188 return mKeyboardView.isShifted(); 189 } 190 resetShift()191 void resetShift() { 192 if (isSymbolKeyboard() && isShifted()) { 193 mKeyboardView.setKeyboard(mSymbolKeyboard); 194 } 195 mKeyboardView.setShifted(false); 196 } 197 handleShift(boolean isShifted)198 void handleShift(boolean isShifted) { 199 if (isSymbolKeyboard()) { 200 mKeyboardView.setKeyboard(isShifted ? mSymbolKeyboard : mSymbolShiftKeyboard); 201 } 202 mKeyboardView.setShifted(!isShifted); 203 } 204 handleCharacter(InputConnection inputConnection, int primaryCode, boolean isShifted)205 void handleCharacter(InputConnection inputConnection, int primaryCode, boolean isShifted) { 206 if (isQwertyKeyboard() && isShifted) { 207 primaryCode = Character.toUpperCase(primaryCode); 208 } 209 inputConnection.commitText(String.valueOf((char) primaryCode), 1); 210 } 211 } 212