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.InsetsState.TYPE_IME; 20 21 import android.inputmethodservice.InputMethodService; 22 import android.os.Parcel; 23 import android.text.TextUtils; 24 import android.view.SurfaceControl.Transaction; 25 import android.view.inputmethod.EditorInfo; 26 import android.view.inputmethod.InputMethodManager; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 30 import java.util.Arrays; 31 import java.util.function.Supplier; 32 33 /** 34 * Controls the visibility and animations of IME window insets source. 35 * @hide 36 */ 37 public 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 ImeInsetsSourceConsumer( InsetsState state, Supplier<Transaction> transactionSupplier, InsetsController controller)47 public ImeInsetsSourceConsumer( 48 InsetsState state, Supplier<Transaction> transactionSupplier, 49 InsetsController controller) { 50 super(TYPE_IME, state, transactionSupplier, controller); 51 } 52 onPreRendered(EditorInfo info)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 onServedEditorChanged(EditorInfo info)63 public void onServedEditorChanged(EditorInfo info) { 64 if (isDummyOrEmptyEditor(info)) { 65 mShowOnNextImeRender = false; 66 } 67 mFocusedEditor = info; 68 } 69 applyImeVisibility(boolean setVisible)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 mController.applyImeVisibility(setVisible); 77 } 78 79 @Override onWindowFocusGained()80 public void onWindowFocusGained() { 81 mHasWindowFocus = true; 82 getImm().registerImeConsumer(this); 83 } 84 85 @Override onWindowFocusLost()86 public void onWindowFocusLost() { 87 mHasWindowFocus = false; 88 getImm().unregisterImeConsumer(this); 89 } 90 91 /** 92 * Request {@link InputMethodManager} to show the IME. 93 * @return @see {@link android.view.InsetsSourceConsumer.ShowResult}. 94 */ 95 @Override requestShow(boolean fromIme)96 @ShowResult int requestShow(boolean fromIme) { 97 // TODO: ResultReceiver for IME. 98 // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag. 99 if (fromIme) { 100 return ShowResult.SHOW_IMMEDIATELY; 101 } 102 103 return getImm().requestImeShow(null /* resultReceiver */) 104 ? ShowResult.SHOW_DELAYED : ShowResult.SHOW_FAILED; 105 } 106 107 /** 108 * Notify {@link InputMethodService} that IME window is hidden. 109 */ 110 @Override notifyHidden()111 void notifyHidden() { 112 getImm().notifyImeHidden(); 113 } 114 isDummyOrEmptyEditor(EditorInfo info)115 private boolean isDummyOrEmptyEditor(EditorInfo info) { 116 // TODO(b/123044812): Handle dummy input gracefully in IME Insets API 117 return info == null || (info.fieldId <= 0 && info.inputType <= 0); 118 } 119 isServedEditorRendered()120 private boolean isServedEditorRendered() { 121 if (mFocusedEditor == null || mPreRenderedEditor == null 122 || isDummyOrEmptyEditor(mFocusedEditor) 123 || isDummyOrEmptyEditor(mPreRenderedEditor)) { 124 // No view is focused or ready. 125 return false; 126 } 127 return areEditorsSimilar(mFocusedEditor, mPreRenderedEditor); 128 } 129 130 @VisibleForTesting areEditorsSimilar(EditorInfo info1, EditorInfo info2)131 public static boolean areEditorsSimilar(EditorInfo info1, EditorInfo info2) { 132 // We don't need to compare EditorInfo.fieldId (View#id) since that shouldn't change 133 // IME views. 134 boolean areOptionsSimilar = 135 info1.imeOptions == info2.imeOptions 136 && info1.inputType == info2.inputType 137 && TextUtils.equals(info1.packageName, info2.packageName); 138 areOptionsSimilar &= info1.privateImeOptions != null 139 ? info1.privateImeOptions.equals(info2.privateImeOptions) : true; 140 141 if (!areOptionsSimilar) { 142 return false; 143 } 144 145 // compare bundle extras. 146 if ((info1.extras == null && info2.extras == null) || info1.extras == info2.extras) { 147 return true; 148 } 149 if ((info1.extras == null && info2.extras != null) 150 || (info1.extras == null && info2.extras != null)) { 151 return false; 152 } 153 if (info1.extras.hashCode() == info2.extras.hashCode() 154 || info1.extras.equals(info1)) { 155 return true; 156 } 157 if (info1.extras.size() != info2.extras.size()) { 158 return false; 159 } 160 if (info1.extras.toString().equals(info2.extras.toString())) { 161 return true; 162 } 163 164 // Compare bytes 165 Parcel parcel1 = Parcel.obtain(); 166 info1.extras.writeToParcel(parcel1, 0); 167 parcel1.setDataPosition(0); 168 Parcel parcel2 = Parcel.obtain(); 169 info2.extras.writeToParcel(parcel2, 0); 170 parcel2.setDataPosition(0); 171 172 return Arrays.equals(parcel1.createByteArray(), parcel2.createByteArray()); 173 } 174 getImm()175 private InputMethodManager getImm() { 176 return mController.getViewRoot().mContext.getSystemService(InputMethodManager.class); 177 } 178 } 179