| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| |
| package android.view; |
| |
| import static android.os.Trace.TRACE_TAG_VIEW; |
| import static android.view.ImeInsetsSourceConsumerProto.HAS_PENDING_REQUEST; |
| import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER; |
| import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.os.IBinder; |
| import android.os.Trace; |
| import android.util.proto.ProtoOutputStream; |
| import android.view.SurfaceControl.Transaction; |
| import android.view.inputmethod.ImeTracker; |
| import android.view.inputmethod.InputMethodManager; |
| |
| import com.android.internal.inputmethod.ImeTracing; |
| import com.android.internal.inputmethod.SoftInputShowHideReason; |
| |
| import java.util.function.Supplier; |
| |
| /** |
| * Controls the visibility and animations of IME window insets source. |
| * @hide |
| */ |
| public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { |
| |
| /** |
| * Tracks whether are requested to show during the hide animation or requested to hide during |
| * the show animation. If this is true, we should not remove the surface. |
| */ |
| private boolean mHasPendingRequest; |
| |
| /** |
| * Tracks whether we have an outstanding request from the IME to show, but weren't able to |
| * execute it because we didn't have control yet, or we didn't have a leash on the control yet. |
| */ |
| private boolean mIsRequestedVisibleAwaitingLeash; |
| |
| public ImeInsetsSourceConsumer( |
| int id, InsetsState state, Supplier<Transaction> transactionSupplier, |
| InsetsController controller) { |
| super(id, WindowInsets.Type.ime(), state, transactionSupplier, controller); |
| } |
| |
| @Override |
| public boolean onAnimationStateChanged(boolean running) { |
| if (!running) { |
| ImeTracing.getInstance().triggerClientDump( |
| "ImeInsetsSourceConsumer#onAnimationFinished", |
| mController.getHost().getInputMethodManager(), null /* icProto */); |
| } |
| boolean insetsChanged = super.onAnimationStateChanged(running); |
| if (running && !isShowRequested() && mController.isPredictiveBackImeHideAnimInProgress()) { |
| // IME predictive back animation switched from pre-commit to post-commit. |
| insetsChanged |= applyLocalVisibilityOverride(); |
| } |
| |
| if (!isShowRequested()) { |
| mIsRequestedVisibleAwaitingLeash = false; |
| if (!running && !mHasPendingRequest) { |
| final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, |
| ImeTracker.ORIGIN_CLIENT, |
| SoftInputShowHideReason.HIDE_SOFT_INPUT_ON_ANIMATION_STATE_CHANGED, |
| mController.getHost().isHandlingPointerEvent() /* fromUser */); |
| notifyHidden(statsToken); |
| removeSurface(); |
| } |
| } |
| // This method is called |
| // (1) after the animation starts. |
| // (2) after the animation ends (including the case of cancel). |
| // (3) if the IME is not controllable (running == false in this case). |
| // We should reset mHasPendingRequest in all cases. |
| mHasPendingRequest = false; |
| return insetsChanged; |
| } |
| |
| @Override |
| public void onWindowFocusGained(boolean hasViewFocus) { |
| super.onWindowFocusGained(hasViewFocus); |
| getImm().registerImeConsumer(this); |
| if ((mController.getRequestedVisibleTypes() & getType()) != 0 && !hasLeash()) { |
| mIsRequestedVisibleAwaitingLeash = true; |
| } |
| } |
| |
| @Override |
| public void onWindowFocusLost() { |
| super.onWindowFocusLost(); |
| getImm().unregisterImeConsumer(this); |
| mIsRequestedVisibleAwaitingLeash = false; |
| } |
| |
| @Override |
| public boolean applyLocalVisibilityOverride() { |
| ImeTracing.getInstance().triggerClientDump( |
| "ImeInsetsSourceConsumer#applyLocalVisibilityOverride", |
| mController.getHost().getInputMethodManager(), null /* icProto */); |
| return super.applyLocalVisibilityOverride(); |
| } |
| |
| /** |
| * Request {@link InputMethodManager} to show the IME. |
| * @return @see {@link android.view.InsetsSourceConsumer.ShowResult}. |
| */ |
| @Override |
| @ShowResult |
| public int requestShow(boolean fromIme, @Nullable ImeTracker.Token statsToken) { |
| if (fromIme) { |
| ImeTracing.getInstance().triggerClientDump( |
| "ImeInsetsSourceConsumer#requestShow", |
| mController.getHost().getInputMethodManager(), null /* icProto */); |
| } |
| onShowRequested(); |
| |
| // TODO: ResultReceiver for IME. |
| // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag. |
| ImeTracker.forLogging().onProgress(statsToken, |
| ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW); |
| |
| if (!hasLeash()) { |
| // If control or leash is null, schedule to show IME when both available. |
| mIsRequestedVisibleAwaitingLeash = true; |
| } |
| // If we had a request before to show from IME (tracked with mImeRequestedShow), reaching |
| // this code here means that we now got control, so we can start the animation immediately. |
| // If client window is trying to control IME and IME is already visible, it is immediate. |
| if (fromIme |
| || (mState.isSourceOrDefaultVisible(getId(), getType()) && hasLeash())) { |
| return ShowResult.SHOW_IMMEDIATELY; |
| } |
| |
| return getImm().requestImeShow(mController.getHost().getWindowToken(), statsToken) |
| ? ShowResult.IME_SHOW_DELAYED : ShowResult.IME_SHOW_FAILED; |
| } |
| |
| void requestHide(boolean fromIme, @Nullable ImeTracker.Token statsToken) { |
| if (!fromIme) { |
| // Create a new token to track the hide request when we have control and leash, |
| // as we use the passed in token for the insets animation already. |
| final var notifyStatsToken = hasLeash() |
| ? ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, |
| ImeTracker.ORIGIN_CLIENT, |
| SoftInputShowHideReason.HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL, |
| mController.getHost().isHandlingPointerEvent() /* fromUser */) |
| : statsToken; |
| // The insets might be controlled by a remote target. Let the server know we are |
| // requested to hide. |
| notifyHidden(notifyStatsToken); |
| } |
| if (mAnimationState == ANIMATION_STATE_SHOW) { |
| mHasPendingRequest = true; |
| } |
| } |
| |
| /** |
| * Notify {@link com.android.server.inputmethod.InputMethodManagerService} that |
| * IME insets are hidden. |
| * |
| * @param statsToken the token tracking the current IME request or {@code null} otherwise. |
| */ |
| private void notifyHidden(@NonNull ImeTracker.Token statsToken) { |
| ImeTracker.forLogging().onProgress(statsToken, |
| ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN); |
| |
| getImm().notifyImeHidden(mController.getHost().getWindowToken(), statsToken); |
| mIsRequestedVisibleAwaitingLeash = false; |
| Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0); |
| } |
| |
| @Override |
| public void removeSurface() { |
| final IBinder window = mController.getHost().getWindowToken(); |
| if (window != null) { |
| getImm().removeImeSurface(window); |
| } |
| } |
| |
| @Override |
| public boolean setControl(@Nullable InsetsSourceControl control, int[] showTypes, |
| int[] hideTypes) { |
| ImeTracing.getInstance().triggerClientDump("ImeInsetsSourceConsumer#setControl", |
| mController.getHost().getInputMethodManager(), null /* icProto */); |
| if (!super.setControl(control, showTypes, hideTypes)) { |
| return false; |
| } |
| if (control == null && !mIsRequestedVisibleAwaitingLeash) { |
| mController.setRequestedVisibleTypes(0 /* visibleTypes */, getType()); |
| removeSurface(); |
| } |
| final boolean hasLeash = control != null && control.getLeash() != null; |
| if (hasLeash) { |
| mIsRequestedVisibleAwaitingLeash = false; |
| } |
| return true; |
| } |
| |
| @Override |
| protected boolean isRequestedVisibleAwaitingControl() { |
| return super.isRequestedVisibleAwaitingControl() || mIsRequestedVisibleAwaitingLeash; |
| } |
| |
| /** |
| * Checks whether the consumer has an insets source control with a leash. |
| */ |
| private boolean hasLeash() { |
| final var control = getControl(); |
| return control != null && control.getLeash() != null; |
| } |
| |
| @Override |
| public void onPerceptible(boolean perceptible) { |
| super.onPerceptible(perceptible); |
| final IBinder window = mController.getHost().getWindowToken(); |
| if (window != null) { |
| getImm().reportPerceptible(window, perceptible); |
| } |
| } |
| |
| @Override |
| public void dumpDebug(ProtoOutputStream proto, long fieldId) { |
| final long token = proto.start(fieldId); |
| super.dumpDebug(proto, INSETS_SOURCE_CONSUMER); |
| proto.write(IS_REQUESTED_VISIBLE_AWAITING_CONTROL, mIsRequestedVisibleAwaitingLeash); |
| proto.write(HAS_PENDING_REQUEST, mHasPendingRequest); |
| proto.end(token); |
| } |
| |
| /** |
| * Called when {@link #requestShow(boolean, ImeTracker.Token)} or |
| * {@link InputMethodManager#showSoftInput(View, int)} is called. |
| */ |
| public void onShowRequested() { |
| if (mAnimationState == ANIMATION_STATE_HIDE |
| || mController.isPredictiveBackImeHideAnimInProgress()) { |
| mHasPendingRequest = true; |
| } |
| } |
| |
| private InputMethodManager getImm() { |
| return mController.getHost().getInputMethodManager(); |
| } |
| } |