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