1 /* 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 17 package android.view; 18 19 import static android.view.InsetsController.AnimationType; 20 import static android.view.InsetsState.ITYPE_IME; 21 22 import android.annotation.Nullable; 23 import android.inputmethodservice.InputMethodService; 24 import android.os.IBinder; 25 import android.os.Parcel; 26 import android.text.TextUtils; 27 import android.view.SurfaceControl.Transaction; 28 import android.view.inputmethod.EditorInfo; 29 import android.view.inputmethod.InputMethodManager; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 33 import java.util.Arrays; 34 import java.util.function.Supplier; 35 36 /** 37 * Controls the visibility and animations of IME window insets source. 38 * @hide 39 */ 40 public 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; 48 49 /** 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 mIsRequestedVisibleAwaitingControl; 54 ImeInsetsSourceConsumer( InsetsState state, Supplier<Transaction> transactionSupplier, InsetsController controller)55 public ImeInsetsSourceConsumer( 56 InsetsState state, Supplier<Transaction> transactionSupplier, 57 InsetsController controller) { 58 super(ITYPE_IME, state, transactionSupplier, controller); 59 } 60 onPreRendered(EditorInfo info)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 onServedEditorChanged(EditorInfo info)71 public void onServedEditorChanged(EditorInfo info) { 72 if (isDummyOrEmptyEditor(info)) { 73 mShowOnNextImeRender = false; 74 } 75 mFocusedEditor = info; 76 } 77 applyImeVisibility(boolean setVisible)78 public void applyImeVisibility(boolean setVisible) { 79 mController.applyImeVisibility(setVisible); 80 } 81 82 @Override onWindowFocusGained()83 public void onWindowFocusGained() { 84 super.onWindowFocusGained(); 85 getImm().registerImeConsumer(this); 86 } 87 88 @Override onWindowFocusLost()89 public void onWindowFocusLost() { 90 super.onWindowFocusLost(); 91 getImm().unregisterImeConsumer(this); 92 mIsRequestedVisibleAwaitingControl = false; 93 } 94 95 @Override hide(boolean animationFinished, @AnimationType int animationType)96 void hide(boolean animationFinished, @AnimationType int animationType) { 97 super.hide(); 98 99 if (animationFinished) { 100 // remove IME surface as IME has finished hide animation. 101 notifyHidden(); 102 removeSurface(); 103 } 104 } 105 106 /** 107 * Request {@link InputMethodManager} to show the IME. 108 * @return @see {@link android.view.InsetsSourceConsumer.ShowResult}. 109 */ 110 @Override requestShow(boolean fromIme)111 public @ShowResult int requestShow(boolean fromIme) { 112 // TODO: ResultReceiver for IME. 113 // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag. 114 115 if (getControl() == null) { 116 // If control is null, schedule to show IME when control is available. 117 mIsRequestedVisibleAwaitingControl = true; 118 } 119 // 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. 121 // If client window is trying to control IME and IME is already visible, it is immediate. 122 if (fromIme || mState.getSource(getType()).isVisible() && getControl() != null) { 123 return ShowResult.SHOW_IMMEDIATELY; 124 } 125 126 return getImm().requestImeShow(mController.getHost().getWindowToken()) 127 ? ShowResult.IME_SHOW_DELAYED : ShowResult.IME_SHOW_FAILED; 128 } 129 130 /** 131 * Notify {@link InputMethodService} that IME window is hidden. 132 */ 133 @Override notifyHidden()134 void notifyHidden() { 135 getImm().notifyImeHidden(mController.getHost().getWindowToken()); 136 } 137 138 @Override removeSurface()139 public void removeSurface() { 140 final IBinder window = mController.getHost().getWindowToken(); 141 if (window != null) { 142 getImm().removeImeSurface(window); 143 } 144 } 145 146 @Override setControl(@ullable InsetsSourceControl control, int[] showTypes, int[] hideTypes)147 public void setControl(@Nullable InsetsSourceControl control, int[] showTypes, 148 int[] hideTypes) { 149 super.setControl(control, showTypes, hideTypes); 150 if (control == null && !mIsRequestedVisibleAwaitingControl) { 151 hide(); 152 removeSurface(); 153 } 154 } 155 156 @Override isRequestedVisibleAwaitingControl()157 protected boolean isRequestedVisibleAwaitingControl() { 158 return mIsRequestedVisibleAwaitingControl || isRequestedVisible(); 159 } 160 161 @Override onPerceptible(boolean perceptible)162 public void onPerceptible(boolean perceptible) { 163 super.onPerceptible(perceptible); 164 final IBinder window = mController.getHost().getWindowToken(); 165 if (window != null) { 166 getImm().reportPerceptible(window, perceptible); 167 } 168 } 169 isDummyOrEmptyEditor(EditorInfo info)170 private boolean isDummyOrEmptyEditor(EditorInfo info) { 171 // TODO(b/123044812): Handle dummy input gracefully in IME Insets API 172 return info == null || (info.fieldId <= 0 && info.inputType <= 0); 173 } 174 isServedEditorRendered()175 private boolean isServedEditorRendered() { 176 if (mFocusedEditor == null || mPreRenderedEditor == null 177 || isDummyOrEmptyEditor(mFocusedEditor) 178 || isDummyOrEmptyEditor(mPreRenderedEditor)) { 179 // No view is focused or ready. 180 return false; 181 } 182 return areEditorsSimilar(mFocusedEditor, mPreRenderedEditor); 183 } 184 185 @VisibleForTesting areEditorsSimilar(EditorInfo info1, EditorInfo info2)186 public static boolean areEditorsSimilar(EditorInfo info1, EditorInfo info2) { 187 // We don't need to compare EditorInfo.fieldId (View#id) since that shouldn't change 188 // IME views. 189 boolean areOptionsSimilar = 190 info1.imeOptions == info2.imeOptions 191 && info1.inputType == info2.inputType 192 && TextUtils.equals(info1.packageName, info2.packageName); 193 areOptionsSimilar &= info1.privateImeOptions != null 194 ? info1.privateImeOptions.equals(info2.privateImeOptions) : true; 195 196 if (!areOptionsSimilar) { 197 return false; 198 } 199 200 // compare bundle extras. 201 if ((info1.extras == null && info2.extras == null) || info1.extras == info2.extras) { 202 return true; 203 } 204 if ((info1.extras == null && info2.extras != null) 205 || (info1.extras == null && info2.extras != null)) { 206 return false; 207 } 208 if (info1.extras.hashCode() == info2.extras.hashCode() 209 || info1.extras.equals(info1)) { 210 return true; 211 } 212 if (info1.extras.size() != info2.extras.size()) { 213 return false; 214 } 215 if (info1.extras.toString().equals(info2.extras.toString())) { 216 return true; 217 } 218 219 // Compare bytes 220 Parcel parcel1 = Parcel.obtain(); 221 info1.extras.writeToParcel(parcel1, 0); 222 parcel1.setDataPosition(0); 223 Parcel parcel2 = Parcel.obtain(); 224 info2.extras.writeToParcel(parcel2, 0); 225 parcel2.setDataPosition(0); 226 227 return Arrays.equals(parcel1.createByteArray(), parcel2.createByteArray()); 228 } 229 getImm()230 private InputMethodManager getImm() { 231 return mController.getHost().getInputMethodManager(); 232 } 233 } 234