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 // Placeholder 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 placeholder 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 onUpdateCursorAnchorInfo(CursorAnchorInfo info)201 public void onUpdateCursorAnchorInfo(CursorAnchorInfo info) { 202 } 203 204 @Override onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd)205 public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, 206 int candidatesStart, int candidatesEnd) { 207 } 208 209 @Override onGenericMotionEvent(MotionEvent event)210 public boolean onGenericMotionEvent(MotionEvent event) { 211 return false; 212 } 213 214 @Override onKeyDown(int keyCode, KeyEvent event)215 public boolean onKeyDown(int keyCode, KeyEvent event) { 216 if (DEBUG) { 217 Log.v(TAG, "onKeyDown clientId=" + mClientId + " keyCode=" + keyCode 218 + " event=" + event); 219 } 220 if (keyCode == KeyEvent.KEYCODE_BACK) { 221 final SoftInputWindow window = 222 mSoftInputWindowManager.getSoftInputWindow(mSelfReportedDisplayId); 223 if (window != null && window.isShowing()) { 224 event.startTracking(); 225 return true; 226 } 227 } 228 return false; 229 } 230 231 @Override onKeyLongPress(int keyCode, KeyEvent event)232 public boolean onKeyLongPress(int keyCode, KeyEvent event) { 233 return false; 234 } 235 236 @Override onKeyMultiple(int keyCode, KeyEvent event)237 public boolean onKeyMultiple(int keyCode, KeyEvent event) { 238 return false; 239 } 240 241 @Override onKeyUp(int keyCode, KeyEvent event)242 public boolean onKeyUp(int keyCode, KeyEvent event) { 243 if (DEBUG) { 244 Log.v(TAG, "onKeyUp clientId=" + mClientId + "keyCode=" + keyCode 245 + " event=" + event); 246 } 247 if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() && !event.isCanceled()) { 248 final SoftInputWindow window = 249 mSoftInputWindowManager.getSoftInputWindow(mSelfReportedDisplayId); 250 if (window != null && window.isShowing()) { 251 window.hide(); 252 return true; 253 } 254 } 255 return false; 256 } 257 258 @Override onTrackballEvent(MotionEvent event)259 public boolean onTrackballEvent(MotionEvent event) { 260 return false; 261 } 262 } 263