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