blob: 7026d2b1389c45f590ec595d81e2a4f6e05be242 [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
19import static android.view.InsetsState.TYPE_IME;
20
21import android.os.Parcel;
22import android.text.TextUtils;
23import android.view.SurfaceControl.Transaction;
24import android.view.WindowInsets.Type;
25import android.view.inputmethod.EditorInfo;
26import android.view.inputmethod.InputMethodManager;
27
28import com.android.internal.annotations.VisibleForTesting;
29
30import java.util.Arrays;
31import java.util.function.Supplier;
32
33/**
34 * Controls the visibility and animations of IME window insets source.
35 * @hide
36 */
37public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
38 private EditorInfo mFocusedEditor;
39 private EditorInfo mPreRenderedEditor;
40 /**
41 * Determines if IME would be shown next time IME is pre-rendered for currently focused
42 * editor {@link #mFocusedEditor} if {@link #isServedEditorRendered} is {@code true}.
43 */
44 private boolean mShowOnNextImeRender;
45 private boolean mHasWindowFocus;
46
47 public ImeInsetsSourceConsumer(
48 InsetsState state, Supplier<Transaction> transactionSupplier,
49 InsetsController controller) {
50 super(TYPE_IME, state, transactionSupplier, controller);
51 }
52
53 public void onPreRendered(EditorInfo info) {
54 mPreRenderedEditor = info;
55 if (mShowOnNextImeRender) {
56 mShowOnNextImeRender = false;
57 if (isServedEditorRendered()) {
58 applyImeVisibility(true /* setVisible */);
59 }
60 }
61 }
62
63 public void onServedEditorChanged(EditorInfo info) {
64 if (isDummyOrEmptyEditor(info)) {
65 mShowOnNextImeRender = false;
66 }
67 mFocusedEditor = info;
68 }
69
70 public void applyImeVisibility(boolean setVisible) {
71 if (!mHasWindowFocus) {
72 // App window doesn't have focus, any visibility changes would be no-op.
73 return;
74 }
75
76 if (setVisible) {
77 mController.show(Type.IME);
78 } else {
79 mController.hide(Type.IME);
80 }
81 }
82
83 @Override
84 public void onWindowFocusGained() {
85 mHasWindowFocus = true;
86 getImm().registerImeConsumer(this);
87 }
88
89 @Override
90 public void onWindowFocusLost() {
91 mHasWindowFocus = false;
92 }
93
94 private boolean isDummyOrEmptyEditor(EditorInfo info) {
95 // TODO(b/123044812): Handle dummy input gracefully in IME Insets API
96 return info == null || (info.fieldId <= 0 && info.inputType <= 0);
97 }
98
99 private boolean isServedEditorRendered() {
100 if (mFocusedEditor == null || mPreRenderedEditor == null
101 || isDummyOrEmptyEditor(mFocusedEditor)
102 || isDummyOrEmptyEditor(mPreRenderedEditor)) {
103 // No view is focused or ready.
104 return false;
105 }
106 return areEditorsSimilar(mFocusedEditor, mPreRenderedEditor);
107 }
108
109 @VisibleForTesting
110 public static boolean areEditorsSimilar(EditorInfo info1, EditorInfo info2) {
111 // We don't need to compare EditorInfo.fieldId (View#id) since that shouldn't change
112 // IME views.
113 boolean areOptionsSimilar =
114 info1.imeOptions == info2.imeOptions
115 && info1.inputType == info2.inputType
116 && TextUtils.equals(info1.packageName, info2.packageName);
117 areOptionsSimilar &= info1.privateImeOptions != null
118 ? info1.privateImeOptions.equals(info2.privateImeOptions) : true;
119
120 if (!areOptionsSimilar) {
121 return false;
122 }
123
124 // compare bundle extras.
125 if ((info1.extras == null && info2.extras == null) || info1.extras == info2.extras) {
126 return true;
127 }
128 if ((info1.extras == null && info2.extras != null)
129 || (info1.extras == null && info2.extras != null)) {
130 return false;
131 }
132 if (info1.extras.hashCode() == info2.extras.hashCode()
133 || info1.extras.equals(info1)) {
134 return true;
135 }
136 if (info1.extras.size() != info2.extras.size()) {
137 return false;
138 }
139 if (info1.extras.toString().equals(info2.extras.toString())) {
140 return true;
141 }
142
143 // Compare bytes
144 Parcel parcel1 = Parcel.obtain();
145 info1.extras.writeToParcel(parcel1, 0);
146 parcel1.setDataPosition(0);
147 Parcel parcel2 = Parcel.obtain();
148 info2.extras.writeToParcel(parcel2, 0);
149 parcel2.setDataPosition(0);
150
151 return Arrays.equals(parcel1.createByteArray(), parcel2.createByteArray());
152 }
153
154 private InputMethodManager getImm() {
155 return mController.getViewRoot().mDisplayContext.getSystemService(InputMethodManager.class);
156 }
157}