blob: 356b3448430a6d7e711021f74237ce3bed8a6e5f [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2007-2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package android.inputmethodservice;
18
Yohei Yukawa13a9ffb2018-08-29 19:56:02 -070019import static java.lang.annotation.RetentionPolicy.SOURCE;
20
21import android.annotation.IntDef;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.app.Dialog;
23import android.content.Context;
Heemin Seog28097c42019-08-15 13:18:41 -070024import android.content.pm.PackageManager;
Tadashi G. Takaoka7bd6c202011-01-25 18:02:55 +090025import android.graphics.Rect;
Yohei Yukawa13a9ffb2018-08-29 19:56:02 -070026import android.os.Debug;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.os.IBinder;
Yohei Yukawa13a9ffb2018-08-29 19:56:02 -070028import android.util.Log;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.view.Gravity;
Dianne Hackborn83fe3f52009-09-12 23:38:30 -070030import android.view.KeyEvent;
Tadashi G. Takaoka7bd6c202011-01-25 18:02:55 +090031import android.view.MotionEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.view.WindowManager;
33
Yohei Yukawa13a9ffb2018-08-29 19:56:02 -070034import java.lang.annotation.Retention;
35
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036/**
37 * A SoftInputWindow is a 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
39 * the application user interface away from it so that the focused item is
40 * always visible.
Dianne Hackbornc03c9162014-05-02 10:45:59 -070041 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042 */
Dianne Hackbornc03c9162014-05-02 10:45:59 -070043public class SoftInputWindow extends Dialog {
Yohei Yukawa13a9ffb2018-08-29 19:56:02 -070044 private static final boolean DEBUG = false;
45 private static final String TAG = "SoftInputWindow";
46
Dianne Hackbornc03c9162014-05-02 10:45:59 -070047 final String mName;
48 final Callback mCallback;
49 final KeyEvent.Callback mKeyEventCallback;
Dianne Hackborn83fe3f52009-09-12 23:38:30 -070050 final KeyEvent.DispatcherState mDispatcherState;
Dianne Hackborne30e02f2014-05-27 18:24:45 -070051 final int mWindowType;
52 final int mGravity;
Dianne Hackbornc03c9162014-05-02 10:45:59 -070053 final boolean mTakesFocus;
Heemin Seog28097c42019-08-15 13:18:41 -070054 final boolean mAutomotiveHideNavBarForKeyboard;
Tadashi G. Takaoka7bd6c202011-01-25 18:02:55 +090055 private final Rect mBounds = new Rect();
Dianne Hackbornc03c9162014-05-02 10:45:59 -070056
Yohei Yukawa13a9ffb2018-08-29 19:56:02 -070057 @Retention(SOURCE)
58 @IntDef(value = {SoftInputWindowState.TOKEN_PENDING, SoftInputWindowState.TOKEN_SET,
59 SoftInputWindowState.SHOWN_AT_LEAST_ONCE, SoftInputWindowState.REJECTED_AT_LEAST_ONCE})
60 private @interface SoftInputWindowState {
61 /**
62 * The window token is not set yet.
63 */
64 int TOKEN_PENDING = 0;
65 /**
66 * The window token was set, but the window is not shown yet.
67 */
68 int TOKEN_SET = 1;
69 /**
70 * The window was shown at least once.
71 */
72 int SHOWN_AT_LEAST_ONCE = 2;
73 /**
74 * {@link android.view.WindowManager.BadTokenException} was sent when calling
75 * {@link Dialog#show()} at least once.
76 */
77 int REJECTED_AT_LEAST_ONCE = 3;
78 /**
79 * The window is considered destroyed. Any incoming request should be ignored.
80 */
81 int DESTROYED = 4;
82 }
83
84 @SoftInputWindowState
85 private int mWindowState = SoftInputWindowState.TOKEN_PENDING;
86
Dianne Hackbornc03c9162014-05-02 10:45:59 -070087 public interface Callback {
88 public void onBackPressed();
89 }
90
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080091 public void setToken(IBinder token) {
Yohei Yukawa13a9ffb2018-08-29 19:56:02 -070092 switch (mWindowState) {
93 case SoftInputWindowState.TOKEN_PENDING:
94 // Normal scenario. Nothing to worry about.
95 WindowManager.LayoutParams lp = getWindow().getAttributes();
96 lp.token = token;
97 getWindow().setAttributes(lp);
98 updateWindowState(SoftInputWindowState.TOKEN_SET);
99 return;
100 case SoftInputWindowState.TOKEN_SET:
101 case SoftInputWindowState.SHOWN_AT_LEAST_ONCE:
102 case SoftInputWindowState.REJECTED_AT_LEAST_ONCE:
103 throw new IllegalStateException("setToken can be called only once");
104 case SoftInputWindowState.DESTROYED:
105 // Just ignore. Since there are multiple event queues from the token is issued
106 // in the system server to the timing when it arrives here, it can be delivered
107 // after the is already destroyed. No one should be blamed because of such an
108 // unfortunate but possible scenario.
109 Log.i(TAG, "Ignoring setToken() because window is already destroyed.");
110 return;
111 default:
112 throw new IllegalStateException("Unexpected state=" + mWindowState);
113 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 }
Yohei Yukawa13a9ffb2018-08-29 19:56:02 -0700115
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116 /**
Dianne Hackborn2c84cfc2011-10-31 15:39:59 -0700117 * Create a SoftInputWindow that uses a custom style.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800118 *
119 * @param context The Context in which the DockWindow should run. In
120 * particular, it uses the window manager and theme from this context
121 * to present its UI.
122 * @param theme A style resource describing the theme to use for the window.
123 * See <a href="{@docRoot}reference/available-resources.html#stylesandthemes">Style
124 * and Theme Resources</a> for more information about defining and
125 * using styles. This theme is applied on top of the current theme in
126 * <var>context</var>. If 0, the default dialog theme will be used.
127 */
Dianne Hackbornc03c9162014-05-02 10:45:59 -0700128 public SoftInputWindow(Context context, String name, int theme, Callback callback,
129 KeyEvent.Callback keyEventCallback, KeyEvent.DispatcherState dispatcherState,
Dianne Hackborne30e02f2014-05-27 18:24:45 -0700130 int windowType, int gravity, boolean takesFocus) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800131 super(context, theme);
Dianne Hackbornc03c9162014-05-02 10:45:59 -0700132 mName = name;
133 mCallback = callback;
134 mKeyEventCallback = keyEventCallback;
Dianne Hackborn83fe3f52009-09-12 23:38:30 -0700135 mDispatcherState = dispatcherState;
Dianne Hackborne30e02f2014-05-27 18:24:45 -0700136 mWindowType = windowType;
137 mGravity = gravity;
Dianne Hackbornc03c9162014-05-02 10:45:59 -0700138 mTakesFocus = takesFocus;
Heemin Seog28097c42019-08-15 13:18:41 -0700139 mAutomotiveHideNavBarForKeyboard = context.getResources().getBoolean(
140 com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800141 initDockWindow();
142 }
143
Dianne Hackborn83fe3f52009-09-12 23:38:30 -0700144 @Override
145 public void onWindowFocusChanged(boolean hasFocus) {
146 super.onWindowFocusChanged(hasFocus);
147 mDispatcherState.reset();
148 }
149
Tadashi G. Takaoka7bd6c202011-01-25 18:02:55 +0900150 @Override
151 public boolean dispatchTouchEvent(MotionEvent ev) {
152 getWindow().getDecorView().getHitRect(mBounds);
Jeff Brownfe9f8ab2011-05-06 18:20:01 -0700153
154 if (ev.isWithinBoundsNoHistory(mBounds.left, mBounds.top,
155 mBounds.right - 1, mBounds.bottom - 1)) {
156 return super.dispatchTouchEvent(ev);
157 } else {
158 MotionEvent temp = ev.clampNoHistory(mBounds.left, mBounds.top,
159 mBounds.right - 1, mBounds.bottom - 1);
160 boolean handled = super.dispatchTouchEvent(temp);
161 temp.recycle();
162 return handled;
163 }
Tadashi G. Takaoka7bd6c202011-01-25 18:02:55 +0900164 }
165
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800166 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800167 * Set which boundary of the screen the DockWindow sticks to.
168 *
Andrew Solovay5c05ded2018-10-02 14:14:42 -0700169 * @param gravity The boundary of the screen to stick. See {@link
170 * android.view.Gravity.LEFT}, {@link android.view.Gravity.TOP},
171 * {@link android.view.Gravity.BOTTOM}, {@link
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800172 * android.view.Gravity.RIGHT}.
173 */
174 public void setGravity(int gravity) {
175 WindowManager.LayoutParams lp = getWindow().getAttributes();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800176 lp.gravity = gravity;
Dianne Hackborne30e02f2014-05-27 18:24:45 -0700177 updateWidthHeight(lp);
178 getWindow().setAttributes(lp);
179 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800180
Dianne Hackborn20d94742014-05-29 18:35:45 -0700181 public int getGravity() {
182 return getWindow().getAttributes().gravity;
183 }
184
Dianne Hackborne30e02f2014-05-27 18:24:45 -0700185 private void updateWidthHeight(WindowManager.LayoutParams lp) {
186 if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) {
187 lp.width = WindowManager.LayoutParams.MATCH_PARENT;
188 lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
189 } else {
190 lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
191 lp.height = WindowManager.LayoutParams.MATCH_PARENT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800192 }
193 }
194
Dianne Hackbornc03c9162014-05-02 10:45:59 -0700195 public boolean onKeyDown(int keyCode, KeyEvent event) {
196 if (mKeyEventCallback != null && mKeyEventCallback.onKeyDown(keyCode, event)) {
197 return true;
198 }
199 return super.onKeyDown(keyCode, event);
200 }
201
202 public boolean onKeyLongPress(int keyCode, KeyEvent event) {
203 if (mKeyEventCallback != null && mKeyEventCallback.onKeyLongPress(keyCode, event)) {
204 return true;
205 }
206 return super.onKeyLongPress(keyCode, event);
207 }
208
209 public boolean onKeyUp(int keyCode, KeyEvent event) {
210 if (mKeyEventCallback != null && mKeyEventCallback.onKeyUp(keyCode, event)) {
211 return true;
212 }
213 return super.onKeyUp(keyCode, event);
214 }
215
216 public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
217 if (mKeyEventCallback != null && mKeyEventCallback.onKeyMultiple(keyCode, count, event)) {
218 return true;
219 }
220 return super.onKeyMultiple(keyCode, count, event);
221 }
222
223 public void onBackPressed() {
224 if (mCallback != null) {
225 mCallback.onBackPressed();
226 } else {
227 super.onBackPressed();
228 }
229 }
230
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800231 private void initDockWindow() {
232 WindowManager.LayoutParams lp = getWindow().getAttributes();
233
Dianne Hackborne30e02f2014-05-27 18:24:45 -0700234 lp.type = mWindowType;
Dianne Hackbornc03c9162014-05-02 10:45:59 -0700235 lp.setTitle(mName);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800236
Dianne Hackborne30e02f2014-05-27 18:24:45 -0700237 lp.gravity = mGravity;
238 updateWidthHeight(lp);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800239
240 getWindow().setAttributes(lp);
Dianne Hackbornc03c9162014-05-02 10:45:59 -0700241
242 int windowSetFlags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
243 int windowModFlags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800244 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
Dianne Hackbornc03c9162014-05-02 10:45:59 -0700245 WindowManager.LayoutParams.FLAG_DIM_BEHIND;
246
247 if (!mTakesFocus) {
248 windowSetFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
249 } else {
250 windowSetFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
251 windowModFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
252 }
253
Heemin Seog28097c42019-08-15 13:18:41 -0700254 if (isAutomotive() && mAutomotiveHideNavBarForKeyboard) {
255 windowSetFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
256 windowModFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
257 }
258
Dianne Hackbornc03c9162014-05-02 10:45:59 -0700259 getWindow().setFlags(windowSetFlags, windowModFlags);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800260 }
Yohei Yukawa13a9ffb2018-08-29 19:56:02 -0700261
262 @Override
263 public final void show() {
264 switch (mWindowState) {
265 case SoftInputWindowState.TOKEN_PENDING:
266 throw new IllegalStateException("Window token is not set yet.");
267 case SoftInputWindowState.TOKEN_SET:
268 case SoftInputWindowState.SHOWN_AT_LEAST_ONCE:
269 // Normal scenario. Nothing to worry about.
270 try {
271 super.show();
272 updateWindowState(SoftInputWindowState.SHOWN_AT_LEAST_ONCE);
273 } catch (WindowManager.BadTokenException e) {
274 // Just ignore this exception. Since show() can be requested from other
275 // components such as the system and there could be multiple event queues before
276 // the request finally arrives here, the system may have already invalidated the
277 // window token attached to our window. In such a scenario, receiving
278 // BadTokenException here is an expected behavior. We just ignore it and update
279 // the state so that we do not touch this window later.
280 Log.i(TAG, "Probably the IME window token is already invalidated."
281 + " show() does nothing.");
282 updateWindowState(SoftInputWindowState.REJECTED_AT_LEAST_ONCE);
283 }
284 return;
285 case SoftInputWindowState.REJECTED_AT_LEAST_ONCE:
286 // Just ignore. In general we cannot completely avoid this kind of race condition.
287 Log.i(TAG, "Not trying to call show() because it was already rejected once.");
288 return;
289 case SoftInputWindowState.DESTROYED:
290 // Just ignore. In general we cannot completely avoid this kind of race condition.
291 Log.i(TAG, "Ignoring show() because the window is already destroyed.");
292 return;
293 default:
294 throw new IllegalStateException("Unexpected state=" + mWindowState);
295 }
296 }
297
298 final void dismissForDestroyIfNecessary() {
299 switch (mWindowState) {
300 case SoftInputWindowState.TOKEN_PENDING:
301 case SoftInputWindowState.TOKEN_SET:
302 // nothing to do because the window has never been shown.
303 updateWindowState(SoftInputWindowState.DESTROYED);
304 return;
305 case SoftInputWindowState.SHOWN_AT_LEAST_ONCE:
306 // Disable exit animation for the current IME window
307 // to avoid the race condition between the exit and enter animations
308 // when the current IME is being switched to another one.
309 try {
310 getWindow().setWindowAnimations(0);
311 dismiss();
312 } catch (WindowManager.BadTokenException e) {
313 // Just ignore this exception. Since show() can be requested from other
314 // components such as the system and there could be multiple event queues before
315 // the request finally arrives here, the system may have already invalidated the
316 // window token attached to our window. In such a scenario, receiving
317 // BadTokenException here is an expected behavior. We just ignore it and update
318 // the state so that we do not touch this window later.
319 Log.i(TAG, "Probably the IME window token is already invalidated. "
320 + "No need to dismiss it.");
321 }
322 // Either way, consider that the window is destroyed.
323 updateWindowState(SoftInputWindowState.DESTROYED);
324 return;
325 case SoftInputWindowState.REJECTED_AT_LEAST_ONCE:
326 // Just ignore. In general we cannot completely avoid this kind of race condition.
327 Log.i(TAG,
328 "Not trying to dismiss the window because it is most likely unnecessary.");
329 // Anyway, consider that the window is destroyed.
330 updateWindowState(SoftInputWindowState.DESTROYED);
331 return;
332 case SoftInputWindowState.DESTROYED:
333 throw new IllegalStateException(
334 "dismissForDestroyIfNecessary can be called only once");
335 default:
336 throw new IllegalStateException("Unexpected state=" + mWindowState);
337 }
338 }
339
340 private void updateWindowState(@SoftInputWindowState int newState) {
341 if (DEBUG) {
342 if (mWindowState != newState) {
343 Log.d(TAG, "WindowState: " + stateToString(mWindowState) + " -> "
344 + stateToString(newState) + " @ " + Debug.getCaller());
345 }
346 }
347 mWindowState = newState;
348 }
349
Heemin Seog28097c42019-08-15 13:18:41 -0700350 private boolean isAutomotive() {
351 return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
352 }
353
Yohei Yukawa13a9ffb2018-08-29 19:56:02 -0700354 private static String stateToString(@SoftInputWindowState int state) {
355 switch (state) {
356 case SoftInputWindowState.TOKEN_PENDING:
357 return "TOKEN_PENDING";
358 case SoftInputWindowState.TOKEN_SET:
359 return "TOKEN_SET";
360 case SoftInputWindowState.SHOWN_AT_LEAST_ONCE:
361 return "SHOWN_AT_LEAST_ONCE";
362 case SoftInputWindowState.REJECTED_AT_LEAST_ONCE:
363 return "REJECTED_AT_LEAST_ONCE";
364 case SoftInputWindowState.DESTROYED:
365 return "DESTROYED";
366 default:
367 throw new IllegalStateException("Unknown state=" + state);
368 }
369 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800370}