diff options
10 files changed, 210 insertions, 17 deletions
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 97625869209d..d1115c7b4d1d 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -250,4 +250,10 @@ interface IWindowSession { */ void updateTapExcludeRegion(IWindow window, int regionId, int left, int top, int width, int height); + + /** + * Called when the client has changed the local insets state, and now the server should reflect + * that new state. + */ + void insetsModified(IWindow window, in InsetsState state); } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 4ab1f266cc70..01af37e75cb9 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -19,7 +19,9 @@ package android.view; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Rect; +import android.os.RemoteException; import android.util.ArraySet; +import android.util.Log; import android.util.SparseArray; import android.view.SurfaceControl.Transaction; import android.view.WindowInsets.Type.InsetType; @@ -36,7 +38,11 @@ import java.util.ArrayList; */ public class InsetsController implements WindowInsetsController { + private final String TAG = "InsetsControllerImpl"; + private final InsetsState mState = new InsetsState(); + private final InsetsState mTmpState = new InsetsState(); + private final Rect mFrame = new Rect(); private final SparseArray<InsetsSourceConsumer> mSourceConsumers = new SparseArray<>(); private final ViewRootImpl mViewRoot; @@ -61,8 +67,12 @@ public class InsetsController implements WindowInsetsController { return false; } mState.set(state); + mTmpState.set(state, true /* copySources */); applyLocalVisibilityOverride(); mViewRoot.notifyInsetsChanged(); + if (!mState.equals(mTmpState)) { + sendStateToWindowManager(); + } return true; } @@ -163,6 +173,27 @@ public class InsetsController implements WindowInsetsController { @VisibleForTesting public void notifyVisibilityChanged() { mViewRoot.notifyInsetsChanged(); + sendStateToWindowManager(); + } + + /** + * Sends the local visibility state back to window manager. + */ + private void sendStateToWindowManager() { + InsetsState tmpState = new InsetsState(); + for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { + final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); + if (consumer.getControl() != null) { + tmpState.addSource(mState.getSource(consumer.getType())); + } + } + + // TODO: Put this on a dispatcher thread. + try { + mViewRoot.mWindowSession.insetsModified(mViewRoot.mWindow, tmpState); + } catch (RemoteException e) { + Log.e(TAG, "Failed to call insetsModified", e); + } } void dump(String prefix, PrintWriter pw) { diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index f8148a906bb3..fbc72a0f978e 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -65,6 +65,10 @@ public class InsetsSource implements Parcelable { return mFrame; } + public boolean isVisible() { + return mVisible; + } + /** * Calculates the insets this source will cause to a client window. * diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index ec85c4c56cfc..145b09763676 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -35,7 +35,7 @@ public class InsetsSourceConsumer { private final InsetsState mState; private final InsetsController mController; private @Nullable InsetsSourceControl mSourceControl; - private boolean mHidden; + private boolean mVisible; public InsetsSourceConsumer(@InternalInsetType int type, InsetsState state, Supplier<Transaction> transactionSupplier, InsetsController controller) { @@ -43,6 +43,7 @@ public class InsetsSourceConsumer { mState = state; mTransactionSupplier = transactionSupplier; mController = controller; + mVisible = InsetsState.getDefaultVisibly(type); } public void setControl(@Nullable InsetsSourceControl control) { @@ -51,8 +52,9 @@ public class InsetsSourceConsumer { } mSourceControl = control; applyHiddenToControl(); - applyLocalVisibilityOverride(); - mController.notifyVisibilityChanged(); + if (applyLocalVisibilityOverride()) { + mController.notifyVisibilityChanged(); + } } @VisibleForTesting @@ -66,28 +68,32 @@ public class InsetsSourceConsumer { @VisibleForTesting public void show() { - setHidden(false); + setVisible(true); } @VisibleForTesting public void hide() { - setHidden(true); + setVisible(false); } - void applyLocalVisibilityOverride() { + boolean applyLocalVisibilityOverride() { // If we don't have control, we are not able to change the visibility. if (mSourceControl == null) { - return; + return false; + } + if (mState.getSource(mType).isVisible() == mVisible) { + return false; } - mState.getSource(mType).setVisible(!mHidden); + mState.getSource(mType).setVisible(mVisible); + return true; } - private void setHidden(boolean hidden) { - if (mHidden == hidden) { + private void setVisible(boolean visible) { + if (mVisible == visible) { return; } - mHidden = hidden; + mVisible = visible; applyHiddenToControl(); applyLocalVisibilityOverride(); mController.notifyVisibilityChanged(); @@ -100,10 +106,10 @@ public class InsetsSourceConsumer { // TODO: Animation final Transaction t = mTransactionSupplier.get(); - if (mHidden) { - t.hide(mSourceControl.getLeash()); - } else { + if (mVisible) { t.show(mSourceControl.getLeash()); + } else { + t.hide(mSourceControl.getLeash()); } t.apply(); } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 63025dc16f17..3f8f882a869f 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -32,6 +32,7 @@ import android.view.WindowInsets.Type.InsetType; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; /** * Holder for state of system windows that cause window insets for all other windows in the system. @@ -200,6 +201,18 @@ public class InsetsState implements Parcelable { } } + public void addSource(InsetsSource source) { + mSources.put(source.getType(), source); + } + + public int getSourcesCount() { + return mSources.size(); + } + + public InsetsSource sourceAt(int index) { + return mSources.valueAt(index); + } + public static @InternalInsetType ArraySet<Integer> toInternalType(@InsetType int insetTypes) { final ArraySet<Integer> result = new ArraySet<>(); if ((insetTypes & Type.TOP_BAR) != 0) { @@ -216,6 +229,20 @@ public class InsetsState implements Parcelable { return result; } + public static boolean getDefaultVisibly(@InsetType int type) { + switch (type) { + case TYPE_TOP_BAR: + case TYPE_SIDE_BAR_1: + case TYPE_SIDE_BAR_2: + case TYPE_SIDE_BAR_3: + return true; + case TYPE_IME: + return false; + default: + return true; + } + } + public void dump(String prefix, PrintWriter pw) { pw.println(prefix + "InsetsState"); for (int i = mSources.size() - 1; i >= 0; i--) { diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index d41a718147f2..9807f26ad367 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -20,8 +20,13 @@ import static android.view.InsetsState.INSET_SIDE_BOTTOM; import static android.view.InsetsState.INSET_SIDE_TOP; import static android.view.InsetsState.TYPE_IME; import static android.view.InsetsState.TYPE_NAVIGATION_BAR; +import static android.view.InsetsState.TYPE_SIDE_BAR_1; +import static android.view.InsetsState.TYPE_SIDE_BAR_2; +import static android.view.InsetsState.TYPE_SIDE_BAR_3; import static android.view.InsetsState.TYPE_TOP_BAR; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertNotEquals; import android.graphics.Rect; @@ -133,4 +138,13 @@ public class InsetsStateTest { p.recycle(); assertEquals(mState, mState2); } + + @Test + public void testGetDefaultVisibility() { + assertTrue(InsetsState.getDefaultVisibly(TYPE_TOP_BAR)); + assertTrue(InsetsState.getDefaultVisibly(TYPE_SIDE_BAR_1)); + assertTrue(InsetsState.getDefaultVisibly(TYPE_SIDE_BAR_2)); + assertTrue(InsetsState.getDefaultVisibly(TYPE_SIDE_BAR_3)); + assertFalse(InsetsState.getDefaultVisibly(TYPE_IME)); + } } diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 282838f7d58b..49a3186be9b4 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.graphics.Point; import android.graphics.Rect; import android.util.proto.ProtoOutputStream; +import android.view.InsetsState; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.InsetsSource; @@ -47,8 +48,18 @@ class InsetsSourceProvider { private WindowState mWin; private TriConsumer<DisplayFrames, WindowState, Rect> mFrameProvider; + /** 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; + + InsetsSourceProvider(InsetsSource source, InsetsStateController stateController, DisplayContent displayContent) { + mClientVisible = InsetsState.getDefaultVisibly(source.getType()); mSource = source; mDisplayContent = displayContent; mStateController = stateController; @@ -73,10 +84,9 @@ class InsetsSourceProvider { mWin = win; mFrameProvider = frameProvider; if (win == null) { - mSource.setVisible(false); + setServerVisible(false); mSource.setFrame(new Rect()); } else { - mSource.setVisible(true); mWin.setInsetProvider(this); } } @@ -96,7 +106,7 @@ class InsetsSourceProvider { mTmpRect.inset(mWin.mGivenContentInsets); } mSource.setFrame(mTmpRect); - mSource.setVisible(mWin.isVisible() && !mWin.mGivenInsetsPending); + setServerVisible(mWin.isVisible() && !mWin.mGivenInsetsPending); } @@ -113,6 +123,29 @@ class InsetsSourceProvider { false /* TODO hidden */); mControllingWin = target; mControl = new InsetsSourceControl(mSource.getType(), mAdapter.mCapturedLeash); + setClientVisible(InsetsState.getDefaultVisibly(mSource.getType())); + } + + boolean onInsetsModified(WindowState caller, InsetsSource modifiedSource) { + if (mControllingWin != caller || modifiedSource.isVisible() == mClientVisible) { + return false; + } + setClientVisible(modifiedSource.isVisible()); + return true; + } + + private void setClientVisible(boolean clientVisible) { + mClientVisible = clientVisible; + updateVisibility(); + } + + private void setServerVisible(boolean serverVisible) { + mServerVisible = serverVisible; + updateVisibility(); + } + + private void updateVisibility() { + mSource.setVisible(mServerVisible && mClientVisible); } InsetsSourceControl getControl() { @@ -125,6 +158,7 @@ class InsetsSourceProvider { // Cancelling the animation will invoke onAnimationCancelled, resetting all the fields. mWin.cancelAnimation(); } + setClientVisible(InsetsState.getDefaultVisibly(mSource.getType())); } private class ControlAdapter implements AnimationAdapter { diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 592b7fba4bfd..8e119bb195d6 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -25,6 +25,7 @@ import android.annotation.Nullable; import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseArray; +import android.view.InsetsSource; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.ViewRootImpl; @@ -117,6 +118,21 @@ class InsetsStateController { } } + void onInsetsModified(WindowState windowState, InsetsState state) { + boolean changed = false; + for (int i = state.getSourcesCount() - 1; i >= 0; i--) { + final InsetsSource source = state.sourceAt(i); + final InsetsSourceProvider provider = mControllers.get(source.getType()); + if (provider == null) { + continue; + } + changed |= provider.onInsetsModified(windowState, source); + } + if (changed) { + notifyInsetsChanged(); + } + } + void onImeTargetChanged(@Nullable WindowState imeTarget) { onControlChanged(TYPE_IME, imeTarget); notifyPendingInsetsControlChanged(); diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 37b5a7c30218..d85fdb03e4a6 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -430,6 +430,18 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } } + @Override + public void insetsModified(IWindow window, InsetsState state) { + synchronized (mService.mWindowMap) { + final WindowState windowState = mService.windowForClientLocked(this, window, + false /* throwOnError */); + if (windowState != null) { + windowState.getDisplayContent().getInsetsStateController().onInsetsModified( + windowState, state); + } + } + } + void windowAddedLocked(String packageName) { mPackageName = packageName; mRelayoutTag = "relayoutWindow: " + mPackageName; diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java index 8821544903c1..374078625f4a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java @@ -19,13 +19,19 @@ package com.android.server.wm; import static android.view.InsetsState.TYPE_TOP_BAR; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import android.graphics.Insets; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.view.InsetsSource; +import android.view.InsetsState; +import org.junit.Before; import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; @@ -81,4 +87,41 @@ public class InsetsSourceProviderTest extends WindowTestsBase { mProvider.onPostLayout(); assertEquals(new Rect(10, 10, 20, 20), mProvider.getSource().getFrame()); } + + @Test + public void testUpdateControlForTarget() { + final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow"); + final WindowState target = createWindow(null, TYPE_APPLICATION, "target"); + topBar.getFrameLw().set(0, 0, 500, 100); + mProvider.setWindow(topBar, null); + mProvider.updateControlForTarget(target); + assertNotNull(mProvider.getControl()); + mProvider.updateControlForTarget(null); + assertNull(mProvider.getControl()); + } + + @Test + public void testInsetsModified() { + final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow"); + final WindowState target = createWindow(null, TYPE_APPLICATION, "target"); + topBar.getFrameLw().set(0, 0, 500, 100); + mProvider.setWindow(topBar, null); + mProvider.updateControlForTarget(target); + InsetsState state = new InsetsState(); + state.getSource(TYPE_TOP_BAR).setVisible(false); + mProvider.onInsetsModified(target, state.getSource(TYPE_TOP_BAR)); + assertFalse(mSource.isVisible()); + } + + @Test + public void testInsetsModified_noControl() { + final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow"); + final WindowState target = createWindow(null, TYPE_APPLICATION, "target"); + topBar.getFrameLw().set(0, 0, 500, 100); + mProvider.setWindow(topBar, null); + InsetsState state = new InsetsState(); + state.getSource(TYPE_TOP_BAR).setVisible(false); + mProvider.onInsetsModified(target, state.getSource(TYPE_TOP_BAR)); + assertTrue(mSource.isVisible()); + } } |