From 1731df2cef3194269f4c5cd9087d3104c0d8ea03 Mon Sep 17 00:00:00 2001 From: Bryce Lee Date: Wed, 11 Aug 2021 15:53:27 -0700 Subject: [PATCH] CommunalStateController Introduction. This changelist adds the CommunalStateController, which is responsible for reporting the whether the communal view is showing and other state. The communal view state is reported through the CommunalSurfaceViewController when shown and CommunalSurfaceHostViewController when hiding. Bug: 195601027 Test: atest CommunalSurfaceViewControllerTest#testCommunalStateControllerShowNotified Test: atest CommunalHostViewControllerTest#testCommunalStateControllerHideNotified Test: atest CommunalStateControllerTest Change-Id: I2a18a50c8c78e811cd234773994f4591a32303b5 --- .../communal/CommunalHostViewController.java | 4 + .../communal/CommunalStateController.java | 91 +++++++++++++++++++ .../communal/service/CommunalService.java | 7 +- .../communal/service/CommunalSourceImpl.java | 15 ++- .../CommunalSurfaceViewController.java | 6 +- .../CommunalHostViewControllerTest.java | 30 +++++- .../communal/CommunalStateControllerTest.java | 91 +++++++++++++++++++ .../CommunalSurfaceViewControllerTest.java | 19 +++- 8 files changed, 253 insertions(+), 10 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/communal/CommunalStateController.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/communal/CommunalStateControllerTest.java diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalHostViewController.java b/packages/SystemUI/src/com/android/systemui/communal/CommunalHostViewController.java index 5f4c4df30450e..cb0c2827c7a53 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalHostViewController.java +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalHostViewController.java @@ -47,6 +47,7 @@ public class CommunalHostViewController extends ViewController private static final String STATE_LIST_FORMAT = "[%s]"; private final Executor mMainExecutor; + private final CommunalStateController mCommunalStateController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final KeyguardStateController mKeyguardStateController; private final StatusBarStateController mStatusBarStateController; @@ -117,10 +118,12 @@ public class CommunalHostViewController extends ViewController @Inject protected CommunalHostViewController(@Main Executor mainExecutor, + CommunalStateController communalStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, KeyguardStateController keyguardStateController, StatusBarStateController statusBarStateController, CommunalHostView view) { super(view); + mCommunalStateController = communalStateController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mMainExecutor = mainExecutor; mKeyguardStateController = keyguardStateController; @@ -247,6 +250,7 @@ public class CommunalHostViewController extends ViewController } else { mView.removeAllViews(); mView.setVisibility(View.INVISIBLE); + mCommunalStateController.setCommunalViewShowing(false); } }); } diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalStateController.java b/packages/SystemUI/src/com/android/systemui/communal/CommunalStateController.java new file mode 100644 index 0000000000000..e5385ec1543fc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalStateController.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2021 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.systemui.communal; + +import android.annotation.NonNull; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.statusbar.policy.CallbackController; + +import java.util.ArrayList; +import java.util.Objects; + +import javax.inject.Inject; + +/** + * CommunalStateController enables publishing and listening to communal-related state changes. + */ +@SysUISingleton +public class CommunalStateController implements + CallbackController { + private final ArrayList mCallbacks = new ArrayList<>(); + private boolean mCommunalViewShowing; + + /** + * Callback for communal events. + */ + public interface Callback { + /** + * Called when the visibility of the communal view changes. + */ + default void onCommunalViewShowingChanged() { + } + } + + @VisibleForTesting + @Inject + public CommunalStateController() { + } + + /** + * Sets whether the communal view is showing. + * @param communalViewShowing {@code true} if the view is showing, {@code false} otherwise. + */ + public void setCommunalViewShowing(boolean communalViewShowing) { + if (mCommunalViewShowing != communalViewShowing) { + mCommunalViewShowing = communalViewShowing; + + final ArrayList callbacks = new ArrayList<>(mCallbacks); + for (Callback callback : callbacks) { + callback.onCommunalViewShowingChanged(); + } + } + } + + /** + * Returns whether the communal view is showing. + * @return {@code true} if the view is showing, {@code false} otherwise. + */ + public boolean getCommunalViewShowing() { + return mCommunalViewShowing; + } + + @Override + public void addCallback(@NonNull Callback callback) { + Objects.requireNonNull(callback, "Callback must not be null. b/128895449"); + if (!mCallbacks.contains(callback)) { + mCallbacks.add(callback); + } + } + + @Override + public void removeCallback(@NonNull Callback callback) { + Objects.requireNonNull(callback, "Callback must not be null. b/128895449"); + mCallbacks.remove(callback); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/service/CommunalService.java b/packages/SystemUI/src/com/android/systemui/communal/service/CommunalService.java index 7c010a9039ddf..1612670f6fa43 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/service/CommunalService.java +++ b/packages/SystemUI/src/com/android/systemui/communal/service/CommunalService.java @@ -38,18 +38,21 @@ import javax.inject.Inject; public class CommunalService extends Service { final Executor mMainExecutor; final CommunalSourceMonitor mMonitor; + private final CommunalSourceImpl.Factory mSourceFactory; private ICommunalHost.Stub mBinder = new ICommunalHost.Stub() { @Override public void setSource(ICommunalSource source) { mMonitor.setSource( - source != null ? new CommunalSourceImpl(mMainExecutor, source) : null); + source != null ? mSourceFactory.create(source) : null); } }; @Inject - CommunalService(@Main Executor mainExecutor, CommunalSourceMonitor monitor) { + CommunalService(@Main Executor mainExecutor, CommunalSourceImpl.Factory sourceFactory, + CommunalSourceMonitor monitor) { mMainExecutor = mainExecutor; + mSourceFactory = sourceFactory; mMonitor = monitor; } diff --git a/packages/SystemUI/src/com/android/systemui/communal/service/CommunalSourceImpl.java b/packages/SystemUI/src/com/android/systemui/communal/service/CommunalSourceImpl.java index 8364369006109..df368c337ce43 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/service/CommunalSourceImpl.java +++ b/packages/SystemUI/src/com/android/systemui/communal/service/CommunalSourceImpl.java @@ -26,6 +26,7 @@ import android.view.SurfaceView; import androidx.concurrent.futures.CallbackToFutureAdapter; import com.android.systemui.communal.CommunalSource; +import com.android.systemui.communal.CommunalStateController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.shared.communal.ICommunalSource; import com.android.systemui.shared.communal.ICommunalSurfaceCallback; @@ -49,17 +50,20 @@ public class CommunalSourceImpl implements CommunalSource { private static final boolean DEBUG = false; private final ICommunalSource mSourceProxy; private final Executor mMainExecutor; + private final CommunalStateController mCommunalStateController; static class Factory { private final Executor mExecutor; + private final CommunalStateController mCommunalStateController; @Inject - Factory(@Main Executor executor) { + Factory(@Main Executor executor, CommunalStateController communalStateController) { mExecutor = executor; + mCommunalStateController = communalStateController; } public CommunalSource create(ICommunalSource source) { - return new CommunalSourceImpl(mExecutor, source); + return new CommunalSourceImpl(mExecutor, mCommunalStateController, source); } } @@ -71,8 +75,10 @@ public class CommunalSourceImpl implements CommunalSource { // A list of {@link Callback} that have registered to receive updates. private final ArrayList> mCallbacks = Lists.newArrayList(); - public CommunalSourceImpl(Executor mainExecutor, ICommunalSource sourceProxy) { + public CommunalSourceImpl(Executor mainExecutor, + CommunalStateController communalStateController, ICommunalSource sourceProxy) { mMainExecutor = mainExecutor; + mCommunalStateController = communalStateController; mSourceProxy = sourceProxy; try { @@ -114,7 +120,8 @@ public class CommunalSourceImpl implements CommunalSource { CallbackToFutureAdapter.getFuture(completer -> { final SurfaceView view = new SurfaceView(context); completer.set(new CommunalViewResult(view, - new CommunalSurfaceViewController(view, mMainExecutor, this))); + new CommunalSurfaceViewController(view, mMainExecutor, + mCommunalStateController, this))); return "CommunalSourceImpl::requestCommunalSurface::getCommunalSurface"; }); diff --git a/packages/SystemUI/src/com/android/systemui/communal/service/CommunalSurfaceViewController.java b/packages/SystemUI/src/com/android/systemui/communal/service/CommunalSurfaceViewController.java index 07ba830bb3053..8a67744f0325b 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/service/CommunalSurfaceViewController.java +++ b/packages/SystemUI/src/com/android/systemui/communal/service/CommunalSurfaceViewController.java @@ -24,6 +24,7 @@ import android.view.SurfaceView; import androidx.annotation.NonNull; +import com.android.systemui.communal.CommunalStateController; import com.android.systemui.util.ViewController; import com.google.common.util.concurrent.ListenableFuture; @@ -38,6 +39,7 @@ public class CommunalSurfaceViewController extends ViewController { private static final String TAG = "CommunalSurfaceViewCtlr"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final Executor mMainExecutor; + private final CommunalStateController mCommunalStateController; private final CommunalSourceImpl mSource; @IntDef({STATE_SURFACE_CREATED, STATE_SURFACE_VIEW_ATTACHED}) @@ -72,8 +74,9 @@ public class CommunalSurfaceViewController extends ViewController { }; protected CommunalSurfaceViewController(SurfaceView view, Executor executor, - CommunalSourceImpl source) { + CommunalStateController communalStateController, CommunalSourceImpl source) { super(view); + mCommunalStateController = communalStateController; mSource = source; mMainExecutor = executor; } @@ -146,6 +149,7 @@ public class CommunalSurfaceViewController extends ViewController { mView.setChildSurfacePackage(surfacePackage); mView.setZOrderOnTop(true); mView.postInvalidate(); + mCommunalStateController.setCommunalViewShowing(true); } else { Log.e(TAG, "couldn't get the surface package"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalHostViewControllerTest.java index a22322faa6639..03d26bed725e0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalHostViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalHostViewControllerTest.java @@ -16,6 +16,7 @@ package com.android.systemui.communal; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -45,6 +46,9 @@ import java.lang.ref.WeakReference; @SmallTest @RunWith(AndroidTestingRunner.class) public class CommunalHostViewControllerTest extends SysuiTestCase { + @Mock + private CommunalStateController mCommunalStateController; + @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @@ -71,8 +75,9 @@ public class CommunalHostViewControllerTest extends SysuiTestCase { when(mKeyguardStateController.isShowing()).thenReturn(true); when(mCommunalView.isAttachedToWindow()).thenReturn(true); - mController = new CommunalHostViewController(mFakeExecutor, mKeyguardUpdateMonitor, - mKeyguardStateController, mStatusBarStateController, mCommunalView); + mController = new CommunalHostViewController(mFakeExecutor, mCommunalStateController, + mKeyguardUpdateMonitor, mKeyguardStateController, mStatusBarStateController, + mCommunalView); mController.init(); mFakeExecutor.runAllReady(); Mockito.clearInvocations(mCommunalView); @@ -152,4 +157,25 @@ public class CommunalHostViewControllerTest extends SysuiTestCase { mFakeExecutor.runAllReady(); verify(mCommunalView).setVisibility(View.VISIBLE); } + + @Test + public void testCommunalStateControllerHideNotified() { + ArgumentCaptor callbackCapture = + ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); + + // Capture callback value for later use. + verify(mKeyguardUpdateMonitor).registerCallback(callbackCapture.capture()); + + // Establish a visible communal view. + mController.show(new WeakReference<>(mCommunalSource)); + mFakeExecutor.runAllReady(); + + // Occlude + clearInvocations(mCommunalStateController); + callbackCapture.getValue().onKeyguardOccludedChanged(true); + mFakeExecutor.runAllReady(); + + // Verify state controller is notified communal view is hidden. + verify(mCommunalStateController).setCommunalViewShowing(false); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalStateControllerTest.java new file mode 100644 index 0000000000000..7f85c35af7422 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalStateControllerTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2021 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.systemui.communal; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class CommunalStateControllerTest extends SysuiTestCase { + @Mock + private CommunalStateController.Callback mCallback; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testDefaultCommunalViewShowingState() { + // The state controller should report the communal view as not showing by default. + final CommunalStateController stateController = new CommunalStateController(); + assertThat(stateController.getCommunalViewShowing()).isFalse(); + } + + @Test + public void testNotifyCommunalSurfaceShow() { + final CommunalStateController stateController = new CommunalStateController(); + stateController.addCallback(mCallback); + + // Verify setting communal view to showing propagates to callback. + stateController.setCommunalViewShowing(true); + verify(mCallback).onCommunalViewShowingChanged(); + assertThat(stateController.getCommunalViewShowing()).isTrue(); + + clearInvocations(mCallback); + + // Verify setting communal view to not showing propagates to callback. + stateController.setCommunalViewShowing(false); + verify(mCallback).onCommunalViewShowingChanged(); + assertThat(stateController.getCommunalViewShowing()).isFalse(); + } + + @Test + public void testCallbackRegistration() { + final CommunalStateController stateController = new CommunalStateController(); + stateController.addCallback(mCallback); + + // Verify setting communal view to showing propagates to callback. + stateController.setCommunalViewShowing(true); + verify(mCallback).onCommunalViewShowingChanged(); + + clearInvocations(mCallback); + + stateController.removeCallback(mCallback); + clearInvocations(mCallback); + + // Verify callback not invoked after removing from state controller. + stateController.setCommunalViewShowing(false); + verify(mCallback, never()).onCommunalViewShowingChanged(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/service/CommunalSurfaceViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/service/CommunalSurfaceViewControllerTest.java index a9e85cc005580..a31e54350d7e9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/service/CommunalSurfaceViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/service/CommunalSurfaceViewControllerTest.java @@ -33,6 +33,7 @@ import android.view.SurfaceView; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.communal.CommunalStateController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -68,6 +69,9 @@ public class CommunalSurfaceViewControllerTest extends SysuiTestCase { @Mock private SurfaceControlViewHost.SurfacePackage mSurfacePackage; + @Mock + private CommunalStateController mCommunalStateController; + private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); private SurfaceHolder.Callback mCallback; @@ -89,7 +93,7 @@ public class CommunalSurfaceViewControllerTest extends SysuiTestCase { when(mSurfaceView.getMeasuredHeight()).thenReturn(MEASURED_HEIGHT); when(mSurfaceView.isAttachedToWindow()).thenReturn(false); mController = new CommunalSurfaceViewController(mSurfaceView, mFakeExecutor, - mCommunalSource); + mCommunalStateController, mCommunalSource); mController.init(); verify(mSurfaceHolder).addCallback(callbackCapture.capture()); mCallback = callbackCapture.getValue(); @@ -131,6 +135,19 @@ public class CommunalSurfaceViewControllerTest extends SysuiTestCase { verify(mSurfaceView).setWillNotDraw(false); } + @Test + public void testCommunalStateControllerShowNotified() { + // Move CommunalSurfaceView to show + mController.onViewAttached(); + mCallback.surfaceCreated(mSurfaceHolder); + when(mSurfaceView.isAttachedToWindow()).thenReturn(true); + mPackageFuture.set(mSurfacePackage); + mFakeExecutor.runAllReady(); + + // Ensure state controller is informed that the communal view is showing. + verify(mCommunalStateController).setCommunalViewShowing(true); + } + // Invoked to setup surface view package. private void givenSurfacePresent() { mController.onViewAttached();