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.HAS_PENDING_REQUEST; 21 import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER; 22 import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL; 23 24 import android.annotation.Nullable; 25 import android.os.IBinder; 26 import android.os.Process; 27 import android.os.Trace; 28 import android.util.proto.ProtoOutputStream; 29 import android.view.SurfaceControl.Transaction; 30 import android.view.inputmethod.ImeTracker; 31 import android.view.inputmethod.InputMethodManager; 32 33 import com.android.internal.inputmethod.ImeTracing; 34 import com.android.internal.inputmethod.SoftInputShowHideReason; 35 36 import java.util.function.Supplier; 37 38 /** 39 * Controls the visibility and animations of IME window insets source. 40 * @hide 41 */ 42 public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { 43 44 /** 45 * Tracks whether are requested to show during the hide animation or requested to hide during 46 * the show animation. If this is true, we should not remove the surface. 47 */ 48 private boolean mHasPendingRequest; 49 50 /** 51 * Tracks whether we have an outstanding request from the IME to show, but weren't able to 52 * execute it because we didn't have control yet. 53 */ 54 private boolean mIsRequestedVisibleAwaitingControl; 55 ImeInsetsSourceConsumer( int id, InsetsState state, Supplier<Transaction> transactionSupplier, InsetsController controller)56 public ImeInsetsSourceConsumer( 57 int id, InsetsState state, Supplier<Transaction> transactionSupplier, 58 InsetsController controller) { 59 super(id, WindowInsets.Type.ime(), state, transactionSupplier, controller); 60 } 61 62 @Override onAnimationStateChanged(boolean running)63 public boolean onAnimationStateChanged(boolean running) { 64 if (!running) { 65 ImeTracing.getInstance().triggerClientDump( 66 "ImeInsetsSourceConsumer#onAnimationFinished", 67 mController.getHost().getInputMethodManager(), null /* icProto */); 68 } 69 final boolean insetsChanged = super.onAnimationStateChanged(running); 70 if (!isShowRequested()) { 71 mIsRequestedVisibleAwaitingControl = false; 72 if (!running && !mHasPendingRequest) { 73 notifyHidden(null /* statsToken */); 74 removeSurface(); 75 } 76 } 77 // This method is called 78 // (1) after the animation starts. 79 // (2) after the animation ends (including the case of cancel). 80 // (3) if the IME is not controllable (running == false in this case). 81 // We should reset mHasPendingRequest in all cases. 82 mHasPendingRequest = false; 83 return insetsChanged; 84 } 85 86 @Override onWindowFocusGained(boolean hasViewFocus)87 public void onWindowFocusGained(boolean hasViewFocus) { 88 super.onWindowFocusGained(hasViewFocus); 89 getImm().registerImeConsumer(this); 90 if ((mController.getRequestedVisibleTypes() & getType()) != 0 && getControl() == null) { 91 mIsRequestedVisibleAwaitingControl = true; 92 } 93 } 94 95 @Override onWindowFocusLost()96 public void onWindowFocusLost() { 97 super.onWindowFocusLost(); 98 getImm().unregisterImeConsumer(this); 99 mIsRequestedVisibleAwaitingControl = false; 100 } 101 102 @Override applyLocalVisibilityOverride()103 public boolean applyLocalVisibilityOverride() { 104 ImeTracing.getInstance().triggerClientDump( 105 "ImeInsetsSourceConsumer#applyLocalVisibilityOverride", 106 mController.getHost().getInputMethodManager(), null /* icProto */); 107 return super.applyLocalVisibilityOverride(); 108 } 109 110 /** 111 * Request {@link InputMethodManager} to show the IME. 112 * @return @see {@link android.view.InsetsSourceConsumer.ShowResult}. 113 */ 114 @Override 115 @ShowResult requestShow(boolean fromIme, @Nullable ImeTracker.Token statsToken)116 public int requestShow(boolean fromIme, @Nullable ImeTracker.Token statsToken) { 117 if (fromIme) { 118 ImeTracing.getInstance().triggerClientDump( 119 "ImeInsetsSourceConsumer#requestShow", 120 mController.getHost().getInputMethodManager(), null /* icProto */); 121 } 122 onShowRequested(); 123 124 // TODO: ResultReceiver for IME. 125 // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag. 126 ImeTracker.forLogging().onProgress(statsToken, 127 ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW); 128 129 if (getControl() == null) { 130 // If control is null, schedule to show IME when control is available. 131 mIsRequestedVisibleAwaitingControl = true; 132 } 133 // If we had a request before to show from IME (tracked with mImeRequestedShow), reaching 134 // this code here means that we now got control, so we can start the animation immediately. 135 // If client window is trying to control IME and IME is already visible, it is immediate. 136 if (fromIme 137 || (mState.isSourceOrDefaultVisible(getId(), getType()) && getControl() != null)) { 138 return ShowResult.SHOW_IMMEDIATELY; 139 } 140 141 return getImm().requestImeShow(mController.getHost().getWindowToken(), statsToken) 142 ? ShowResult.IME_SHOW_DELAYED : ShowResult.IME_SHOW_FAILED; 143 } 144 requestHide(boolean fromIme, @Nullable ImeTracker.Token statsToken)145 void requestHide(boolean fromIme, @Nullable ImeTracker.Token statsToken) { 146 if (!fromIme) { 147 // The insets might be controlled by a remote target. Let the server know we are 148 // requested to hide. 149 notifyHidden(statsToken); 150 } 151 if (mAnimationState == ANIMATION_STATE_SHOW) { 152 mHasPendingRequest = true; 153 } 154 } 155 156 /** 157 * Notify {@link com.android.server.inputmethod.InputMethodManagerService} that 158 * IME insets are hidden. 159 * 160 * @param statsToken the token tracking the current IME hide request or {@code null} otherwise. 161 */ notifyHidden(@ullable ImeTracker.Token statsToken)162 private void notifyHidden(@Nullable ImeTracker.Token statsToken) { 163 // Create a new stats token to track the hide request when: 164 // - we do not already have one, or 165 // - we do already have one, but we have control and use the passed in token 166 // for the insets animation already. 167 if (statsToken == null || getControl() != null) { 168 statsToken = ImeTracker.forLogging().onRequestHide(null /* component */, 169 Process.myUid(), 170 ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, 171 SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API); 172 } 173 174 ImeTracker.forLogging().onProgress(statsToken, 175 ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN); 176 177 getImm().notifyImeHidden(mController.getHost().getWindowToken(), statsToken); 178 mIsRequestedVisibleAwaitingControl = false; 179 Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0); 180 } 181 182 @Override removeSurface()183 public void removeSurface() { 184 final IBinder window = mController.getHost().getWindowToken(); 185 if (window != null) { 186 getImm().removeImeSurface(window); 187 } 188 } 189 190 @Override setControl(@ullable InsetsSourceControl control, int[] showTypes, int[] hideTypes)191 public boolean setControl(@Nullable InsetsSourceControl control, int[] showTypes, 192 int[] hideTypes) { 193 ImeTracing.getInstance().triggerClientDump("ImeInsetsSourceConsumer#setControl", 194 mController.getHost().getInputMethodManager(), null /* icProto */); 195 if (!super.setControl(control, showTypes, hideTypes)) { 196 return false; 197 } 198 if (control == null && !mIsRequestedVisibleAwaitingControl) { 199 mController.setRequestedVisibleTypes(0 /* visibleTypes */, getType()); 200 removeSurface(); 201 } 202 if (control != null) { 203 mIsRequestedVisibleAwaitingControl = false; 204 } 205 return true; 206 } 207 208 @Override isRequestedVisibleAwaitingControl()209 protected boolean isRequestedVisibleAwaitingControl() { 210 return super.isRequestedVisibleAwaitingControl() || mIsRequestedVisibleAwaitingControl; 211 } 212 213 @Override onPerceptible(boolean perceptible)214 public void onPerceptible(boolean perceptible) { 215 super.onPerceptible(perceptible); 216 final IBinder window = mController.getHost().getWindowToken(); 217 if (window != null) { 218 getImm().reportPerceptible(window, perceptible); 219 } 220 } 221 222 @Override dumpDebug(ProtoOutputStream proto, long fieldId)223 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 224 final long token = proto.start(fieldId); 225 super.dumpDebug(proto, INSETS_SOURCE_CONSUMER); 226 proto.write(IS_REQUESTED_VISIBLE_AWAITING_CONTROL, mIsRequestedVisibleAwaitingControl); 227 proto.write(HAS_PENDING_REQUEST, mHasPendingRequest); 228 proto.end(token); 229 } 230 231 /** 232 * Called when {@link #requestShow(boolean, ImeTracker.Token)} or 233 * {@link InputMethodManager#showSoftInput(View, int)} is called. 234 */ onShowRequested()235 public void onShowRequested() { 236 if (mAnimationState == ANIMATION_STATE_HIDE) { 237 mHasPendingRequest = true; 238 } 239 } 240 getImm()241 private InputMethodManager getImm() { 242 return mController.getHost().getInputMethodManager(); 243 } 244 } 245