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 layout.addView(mKeyboardView); 82 83 setContentView(layout, new ViewGroup.LayoutParams( 84 LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); 85 86 // TODO: Check why we need to call this. 87 getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 88 } 89 getClientId()90 int getClientId() { 91 return mClientId; 92 } 93 getTargetWindowHandle()94 int getTargetWindowHandle() { 95 return mTargetWindowHandle; 96 } 97 isQwertyKeyboard()98 boolean isQwertyKeyboard() { 99 return mKeyboardView.getKeyboard() == mQwertygKeyboard; 100 } 101 isSymbolKeyboard()102 boolean isSymbolKeyboard() { 103 Keyboard keyboard = mKeyboardView.getKeyboard(); 104 return keyboard == mSymbolKeyboard || keyboard == mSymbolShiftKeyboard; 105 } 106 onFinishClient()107 void onFinishClient() { 108 mKeyboardView.setOnKeyboardActionListener(sNoopListener); 109 mClientId = MultiClientInputMethodServiceDelegate.INVALID_CLIENT_ID; 110 mTargetWindowHandle = MultiClientInputMethodServiceDelegate.INVALID_WINDOW_HANDLE; 111 } 112 onDummyStartInput(int clientId, int targetWindowHandle)113 void onDummyStartInput(int clientId, int targetWindowHandle) { 114 if (DEBUG) { 115 Log.v(TAG, "onDummyStartInput clientId=" + clientId 116 + " targetWindowHandle=" + targetWindowHandle); 117 } 118 mKeyboardView.setOnKeyboardActionListener(sNoopListener); 119 mClientId = clientId; 120 mTargetWindowHandle = targetWindowHandle; 121 } 122 onStartInput(int clientId, int targetWindowHandle, InputConnection inputConnection)123 void onStartInput(int clientId, int targetWindowHandle, InputConnection inputConnection) { 124 if (DEBUG) { 125 Log.v(TAG, "onStartInput clientId=" + clientId 126 + " targetWindowHandle=" + targetWindowHandle); 127 } 128 mClientId = clientId; 129 mTargetWindowHandle = targetWindowHandle; 130 mKeyboardView.setOnKeyboardActionListener(new NoopKeyboardActionListener() { 131 @Override 132 public void onKey(int primaryCode, int[] keyCodes) { 133 if (DEBUG) { 134 Log.v(TAG, "onKey clientId=" + clientId + " primaryCode=" + primaryCode 135 + " keyCodes=" + Arrays.toString(keyCodes)); 136 } 137 boolean isShifted = isShifted(); // Store the current state before resetting it. 138 resetShift(); 139 switch (primaryCode) { 140 case Keyboard.KEYCODE_CANCEL: 141 hide(); 142 break; 143 case Keyboard.KEYCODE_DELETE: 144 inputConnection.sendKeyEvent( 145 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); 146 inputConnection.sendKeyEvent( 147 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL)); 148 break; 149 case Keyboard.KEYCODE_MODE_CHANGE: 150 handleSwitchKeyboard(); 151 break; 152 case Keyboard.KEYCODE_SHIFT: 153 handleShift(isShifted); 154 break; 155 default: 156 handleCharacter(inputConnection, primaryCode, isShifted); 157 break; 158 } 159 } 160 161 @Override 162 public void onText(CharSequence text) { 163 if (DEBUG) { 164 Log.v(TAG, "onText clientId=" + clientId + " text=" + text); 165 } 166 if (inputConnection == null) { 167 return; 168 } 169 inputConnection.commitText(text, 0); 170 } 171 }); 172 } 173 handleSwitchKeyboard()174 void handleSwitchKeyboard() { 175 if (isQwertyKeyboard()) { 176 mKeyboardView.setKeyboard(mSymbolKeyboard); 177 } else { 178 mKeyboardView.setKeyboard(mQwertygKeyboard); 179 } 180 181 } 182 isShifted()183 boolean isShifted() { 184 return mKeyboardView.isShifted(); 185 } 186 resetShift()187 void resetShift() { 188 if (isSymbolKeyboard() && isShifted()) { 189 mKeyboardView.setKeyboard(mSymbolKeyboard); 190 } 191 mKeyboardView.setShifted(false); 192 } 193 handleShift(boolean isShifted)194 void handleShift(boolean isShifted) { 195 if (isSymbolKeyboard()) { 196 mKeyboardView.setKeyboard(isShifted ? mSymbolKeyboard : mSymbolShiftKeyboard); 197 } 198 mKeyboardView.setShifted(!isShifted); 199 } 200 handleCharacter(InputConnection inputConnection, int primaryCode, boolean isShifted)201 void handleCharacter(InputConnection inputConnection, int primaryCode, boolean isShifted) { 202 if (isQwertyKeyboard() && isShifted) { 203 primaryCode = Character.toUpperCase(primaryCode); 204 } 205 inputConnection.commitText(String.valueOf((char) primaryCode), 1); 206 } 207 } 208