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.os.Trace.TRACE_TAG_VIEW; 20 import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER; 21 import static android.view.ImeInsetsSourceConsumerProto.IS_HIDE_ANIMATION_RUNNING; 22 import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL; 23 import static android.view.ImeInsetsSourceConsumerProto.IS_SHOW_REQUESTED_DURING_HIDE_ANIMATION; 24 import static android.view.InsetsController.AnimationType; 25 import static android.view.InsetsState.ITYPE_IME; 26 27 import android.annotation.Nullable; 28 import android.os.IBinder; 29 import android.os.Trace; 30 import android.util.proto.ProtoOutputStream; 31 import android.view.SurfaceControl.Transaction; 32 import android.view.inputmethod.InputMethodManager; 33 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 42 /** 43 * Tracks whether we have an outstanding request from the IME to show, but weren't able to 44 * execute it because we didn't have control yet. 45 */ 46 private boolean mIsRequestedVisibleAwaitingControl; 47 48 private boolean mIsHideAnimationRunning; 49 50 /** 51 * Tracks whether {@link WindowInsetsController#show(int)} or 52 * {@link InputMethodManager#showSoftInput(View, int)} is called during IME hide animation. 53 * If it was called, we should not call {@link InputMethodManager#notifyImeHidden(IBinder)}, 54 * because the IME is being shown. 55 */ 56 private boolean mIsShowRequestedDuringHideAnimation; 57 ImeInsetsSourceConsumer( InsetsState state, Supplier<Transaction> transactionSupplier, InsetsController controller)58 public ImeInsetsSourceConsumer( 59 InsetsState state, Supplier<Transaction> transactionSupplier, 60 InsetsController controller) { 61 super(ITYPE_IME, state, transactionSupplier, controller); 62 } 63 64 @Override onWindowFocusGained(boolean hasViewFocus)65 public void onWindowFocusGained(boolean hasViewFocus) { 66 super.onWindowFocusGained(hasViewFocus); 67 getImm().registerImeConsumer(this); 68 if (isRequestedVisible() && getControl() == null) { 69 mIsRequestedVisibleAwaitingControl = true; 70 } 71 } 72 73 @Override onWindowFocusLost()74 public void onWindowFocusLost() { 75 super.onWindowFocusLost(); 76 getImm().unregisterImeConsumer(this); 77 mIsRequestedVisibleAwaitingControl = false; 78 } 79 80 @Override show(boolean fromIme)81 public void show(boolean fromIme) { 82 super.show(fromIme); 83 onShowRequested(); 84 } 85 86 @Override hide()87 public void hide() { 88 super.hide(); 89 mIsRequestedVisibleAwaitingControl = false; 90 } 91 92 @Override hide(boolean animationFinished, @AnimationType int animationType)93 void hide(boolean animationFinished, @AnimationType int animationType) { 94 hide(); 95 96 if (animationFinished) { 97 // Remove IME surface as IME has finished hide animation, if there is no pending 98 // show request. 99 if (!mIsShowRequestedDuringHideAnimation) { 100 notifyHidden(); 101 removeSurface(); 102 } 103 } 104 // This method is called 105 // (1) before the hide animation starts. 106 // (2) after the hide animation ends. 107 // (3) if the IME is not controllable (animationFinished == true in this case). 108 // We should reset mIsShowRequestedDuringHideAnimation in all cases. 109 mIsHideAnimationRunning = !animationFinished; 110 mIsShowRequestedDuringHideAnimation = false; 111 } 112 113 /** 114 * Request {@link InputMethodManager} to show the IME. 115 * @return @see {@link android.view.InsetsSourceConsumer.ShowResult}. 116 */ 117 @Override requestShow(boolean fromIme)118 public @ShowResult int requestShow(boolean fromIme) { 119 // TODO: ResultReceiver for IME. 120 // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag. 121 if (getControl() == null) { 122 // If control is null, schedule to show IME when control is available. 123 mIsRequestedVisibleAwaitingControl = true; 124 } 125 // If we had a request before to show from IME (tracked with mImeRequestedShow), reaching 126 // this code here means that we now got control, so we can start the animation immediately. 127 // If client window is trying to control IME and IME is already visible, it is immediate. 128 if (fromIme || mState.getSource(getType()).isVisible() && getControl() != null) { 129 return ShowResult.SHOW_IMMEDIATELY; 130 } 131 132 return getImm().requestImeShow(mController.getHost().getWindowToken()) 133 ? ShowResult.IME_SHOW_DELAYED : ShowResult.IME_SHOW_FAILED; 134 } 135 136 /** 137 * Notify {@link com.android.server.inputmethod.InputMethodManagerService} that 138 * IME insets are hidden. 139 */ 140 @Override notifyHidden()141 void notifyHidden() { 142 getImm().notifyImeHidden(mController.getHost().getWindowToken()); 143 Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0); 144 } 145 146 @Override removeSurface()147 public void removeSurface() { 148 final IBinder window = mController.getHost().getWindowToken(); 149 if (window != null) { 150 getImm().removeImeSurface(window); 151 } 152 } 153 154 @Override setControl(@ullable InsetsSourceControl control, int[] showTypes, int[] hideTypes)155 public boolean setControl(@Nullable InsetsSourceControl control, int[] showTypes, 156 int[] hideTypes) { 157 if (!super.setControl(control, showTypes, hideTypes)) { 158 return false; 159 } 160 if (control == null && !mIsRequestedVisibleAwaitingControl) { 161 hide(); 162 removeSurface(); 163 } 164 if (control != null) { 165 mIsRequestedVisibleAwaitingControl = false; 166 } 167 return true; 168 } 169 170 @Override isRequestedVisibleAwaitingControl()171 protected boolean isRequestedVisibleAwaitingControl() { 172 return mIsRequestedVisibleAwaitingControl || isRequestedVisible(); 173 } 174 175 @Override onPerceptible(boolean perceptible)176 public void onPerceptible(boolean perceptible) { 177 super.onPerceptible(perceptible); 178 final IBinder window = mController.getHost().getWindowToken(); 179 if (window != null) { 180 getImm().reportPerceptible(window, perceptible); 181 } 182 } 183 184 @Override dumpDebug(ProtoOutputStream proto, long fieldId)185 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 186 final long token = proto.start(fieldId); 187 super.dumpDebug(proto, INSETS_SOURCE_CONSUMER); 188 proto.write(IS_REQUESTED_VISIBLE_AWAITING_CONTROL, mIsRequestedVisibleAwaitingControl); 189 proto.write(IS_HIDE_ANIMATION_RUNNING, mIsHideAnimationRunning); 190 proto.write(IS_SHOW_REQUESTED_DURING_HIDE_ANIMATION, mIsShowRequestedDuringHideAnimation); 191 proto.end(token); 192 } 193 194 /** 195 * Called when {@link #show} or {@link InputMethodManager#showSoftInput(View, int)} is called. 196 */ onShowRequested()197 public void onShowRequested() { 198 if (mIsHideAnimationRunning) { 199 mIsShowRequestedDuringHideAnimation = true; 200 } 201 } 202 getImm()203 private InputMethodManager getImm() { 204 return mController.getHost().getInputMethodManager(); 205 } 206 } 207