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#mDisplayToLastClientId. 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 final int lastClientId = mInputMethod.mDisplayToLastClientId.get(mSelfReportedDisplayId); 161 if (lastClientId != mClientId) { 162 // deactivate previous client and activate current. 163 mDelegate.setActive(lastClientId, false /* active */); 164 mDelegate.setActive(mClientId, true /* active */); 165 } 166 if (inputConnection == null || editorInfo == null) { 167 // Dummy InputConnection case. 168 if (window.getClientId() == mClientId) { 169 // Special hack for temporary focus changes (e.g. notification shade). 170 // If we have already established a connection to this client, and if a dummy 171 // InputConnection is notified, just ignore this event. 172 } else { 173 window.onDummyStartInput(mClientId, targetWindowHandle); 174 } 175 } else { 176 window.onStartInput(mClientId, targetWindowHandle, inputConnection); 177 } 178 179 switch (state) { 180 case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE: 181 if (forwardNavigation) { 182 window.show(); 183 } 184 break; 185 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: 186 window.show(); 187 break; 188 case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN: 189 if (forwardNavigation) { 190 window.hide(); 191 } 192 break; 193 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: 194 window.hide(); 195 break; 196 } 197 mInputMethod.mDisplayToLastClientId.put(mSelfReportedDisplayId, mClientId); 198 } 199 200 @Override onToggleSoftInput(int showFlags, int hideFlags)201 public void onToggleSoftInput(int showFlags, int hideFlags) { 202 // TODO: Implement 203 Log.w(TAG, "onToggleSoftInput is not yet implemented. clientId=" + mClientId 204 + " showFlags=" + showFlags + " hideFlags=" + hideFlags); 205 } 206 207 @Override onUpdateCursorAnchorInfo(CursorAnchorInfo info)208 public void onUpdateCursorAnchorInfo(CursorAnchorInfo info) { 209 } 210 211 @Override onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd)212 public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, 213 int candidatesStart, int candidatesEnd) { 214 } 215 216 @Override onGenericMotionEvent(MotionEvent event)217 public boolean onGenericMotionEvent(MotionEvent event) { 218 return false; 219 } 220 221 @Override onKeyDown(int keyCode, KeyEvent event)222 public boolean onKeyDown(int keyCode, KeyEvent event) { 223 if (DEBUG) { 224 Log.v(TAG, "onKeyDown clientId=" + mClientId + " keyCode=" + keyCode 225 + " event=" + event); 226 } 227 if (keyCode == KeyEvent.KEYCODE_BACK) { 228 final SoftInputWindow window = 229 mSoftInputWindowManager.getSoftInputWindow(mSelfReportedDisplayId); 230 if (window != null && window.isShowing()) { 231 event.startTracking(); 232 return true; 233 } 234 } 235 return false; 236 } 237 238 @Override onKeyLongPress(int keyCode, KeyEvent event)239 public boolean onKeyLongPress(int keyCode, KeyEvent event) { 240 return false; 241 } 242 243 @Override onKeyMultiple(int keyCode, KeyEvent event)244 public boolean onKeyMultiple(int keyCode, KeyEvent event) { 245 return false; 246 } 247 248 @Override onKeyUp(int keyCode, KeyEvent event)249 public boolean onKeyUp(int keyCode, KeyEvent event) { 250 if (DEBUG) { 251 Log.v(TAG, "onKeyUp clientId=" + mClientId + "keyCode=" + keyCode 252 + " event=" + event); 253 } 254 if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() && !event.isCanceled()) { 255 final SoftInputWindow window = 256 mSoftInputWindowManager.getSoftInputWindow(mSelfReportedDisplayId); 257 if (window != null && window.isShowing()) { 258 window.hide(); 259 return true; 260 } 261 } 262 return false; 263 } 264 265 @Override onTrackballEvent(MotionEvent event)266 public boolean onTrackballEvent(MotionEvent event) { 267 return false; 268 } 269 } 270