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