261 lines
10 KiB
Java
261 lines
10 KiB
Java
/*
|
|
* 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();
|
|
}
|
|
}
|