• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.inputmethodservice.MultiClientInputMethodServiceDelegate;
20 import android.os.Bundle;
21 import android.os.Looper;
22 import android.os.ResultReceiver;
23 import android.util.Log;
24 import android.view.KeyEvent;
25 import android.view.MotionEvent;
26 import android.view.WindowManager;
27 import android.view.inputmethod.CompletionInfo;
28 import android.view.inputmethod.CursorAnchorInfo;
29 import android.view.inputmethod.EditorInfo;
30 import android.view.inputmethod.InputConnection;
31 
32 final class ClientCallbackImpl implements MultiClientInputMethodServiceDelegate.ClientCallback {
33     private static final String TAG = "ClientCallbackImpl";
34     private static final boolean DEBUG = false;
35 
36     private final MultiClientInputMethodServiceDelegate mDelegate;
37     private final SoftInputWindowManager mSoftInputWindowManager;
38     private final int mClientId;
39     private final int mUid;
40     private final int mPid;
41     private final int mSelfReportedDisplayId;
42     private final KeyEvent.DispatcherState mDispatcherState;
43     private final Looper mLooper;
44     private final MultiClientInputMethod mInputMethod;
45 
ClientCallbackImpl(MultiClientInputMethod inputMethod, MultiClientInputMethodServiceDelegate delegate, SoftInputWindowManager softInputWindowManager, int clientId, int uid, int pid, int selfReportedDisplayId)46     ClientCallbackImpl(MultiClientInputMethod inputMethod,
47             MultiClientInputMethodServiceDelegate delegate,
48             SoftInputWindowManager softInputWindowManager, int clientId, int uid, int pid,
49             int selfReportedDisplayId) {
50         mInputMethod = inputMethod;
51         mDelegate = delegate;
52         mSoftInputWindowManager = softInputWindowManager;
53         mClientId = clientId;
54         mUid = uid;
55         mPid = pid;
56         mSelfReportedDisplayId = selfReportedDisplayId;
57         mDispatcherState = new KeyEvent.DispatcherState();
58         // For simplicity, we use the main looper for this sample.
59         // To use other looper thread, make sure that the IME Window also runs on the same looper
60         // and introduce an appropriate synchronization mechanism instead of directly accessing
61         // MultiClientInputMethod#mLastClientId.
62         mLooper = Looper.getMainLooper();
63     }
64 
getDispatcherState()65     KeyEvent.DispatcherState getDispatcherState() {
66         return mDispatcherState;
67     }
68 
getLooper()69     Looper getLooper() {
70         return mLooper;
71     }
72 
73     @Override
onAppPrivateCommand(String action, Bundle data)74     public void onAppPrivateCommand(String action, Bundle data) {
75     }
76 
77     @Override
onDisplayCompletions(CompletionInfo[] completions)78     public void onDisplayCompletions(CompletionInfo[] completions) {
79     }
80 
81     @Override
onFinishSession()82     public void onFinishSession() {
83         if (DEBUG) {
84             Log.v(TAG, "onFinishSession clientId=" + mClientId);
85         }
86         final SoftInputWindow window =
87                 mSoftInputWindowManager.getSoftInputWindow(mSelfReportedDisplayId);
88         if (window == null) {
89             return;
90         }
91         // SoftInputWindow also needs to be cleaned up when this IME client is still associated with
92         // it.
93         if (mClientId == window.getClientId()) {
94             window.onFinishClient();
95         }
96     }
97 
98     @Override
onHideSoftInput(int flags, ResultReceiver resultReceiver)99     public void onHideSoftInput(int flags, ResultReceiver resultReceiver) {
100         if (DEBUG) {
101             Log.v(TAG, "onHideSoftInput clientId=" + mClientId + " flags=" + flags);
102         }
103         final SoftInputWindow window =
104                 mSoftInputWindowManager.getSoftInputWindow(mSelfReportedDisplayId);
105         if (window == null) {
106             return;
107         }
108         // Seems that the Launcher3 has a bug to call onHideSoftInput() too early so we cannot
109         // enforce clientId check yet.
110         // TODO: Check clientId like we do so for onShowSoftInput().
111         window.hide();
112     }
113 
114     @Override
onShowSoftInput(int flags, ResultReceiver resultReceiver)115     public void onShowSoftInput(int flags, ResultReceiver resultReceiver) {
116         if (DEBUG) {
117             Log.v(TAG, "onShowSoftInput clientId=" + mClientId + " flags=" + flags);
118         }
119         final SoftInputWindow window =
120                 mSoftInputWindowManager.getSoftInputWindow(mSelfReportedDisplayId);
121         if (window == null) {
122             return;
123         }
124         if (mClientId != window.getClientId()) {
125             Log.w(TAG, "onShowSoftInput() from a background client is ignored."
126                     + " windowClientId=" + window.getClientId()
127                     + " clientId=" + mClientId);
128             return;
129         }
130         window.show();
131     }
132 
133     @Override
onStartInputOrWindowGainedFocus(InputConnection inputConnection, EditorInfo editorInfo, int startInputFlags, int softInputMode, int targetWindowHandle)134     public void onStartInputOrWindowGainedFocus(InputConnection inputConnection,
135             EditorInfo editorInfo, int startInputFlags, int softInputMode, int targetWindowHandle) {
136         if (DEBUG) {
137             Log.v(TAG, "onStartInputOrWindowGainedFocus clientId=" + mClientId
138                     + " editorInfo=" + editorInfo
139                     + " startInputFlags="
140                     + InputMethodDebug.startInputFlagsToString(startInputFlags)
141                     + " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode)
142                     + " targetWindowHandle=" + targetWindowHandle);
143         }
144 
145         final int state = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
146         final boolean forwardNavigation =
147                 (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0;
148 
149         final SoftInputWindow window =
150                 mSoftInputWindowManager.getOrCreateSoftInputWindow(mSelfReportedDisplayId);
151         if (window == null) {
152             return;
153         }
154 
155         if (window.getTargetWindowHandle() != targetWindowHandle) {
156             // Target window has changed.  Report new IME target window to the system.
157             mDelegate.reportImeWindowTarget(
158                     mClientId, targetWindowHandle, window.getWindow().getAttributes().token);
159         }
160         if (inputConnection == null || editorInfo == null) {
161             // deactivate previous client.
162             if (mInputMethod.mLastClientId != mClientId) {
163                 mDelegate.setActive(mInputMethod.mLastClientId, false /* active */);
164             }
165             // Dummy InputConnection case.
166             if (window.getClientId() == mClientId) {
167                 // Special hack for temporary focus changes (e.g. notification shade).
168                 // If we have already established a connection to this client, and if a dummy
169                 // InputConnection is notified, just ignore this event.
170             } else {
171                 window.onDummyStartInput(mClientId, targetWindowHandle);
172             }
173         } else {
174             if (mInputMethod.mLastClientId != mClientId) {
175                 mDelegate.setActive(mClientId, true /* active */);
176             }
177             window.onStartInput(mClientId, targetWindowHandle, inputConnection);
178         }
179 
180         switch (state) {
181             case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
182                 if (forwardNavigation) {
183                     window.show();
184                 }
185                 break;
186             case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
187                 window.show();
188                 break;
189             case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
190                 if (forwardNavigation) {
191                     window.hide();
192                 }
193                 break;
194             case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
195                 window.hide();
196                 break;
197         }
198         mInputMethod.mLastClientId = mClientId;
199     }
200 
201     @Override
onToggleSoftInput(int showFlags, int hideFlags)202     public void onToggleSoftInput(int showFlags, int hideFlags) {
203         // TODO: Implement
204         Log.w(TAG, "onToggleSoftInput is not yet implemented. clientId=" + mClientId
205                 + " showFlags=" + showFlags + " hideFlags=" + hideFlags);
206     }
207 
208     @Override
onUpdateCursorAnchorInfo(CursorAnchorInfo info)209     public void onUpdateCursorAnchorInfo(CursorAnchorInfo info) {
210     }
211 
212     @Override
onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd)213     public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd,
214             int candidatesStart, int candidatesEnd) {
215     }
216 
217     @Override
onGenericMotionEvent(MotionEvent event)218     public boolean onGenericMotionEvent(MotionEvent event) {
219         return false;
220     }
221 
222     @Override
onKeyDown(int keyCode, KeyEvent event)223     public boolean onKeyDown(int keyCode, KeyEvent event) {
224         if (DEBUG) {
225             Log.v(TAG, "onKeyDown clientId=" + mClientId + " keyCode=" + keyCode
226                     + " event=" + event);
227         }
228         if (keyCode == KeyEvent.KEYCODE_BACK) {
229             final SoftInputWindow window =
230                     mSoftInputWindowManager.getSoftInputWindow(mSelfReportedDisplayId);
231             if (window != null && window.isShowing()) {
232                 event.startTracking();
233                 return true;
234             }
235         }
236         return false;
237     }
238 
239     @Override
onKeyLongPress(int keyCode, KeyEvent event)240     public boolean onKeyLongPress(int keyCode, KeyEvent event) {
241         return false;
242     }
243 
244     @Override
onKeyMultiple(int keyCode, KeyEvent event)245     public boolean onKeyMultiple(int keyCode, KeyEvent event) {
246         return false;
247     }
248 
249     @Override
onKeyUp(int keyCode, KeyEvent event)250     public boolean onKeyUp(int keyCode, KeyEvent event) {
251         if (DEBUG) {
252             Log.v(TAG, "onKeyUp clientId=" + mClientId + "keyCode=" + keyCode
253                     + " event=" + event);
254         }
255         if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() && !event.isCanceled()) {
256             final SoftInputWindow window =
257                     mSoftInputWindowManager.getSoftInputWindow(mSelfReportedDisplayId);
258             if (window != null && window.isShowing()) {
259                 window.hide();
260                 return true;
261             }
262         }
263         return false;
264     }
265 
266     @Override
onTrackballEvent(MotionEvent event)267     public boolean onTrackballEvent(MotionEvent event) {
268         return false;
269     }
270 }
271