Play insets animation while forcibly showing system bars

Before this CL, when a system bar is being forcibly shown, InsetsPolicy
would set the control target to null, which just resets the leash
without any animation. This CL sets the proper control target and uses
the InsetsController to play insets animations.

This CL also applies the same logic to the transient bars of immersive
mode.

Fix: 283738201
Test: InsetsPolicyTest
Test: Open an app from kid space, and see if navigation bar is shown
      with animation.
Test: Send a Chat message to a device which is in immersive mode, and
      see if status bar is shown with animation.
Change-Id: I8178a9cabfa62a3942ca6ff51b31df22c0d570d3
This commit is contained in:
Tiger
2023-06-30 17:44:06 +08:00
parent 8e94c2816c
commit 4203281253
5 changed files with 164 additions and 204 deletions

View File

@@ -841,7 +841,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
return mLastDispatchedState;
}
@VisibleForTesting
public boolean onStateChanged(InsetsState state) {
boolean stateChanged = false;
if (!CAPTION_ON_SHELL) {

View File

@@ -21,12 +21,7 @@ import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.InsetsController.ANIMATION_TYPE_HIDE;
import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
import static android.view.InsetsSource.ID_IME;
import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
@@ -39,16 +34,13 @@ import android.app.StatusBarManager;
import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.res.Resources;
import android.os.Handler;
import android.os.IBinder;
import android.util.SparseArray;
import android.view.InsetsAnimationControlCallbacks;
import android.view.InsetsAnimationControlImpl;
import android.view.InsetsAnimationControlRunner;
import android.view.InsetsController;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.InternalInsetsAnimationController;
import android.view.SurfaceControl;
import android.view.SyncRtSurfaceTransactionApplier;
import android.view.WindowInsets;
@@ -56,15 +48,15 @@ import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation;
import android.view.WindowInsetsAnimation.Bounds;
import android.view.WindowInsetsAnimationControlListener;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.DisplayThread;
import com.android.server.statusbar.StatusBarManagerInternal;
import java.io.PrintWriter;
import java.util.List;
/**
* Policy that implements who gets control over the windows generating insets.
@@ -79,48 +71,19 @@ class InsetsPolicy {
private final DisplayContent mDisplayContent;
private final DisplayPolicy mPolicy;
/** For resetting visibilities of insets sources. */
private final InsetsControlTarget mDummyControlTarget = new InsetsControlTarget() {
/** Used to show system bars transiently. This won't affect the layout. */
private final InsetsControlTarget mTransientControlTarget;
@Override
public void notifyInsetsControlChanged() {
boolean hasLeash = false;
final InsetsSourceControl[] controls =
mStateController.getControlsForDispatch(this);
if (controls == null) {
return;
}
for (InsetsSourceControl control : controls) {
if (isTransient(control.getType())) {
// The visibilities of transient bars will be handled with animations.
continue;
}
final SurfaceControl leash = control.getLeash();
if (leash != null) {
hasLeash = true;
// We use alpha to control the visibility here which aligns the logic at
// SurfaceAnimator.createAnimationLeash
final boolean visible =
(control.getType() & WindowInsets.Type.defaultVisible()) != 0;
mDisplayContent.getPendingTransaction().setAlpha(leash, visible ? 1f : 0f);
}
}
if (hasLeash) {
mDisplayContent.scheduleAnimation();
}
}
};
/** Used to show system bars permanently. This will affect the layout. */
private final InsetsControlTarget mPermanentControlTarget;
private WindowState mFocusedWin;
private final BarWindow mStatusBar = new BarWindow(StatusBarManager.WINDOW_STATUS_BAR);
private final BarWindow mNavBar = new BarWindow(StatusBarManager.WINDOW_NAVIGATION_BAR);
private @InsetsType int mShowingTransientTypes;
private @InsetsType int mForcedShowingTypes;
private boolean mAnimatingShown;
private final boolean mHideNavBarForKeyboard;
private final float[] mTmpFloat9 = new float[9];
InsetsPolicy(InsetsStateController stateController, DisplayContent displayContent) {
mStateController = stateController;
@@ -128,6 +91,10 @@ class InsetsPolicy {
mPolicy = displayContent.getDisplayPolicy();
final Resources r = mPolicy.getContext().getResources();
mHideNavBarForKeyboard = r.getBoolean(R.bool.config_hideNavBarForKeyboard);
mTransientControlTarget = new ControlTarget(
stateController, displayContent.mWmService.mH, "TransientControlTarget");
mPermanentControlTarget = new ControlTarget(
stateController, displayContent.mWmService.mH, "PermanentControlTarget");
}
/** Updates the target which can control system bars. */
@@ -144,13 +111,13 @@ class InsetsPolicy {
final WindowState topApp = mPolicy.getTopFullscreenOpaqueWindow();
mStateController.onBarControlTargetChanged(
statusControlTarget,
statusControlTarget == mDummyControlTarget
statusControlTarget == mTransientControlTarget
? getStatusControlTarget(focusedWin, true /* fake */)
: statusControlTarget == notificationShade
? getStatusControlTarget(topApp, true /* fake */)
: null,
navControlTarget,
navControlTarget == mDummyControlTarget
navControlTarget == mTransientControlTarget
? getNavControlTarget(focusedWin, true /* fake */)
: navControlTarget == notificationShade
? getNavControlTarget(topApp, true /* fake */)
@@ -200,19 +167,19 @@ class InsetsPolicy {
mFocusedWin,
(showingTransientTypes & (Type.statusBars() | Type.navigationBars())) != 0,
isGestureOnSystemBar);
// The leashes can be created while updating bar control target. The surface transaction
// of the new leashes might not be applied yet. The callback posted here ensures we can
// get the valid leashes because the surface transaction will be applied in the next
// animation frame which will be triggered if a new leash is created.
mDisplayContent.mWmService.mAnimator.getChoreographer().postFrameCallback(time -> {
synchronized (mDisplayContent.mWmService.mGlobalLock) {
startAnimation(true /* show */, null /* callback */);
}
});
}
}
@VisibleForTesting
InsetsControlTarget getTransientControlTarget() {
return mTransientControlTarget;
}
@VisibleForTesting
InsetsControlTarget getPermanentControlTarget() {
return mPermanentControlTarget;
}
void hideTransient() {
if (mShowingTransientTypes == 0) {
return;
@@ -223,23 +190,8 @@ class InsetsPolicy {
/* areVisible= */ false,
/* wereRevealedFromSwipeOnSystemBar= */ false);
startAnimation(false /* show */, () -> {
synchronized (mDisplayContent.mWmService.mGlobalLock) {
final SparseArray<InsetsSourceProvider> providers =
mStateController.getSourceProviders();
for (int i = providers.size() - 1; i >= 0; i--) {
final InsetsSourceProvider provider = providers.valueAt(i);
if (!isTransient(provider.getSource().getType())) {
continue;
}
// We are about to clear mShowingTransientTypes, we don't want the transient bar
// can cause insets on the client. Restore the client visibility.
provider.setClientVisible(false);
}
mShowingTransientTypes = 0;
updateBarControlTarget(mFocusedWin);
}
});
mShowingTransientTypes = 0;
updateBarControlTarget(mFocusedWin);
}
boolean isTransient(@InsetsType int type) {
@@ -502,7 +454,7 @@ class InsetsPolicy {
private @Nullable InsetsControlTarget getStatusControlTarget(@Nullable WindowState focusedWin,
boolean fake) {
if (!fake && isTransient(Type.statusBars())) {
return mDummyControlTarget;
return mTransientControlTarget;
}
final WindowState notificationShade = mPolicy.getNotificationShade();
if (focusedWin == notificationShade) {
@@ -519,13 +471,13 @@ class InsetsPolicy {
if (areTypesForciblyShowing(Type.statusBars())) {
// Status bar is forcibly shown. We don't want the client to control the status bar, and
// we will dispatch the real visibility of status bar to the client.
return null;
return mPermanentControlTarget;
}
if (forceShowsStatusBarTransiently() && !fake) {
// Status bar is forcibly shown transiently, and its new visibility won't be
// dispatched to the client so that we can keep the layout stable. We will dispatch the
// fake control to the client, so that it can re-show the bar during this scenario.
return mDummyControlTarget;
return mTransientControlTarget;
}
if (!canBeTopFullscreenOpaqueWindow(focusedWin)
&& mPolicy.topAppHidesSystemBar(Type.statusBars())
@@ -556,7 +508,7 @@ class InsetsPolicy {
return null;
}
if (!fake && isTransient(Type.navigationBars())) {
return mDummyControlTarget;
return mTransientControlTarget;
}
if (focusedWin == mPolicy.getNotificationShade()) {
// Notification shade has control anyways, no reason to force anything.
@@ -579,13 +531,13 @@ class InsetsPolicy {
if (areTypesForciblyShowing(Type.navigationBars())) {
// Navigation bar is forcibly shown. We don't want the client to control the navigation
// bar, and we will dispatch the real visibility of navigation bar to the client.
return null;
return mPermanentControlTarget;
}
if (forceShowsNavigationBarTransiently() && !fake) {
// Navigation bar is forcibly shown transiently, and its new visibility won't be
// dispatched to the client so that we can keep the layout stable. We will dispatch the
// fake control to the client, so that it can re-show the bar during this scenario.
return mDummyControlTarget;
return mTransientControlTarget;
}
final WindowState notificationShade = mPolicy.getNotificationShade();
if (!canBeTopFullscreenOpaqueWindow(focusedWin)
@@ -662,34 +614,6 @@ class InsetsPolicy {
&& (win.mAttrs.privateFlags & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0;
}
@VisibleForTesting
void startAnimation(boolean show, Runnable callback) {
@InsetsType int typesReady = 0;
final SparseArray<InsetsSourceControl> controlsReady = new SparseArray<>();
final InsetsSourceControl[] controls =
mStateController.getControlsForDispatch(mDummyControlTarget);
if (controls == null) {
if (callback != null) {
DisplayThread.getHandler().post(callback);
}
return;
}
for (InsetsSourceControl control : controls) {
if (isTransient(control.getType()) && control.getLeash() != null) {
typesReady |= control.getType();
controlsReady.put(control.getId(), new InsetsSourceControl(control));
}
}
controlAnimationUnchecked(typesReady, controlsReady, show, callback);
}
private void controlAnimationUnchecked(int typesReady,
SparseArray<InsetsSourceControl> controls, boolean show, Runnable callback) {
InsetsPolicyAnimationControlListener listener =
new InsetsPolicyAnimationControlListener(show, callback, typesReady);
listener.mControlCallbacks.controlAnimationUnchecked(typesReady, controls, show);
}
private void dispatchTransientSystemBarsVisibilityChanged(
@Nullable WindowState focusedWindow,
boolean areVisible,
@@ -760,105 +684,138 @@ class InsetsPolicy {
}
}
private class InsetsPolicyAnimationControlListener extends
InsetsController.InternalAnimationControlListener {
Runnable mFinishCallback;
InsetsPolicyAnimationControlCallbacks mControlCallbacks;
private static class ControlTarget implements InsetsControlTarget {
InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) {
super(show, false /* hasCallbacks */, types, BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
false /* disable */, 0 /* floatingImeBottomInsets */,
null /* loggingListener */, null /* jankContext */);
mFinishCallback = finishCallback;
mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this);
private final InsetsState mState = new InsetsState();
private final InsetsController mInsetsController;
private final InsetsStateController mStateController;
private final String mName;
ControlTarget(InsetsStateController stateController, Handler handler, String name) {
mStateController = stateController;
mInsetsController = new InsetsController(new Host(handler, name));
mName = name;
}
@Override
protected void onAnimationFinish() {
super.onAnimationFinish();
if (mFinishCallback != null) {
DisplayThread.getHandler().post(mFinishCallback);
}
public void notifyInsetsControlChanged() {
mState.set(mStateController.getRawInsetsState(), true /* copySources */);
mInsetsController.onStateChanged(mState);
mInsetsController.onControlsChanged(mStateController.getControlsForDispatch(this));
}
private class InsetsPolicyAnimationControlCallbacks implements
InsetsAnimationControlCallbacks {
private InsetsAnimationControlImpl mAnimationControl = null;
private InsetsPolicyAnimationControlListener mListener;
@Override
public String toString() {
return mName;
}
}
InsetsPolicyAnimationControlCallbacks(InsetsPolicyAnimationControlListener listener) {
mListener = listener;
private static class Host implements InsetsController.Host {
private final float[] mTmpFloat9 = new float[9];
private final Handler mHandler;
private final String mName;
Host(Handler handler, String name) {
mHandler = handler;
mName = name;
}
@Override
public Handler getHandler() {
return mHandler;
}
@Override
public void notifyInsetsChanged() { }
@Override
public void dispatchWindowInsetsAnimationPrepare(
@NonNull WindowInsetsAnimation animation) { }
@Override
public Bounds dispatchWindowInsetsAnimationStart(
@NonNull WindowInsetsAnimation animation,
@NonNull Bounds bounds) {
return bounds;
}
@Override
public WindowInsets dispatchWindowInsetsAnimationProgress(
@NonNull WindowInsets insets,
@NonNull List<WindowInsetsAnimation> runningAnimations) {
return insets;
}
@Override
public void dispatchWindowInsetsAnimationEnd(
@NonNull WindowInsetsAnimation animation) { }
@Override
public void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... p) {
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
for (int i = p.length - 1; i >= 0; i--) {
SyncRtSurfaceTransactionApplier.applyParams(t, p[i], mTmpFloat9);
}
t.apply();
t.close();
}
private void controlAnimationUnchecked(int typesReady,
SparseArray<InsetsSourceControl> controls, boolean show) {
if (typesReady == 0) {
// nothing to animate.
return;
}
mAnimatingShown = show;
@Override
public void updateRequestedVisibleTypes(int types) { }
final InsetsState state = mFocusedWin.getInsetsState();
@Override
public boolean hasAnimationCallbacks() {
return false;
}
// We are about to playing the default animation. Passing a null frame indicates
// the controlled types should be animated regardless of the frame.
mAnimationControl = new InsetsAnimationControlImpl(controls,
null /* frame */, state, mListener, typesReady, this,
mListener.getDurationMs(), getInsetsInterpolator(),
show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, show
? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
: LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
null /* translator */, null /* statsToken */);
SurfaceAnimationThread.getHandler().post(
() -> mListener.onReady(mAnimationControl, typesReady));
}
@Override
public void setSystemBarsAppearance(int appearance, int mask) { }
/** Called on SurfaceAnimationThread without global WM lock held. */
@Override
public void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner) {
if (mAnimationControl.applyChangeInsets(null /* outState */)) {
mAnimationControl.finish(mAnimatingShown);
}
}
@Override
public int getSystemBarsAppearance() {
return 0;
}
@Override
public void notifyFinished(InsetsAnimationControlRunner runner, boolean shown) {
// Nothing's needed here. Finish steps is handled in the listener
// onAnimationFinished callback.
}
@Override
public void setSystemBarsBehavior(int behavior) { }
/** Called on SurfaceAnimationThread without global WM lock held. */
@Override
public void applySurfaceParams(
final SyncRtSurfaceTransactionApplier.SurfaceParams... params) {
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
for (int i = params.length - 1; i >= 0; i--) {
SyncRtSurfaceTransactionApplier.SurfaceParams surfaceParams = params[i];
applyParams(t, surfaceParams, mTmpFloat9);
}
t.apply();
t.close();
}
@Override
public int getSystemBarsBehavior() {
return BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
}
// Since we don't push applySurfaceParams to a Handler-queue we don't need
// to push release in this case.
@Override
public void releaseSurfaceControlFromRt(SurfaceControl sc) {
sc.release();
}
@Override
public void releaseSurfaceControlFromRt(SurfaceControl surfaceControl) {
surfaceControl.release();
}
@Override
public <T extends InsetsAnimationControlRunner & InternalInsetsAnimationController>
void startAnimation(T runner, WindowInsetsAnimationControlListener listener, int types,
WindowInsetsAnimation animation,
Bounds bounds) {
}
@Override
public void addOnPreDrawRunnable(Runnable r) { }
@Override
public void reportPerceptible(int types, boolean perceptible) {
// No-op for now - only client windows report perceptibility for now, with policy
// controllers assumed to always be perceptible.
}
@Override
public void postInsetsAnimationCallback(Runnable r) { }
@Override
public InputMethodManager getInputMethodManager() {
return null;
}
@Nullable
@Override
public String getRootViewTitle() {
return mName;
}
@Override
public int dipToPx(int dips) {
return 0;
}
@Nullable
@Override
public IBinder getWindowToken() {
return null;
}
}
}

View File

@@ -31,17 +31,12 @@ import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.verify;
import android.app.StatusBarManager;
@@ -268,8 +263,6 @@ public class InsetsPolicyTest extends WindowTestsBase {
navBar.setHasSurface(true);
navBarProvider.setServerVisible(true);
final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
spyOn(policy);
doNothing().when(policy).startAnimation(anyBoolean(), any());
// Make both system bars invisible.
mAppWindow.setRequestedVisibleTypes(
@@ -305,8 +298,6 @@ public class InsetsPolicyTest extends WindowTestsBase {
addNavigationBar().getControllableInsetProvider().setServerVisible(true);
final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
spyOn(policy);
doNothing().when(policy).startAnimation(anyBoolean(), any());
policy.updateBarControlTarget(mAppWindow);
policy.showTransient(navigationBars() | statusBars(),
true /* isGestureOnSystemBar */);
@@ -341,8 +332,6 @@ public class InsetsPolicyTest extends WindowTestsBase {
mAppWindow.mAboveInsetsState.addSource(navBarSource);
mAppWindow.mAboveInsetsState.addSource(statusBarSource);
final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
spyOn(policy);
doNothing().when(policy).startAnimation(anyBoolean(), any());
policy.updateBarControlTarget(mAppWindow);
policy.showTransient(navigationBars() | statusBars(),
true /* isGestureOnSystemBar */);
@@ -390,8 +379,6 @@ public class InsetsPolicyTest extends WindowTestsBase {
final WindowState app2 = addWindow(TYPE_APPLICATION, "app");
final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
spyOn(policy);
doNothing().when(policy).startAnimation(anyBoolean(), any());
policy.updateBarControlTarget(app);
policy.showTransient(navigationBars() | statusBars(),
true /* isGestureOnSystemBar */);

View File

@@ -77,6 +77,10 @@ class TestDisplayContent extends DisplayContent {
spyOn(inputMonitor);
doNothing().when(inputMonitor).resumeDispatchingLw(any());
final InsetsPolicy insetsPolicy = getInsetsPolicy();
WindowTestsBase.suppressInsetsAnimation(insetsPolicy.getPermanentControlTarget());
WindowTestsBase.suppressInsetsAnimation(insetsPolicy.getTransientControlTarget());
// For devices that set the sysprop ro.bootanim.set_orientation_<display_id>
// See DisplayRotation#readDefaultDisplayRotation for context.
// Without that, meaning of height and width in context of the tests can be swapped if

View File

@@ -222,6 +222,10 @@ class WindowTestsBase extends SystemServiceTestsBase {
displayPolicy.finishWindowsDrawn();
displayPolicy.finishScreenTurningOn();
final InsetsPolicy insetsPolicy = mDefaultDisplay.getInsetsPolicy();
suppressInsetsAnimation(insetsPolicy.getTransientControlTarget());
suppressInsetsAnimation(insetsPolicy.getPermanentControlTarget());
mTransaction = mSystemServicesTestRule.mTransaction;
mMockSession = mock(Session.class);
@@ -278,6 +282,15 @@ class WindowTestsBase extends SystemServiceTestsBase {
checkDeviceSpecificOverridesNotApplied();
}
/**
* The test doesn't create real SurfaceControls, but mocked ones. This prevents the target from
* controlling them, or it will cause {@link NullPointerException}.
*/
static void suppressInsetsAnimation(InsetsControlTarget target) {
spyOn(target);
Mockito.doNothing().when(target).notifyInsetsControlChanged();
}
@After
public void tearDown() throws Exception {
if (mUseFakeSettingsProvider) {