blob: ef9d990168d2c2cb1d79e02868aad082562066d4 [file] [log] [blame]
Tarandeep Singh2cbcd7f2019-01-25 11:47:57 -08001/*
2 * Copyright (C) 2019 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
17package android.view;
18
Tarandeep Singhb9538cd2020-02-20 17:51:18 -080019import static android.view.InsetsController.AnimationType;
Tiger Huang332793b2019-10-29 23:21:27 +080020import static android.view.InsetsState.ITYPE_IME;
Tarandeep Singh2cbcd7f2019-01-25 11:47:57 -080021
Jorim Jaggiabaa9042020-01-29 15:02:50 +010022import android.annotation.Nullable;
Tarandeep Singh46d59f02019-01-29 18:09:15 -080023import android.inputmethodservice.InputMethodService;
Adrian Roosc22eec92020-06-12 18:48:10 +020024import android.os.IBinder;
Tarandeep Singh2cbcd7f2019-01-25 11:47:57 -080025import android.os.Parcel;
26import android.text.TextUtils;
27import android.view.SurfaceControl.Transaction;
Tarandeep Singh2cbcd7f2019-01-25 11:47:57 -080028import android.view.inputmethod.EditorInfo;
29import android.view.inputmethod.InputMethodManager;
30
31import com.android.internal.annotations.VisibleForTesting;
32
33import java.util.Arrays;
34import java.util.function.Supplier;
35
36/**
37 * Controls the visibility and animations of IME window insets source.
38 * @hide
39 */
40public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
41 private EditorInfo mFocusedEditor;
42 private EditorInfo mPreRenderedEditor;
43 /**
44 * Determines if IME would be shown next time IME is pre-rendered for currently focused
45 * editor {@link #mFocusedEditor} if {@link #isServedEditorRendered} is {@code true}.
46 */
47 private boolean mShowOnNextImeRender;
Tarandeep Singh2cbcd7f2019-01-25 11:47:57 -080048
Jorim Jaggi3182ef12020-01-30 00:16:18 +010049 /**
50 * Tracks whether we have an outstanding request from the IME to show, but weren't able to
51 * execute it because we didn't have control yet.
52 */
Taran Singh9641cfc2020-04-09 16:59:10 -070053 private boolean mIsRequestedVisibleAwaitingControl;
Jorim Jaggi3182ef12020-01-30 00:16:18 +010054
Tarandeep Singh2cbcd7f2019-01-25 11:47:57 -080055 public ImeInsetsSourceConsumer(
56 InsetsState state, Supplier<Transaction> transactionSupplier,
57 InsetsController controller) {
Tiger Huang332793b2019-10-29 23:21:27 +080058 super(ITYPE_IME, state, transactionSupplier, controller);
Tarandeep Singh2cbcd7f2019-01-25 11:47:57 -080059 }
60
61 public void onPreRendered(EditorInfo info) {
62 mPreRenderedEditor = info;
63 if (mShowOnNextImeRender) {
64 mShowOnNextImeRender = false;
65 if (isServedEditorRendered()) {
66 applyImeVisibility(true /* setVisible */);
67 }
68 }
69 }
70
71 public void onServedEditorChanged(EditorInfo info) {
72 if (isDummyOrEmptyEditor(info)) {
73 mShowOnNextImeRender = false;
74 }
75 mFocusedEditor = info;
76 }
77
78 public void applyImeVisibility(boolean setVisible) {
Tarandeep Singh46d59f02019-01-29 18:09:15 -080079 mController.applyImeVisibility(setVisible);
Tarandeep Singh2cbcd7f2019-01-25 11:47:57 -080080 }
81
82 @Override
83 public void onWindowFocusGained() {
Tarandeep Singh92d2dd32019-08-07 14:45:01 -070084 super.onWindowFocusGained();
Tarandeep Singh2cbcd7f2019-01-25 11:47:57 -080085 getImm().registerImeConsumer(this);
86 }
87
88 @Override
89 public void onWindowFocusLost() {
Tarandeep Singh92d2dd32019-08-07 14:45:01 -070090 super.onWindowFocusLost();
Louis Chang3d4a7d12019-04-12 16:18:30 +080091 getImm().unregisterImeConsumer(this);
Taran Singh9641cfc2020-04-09 16:59:10 -070092 mIsRequestedVisibleAwaitingControl = false;
Jorim Jaggiabaa9042020-01-29 15:02:50 +010093 }
94
Tarandeep Singh94c9a832020-02-03 14:55:30 -080095 @Override
Tarandeep Singhb9538cd2020-02-20 17:51:18 -080096 void hide(boolean animationFinished, @AnimationType int animationType) {
Tarandeep Singh94c9a832020-02-03 14:55:30 -080097 super.hide();
Tarandeep Singhb9538cd2020-02-20 17:51:18 -080098
Adrian Roos3b19ff12020-04-01 19:48:16 +020099 if (animationFinished) {
Tarandeep Singh94c9a832020-02-03 14:55:30 -0800100 // remove IME surface as IME has finished hide animation.
Adrian Roos3b19ff12020-04-01 19:48:16 +0200101 notifyHidden();
Tarandeep Singh94c9a832020-02-03 14:55:30 -0800102 removeSurface();
103 }
104 }
105
Tarandeep Singh46d59f02019-01-29 18:09:15 -0800106 /**
107 * Request {@link InputMethodManager} to show the IME.
108 * @return @see {@link android.view.InsetsSourceConsumer.ShowResult}.
109 */
110 @Override
Jorim Jaggid7f10ed2020-01-08 21:41:55 +0100111 public @ShowResult int requestShow(boolean fromIme) {
Tarandeep Singh46d59f02019-01-29 18:09:15 -0800112 // TODO: ResultReceiver for IME.
113 // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag.
Jorim Jaggi3182ef12020-01-30 00:16:18 +0100114
Taran Singh9641cfc2020-04-09 16:59:10 -0700115 if (getControl() == null) {
116 // If control is null, schedule to show IME when control is available.
117 mIsRequestedVisibleAwaitingControl = true;
118 }
Jorim Jaggi3182ef12020-01-30 00:16:18 +0100119 // If we had a request before to show from IME (tracked with mImeRequestedShow), reaching
120 // this code here means that we now got control, so we can start the animation immediately.
Tarandeep Singhb9538cd2020-02-20 17:51:18 -0800121 // If client window is trying to control IME and IME is already visible, it is immediate.
Taran Singh9641cfc2020-04-09 16:59:10 -0700122 if (fromIme || mState.getSource(getType()).isVisible()) {
Tarandeep Singh46d59f02019-01-29 18:09:15 -0800123 return ShowResult.SHOW_IMMEDIATELY;
124 }
125
126 return getImm().requestImeShow(null /* resultReceiver */)
Jorim Jaggid7f10ed2020-01-08 21:41:55 +0100127 ? ShowResult.IME_SHOW_DELAYED : ShowResult.IME_SHOW_FAILED;
Tarandeep Singh46d59f02019-01-29 18:09:15 -0800128 }
129
130 /**
131 * Notify {@link InputMethodService} that IME window is hidden.
132 */
133 @Override
134 void notifyHidden() {
135 getImm().notifyImeHidden();
136 }
137
Jorim Jaggi3182ef12020-01-30 00:16:18 +0100138 @Override
Tarandeep Singh94c9a832020-02-03 14:55:30 -0800139 public void removeSurface() {
140 getImm().removeImeSurface();
141 }
142
143 @Override
Jorim Jaggi3182ef12020-01-30 00:16:18 +0100144 public void setControl(@Nullable InsetsSourceControl control, int[] showTypes,
145 int[] hideTypes) {
146 super.setControl(control, showTypes, hideTypes);
Taran Singh9641cfc2020-04-09 16:59:10 -0700147 if (control == null && !mIsRequestedVisibleAwaitingControl) {
Jorim Jaggi3182ef12020-01-30 00:16:18 +0100148 hide();
149 }
150 }
151
Taran Singh9641cfc2020-04-09 16:59:10 -0700152 @Override
153 protected boolean isRequestedVisibleAwaitingControl() {
Taran Singh335ffd02020-04-20 17:10:41 -0700154 return mIsRequestedVisibleAwaitingControl || isRequestedVisible();
Taran Singh9641cfc2020-04-09 16:59:10 -0700155 }
156
Adrian Roosc22eec92020-06-12 18:48:10 +0200157 @Override
158 public void onPerceptible(boolean perceptible) {
159 super.onPerceptible(perceptible);
160 final IBinder window = mController.getHost().getWindowToken();
161 if (window != null) {
162 getImm().reportPerceptible(window, perceptible);
163 }
164 }
165
Tarandeep Singh2cbcd7f2019-01-25 11:47:57 -0800166 private boolean isDummyOrEmptyEditor(EditorInfo info) {
167 // TODO(b/123044812): Handle dummy input gracefully in IME Insets API
168 return info == null || (info.fieldId <= 0 && info.inputType <= 0);
169 }
170
171 private boolean isServedEditorRendered() {
172 if (mFocusedEditor == null || mPreRenderedEditor == null
173 || isDummyOrEmptyEditor(mFocusedEditor)
174 || isDummyOrEmptyEditor(mPreRenderedEditor)) {
175 // No view is focused or ready.
176 return false;
177 }
178 return areEditorsSimilar(mFocusedEditor, mPreRenderedEditor);
179 }
180
181 @VisibleForTesting
182 public static boolean areEditorsSimilar(EditorInfo info1, EditorInfo info2) {
183 // We don't need to compare EditorInfo.fieldId (View#id) since that shouldn't change
184 // IME views.
185 boolean areOptionsSimilar =
186 info1.imeOptions == info2.imeOptions
187 && info1.inputType == info2.inputType
188 && TextUtils.equals(info1.packageName, info2.packageName);
189 areOptionsSimilar &= info1.privateImeOptions != null
190 ? info1.privateImeOptions.equals(info2.privateImeOptions) : true;
191
192 if (!areOptionsSimilar) {
193 return false;
194 }
195
196 // compare bundle extras.
197 if ((info1.extras == null && info2.extras == null) || info1.extras == info2.extras) {
198 return true;
199 }
200 if ((info1.extras == null && info2.extras != null)
201 || (info1.extras == null && info2.extras != null)) {
202 return false;
203 }
204 if (info1.extras.hashCode() == info2.extras.hashCode()
205 || info1.extras.equals(info1)) {
206 return true;
207 }
208 if (info1.extras.size() != info2.extras.size()) {
209 return false;
210 }
211 if (info1.extras.toString().equals(info2.extras.toString())) {
212 return true;
213 }
214
215 // Compare bytes
216 Parcel parcel1 = Parcel.obtain();
217 info1.extras.writeToParcel(parcel1, 0);
218 parcel1.setDataPosition(0);
219 Parcel parcel2 = Parcel.obtain();
220 info2.extras.writeToParcel(parcel2, 0);
221 parcel2.setDataPosition(0);
222
223 return Arrays.equals(parcel1.createByteArray(), parcel2.createByteArray());
224 }
225
226 private InputMethodManager getImm() {
Jorim Jaggibf87c152020-04-22 17:18:25 +0200227 return mController.getHost().getInputMethodManager();
Tarandeep Singh2cbcd7f2019-01-25 11:47:57 -0800228 }
229}