blob: 0513feef801f37a4e8d3a0c21105e342dd544446 [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;
Tadashi G. Takaoka7bd6c202011-01-25 18:02:55 +090024import android.graphics.Rect;
Yohei Yukawa13a9ffb2018-08-29 19:56:02 -070025import android.os.Debug;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.os.IBinder;
Yohei Yukawa13a9ffb2018-08-29 19:56:02 -070027import android.util.Log;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.view.Gravity;
Dianne Hackborn83fe3f52009-09-12 23:38:30 -070029import android.view.KeyEvent;
Tadashi G. Takaoka7bd6c202011-01-25 18:02:55 +090030import android.view.MotionEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.view.WindowManager;
32
Yohei Yukawa13a9ffb2018-08-29 19:56:02 -070033import java.lang.annotation.Retention;
34
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035/**
36 * A SoftInputWindow is a Dialog that is intended to be used for a top-level input
37 * method window. It will be displayed along the edge of the screen, moving
38 * the application user interface away from it so that the focused item is
39 * always visible.
Dianne Hackbornc03c9162014-05-02 10:45:59 -070040 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041 */
Dianne Hackbornc03c9162014-05-02 10:45:59 -070042public class SoftInputWindow extends Dialog {
Yohei Yukawa13a9ffb2018-08-29 19:56:02 -070043 private static final boolean DEBUG = false;
44 private static final String TAG = "SoftInputWindow";
45
Dianne Hackbornc03c9162014-05-02 10:45:59 -070046 final String mName;
47 final Callback mCallback;
48 final KeyEvent.Callback mKeyEventCallback;
Dianne Hackborn83fe3f52009-09-12 23:38:30 -070049 final KeyEvent.DispatcherState mDispatcherState;
Dianne Hackborne30e02f2014-05-27 18:24:45 -070050 final int mWindowType;
51 final int mGravity;
Dianne Hackbornc03c9162014-05-02 10:45:59 -070052 final boolean mTakesFocus;
Tadashi G. Takaoka7bd6c202011-01-25 18:02:55 +090053 private final Rect mBounds = new Rect();
Dianne Hackbornc03c9162014-05-02 10:45:59 -070054
Yohei Yukawa13a9ffb2018-08-29 19:56:02 -070055 @Retention(SOURCE)
56 @IntDef(value = {SoftInputWindowState.TOKEN_PENDING, SoftInputWindowState.TOKEN_SET,
57 SoftInputWindowState.SHOWN_AT_LEAST_ONCE, SoftInputWindowState.REJECTED_AT_LEAST_ONCE})
58 private @interface SoftInputWindowState {
59 /**
60 * The window token is not set yet.
61 */
62 int TOKEN_PENDING = 0;
63 /**
64 * The window token was set, but the window is not shown yet.
65 */
66 int TOKEN_SET = 1;
67 /**
68 * The window was shown at least once.
69 */
70 int SHOWN_AT_LEAST_ONCE = 2;
71 /**
72 * {@link android.view.WindowManager.BadTokenException} was sent when calling
73 * {@link Dialog#show()} at least once.
74 */
75 int REJECTED_AT_LEAST_ONCE = 3;
76 /**
77 * The window is considered destroyed. Any incoming request should be ignored.
78 */
79 int DESTROYED = 4;
80 }
81
82 @SoftInputWindowState
83 private int mWindowState = SoftInputWindowState.TOKEN_PENDING;
84
Dianne Hackbornc03c9162014-05-02 10:45:59 -070085 public interface Callback {
86 public void onBackPressed();
87 }
88
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080089 public void setToken(IBinder token) {
Yohei Yukawa13a9ffb2018-08-29 19:56:02 -070090 switch (mWindowState) {
91 case SoftInputWindowState.TOKEN_PENDING:
92 // Normal scenario. Nothing to worry about.
93 WindowManager.LayoutParams lp = getWindow().getAttributes();
94 lp.token = token;
95 getWindow().setAttributes(lp);
96 updateWindowState(SoftInputWindowState.TOKEN_SET);
97 return;
98 case SoftInputWindowState.TOKEN_SET:
99 case SoftInputWindowState.SHOWN_AT_LEAST_ONCE:
100 case SoftInputWindowState.REJECTED_AT_LEAST_ONCE:
101 throw new IllegalStateException("setToken can be called only once");
102 case SoftInputWindowState.DESTROYED:
103 // Just ignore. Since there are multiple event queues from the token is issued
104 // in the system server to the timing when it arrives here, it can be delivered
105 // after the is already destroyed. No one should be blamed because of such an
106 // unfortunate but possible scenario.
107 Log.i(TAG, "Ignoring setToken() because window is already destroyed.");
108 return;
109 default:
110 throw new IllegalStateException("Unexpected state=" + mWindowState);
111 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112 }
Yohei Yukawa13a9ffb2018-08-29 19:56:02 -0700113
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 /**
Dianne Hackborn2c84cfc2011-10-31 15:39:59 -0700115 * Create a SoftInputWindow that uses a custom style.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116 *
117 * @param context The Context in which the DockWindow should run. In
118 * particular, it uses the window manager and theme from this context
119 * to present its UI.
120 * @param theme A style resource describing the theme to use for the window.
121 * See <a href="{@docRoot}reference/available-resources.html#stylesandthemes">Style
122 * and Theme Resources</a> for more information about defining and
123 * using styles. This theme is applied on top of the current theme in
124 * <var>context</var>. If 0, the default dialog theme will be used.
125 */
Dianne Hackbornc03c9162014-05-02 10:45:59 -0700126 public SoftInputWindow(Context context, String name, int theme, Callback callback,
127 KeyEvent.Callback keyEventCallback, KeyEvent.DispatcherState dispatcherState,
Dianne Hackborne30e02f2014-05-27 18:24:45 -0700128 int windowType, int gravity, boolean takesFocus) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800129 super(context, theme);
Dianne Hackbornc03c9162014-05-02 10:45:59 -0700130 mName = name;
131 mCallback = callback;
132 mKeyEventCallback = keyEventCallback;
Dianne Hackborn83fe3f52009-09-12 23:38:30 -0700133 mDispatcherState = dispatcherState;
Dianne Hackborne30e02f2014-05-27 18:24:45 -0700134 mWindowType = windowType;
135 mGravity = gravity;
Dianne Hackbornc03c9162014-05-02 10:45:59 -0700136 mTakesFocus = takesFocus;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800137 initDockWindow();
138 }
139
Dianne Hackborn83fe3f52009-09-12 23:38:30 -0700140 @Override
141 public void onWindowFocusChanged(boolean hasFocus) {
142 super.onWindowFocusChanged(hasFocus);
143 mDispatcherState.reset();
144 }
145
Tadashi G. Takaoka7bd6c202011-01-25 18:02:55 +0900146 @Override
147 public boolean dispatchTouchEvent(MotionEvent ev) {
148 getWindow().getDecorView().getHitRect(mBounds);
Jeff Brownfe9f8ab2011-05-06 18:20:01 -0700149
150 if (ev.isWithinBoundsNoHistory(mBounds.left, mBounds.top,
151 mBounds.right - 1, mBounds.bottom - 1)) {
152 return super.dispatchTouchEvent(ev);
153 } else {
154 MotionEvent temp = ev.clampNoHistory(mBounds.left, mBounds.top,
155 mBounds.right - 1, mBounds.bottom - 1);
156 boolean handled = super.dispatchTouchEvent(temp);
157 temp.recycle();
158 return handled;
159 }
Tadashi G. Takaoka7bd6c202011-01-25 18:02:55 +0900160 }
161
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800162 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800163 * Set which boundary of the screen the DockWindow sticks to.
164 *
Andrew Solovaya44f2c072018-10-02 14:14:42 -0700165 * @param gravity The boundary of the screen to stick. See {@link
166 * android.view.Gravity.LEFT}, {@link android.view.Gravity.TOP},
167 * {@link android.view.Gravity.BOTTOM}, {@link
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800168 * android.view.Gravity.RIGHT}.
169 */
170 public void setGravity(int gravity) {
171 WindowManager.LayoutParams lp = getWindow().getAttributes();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800172 lp.gravity = gravity;
Dianne Hackborne30e02f2014-05-27 18:24:45 -0700173 updateWidthHeight(lp);
174 getWindow().setAttributes(lp);
175 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800176
Dianne Hackborn20d94742014-05-29 18:35:45 -0700177 public int getGravity() {
178 return getWindow().getAttributes().gravity;
179 }
180
Dianne Hackborne30e02f2014-05-27 18:24:45 -0700181 private void updateWidthHeight(WindowManager.LayoutParams lp) {
182 if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) {
183 lp.width = WindowManager.LayoutParams.MATCH_PARENT;
184 lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
185 } else {
186 lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
187 lp.height = WindowManager.LayoutParams.MATCH_PARENT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800188 }
189 }
190
Dianne Hackbornc03c9162014-05-02 10:45:59 -0700191 public boolean onKeyDown(int keyCode, KeyEvent event) {
192 if (mKeyEventCallback != null && mKeyEventCallback.onKeyDown(keyCode, event)) {
193 return true;
194 }
195 return super.onKeyDown(keyCode, event);
196 }
197
198 public boolean onKeyLongPress(int keyCode, KeyEvent event) {
199 if (mKeyEventCallback != null && mKeyEventCallback.onKeyLongPress(keyCode, event)) {
200 return true;
201 }
202 return super.onKeyLongPress(keyCode, event);
203 }
204
205 public boolean onKeyUp(int keyCode, KeyEvent event) {
206 if (mKeyEventCallback != null && mKeyEventCallback.onKeyUp(keyCode, event)) {
207 return true;
208 }
209 return super.onKeyUp(keyCode, event);
210 }
211
212 public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
213 if (mKeyEventCallback != null && mKeyEventCallback.onKeyMultiple(keyCode, count, event)) {
214 return true;
215 }
216 return super.onKeyMultiple(keyCode, count, event);
217 }
218
219 public void onBackPressed() {
220 if (mCallback != null) {
221 mCallback.onBackPressed();
222 } else {
223 super.onBackPressed();
224 }
225 }
226
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800227 private void initDockWindow() {
228 WindowManager.LayoutParams lp = getWindow().getAttributes();
229
Dianne Hackborne30e02f2014-05-27 18:24:45 -0700230 lp.type = mWindowType;
Dianne Hackbornc03c9162014-05-02 10:45:59 -0700231 lp.setTitle(mName);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800232
Dianne Hackborne30e02f2014-05-27 18:24:45 -0700233 lp.gravity = mGravity;
234 updateWidthHeight(lp);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800235
236 getWindow().setAttributes(lp);
Dianne Hackbornc03c9162014-05-02 10:45:59 -0700237
238 int windowSetFlags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
239 int windowModFlags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800240 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
Dianne Hackbornc03c9162014-05-02 10:45:59 -0700241 WindowManager.LayoutParams.FLAG_DIM_BEHIND;
242
243 if (!mTakesFocus) {
244 windowSetFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
245 } else {
246 windowSetFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
247 windowModFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
248 }
249
250 getWindow().setFlags(windowSetFlags, windowModFlags);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800251 }
Yohei Yukawa13a9ffb2018-08-29 19:56:02 -0700252
253 @Override
254 public final void show() {
255 switch (mWindowState) {
256 case SoftInputWindowState.TOKEN_PENDING:
257 throw new IllegalStateException("Window token is not set yet.");
258 case SoftInputWindowState.TOKEN_SET:
259 case SoftInputWindowState.SHOWN_AT_LEAST_ONCE:
260 // Normal scenario. Nothing to worry about.
261 try {
262 super.show();
263 updateWindowState(SoftInputWindowState.SHOWN_AT_LEAST_ONCE);
264 } catch (WindowManager.BadTokenException e) {
265 // Just ignore this exception. Since show() can be requested from other
266 // components such as the system and there could be multiple event queues before
267 // the request finally arrives here, the system may have already invalidated the
268 // window token attached to our window. In such a scenario, receiving
269 // BadTokenException here is an expected behavior. We just ignore it and update
270 // the state so that we do not touch this window later.
271 Log.i(TAG, "Probably the IME window token is already invalidated."
272 + " show() does nothing.");
273 updateWindowState(SoftInputWindowState.REJECTED_AT_LEAST_ONCE);
274 }
275 return;
276 case SoftInputWindowState.REJECTED_AT_LEAST_ONCE:
277 // Just ignore. In general we cannot completely avoid this kind of race condition.
278 Log.i(TAG, "Not trying to call show() because it was already rejected once.");
279 return;
280 case SoftInputWindowState.DESTROYED:
281 // Just ignore. In general we cannot completely avoid this kind of race condition.
282 Log.i(TAG, "Ignoring show() because the window is already destroyed.");
283 return;
284 default:
285 throw new IllegalStateException("Unexpected state=" + mWindowState);
286 }
287 }
288
289 final void dismissForDestroyIfNecessary() {
290 switch (mWindowState) {
291 case SoftInputWindowState.TOKEN_PENDING:
292 case SoftInputWindowState.TOKEN_SET:
293 // nothing to do because the window has never been shown.
294 updateWindowState(SoftInputWindowState.DESTROYED);
295 return;
296 case SoftInputWindowState.SHOWN_AT_LEAST_ONCE:
297 // Disable exit animation for the current IME window
298 // to avoid the race condition between the exit and enter animations
299 // when the current IME is being switched to another one.
300 try {
301 getWindow().setWindowAnimations(0);
302 dismiss();
303 } catch (WindowManager.BadTokenException e) {
304 // Just ignore this exception. Since show() can be requested from other
305 // components such as the system and there could be multiple event queues before
306 // the request finally arrives here, the system may have already invalidated the
307 // window token attached to our window. In such a scenario, receiving
308 // BadTokenException here is an expected behavior. We just ignore it and update
309 // the state so that we do not touch this window later.
310 Log.i(TAG, "Probably the IME window token is already invalidated. "
311 + "No need to dismiss it.");
312 }
313 // Either way, consider that the window is destroyed.
314 updateWindowState(SoftInputWindowState.DESTROYED);
315 return;
316 case SoftInputWindowState.REJECTED_AT_LEAST_ONCE:
317 // Just ignore. In general we cannot completely avoid this kind of race condition.
318 Log.i(TAG,
319 "Not trying to dismiss the window because it is most likely unnecessary.");
320 // Anyway, consider that the window is destroyed.
321 updateWindowState(SoftInputWindowState.DESTROYED);
322 return;
323 case SoftInputWindowState.DESTROYED:
324 throw new IllegalStateException(
325 "dismissForDestroyIfNecessary can be called only once");
326 default:
327 throw new IllegalStateException("Unexpected state=" + mWindowState);
328 }
329 }
330
331 private void updateWindowState(@SoftInputWindowState int newState) {
332 if (DEBUG) {
333 if (mWindowState != newState) {
334 Log.d(TAG, "WindowState: " + stateToString(mWindowState) + " -> "
335 + stateToString(newState) + " @ " + Debug.getCaller());
336 }
337 }
338 mWindowState = newState;
339 }
340
341 private static String stateToString(@SoftInputWindowState int state) {
342 switch (state) {
343 case SoftInputWindowState.TOKEN_PENDING:
344 return "TOKEN_PENDING";
345 case SoftInputWindowState.TOKEN_SET:
346 return "TOKEN_SET";
347 case SoftInputWindowState.SHOWN_AT_LEAST_ONCE:
348 return "SHOWN_AT_LEAST_ONCE";
349 case SoftInputWindowState.REJECTED_AT_LEAST_ONCE:
350 return "REJECTED_AT_LEAST_ONCE";
351 case SoftInputWindowState.DESTROYED:
352 return "DESTROYED";
353 default:
354 throw new IllegalStateException("Unknown state=" + state);
355 }
356 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800357}