diff options
7 files changed, 289 insertions, 3 deletions
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml index 70a770912c7f..c972624c04b1 100644 --- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml +++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml @@ -35,10 +35,20 @@ app:layout_constraintBottom_toBottomOf="parent" /> <LinearLayout + android:id="@+id/dream_overlay_extra_items" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:orientation="horizontal" + android:gravity="center_vertical" + android:layout_marginEnd="@dimen/dream_overlay_status_bar_extra_margin" + app:layout_constraintEnd_toStartOf="@+id/dream_overlay_system_status" /> + + <LinearLayout android:id="@+id/dream_overlay_system_status" android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal" + android:layout_marginStart="@dimen/dream_overlay_status_bar_extra_margin" app:layout_constraintEnd_toEndOf="parent"> <com.android.systemui.statusbar.AlphaOptimizedImageView diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 3fb00a33e6b6..8ea2c0cbe8f5 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1455,6 +1455,7 @@ <dimen name="dream_overlay_camera_mic_off_indicator_size">8dp</dimen> <dimen name="dream_overlay_notification_indicator_size">6dp</dimen> <dimen name="dream_overlay_grey_chip_width">56dp</dimen> + <dimen name="dream_overlay_status_bar_extra_margin">16dp</dimen> <!-- Dream overlay complications related dimensions --> <dimen name="dream_overlay_complication_clock_time_text_size">100sp</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProvider.java new file mode 100644 index 000000000000..193d6f5c0061 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProvider.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2022 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 android.view.View; + +import androidx.annotation.NonNull; + +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.statusbar.policy.CallbackController; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Executor; + +import javax.inject.Inject; + +/** + * {@link DreamOverlayStatusBarItemsProvider} provides extra dream overlay status bar items. A + * callback can be registered that will be informed of items being added or removed from the + * provider. + */ +@SysUISingleton +public class DreamOverlayStatusBarItemsProvider implements + CallbackController<DreamOverlayStatusBarItemsProvider.Callback> { + /** + * Represents one item in the dream overlay status bar. + */ + public interface StatusBarItem { + /** + * Return the {@link View} associated with this item. + */ + View getView(); + } + + /** + * A callback to be registered with the provider to be informed of when the list of status bar + * items has changed. + */ + public interface Callback { + /** + * Inform the callback that status bar items have changed. + */ + void onStatusBarItemsChanged(List<StatusBarItem> newItems); + } + + private final Executor mExecutor; + private final List<StatusBarItem> mItems = new ArrayList<>(); + private final List<Callback> mCallbacks = new ArrayList<>(); + + @Inject + public DreamOverlayStatusBarItemsProvider(@Main Executor executor) { + mExecutor = executor; + } + + @Override + public void addCallback(@NonNull Callback callback) { + mExecutor.execute(() -> { + Objects.requireNonNull(callback, "Callback must not be null."); + if (mCallbacks.contains(callback)) { + return; + } + + mCallbacks.add(callback); + if (!mItems.isEmpty()) { + callback.onStatusBarItemsChanged(mItems); + } + }); + } + + @Override + public void removeCallback(@NonNull Callback callback) { + mExecutor.execute(() -> { + Objects.requireNonNull(callback, "Callback must not be null."); + mCallbacks.remove(callback); + }); + } + + /** + * Adds an item to the dream overlay status bar. + */ + public void addStatusBarItem(StatusBarItem item) { + mExecutor.execute(() -> { + if (!mItems.contains(item)) { + mItems.add(item); + mCallbacks.forEach(callback -> callback.onStatusBarItemsChanged(mItems)); + } + }); + } + + /** + * Removes an item from the dream overlay status bar. + */ + public void removeStatusBarItem(StatusBarItem item) { + mExecutor.execute(() -> { + if (mItems.remove(item)) { + mCallbacks.forEach(callback -> callback.onStatusBarItemsChanged(mItems)); + } + }); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java index a25257d6cf42..7e4a108aadf1 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.Context; import android.util.AttributeSet; import android.view.View; +import android.view.ViewGroup; import androidx.constraintlayout.widget.ConstraintLayout; @@ -29,6 +30,7 @@ import com.android.systemui.R; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; @@ -58,6 +60,7 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { public static final int STATUS_ICON_PRIORITY_MODE_ON = 6; private final Map<Integer, View> mStatusIcons = new HashMap<>(); + private ViewGroup mSystemStatusViewGroup; public DreamOverlayStatusBarView(Context context) { this(context, null); @@ -94,6 +97,8 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { fetchStatusIconForResId(R.id.dream_overlay_notification_indicator)); mStatusIcons.put(STATUS_ICON_PRIORITY_MODE_ON, fetchStatusIconForResId(R.id.dream_overlay_priority_mode)); + + mSystemStatusViewGroup = findViewById(R.id.dream_overlay_extra_items); } void showIcon(@StatusIconType int iconType, boolean show, @Nullable String contentDescription) { @@ -107,6 +112,11 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { icon.setVisibility(show ? View.VISIBLE : View.GONE); } + void setExtraStatusBarItemViews(List<View> views) { + mSystemStatusViewGroup.removeAllViews(); + views.forEach(view -> mSystemStatusViewGroup.addView(view)); + } + private View fetchStatusIconForResId(int resId) { final View statusIcon = findViewById(resId); return Objects.requireNonNull(statusIcon); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java index 55c1806e1899..65cfae1ac14b 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java @@ -38,6 +38,7 @@ import android.view.View; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem; import com.android.systemui.dreams.dagger.DreamOverlayComponent; import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; import com.android.systemui.statusbar.policy.NextAlarmController; @@ -47,10 +48,13 @@ import com.android.systemui.touch.TouchInsetManager; import com.android.systemui.util.ViewController; import com.android.systemui.util.time.DateFormatUtil; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.concurrent.Executor; +import java.util.stream.Collectors; import javax.inject.Inject; @@ -69,7 +73,10 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve private final Optional<DreamOverlayNotificationCountProvider> mDreamOverlayNotificationCountProvider; private final ZenModeController mZenModeController; + private final DreamOverlayStatusBarItemsProvider mStatusBarItemsProvider; private final Executor mMainExecutor; + private final List<DreamOverlayStatusBarItemsProvider.StatusBarItem> mExtraStatusBarItems = + new ArrayList<>(); private boolean mIsAttached; @@ -116,6 +123,9 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve ? buildNotificationsContentDescription(notificationCount) : null); + private final DreamOverlayStatusBarItemsProvider.Callback mStatusBarItemsProviderCallback = + this::onStatusBarItemsChanged; + @Inject public DreamOverlayStatusBarViewController( DreamOverlayStatusBarView view, @@ -129,7 +139,8 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve IndividualSensorPrivacyController sensorPrivacyController, Optional<DreamOverlayNotificationCountProvider> dreamOverlayNotificationCountProvider, ZenModeController zenModeController, - StatusBarWindowStateController statusBarWindowStateController) { + StatusBarWindowStateController statusBarWindowStateController, + DreamOverlayStatusBarItemsProvider statusBarItemsProvider) { super(view); mResources = resources; mMainExecutor = mainExecutor; @@ -140,6 +151,7 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve mDateFormatUtil = dateFormatUtil; mSensorPrivacyController = sensorPrivacyController; mDreamOverlayNotificationCountProvider = dreamOverlayNotificationCountProvider; + mStatusBarItemsProvider = statusBarItemsProvider; mZenModeController = zenModeController; // Register to receive show/hide updates for the system status bar. Our custom status bar @@ -166,6 +178,8 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve mDreamOverlayNotificationCountProvider.ifPresent( provider -> provider.addCallback(mNotificationCountCallback)); + mStatusBarItemsProvider.addCallback(mStatusBarItemsProviderCallback); + mTouchInsetSession.addViewToTracking(mView); } @@ -177,6 +191,7 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); mDreamOverlayNotificationCountProvider.ifPresent( provider -> provider.removeCallback(mNotificationCountCallback)); + mStatusBarItemsProvider.removeCallback(mStatusBarItemsProviderCallback); mTouchInsetSession.clear(); mIsAttached = false; @@ -271,4 +286,16 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve } }); } + + private void onStatusBarItemsChanged(List<StatusBarItem> newItems) { + mMainExecutor.execute(() -> { + mExtraStatusBarItems.clear(); + mExtraStatusBarItems.addAll(newItems); + mView.setExtraStatusBarItemViews( + newItems + .stream() + .map(StatusBarItem::getView) + .collect(Collectors.toList())); + }); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java new file mode 100644 index 000000000000..a78886f8d504 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 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.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +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.List; +import java.util.concurrent.Executor; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class DreamOverlayStatusBarItemsProviderTest extends SysuiTestCase { + @Mock + DreamOverlayStatusBarItemsProvider.Callback mCallback; + @Mock + DreamOverlayStatusBarItemsProvider.StatusBarItem mStatusBarItem; + + private final Executor mMainExecutor = Runnable::run; + + DreamOverlayStatusBarItemsProvider mProvider; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mProvider = new DreamOverlayStatusBarItemsProvider(mMainExecutor); + } + + @Test + public void addingCallbackCallsOnStatusBarItemsChanged() { + mProvider.addStatusBarItem(mStatusBarItem); + mProvider.addCallback(mCallback); + verify(mCallback).onStatusBarItemsChanged(List.of(mStatusBarItem)); + } + + @Test + public void addingStatusBarItemCallsOnStatusBarItemsChanged() { + mProvider.addCallback(mCallback); + mProvider.addStatusBarItem(mStatusBarItem); + verify(mCallback).onStatusBarItemsChanged(List.of(mStatusBarItem)); + } + + @Test + public void addingDuplicateStatusBarItemDoesNotCallOnStatusBarItemsChanged() { + mProvider.addCallback(mCallback); + mProvider.addStatusBarItem(mStatusBarItem); + mProvider.addStatusBarItem(mStatusBarItem); + // Called only once for addStatusBarItem. + verify(mCallback, times(1)) + .onStatusBarItemsChanged(List.of(mStatusBarItem)); + } + + @Test + public void removingStatusBarItemCallsOnStatusBarItemsChanged() { + mProvider.addCallback(mCallback); + mProvider.addStatusBarItem(mStatusBarItem); + mProvider.removeStatusBarItem(mStatusBarItem); + // Called once for addStatusBarItem and once for removeStatusBarItem. + verify(mCallback, times(2)).onStatusBarItemsChanged(any()); + } + + @Test + public void removingNonexistentStatusBarItemDoesNotCallOnStatusBarItemsChanged() { + mProvider.addCallback(mCallback); + mProvider.removeStatusBarItem(mStatusBarItem); + verify(mCallback, never()).onStatusBarItemsChanged(any()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java index 60e5a9423c61..01309f86a137 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java @@ -57,6 +57,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; import java.util.Optional; import java.util.concurrent.Executor; @@ -94,6 +95,12 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { DreamOverlayNotificationCountProvider mDreamOverlayNotificationCountProvider; @Mock StatusBarWindowStateController mStatusBarWindowStateController; + @Mock + DreamOverlayStatusBarItemsProvider mDreamOverlayStatusBarItemsProvider; + @Mock + DreamOverlayStatusBarItemsProvider.StatusBarItem mStatusBarItem; + @Mock + View mStatusBarItemView; private final Executor mMainExecutor = Runnable::run; @@ -118,7 +125,8 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { mSensorPrivacyController, Optional.of(mDreamOverlayNotificationCountProvider), mZenModeController, - mStatusBarWindowStateController); + mStatusBarWindowStateController, + mDreamOverlayStatusBarItemsProvider); } @Test @@ -128,6 +136,7 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { verify(mSensorPrivacyController).addCallback(any()); verify(mZenModeController).addCallback(any()); verify(mDreamOverlayNotificationCountProvider).addCallback(any()); + verify(mDreamOverlayStatusBarItemsProvider).addCallback(any()); } @Test @@ -256,7 +265,8 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { mSensorPrivacyController, Optional.empty(), mZenModeController, - mStatusBarWindowStateController); + mStatusBarWindowStateController, + mDreamOverlayStatusBarItemsProvider); controller.onViewAttached(); verify(mView, never()).showIcon( eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any()); @@ -294,6 +304,7 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { verify(mSensorPrivacyController).removeCallback(any()); verify(mZenModeController).removeCallback(any()); verify(mDreamOverlayNotificationCountProvider).removeCallback(any()); + verify(mDreamOverlayStatusBarItemsProvider).removeCallback(any()); } @Test @@ -462,4 +473,18 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { verify(mView, never()).setVisibility(anyInt()); } + + @Test + public void testExtraStatusBarItemSetWhenItemsChange() { + mController.onViewAttached(); + when(mStatusBarItem.getView()).thenReturn(mStatusBarItemView); + + final ArgumentCaptor<DreamOverlayStatusBarItemsProvider.Callback> + callbackCapture = ArgumentCaptor.forClass( + DreamOverlayStatusBarItemsProvider.Callback.class); + verify(mDreamOverlayStatusBarItemsProvider).addCallback(callbackCapture.capture()); + callbackCapture.getValue().onStatusBarItemsChanged(List.of(mStatusBarItem)); + + verify(mView).setExtraStatusBarItemViews(List.of(mStatusBarItemView)); + } } |