blob: 35286ba007c4d809ba5e808eec4f4575f538248a [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.ANIMATION_TYPE_USER;
20import static android.view.InsetsController.AnimationType;
Tiger Huang332793b2019-10-29 23:21:27 +080021import static android.view.InsetsState.ITYPE_IME;
Tarandeep Singh2cbcd7f2019-01-25 11:47:57 -080022
Jorim Jaggiabaa9042020-01-29 15:02:50 +010023import android.annotation.Nullable;
Tarandeep Singh46d59f02019-01-29 18:09:15 -080024import android.inputmethodservice.InputMethodService;
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 */
53 private boolean mImeRequestedShow;
54
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);
Jorim Jaggi3182ef12020-01-30 00:16:18 +010092 mImeRequestedShow = false;
Tarandeep Singh2cbcd7f2019-01-25 11:47:57 -080093 }
94
Jorim Jaggiabaa9042020-01-29 15:02:50 +010095 @Override
Jorim Jaggi3182ef12020-01-30 00:16:18 +010096 public void show(boolean fromIme) {
97 super.show(fromIme);
98 if (fromIme) {
99 mImeRequestedShow = true;
Jorim Jaggiabaa9042020-01-29 15:02:50 +0100100 }
101 }
102
Tarandeep Singh94c9a832020-02-03 14:55:30 -0800103 @Override
Tarandeep Singhb9538cd2020-02-20 17:51:18 -0800104 void hide(boolean animationFinished, @AnimationType int animationType) {
Tarandeep Singh94c9a832020-02-03 14:55:30 -0800105 super.hide();
Tarandeep Singhb9538cd2020-02-20 17:51:18 -0800106
107 if (!animationFinished) {
108 if (animationType == ANIMATION_TYPE_USER) {
109 // if controlWindowInsetsAnimation is hiding keyboard.
110 notifyHidden();
111 }
112 } else {
Tarandeep Singh94c9a832020-02-03 14:55:30 -0800113 // remove IME surface as IME has finished hide animation.
114 removeSurface();
115 }
116 }
117
Tarandeep Singh46d59f02019-01-29 18:09:15 -0800118 /**
119 * Request {@link InputMethodManager} to show the IME.
120 * @return @see {@link android.view.InsetsSourceConsumer.ShowResult}.
121 */
122 @Override
Jorim Jaggid7f10ed2020-01-08 21:41:55 +0100123 public @ShowResult int requestShow(boolean fromIme) {
Tarandeep Singh46d59f02019-01-29 18:09:15 -0800124 // TODO: ResultReceiver for IME.
125 // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag.
Jorim Jaggi3182ef12020-01-30 00:16:18 +0100126
127 // If we had a request before to show from IME (tracked with mImeRequestedShow), reaching
128 // this code here means that we now got control, so we can start the animation immediately.
Tarandeep Singhb9538cd2020-02-20 17:51:18 -0800129 // If client window is trying to control IME and IME is already visible, it is immediate.
130 if (fromIme || mImeRequestedShow || mState.getSource(getType()).isVisible()) {
Jorim Jaggi3182ef12020-01-30 00:16:18 +0100131 mImeRequestedShow = false;
Tarandeep Singh46d59f02019-01-29 18:09:15 -0800132 return ShowResult.SHOW_IMMEDIATELY;
133 }
134
135 return getImm().requestImeShow(null /* resultReceiver */)
Jorim Jaggid7f10ed2020-01-08 21:41:55 +0100136 ? ShowResult.IME_SHOW_DELAYED : ShowResult.IME_SHOW_FAILED;
Tarandeep Singh46d59f02019-01-29 18:09:15 -0800137 }
138
139 /**
140 * Notify {@link InputMethodService} that IME window is hidden.
141 */
142 @Override
143 void notifyHidden() {
144 getImm().notifyImeHidden();
145 }
146
Jorim Jaggi3182ef12020-01-30 00:16:18 +0100147 @Override
Tarandeep Singh94c9a832020-02-03 14:55:30 -0800148 public void removeSurface() {
149 getImm().removeImeSurface();
150 }
151
152 @Override
Jorim Jaggi3182ef12020-01-30 00:16:18 +0100153 public void setControl(@Nullable InsetsSourceControl control, int[] showTypes,
154 int[] hideTypes) {
155 super.setControl(control, showTypes, hideTypes);
156 if (control == null) {
157 hide();
158 }
159 }
160
Tarandeep Singh2cbcd7f2019-01-25 11:47:57 -0800161 private boolean isDummyOrEmptyEditor(EditorInfo info) {
162 // TODO(b/123044812): Handle dummy input gracefully in IME Insets API
163 return info == null || (info.fieldId <= 0 && info.inputType <= 0);
164 }
165
166 private boolean isServedEditorRendered() {
167 if (mFocusedEditor == null || mPreRenderedEditor == null
168 || isDummyOrEmptyEditor(mFocusedEditor)
169 || isDummyOrEmptyEditor(mPreRenderedEditor)) {
170 // No view is focused or ready.
171 return false;
172 }
173 return areEditorsSimilar(mFocusedEditor, mPreRenderedEditor);
174 }
175
176 @VisibleForTesting
177 public static boolean areEditorsSimilar(EditorInfo info1, EditorInfo info2) {
178 // We don't need to compare EditorInfo.fieldId (View#id) since that shouldn't change
179 // IME views.
180 boolean areOptionsSimilar =
181 info1.imeOptions == info2.imeOptions
182 && info1.inputType == info2.inputType
183 && TextUtils.equals(info1.packageName, info2.packageName);
184 areOptionsSimilar &= info1.privateImeOptions != null
185 ? info1.privateImeOptions.equals(info2.privateImeOptions) : true;
186
187 if (!areOptionsSimilar) {
188 return false;
189 }
190
191 // compare bundle extras.
192 if ((info1.extras == null && info2.extras == null) || info1.extras == info2.extras) {
193 return true;
194 }
195 if ((info1.extras == null && info2.extras != null)
196 || (info1.extras == null && info2.extras != null)) {
197 return false;
198 }
199 if (info1.extras.hashCode() == info2.extras.hashCode()
200 || info1.extras.equals(info1)) {
201 return true;
202 }
203 if (info1.extras.size() != info2.extras.size()) {
204 return false;
205 }
206 if (info1.extras.toString().equals(info2.extras.toString())) {
207 return true;
208 }
209
210 // Compare bytes
211 Parcel parcel1 = Parcel.obtain();
212 info1.extras.writeToParcel(parcel1, 0);
213 parcel1.setDataPosition(0);
214 Parcel parcel2 = Parcel.obtain();
215 info2.extras.writeToParcel(parcel2, 0);
216 parcel2.setDataPosition(0);
217
218 return Arrays.equals(parcel1.createByteArray(), parcel2.createByteArray());
219 }
220
221 private InputMethodManager getImm() {
Charles Chen5299ad02019-04-18 13:09:53 +0000222 return mController.getViewRoot().mContext.getSystemService(InputMethodManager.class);
Tarandeep Singh2cbcd7f2019-01-25 11:47:57 -0800223 }
224}