Merge "Play insets animation while forcibly showing system bars" into udc-qpr-dev

This commit is contained in:
Tiger Huang
2023-07-04 11:07:22 +00:00
committed by Android (Google) Code Review
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) {