Centralized Dream Overlay State.
This changelist introduces DreamOverlayStateController, a singleton for managing dream overlay state. The controller currently maintains the collection of overlays (by way of DreamOverlayProviders) to show over a dream. Entities, such as the DreamOverlayService, can register for updates to the collection. Bug: 201676403 Test: atest DreamOverlayServiceTest Test: atest DreamOverlayStateControllerTest Change-Id: I096dbea4b99fbbe7f5ec74d954db5b8af80ef68b
This commit is contained in:
@@ -52,11 +52,21 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
|
||||
private final Context mContext;
|
||||
// The Executor ensures actions and ui updates happen on the same thread.
|
||||
private final Executor mExecutor;
|
||||
// The state controller informs the service of updates to the overlays present.
|
||||
private final DreamOverlayStateController mStateController;
|
||||
|
||||
// The window is populated once the dream informs the service it has begun dreaming.
|
||||
private Window mWindow;
|
||||
private ConstraintLayout mLayout;
|
||||
|
||||
private final DreamOverlayStateController.Callback mOverlayStateCallback =
|
||||
new DreamOverlayStateController.Callback() {
|
||||
@Override
|
||||
public void onOverlayChanged() {
|
||||
mExecutor.execute(() -> reloadOverlaysLocked());
|
||||
}
|
||||
};
|
||||
|
||||
// The service listens to view changes in order to declare that input occurring in areas outside
|
||||
// the overlay should be passed through to the dream underneath.
|
||||
private View.OnAttachStateChangeListener mRootViewAttachListener =
|
||||
@@ -101,6 +111,16 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
|
||||
mExecutor.execute(() -> addOverlayWindowLocked(layoutParams));
|
||||
}
|
||||
|
||||
private void reloadOverlaysLocked() {
|
||||
if (mLayout == null) {
|
||||
return;
|
||||
}
|
||||
mLayout.removeAllViews();
|
||||
for (OverlayProvider overlayProvider : mStateController.getOverlays()) {
|
||||
addOverlay(overlayProvider);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts {@link Window} to host dream overlays into the dream's parent window. Must be called
|
||||
* from the main executing thread. The window attributes closely mirror those that are set by
|
||||
@@ -134,6 +154,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
|
||||
|
||||
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
|
||||
windowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
|
||||
mExecutor.execute(this::reloadOverlaysLocked);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -158,8 +179,17 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
|
||||
}
|
||||
|
||||
@Inject
|
||||
public DreamOverlayService(Context context, @Main Executor executor) {
|
||||
public DreamOverlayService(Context context, @Main Executor executor,
|
||||
DreamOverlayStateController overlayStateController) {
|
||||
mContext = context;
|
||||
mExecutor = executor;
|
||||
mStateController = overlayStateController;
|
||||
mStateController.addCallback(mOverlayStateCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
mStateController.removeCallback(mOverlayStateCallback);
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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.dreams;
|
||||
|
||||
import androidx.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.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* {@link DreamOverlayStateController} is the source of truth for Dream overlay configurations.
|
||||
* Clients can register as listeners for changes to the overlay composition and can query for the
|
||||
* overlays on-demand.
|
||||
*/
|
||||
@SysUISingleton
|
||||
public class DreamOverlayStateController implements
|
||||
CallbackController<DreamOverlayStateController.Callback> {
|
||||
// A counter for guaranteeing unique overlay tokens within the scope of this state controller.
|
||||
private int mNextOverlayTokenId = 0;
|
||||
|
||||
/**
|
||||
* {@link OverlayToken} provides a unique key for identifying {@link OverlayProvider}
|
||||
* instances registered with {@link DreamOverlayStateController}.
|
||||
*/
|
||||
public static class OverlayToken {
|
||||
private final int mId;
|
||||
|
||||
private OverlayToken(int id) {
|
||||
mId = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof OverlayToken)) return false;
|
||||
OverlayToken that = (OverlayToken) o;
|
||||
return mId == that.mId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for dream overlay events.
|
||||
*/
|
||||
public interface Callback {
|
||||
/**
|
||||
* Called when the visibility of the communal view changes.
|
||||
*/
|
||||
default void onOverlayChanged() {
|
||||
}
|
||||
}
|
||||
|
||||
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
|
||||
private final HashMap<OverlayToken, OverlayProvider> mOverlays = new HashMap<>();
|
||||
|
||||
@VisibleForTesting
|
||||
@Inject
|
||||
public DreamOverlayStateController() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an overlay to be presented on top of dreams.
|
||||
* @param provider The {@link OverlayProvider} providing the dream.
|
||||
* @return The {@link OverlayToken} tied to the supplied {@link OverlayProvider}.
|
||||
*/
|
||||
public OverlayToken addOverlay(OverlayProvider provider) {
|
||||
final OverlayToken token = new OverlayToken(mNextOverlayTokenId++);
|
||||
mOverlays.put(token, provider);
|
||||
notifyCallbacks();
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an overlay from being shown on dreams.
|
||||
* @param token The {@link OverlayToken} associated with the {@link OverlayProvider} to be
|
||||
* removed.
|
||||
* @return The removed {@link OverlayProvider}, {@code null} if not found.
|
||||
*/
|
||||
public OverlayProvider removeOverlay(OverlayToken token) {
|
||||
final OverlayProvider removedOverlay = mOverlays.remove(token);
|
||||
|
||||
if (removedOverlay != null) {
|
||||
notifyCallbacks();
|
||||
}
|
||||
|
||||
return removedOverlay;
|
||||
}
|
||||
|
||||
private void notifyCallbacks() {
|
||||
for (Callback callback : mCallbacks) {
|
||||
callback.onOverlayChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCallback(@NonNull Callback callback) {
|
||||
Objects.requireNonNull(callback, "Callback must not be null. b/128895449");
|
||||
if (mCallbacks.contains(callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mCallbacks.add(callback);
|
||||
|
||||
if (mOverlays.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
callback.onOverlayChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCallback(@NonNull Callback callback) {
|
||||
Objects.requireNonNull(callback, "Callback must not be null. b/128895449");
|
||||
mCallbacks.remove(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all registered {@link OverlayProvider} instances.
|
||||
* @return A collection of {@link OverlayProvider}.
|
||||
*/
|
||||
public Collection<OverlayProvider> getOverlays() {
|
||||
return mOverlays.values();
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package com.android.systemui.dreams;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
@@ -48,6 +49,8 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class DreamOverlayServiceTest extends SysuiTestCase {
|
||||
@@ -72,6 +75,9 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
|
||||
@Mock
|
||||
OverlayProvider mProvider;
|
||||
|
||||
@Mock
|
||||
DreamOverlayStateController mDreamOverlayStateController;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
@@ -80,7 +86,8 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void testInteraction() throws Exception {
|
||||
final DreamOverlayService service = new DreamOverlayService(mContext, mMainExecutor);
|
||||
final DreamOverlayService service = new DreamOverlayService(mContext, mMainExecutor,
|
||||
mDreamOverlayStateController);
|
||||
final IBinder proxy = service.onBind(new Intent());
|
||||
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
|
||||
clearInvocations(mWindowManager);
|
||||
@@ -117,4 +124,30 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
|
||||
// Ensure service informs dream host of exit.
|
||||
verify(mDreamOverlayCallback).onExitRequested();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListening() throws Exception {
|
||||
final DreamOverlayService service = new DreamOverlayService(mContext, mMainExecutor,
|
||||
mDreamOverlayStateController);
|
||||
|
||||
final IBinder proxy = service.onBind(new Intent());
|
||||
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
|
||||
|
||||
// Inform the overlay service of dream starting.
|
||||
overlay.startDream(mWindowParams, mDreamOverlayCallback);
|
||||
mMainExecutor.runAllReady();
|
||||
|
||||
// Verify overlay service registered as listener with DreamOverlayStateController
|
||||
// and inform callback of addition.
|
||||
final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
|
||||
ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
|
||||
|
||||
verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
|
||||
when(mDreamOverlayStateController.getOverlays()).thenReturn(Arrays.asList(mProvider));
|
||||
callbackCapture.getValue().onOverlayChanged();
|
||||
mMainExecutor.runAllReady();
|
||||
|
||||
// Verify provider is asked to create overlay.
|
||||
verify(mProvider).onCreateOverlay(any(), any(), any());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.dreams;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.times;
|
||||
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;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class DreamOverlayStateControllerTest extends SysuiTestCase {
|
||||
@Mock
|
||||
DreamOverlayStateController.Callback mCallback;
|
||||
|
||||
@Mock
|
||||
OverlayProvider mProvider;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallback() {
|
||||
final DreamOverlayStateController stateController = new DreamOverlayStateController();
|
||||
stateController.addCallback(mCallback);
|
||||
|
||||
// Add overlay and verify callback is notified.
|
||||
final DreamOverlayStateController.OverlayToken token =
|
||||
stateController.addOverlay(mProvider);
|
||||
|
||||
verify(mCallback, times(1)).onOverlayChanged();
|
||||
|
||||
final Collection<OverlayProvider> providers = stateController.getOverlays();
|
||||
assertEquals(providers.size(), 1);
|
||||
assertTrue(providers.contains(mProvider));
|
||||
|
||||
clearInvocations(mCallback);
|
||||
|
||||
// Remove overlay and verify callback is notified.
|
||||
stateController.removeOverlay(token);
|
||||
verify(mCallback, times(1)).onOverlayChanged();
|
||||
assertTrue(providers.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyOnCallbackAdd() {
|
||||
final DreamOverlayStateController stateController = new DreamOverlayStateController();
|
||||
final DreamOverlayStateController.OverlayToken token =
|
||||
stateController.addOverlay(mProvider);
|
||||
|
||||
// Verify callback occurs on add when an overlay is already present.
|
||||
stateController.addCallback(mCallback);
|
||||
verify(mCallback, times(1)).onOverlayChanged();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user