blob: 9fdfbd0a09da37c7a3eecd167ff905f36f97de87 [file] [log] [blame]
/*
* Copyright (C) 2018 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 com.android.server.wm;
import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
import static android.view.ViewRootImpl.sNewInsetsMode;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_INSETS_CONTROL;
import static com.android.server.wm.WindowManagerService.H.LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.proto.ProtoOutputStream;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.function.TriConsumer;
import com.android.server.protolog.common.ProtoLog;
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
import java.io.PrintWriter;
/**
* Controller for a specific inset source on the server. It's called provider as it provides the
* {@link InsetsSource} to the client that uses it in {@link InsetsSourceConsumer}.
*/
class InsetsSourceProvider {
protected final DisplayContent mDisplayContent;
protected final @NonNull InsetsSource mSource;
protected WindowState mWin;
private final Rect mTmpRect = new Rect();
private final InsetsStateController mStateController;
private final InsetsSourceControl mFakeControl;
private @Nullable InsetsSourceControl mControl;
private @Nullable InsetsControlTarget mControlTarget;
private @Nullable InsetsControlTarget mPendingControlTarget;
private @Nullable InsetsControlTarget mFakeControlTarget;
private @Nullable ControlAdapter mAdapter;
private TriConsumer<DisplayFrames, WindowState, Rect> mFrameProvider;
private TriConsumer<DisplayFrames, WindowState, Rect> mImeFrameProvider;
private final Rect mImeOverrideFrame = new Rect();
private boolean mIsLeashReadyForDispatching;
/** The visibility override from the current controlling window. */
private boolean mClientVisible;
/**
* Whether the window is available and considered visible as in {@link WindowState#isVisible}.
*/
private boolean mServerVisible;
private boolean mSeamlessRotating;
private long mFinishSeamlessRotateFrameNumber = -1;
private final boolean mControllable;
InsetsSourceProvider(InsetsSource source, InsetsStateController stateController,
DisplayContent displayContent) {
mClientVisible = InsetsState.getDefaultVisibility(source.getType());
mSource = source;
mDisplayContent = displayContent;
mStateController = stateController;
mFakeControl = new InsetsSourceControl(source.getType(), null /* leash */,
new Point());
final int type = source.getType();
if (type == ITYPE_STATUS_BAR || type == ITYPE_NAVIGATION_BAR || type == ITYPE_CLIMATE_BAR
|| type == ITYPE_EXTRA_NAVIGATION_BAR) {
mControllable = sNewInsetsMode == NEW_INSETS_MODE_FULL;
} else if (type == ITYPE_IME) {
mControllable = sNewInsetsMode >= NEW_INSETS_MODE_IME;
} else {
mControllable = false;
}
}
InsetsSource getSource() {
return mSource;
}
/**
* @return Whether the current flag configuration allows to control this source.
*/
boolean isControllable() {
return mControllable;
}
/**
* Updates the window that currently backs this source.
*
* @param win The window that links to this source.
* @param frameProvider Based on display frame state and the window, calculates the resulting
* frame that should be reported to clients.
* @param imeFrameProvider Based on display frame state and the window, calculates the resulting
* frame that should be reported to IME.
*/
void setWindow(@Nullable WindowState win,
@Nullable TriConsumer<DisplayFrames, WindowState, Rect> frameProvider,
@Nullable TriConsumer<DisplayFrames, WindowState, Rect> imeFrameProvider) {
if (mWin != null) {
if (mControllable) {
mWin.setControllableInsetProvider(null);
}
// The window may be animating such that we can hand out the leash to the control
// target. Revoke the leash by cancelling the animation to correct the state.
// TODO: Ideally, we should wait for the animation to finish so previous window can
// animate-out as new one animates-in.
mWin.cancelAnimation();
}
ProtoLog.d(WM_DEBUG_IME, "InsetsSource setWin %s", win);
mWin = win;
mFrameProvider = frameProvider;
mImeFrameProvider = imeFrameProvider;
if (win == null) {
setServerVisible(false);
mSource.setFrame(new Rect());
mSource.setVisibleFrame(null);
} else if (mControllable) {
mWin.setControllableInsetProvider(this);
if (mPendingControlTarget != null) {
updateControlForTarget(mPendingControlTarget, true /* force */);
mPendingControlTarget = null;
}
}
}
/**
* @return Whether there is a window which backs this source.
*/
boolean hasWindow() {
return mWin != null;
}
/**
* The source frame can affect the layout of other windows, so this should be called once the
* window gets laid out.
*/
void updateSourceFrame() {
if (mWin == null) {
return;
}
// Make sure we set the valid source frame only when server visible is true, because the
// frame may not yet determined that server side doesn't think the window is ready to
// visible. (i.e. No surface, pending insets that were given during layout, etc..)
if (mServerVisible) {
mTmpRect.set(mWin.getFrameLw());
if (mFrameProvider != null) {
mFrameProvider.accept(mWin.getDisplayContent().mDisplayFrames, mWin, mTmpRect);
} else {
mTmpRect.inset(mWin.mGivenContentInsets);
}
} else {
mTmpRect.setEmpty();
}
mSource.setFrame(mTmpRect);
if (mImeFrameProvider != null) {
mImeOverrideFrame.set(mWin.getFrameLw());
mImeFrameProvider.accept(mWin.getDisplayContent().mDisplayFrames, mWin,
mImeOverrideFrame);
}
if (mWin.mGivenVisibleInsets.left != 0 || mWin.mGivenVisibleInsets.top != 0
|| mWin.mGivenVisibleInsets.right != 0 || mWin.mGivenVisibleInsets.bottom != 0) {
mTmpRect.set(mWin.getFrameLw());
mTmpRect.inset(mWin.mGivenVisibleInsets);
mSource.setVisibleFrame(mTmpRect);
} else {
mSource.setVisibleFrame(null);
}
}
/** @return A new source computed by the specified window frame in the given display frames. */
InsetsSource createSimulatedSource(DisplayFrames displayFrames, WindowFrames windowFrames) {
// Don't copy visible frame because it might not be calculated in the provided display
// frames and it is not significant for this usage.
final InsetsSource source = new InsetsSource(mSource.getType());
source.setVisible(mSource.isVisible());
mTmpRect.set(windowFrames.mFrame);
if (mFrameProvider != null) {
mFrameProvider.accept(displayFrames, mWin, mTmpRect);
}
source.setFrame(mTmpRect);
return source;
}
/**
* Called when a layout pass has occurred.
*/
void onPostLayout() {
if (mWin == null) {
return;
}
setServerVisible(mWin.wouldBeVisibleIfPolicyIgnored() && mWin.isVisibleByPolicy()
&& !mWin.mGivenInsetsPending);
updateSourceFrame();
if (mControl != null) {
final Rect frame = mWin.getWindowFrames().mFrame;
if (mControl.setSurfacePosition(frame.left, frame.top) && mControlTarget != null) {
// The leash has been stale, we need to create a new one for the client.
updateControlForTarget(mControlTarget, true /* force */);
mStateController.notifyControlChanged(mControlTarget);
}
}
}
/**
* @see InsetsStateController#onControlFakeTargetChanged(int, InsetsControlTarget)
*/
void updateControlForFakeTarget(@Nullable InsetsControlTarget fakeTarget) {
if (fakeTarget == mFakeControlTarget) {
return;
}
mFakeControlTarget = fakeTarget;
}
void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force) {
if (mSeamlessRotating) {
// We are un-rotating the window against the display rotation. We don't want the target
// to control the window for now.
return;
}
if (target != null && target.getWindow() != null) {
// ime control target could be a different window.
// Refer WindowState#getImeControlTarget().
target = target.getWindow().getImeControlTarget();
}
if (mWin != null && mWin.getSurfaceControl() == null) {
// if window doesn't have a surface, set it null and return.
setWindow(null, null, null);
}
if (mWin == null) {
mPendingControlTarget = target;
return;
}
if (target == mControlTarget && !force) {
return;
}
if (target == null) {
// Cancelling the animation will invoke onAnimationCancelled, resetting all the fields.
mWin.cancelAnimation();
setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
return;
}
mAdapter = new ControlAdapter();
if (getSource().getType() == ITYPE_IME) {
setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
}
final Transaction t = mDisplayContent.getPendingTransaction();
mWin.startAnimation(t, mAdapter, !mClientVisible /* hidden */,
ANIMATION_TYPE_INSETS_CONTROL);
// The leash was just created. We cannot dispatch it until its surface transaction is
// applied. Otherwise, the client's operation to the leash might be overwritten by us.
mIsLeashReadyForDispatching = false;
final SurfaceControl leash = mAdapter.mCapturedLeash;
final long frameNumber = mFinishSeamlessRotateFrameNumber;
mFinishSeamlessRotateFrameNumber = -1;
if (frameNumber >= 0 && mWin.mHasSurface && leash != null) {
// We just finished the seamless rotation. We don't want to change the position or the
// window crop of the surface controls (including the leash) until the client finishes
// drawing the new frame of the new orientation. Although we cannot defer the reparent
// operation, it is fine, because reparent won't cause any visual effect.
final SurfaceControl barrier = mWin.getClientViewRootSurface();
t.deferTransactionUntil(mWin.getSurfaceControl(), barrier, frameNumber);
t.deferTransactionUntil(leash, barrier, frameNumber);
}
mControlTarget = target;
updateVisibility();
mControl = new InsetsSourceControl(mSource.getType(), leash,
new Point(mWin.getWindowFrames().mFrame.left, mWin.getWindowFrames().mFrame.top));
ProtoLog.d(WM_DEBUG_IME,
"InsetsSource Control %s for target %s", mControl, mControlTarget);
}
void startSeamlessRotation() {
if (!mSeamlessRotating) {
mSeamlessRotating = true;
// This will revoke the leash and clear the control target.
mWin.cancelAnimation();
}
}
void finishSeamlessRotation(boolean timeout) {
if (mSeamlessRotating) {
mSeamlessRotating = false;
mFinishSeamlessRotateFrameNumber = timeout ? -1 : mWin.getFrameNumber();
}
}
boolean onInsetsModified(InsetsControlTarget caller, InsetsSource modifiedSource) {
if (mControlTarget != caller || modifiedSource.isVisible() == mClientVisible) {
return false;
}
setClientVisible(modifiedSource.isVisible());
return true;
}
void onSurfaceTransactionApplied() {
mIsLeashReadyForDispatching = true;
}
private void setClientVisible(boolean clientVisible) {
if (mClientVisible == clientVisible) {
return;
}
mClientVisible = clientVisible;
mDisplayContent.mWmService.mH.obtainMessage(
LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED, mDisplayContent).sendToTarget();
updateVisibility();
}
@VisibleForTesting
void setServerVisible(boolean serverVisible) {
mServerVisible = serverVisible;
updateVisibility();
}
private void updateVisibility() {
mSource.setVisible(mServerVisible && (isMirroredSource() || mClientVisible));
ProtoLog.d(WM_DEBUG_IME,
"InsetsSource updateVisibility serverVisible: %s clientVisible: %s",
mServerVisible, mClientVisible);
}
private boolean isMirroredSource() {
if (mWin == null) {
return false;
}
final int[] provides = mWin.mAttrs.providesInsetsTypes;
if (provides == null) {
return false;
}
for (int i = 0; i < provides.length; i++) {
if (provides[i] == ITYPE_IME) {
return true;
}
}
return false;
}
InsetsSourceControl getControl(InsetsControlTarget target) {
if (target == mControlTarget) {
if (!mIsLeashReadyForDispatching && mControl != null) {
// The surface transaction of preparing leash is not applied yet. We don't send it
// to the client in case that the client applies its transaction sooner than ours
// that we could unexpectedly overwrite the surface state.
return new InsetsSourceControl(mControl.getType(), null /* leash */,
mControl.getSurfacePosition());
}
return mControl;
}
if (target == mFakeControlTarget) {
return mFakeControl;
}
return null;
}
InsetsControlTarget getControlTarget() {
return mControlTarget;
}
boolean isClientVisible() {
return sNewInsetsMode == NEW_INSETS_MODE_NONE || mClientVisible;
}
/**
* @return Whether this provider uses a different frame to dispatch to the IME.
*/
boolean overridesImeFrame() {
return mImeFrameProvider != null;
}
/**
* @return Rect to dispatch to the IME as frame. Only valid if {@link #overridesImeFrame()}
* returns {@code true}.
*/
Rect getImeOverrideFrame() {
return mImeOverrideFrame;
}
public void dump(PrintWriter pw, String prefix) {
pw.println(prefix + "InsetsSourceProvider");
pw.print(prefix + " mSource="); mSource.dump(prefix + " ", pw);
if (mControl != null) {
pw.print(prefix + " mControl=");
mControl.dump(prefix + " ", pw);
}
pw.print(prefix + " mFakeControl="); mFakeControl.dump(prefix + " ", pw);
pw.print(" mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching);
pw.print(" mImeOverrideFrame="); pw.print(mImeOverrideFrame.toString());
if (mWin != null) {
pw.print(prefix + " mWin=");
mWin.dump(pw, prefix + " ", false /* dumpAll */);
}
if (mAdapter != null) {
pw.print(prefix + " mAdapter=");
mAdapter.dump(pw, prefix + " ");
}
if (mControlTarget != null) {
pw.print(prefix + " mControlTarget=");
if (mControlTarget.getWindow() != null) {
mControlTarget.getWindow().dump(pw, prefix + " ", false /* dumpAll */);
}
}
if (mPendingControlTarget != null) {
pw.print(prefix + " mPendingControlTarget=");
if (mPendingControlTarget.getWindow() != null) {
mPendingControlTarget.getWindow().dump(pw, prefix + " ", false /* dumpAll */);
}
}
if (mFakeControlTarget != null) {
pw.print(prefix + " mFakeControlTarget=");
if (mFakeControlTarget.getWindow() != null) {
mFakeControlTarget.getWindow().dump(pw, prefix + " ", false /* dumpAll */);
}
}
}
private class ControlAdapter implements AnimationAdapter {
private SurfaceControl mCapturedLeash;
@Override
public boolean getShowWallpaper() {
return false;
}
@Override
public void startAnimation(SurfaceControl animationLeash, Transaction t,
@AnimationType int type, OnAnimationFinishedCallback finishCallback) {
// TODO(b/118118435): We can remove the type check when implementing the transient bar
// animation.
if (mSource.getType() == ITYPE_IME) {
// TODO: use 0 alpha and remove t.hide() once b/138459974 is fixed.
t.setAlpha(animationLeash, 1 /* alpha */);
t.hide(animationLeash);
}
ProtoLog.i(WM_DEBUG_IME,
"ControlAdapter startAnimation mSource: %s controlTarget: %s", mSource,
mControlTarget);
mCapturedLeash = animationLeash;
final Rect frame = mWin.getWindowFrames().mFrame;
t.setPosition(mCapturedLeash, frame.left, frame.top);
}
@Override
public void onAnimationCancelled(SurfaceControl animationLeash) {
if (mAdapter == this) {
mStateController.notifyControlRevoked(mControlTarget, InsetsSourceProvider.this);
mControl = null;
mControlTarget = null;
mAdapter = null;
setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
ProtoLog.i(WM_DEBUG_IME,
"ControlAdapter onAnimationCancelled mSource: %s mControlTarget: %s",
mSource, mControlTarget);
}
}
@Override
public long getDurationHint() {
return 0;
}
@Override
public long getStatusBarTransitionsStartTime() {
return 0;
}
@Override
public void dump(PrintWriter pw, String prefix) {
pw.println(prefix + "ControlAdapter");
pw.print(prefix + " mCapturedLeash="); pw.print(mCapturedLeash);
}
@Override
public void dumpDebug(ProtoOutputStream proto) {
}
}
}