1 /* 2 * Copyright (C) 2007 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 android.inputmethodservice; 18 19 import static android.inputmethodservice.SoftInputWindowProto.WINDOW_STATE; 20 21 import static java.lang.annotation.RetentionPolicy.SOURCE; 22 23 import android.annotation.IntDef; 24 import android.app.Dialog; 25 import android.os.Debug; 26 import android.os.IBinder; 27 import android.util.Log; 28 import android.util.proto.ProtoOutputStream; 29 import android.view.KeyEvent; 30 import android.view.MotionEvent; 31 import android.view.View; 32 import android.view.WindowManager; 33 34 import java.lang.annotation.Retention; 35 36 /** 37 * A {@link SoftInputWindow} is a {@link Dialog} that is intended to be used for a top-level input 38 * method window. It will be displayed along the edge of the screen, moving the application user 39 * interface away from it so that the focused item is always visible. 40 */ 41 final class SoftInputWindow extends Dialog { 42 private static final boolean DEBUG = false; 43 private static final String TAG = "SoftInputWindow"; 44 45 private final KeyEvent.DispatcherState mDispatcherState; 46 private final InputMethodService mService; 47 48 @Retention(SOURCE) 49 @IntDef(value = {WindowState.TOKEN_PENDING, WindowState.TOKEN_SET, 50 WindowState.SHOWN_AT_LEAST_ONCE, WindowState.REJECTED_AT_LEAST_ONCE, 51 WindowState.DESTROYED}) 52 private @interface WindowState { 53 /** 54 * The window token is not set yet. 55 */ 56 int TOKEN_PENDING = 0; 57 /** 58 * The window token was set, but the window is not shown yet. 59 */ 60 int TOKEN_SET = 1; 61 /** 62 * The window was shown at least once. 63 */ 64 int SHOWN_AT_LEAST_ONCE = 2; 65 /** 66 * {@link android.view.WindowManager.BadTokenException} was sent when calling 67 * {@link Dialog#show()} at least once. 68 */ 69 int REJECTED_AT_LEAST_ONCE = 3; 70 /** 71 * The window is considered destroyed. Any incoming request should be ignored. 72 */ 73 int DESTROYED = 4; 74 } 75 76 @WindowState 77 private int mWindowState = WindowState.TOKEN_PENDING; 78 79 @Override allowsRegisterDefaultOnBackInvokedCallback()80 protected boolean allowsRegisterDefaultOnBackInvokedCallback() { 81 // Do not register OnBackInvokedCallback from Dialog#onStart, InputMethodService will 82 // register CompatOnBackInvokedCallback for input method window. 83 return false; 84 } 85 86 /** 87 * Set {@link IBinder} window token to the window. 88 * 89 * <p>This method can be called only once.</p> 90 * @param token {@link IBinder} token to be associated with the window. 91 */ setToken(IBinder token)92 void setToken(IBinder token) { 93 switch (mWindowState) { 94 case WindowState.TOKEN_PENDING: 95 // Normal scenario. Nothing to worry about. 96 WindowManager.LayoutParams lp = getWindow().getAttributes(); 97 lp.token = token; 98 getWindow().setAttributes(lp); 99 updateWindowState(WindowState.TOKEN_SET); 100 101 // As soon as we have a token, make sure the window is added (but not shown) by 102 // setting visibility to INVISIBLE and calling show() on Dialog. Note that 103 // WindowInsetsController.OnControllableInsetsChangedListener relies on the window 104 // being added to function. 105 getWindow().getDecorView().setVisibility(View.INVISIBLE); 106 show(); 107 return; 108 case WindowState.TOKEN_SET: 109 case WindowState.SHOWN_AT_LEAST_ONCE: 110 case WindowState.REJECTED_AT_LEAST_ONCE: 111 throw new IllegalStateException("setToken can be called only once"); 112 case WindowState.DESTROYED: 113 // Just ignore. Since there are multiple event queues from the token is issued 114 // in the system server to the timing when it arrives here, it can be delivered 115 // after the is already destroyed. No one should be blamed because of such an 116 // unfortunate but possible scenario. 117 Log.i(TAG, "Ignoring setToken() because window is already destroyed."); 118 return; 119 default: 120 throw new IllegalStateException("Unexpected state=" + mWindowState); 121 } 122 } 123 124 /** 125 * Create a SoftInputWindow that uses a custom style. 126 * 127 * @param service The {@link InputMethodService} in which the DockWindow should run. In 128 * particular, it uses the window manager and theme from this context 129 * to present its UI. 130 * @param theme A style resource describing the theme to use for the window. 131 * See <a href="{@docRoot}reference/available-resources.html#stylesandthemes">Style 132 * and Theme Resources</a> for more information about defining and 133 * using styles. This theme is applied on top of the current theme in 134 * <var>context</var>. If 0, the default dialog theme will be used. 135 */ SoftInputWindow(InputMethodService service, int theme, KeyEvent.DispatcherState dispatcherState)136 SoftInputWindow(InputMethodService service, int theme, 137 KeyEvent.DispatcherState dispatcherState) { 138 super(service, theme); 139 mService = service; 140 mDispatcherState = dispatcherState; 141 } 142 143 @Override onWindowFocusChanged(boolean hasFocus)144 public void onWindowFocusChanged(boolean hasFocus) { 145 super.onWindowFocusChanged(hasFocus); 146 mDispatcherState.reset(); 147 } 148 149 @Override show()150 public void show() { 151 switch (mWindowState) { 152 case WindowState.TOKEN_PENDING: 153 throw new IllegalStateException("Window token is not set yet."); 154 case WindowState.TOKEN_SET: 155 case WindowState.SHOWN_AT_LEAST_ONCE: 156 // Normal scenario. Nothing to worry about. 157 try { 158 super.show(); 159 updateWindowState(WindowState.SHOWN_AT_LEAST_ONCE); 160 } catch (WindowManager.BadTokenException 161 | WindowManager.InvalidDisplayException e) { 162 // Just ignore this exception. Since show() can be requested from other 163 // components such as the system and there could be multiple event queues before 164 // the request finally arrives here, the system may have already invalidated the 165 // window token attached to our window. In such a scenario, receiving 166 // BadTokenException here is an expected behavior. We just ignore it and update 167 // the state so that we do not touch this window later. 168 Log.i(TAG, "Probably the IME window token is already invalidated." 169 + " show() does nothing."); 170 updateWindowState(WindowState.REJECTED_AT_LEAST_ONCE); 171 } 172 return; 173 case WindowState.REJECTED_AT_LEAST_ONCE: 174 // Just ignore. In general we cannot completely avoid this kind of race condition. 175 Log.i(TAG, "Not trying to call show() because it was already rejected once."); 176 return; 177 case WindowState.DESTROYED: 178 // Just ignore. In general we cannot completely avoid this kind of race condition. 179 Log.i(TAG, "Ignoring show() because the window is already destroyed."); 180 return; 181 default: 182 throw new IllegalStateException("Unexpected state=" + mWindowState); 183 } 184 } 185 dismissForDestroyIfNecessary()186 void dismissForDestroyIfNecessary() { 187 switch (mWindowState) { 188 case WindowState.TOKEN_PENDING: 189 case WindowState.TOKEN_SET: 190 // nothing to do because the window has never been shown. 191 updateWindowState(WindowState.DESTROYED); 192 return; 193 case WindowState.SHOWN_AT_LEAST_ONCE: 194 // Disable exit animation for the current IME window 195 // to avoid the race condition between the exit and enter animations 196 // when the current IME is being switched to another one. 197 try { 198 getWindow().setWindowAnimations(0); 199 dismiss(); 200 } catch (WindowManager.BadTokenException e) { 201 // Just ignore this exception. Since show() can be requested from other 202 // components such as the system and there could be multiple event queues before 203 // the request finally arrives here, the system may have already invalidated the 204 // window token attached to our window. In such a scenario, receiving 205 // BadTokenException here is an expected behavior. We just ignore it and update 206 // the state so that we do not touch this window later. 207 Log.i(TAG, "Probably the IME window token is already invalidated. " 208 + "No need to dismiss it."); 209 } 210 // Either way, consider that the window is destroyed. 211 updateWindowState(WindowState.DESTROYED); 212 return; 213 case WindowState.REJECTED_AT_LEAST_ONCE: 214 // Just ignore. In general we cannot completely avoid this kind of race condition. 215 Log.i(TAG, 216 "Not trying to dismiss the window because it is most likely unnecessary."); 217 // Anyway, consider that the window is destroyed. 218 updateWindowState(WindowState.DESTROYED); 219 return; 220 case WindowState.DESTROYED: 221 throw new IllegalStateException( 222 "dismissForDestroyIfNecessary can be called only once"); 223 default: 224 throw new IllegalStateException("Unexpected state=" + mWindowState); 225 } 226 } 227 updateWindowState(@indowState int newState)228 private void updateWindowState(@WindowState int newState) { 229 if (DEBUG) { 230 if (mWindowState != newState) { 231 Log.d(TAG, "WindowState: " + stateToString(mWindowState) + " -> " 232 + stateToString(newState) + " @ " + Debug.getCaller()); 233 } 234 } 235 mWindowState = newState; 236 } 237 stateToString(@indowState int state)238 private static String stateToString(@WindowState int state) { 239 switch (state) { 240 case WindowState.TOKEN_PENDING: 241 return "TOKEN_PENDING"; 242 case WindowState.TOKEN_SET: 243 return "TOKEN_SET"; 244 case WindowState.SHOWN_AT_LEAST_ONCE: 245 return "SHOWN_AT_LEAST_ONCE"; 246 case WindowState.REJECTED_AT_LEAST_ONCE: 247 return "REJECTED_AT_LEAST_ONCE"; 248 case WindowState.DESTROYED: 249 return "DESTROYED"; 250 default: 251 throw new IllegalStateException("Unknown state=" + state); 252 } 253 } 254 dumpDebug(ProtoOutputStream proto, long fieldId)255 void dumpDebug(ProtoOutputStream proto, long fieldId) { 256 final long token = proto.start(fieldId); 257 proto.write(WINDOW_STATE, mWindowState); 258 proto.end(token); 259 } 260 } 261