summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java71
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationVisibilityLogger.java150
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/notification/NotificationVisibilityLoggerTest.java139
3 files changed, 359 insertions, 1 deletions
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
index 1738091d14c9..1eead62c042a 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
@@ -48,17 +48,21 @@ import com.android.systemui.car.CarServiceProvider;
import com.android.systemui.car.window.OverlayPanelViewController;
import com.android.systemui.car.window.OverlayViewGlobalStateController;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.StatusBarState;
+import java.util.concurrent.Executor;
+
import javax.inject.Inject;
import javax.inject.Singleton;
/** View controller for the notification panel. */
@Singleton
-public class NotificationPanelViewController extends OverlayPanelViewController {
+public class NotificationPanelViewController extends OverlayPanelViewController
+ implements CommandQueue.Callbacks {
private static final boolean DEBUG = true;
private static final String TAG = "NotificationPanelViewController";
@@ -68,12 +72,14 @@ public class NotificationPanelViewController extends OverlayPanelViewController
private final CarServiceProvider mCarServiceProvider;
private final IStatusBarService mBarService;
private final CommandQueue mCommandQueue;
+ private final Executor mUiBgExecutor;
private final NotificationDataManager mNotificationDataManager;
private final CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper;
private final CarNotificationListener mCarNotificationListener;
private final NotificationClickHandlerFactory mNotificationClickHandlerFactory;
private final StatusBarStateController mStatusBarStateController;
private final boolean mEnableHeadsUpNotificationWhenNotificationShadeOpen;
+ private final NotificationVisibilityLogger mNotificationVisibilityLogger;
private float mInitialBackgroundAlpha;
private float mBackgroundAlphaDiff;
@@ -98,6 +104,7 @@ public class NotificationPanelViewController extends OverlayPanelViewController
@Main Resources resources,
OverlayViewGlobalStateController overlayViewGlobalStateController,
FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
+ @UiBackground Executor uiBgExecutor,
/* Other things */
CarServiceProvider carServiceProvider,
@@ -110,6 +117,7 @@ public class NotificationPanelViewController extends OverlayPanelViewController
CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper,
CarNotificationListener carNotificationListener,
NotificationClickHandlerFactory notificationClickHandlerFactory,
+ NotificationVisibilityLogger notificationVisibilityLogger,
/* Things that need to be replaced */
StatusBarStateController statusBarStateController
@@ -121,12 +129,15 @@ public class NotificationPanelViewController extends OverlayPanelViewController
mCarServiceProvider = carServiceProvider;
mBarService = barService;
mCommandQueue = commandQueue;
+ mUiBgExecutor = uiBgExecutor;
mNotificationDataManager = notificationDataManager;
mCarUxRestrictionManagerWrapper = carUxRestrictionManagerWrapper;
mCarNotificationListener = carNotificationListener;
mNotificationClickHandlerFactory = notificationClickHandlerFactory;
mStatusBarStateController = statusBarStateController;
+ mNotificationVisibilityLogger = notificationVisibilityLogger;
+ mCommandQueue.addCallback(this);
// Notification background setup.
mInitialBackgroundAlpha = (float) mResources.getInteger(
R.integer.config_initialNotificationBackgroundAlpha) / 100;
@@ -151,12 +162,36 @@ public class NotificationPanelViewController extends OverlayPanelViewController
.config_enableHeadsUpNotificationWhenNotificationShadeOpen);
}
+ // CommandQueue.Callbacks
+
+ @Override
+ public void animateExpandNotificationsPanel() {
+ if (!isPanelExpanded()) {
+ toggle();
+ }
+ }
+
+ @Override
+ public void animateCollapsePanels(int flags, boolean force) {
+ if (isPanelExpanded()) {
+ toggle();
+ }
+ }
+
+ // OverlayViewController
+
@Override
protected void onFinishInflate() {
reinflate();
}
@Override
+ protected void hideInternal() {
+ super.hideInternal();
+ mNotificationVisibilityLogger.stop();
+ }
+
+ @Override
protected boolean shouldShowNavigationBar() {
return true;
}
@@ -197,6 +232,11 @@ public class NotificationPanelViewController extends OverlayPanelViewController
mUnseenCountUpdateListener.onUnseenCountUpdate(
mNotificationDataManager.getUnseenNotificationCount());
}
+ mCarNotificationListener.setNotificationsShown(
+ mNotificationDataManager.getSeenNotifications());
+ // This logs both when the notification panel is expanded and when the notification
+ // panel is scrolled.
+ mNotificationVisibilityLogger.log(isPanelExpanded());
});
mNotificationClickHandlerFactory.setNotificationDataManager(mNotificationDataManager);
@@ -332,6 +372,8 @@ public class NotificationPanelViewController extends OverlayPanelViewController
mNotificationDataManager.clearAll();
}
+ // OverlayPanelViewController
+
@Override
protected boolean shouldAnimateCollapsePanel() {
return true;
@@ -364,6 +406,30 @@ public class NotificationPanelViewController extends OverlayPanelViewController
}
@Override
+ protected void onPanelVisible(boolean visible) {
+ super.onPanelVisible(visible);
+ mUiBgExecutor.execute(() -> {
+ try {
+ if (visible) {
+ // When notification panel is open even just a bit, we want to clear
+ // notification effects.
+ boolean clearNotificationEffects =
+ mStatusBarStateController.getState() != StatusBarState.KEYGUARD;
+ mBarService.onPanelRevealed(clearNotificationEffects,
+ mNotificationDataManager.getVisibleNotifications().size());
+ } else {
+ mBarService.onPanelHidden();
+ }
+ } catch (RemoteException ex) {
+ // Won't fail unless the world has ended.
+ Log.e(TAG, String.format(
+ "Unable to notify StatusBarService of panel visibility: %s", visible));
+ }
+ });
+
+ }
+
+ @Override
protected void onPanelExpanded(boolean expand) {
super.onPanelExpanded(expand);
@@ -373,6 +439,9 @@ public class NotificationPanelViewController extends OverlayPanelViewController
}
clearNotificationEffects();
}
+ if (!expand) {
+ mNotificationVisibilityLogger.log(isPanelExpanded());
+ }
}
/**
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationVisibilityLogger.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationVisibilityLogger.java
new file mode 100644
index 000000000000..44c819711bd2
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationVisibilityLogger.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2020 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.car.notification;
+
+import android.os.RemoteException;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.car.notification.AlertEntry;
+import com.android.car.notification.NotificationDataManager;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.dagger.qualifiers.UiBackground;
+
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Handles notification logging, in particular, logging which notifications are visible and which
+ * are not.
+ */
+@Singleton
+public class NotificationVisibilityLogger {
+
+ private static final String TAG = "NotificationVisibilityLogger";
+
+ private final ArraySet<NotificationVisibility> mCurrentlyVisible = new ArraySet<>();
+ private final ArraySet<NotificationVisibility> mNewlyVisible = new ArraySet<>();
+ private final ArraySet<NotificationVisibility> mPreviouslyVisible = new ArraySet<>();
+ private final ArraySet<NotificationVisibility> mTmpCurrentlyVisible = new ArraySet<>();
+
+ private final IStatusBarService mBarService;
+ private final Executor mUiBgExecutor;
+ private final NotificationDataManager mNotificationDataManager;
+
+ private boolean mIsVisible;
+
+ private final Runnable mVisibilityReporter = new Runnable() {
+
+ @Override
+ public void run() {
+ if (mIsVisible) {
+ int count = mNotificationDataManager.getVisibleNotifications().size();
+ for (AlertEntry alertEntry : mNotificationDataManager.getVisibleNotifications()) {
+ NotificationVisibility visObj = NotificationVisibility.obtain(
+ alertEntry.getKey(),
+ /* rank= */ -1,
+ count,
+ mIsVisible,
+ NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA);
+ mTmpCurrentlyVisible.add(visObj);
+ if (!mCurrentlyVisible.contains(visObj)) {
+ mNewlyVisible.add(visObj);
+ }
+ }
+ }
+ mPreviouslyVisible.addAll(mCurrentlyVisible);
+ mPreviouslyVisible.removeAll(mTmpCurrentlyVisible);
+ onNotificationVisibilityChanged(mNewlyVisible, mPreviouslyVisible);
+
+ recycleAllVisibilityObjects(mCurrentlyVisible);
+ mCurrentlyVisible.addAll(mTmpCurrentlyVisible);
+
+ recycleAllVisibilityObjects(mPreviouslyVisible);
+ recycleAllVisibilityObjects(mNewlyVisible);
+ recycleAllVisibilityObjects(mTmpCurrentlyVisible);
+ }
+ };
+
+ @Inject
+ public NotificationVisibilityLogger(
+ @UiBackground Executor uiBgExecutor,
+ IStatusBarService barService,
+ NotificationDataManager notificationDataManager) {
+ mUiBgExecutor = uiBgExecutor;
+ mBarService = barService;
+ mNotificationDataManager = notificationDataManager;
+ }
+
+ /** Triggers a visibility report update to be sent to StatusBarService. */
+ public void log(boolean isVisible) {
+ mIsVisible = isVisible;
+ mUiBgExecutor.execute(mVisibilityReporter);
+ }
+
+ /** Stops logging, clearing all visibility objects. */
+ public void stop() {
+ recycleAllVisibilityObjects(mCurrentlyVisible);
+ }
+
+ /**
+ * Notify StatusBarService of change in notifications' visibility.
+ */
+ private void onNotificationVisibilityChanged(
+ Set<NotificationVisibility> newlyVisible, Set<NotificationVisibility> noLongerVisible) {
+ if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) {
+ return;
+ }
+
+ try {
+ mBarService.onNotificationVisibilityChanged(
+ cloneVisibilitiesAsArr(newlyVisible), cloneVisibilitiesAsArr(noLongerVisible));
+ } catch (RemoteException e) {
+ // Won't fail unless the world has ended.
+ Log.e(TAG, "Failed to notify StatusBarService of notification visibility change");
+ }
+ }
+
+ /**
+ * Clears array and recycles NotificationVisibility objects for reuse.
+ */
+ private static void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
+ for (int i = 0; i < array.size(); i++) {
+ array.valueAt(i).recycle();
+ }
+ array.clear();
+ }
+
+ /**
+ * Converts Set of NotificationVisibility objects to primitive array.
+ */
+ private static NotificationVisibility[] cloneVisibilitiesAsArr(Set<NotificationVisibility> c) {
+ NotificationVisibility[] array = new NotificationVisibility[c.size()];
+ int i = 0;
+ for (NotificationVisibility nv : c) {
+ if (nv != null) {
+ array[i] = nv.clone();
+ }
+ i++;
+ }
+ return array;
+ }
+}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/NotificationVisibilityLoggerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/NotificationVisibilityLoggerTest.java
new file mode 100644
index 000000000000..89dac58cd2a7
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/NotificationVisibilityLoggerTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2020 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.car.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.car.notification.AlertEntry;
+import com.android.car.notification.NotificationDataManager;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class NotificationVisibilityLoggerTest extends SysuiTestCase {
+
+ private static final String PKG = "package_1";
+ private static final String OP_PKG = "OpPackage";
+ private static final int ID = 1;
+ private static final String TAG = "Tag";
+ private static final int UID = 2;
+ private static final int INITIAL_PID = 3;
+ private static final String CHANNEL_ID = "CHANNEL_ID";
+ private static final String CONTENT_TITLE = "CONTENT_TITLE";
+ private static final String OVERRIDE_GROUP_KEY = "OVERRIDE_GROUP_KEY";
+ private static final long POST_TIME = 12345L;
+ private static final UserHandle USER_HANDLE = new UserHandle(12);
+
+ @Mock
+ private IStatusBarService mBarService;
+ @Mock
+ private NotificationDataManager mNotificationDataManager;
+
+ private NotificationVisibilityLogger mNotificationVisibilityLogger;
+ private FakeExecutor mUiBgExecutor;
+ private AlertEntry mMessageNotification;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(/* testClass= */this);
+
+ mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
+ Notification.Builder mNotificationBuilder1 = new Notification.Builder(mContext, CHANNEL_ID)
+ .setContentTitle(CONTENT_TITLE);
+ mMessageNotification = new AlertEntry(new StatusBarNotification(PKG, OP_PKG,
+ ID, TAG, UID, INITIAL_PID, mNotificationBuilder1.build(), USER_HANDLE,
+ OVERRIDE_GROUP_KEY, POST_TIME));
+
+ when(mNotificationDataManager.getVisibleNotifications()).thenReturn(
+ Collections.singletonList(mMessageNotification));
+
+ mNotificationVisibilityLogger = new NotificationVisibilityLogger(
+ mUiBgExecutor, mBarService, mNotificationDataManager);
+ }
+
+ @Test
+ public void log_notifiesStatusBarService() throws RemoteException {
+ mNotificationVisibilityLogger.log(/* isVisible= */ true);
+ mUiBgExecutor.runNextReady();
+
+ verify(mBarService).onNotificationVisibilityChanged(
+ any(NotificationVisibility[].class), any(NotificationVisibility[].class));
+ }
+
+ @Test
+ public void log_isVisibleIsTrue_notifiesOfNewlyVisibleItems() throws RemoteException {
+ ArgumentCaptor<NotificationVisibility[]> newlyVisibleCaptor =
+ ArgumentCaptor.forClass(NotificationVisibility[].class);
+ ArgumentCaptor<NotificationVisibility[]> previouslyVisibleCaptor =
+ ArgumentCaptor.forClass(NotificationVisibility[].class);
+
+ mNotificationVisibilityLogger.log(/* isVisible= */ true);
+ mUiBgExecutor.runNextReady();
+
+ verify(mBarService).onNotificationVisibilityChanged(
+ newlyVisibleCaptor.capture(), previouslyVisibleCaptor.capture());
+ assertThat(newlyVisibleCaptor.getValue().length).isEqualTo(1);
+ assertThat(previouslyVisibleCaptor.getValue().length).isEqualTo(0);
+ }
+
+ @Test
+ public void log_isVisibleIsFalse_notifiesOfPreviouslyVisibleItems() throws RemoteException {
+ ArgumentCaptor<NotificationVisibility[]> newlyVisibleCaptor =
+ ArgumentCaptor.forClass(NotificationVisibility[].class);
+ ArgumentCaptor<NotificationVisibility[]> previouslyVisibleCaptor =
+ ArgumentCaptor.forClass(NotificationVisibility[].class);
+ mNotificationVisibilityLogger.log(/* isVisible= */ true);
+ mUiBgExecutor.runNextReady();
+ reset(mBarService);
+
+ mNotificationVisibilityLogger.log(/* isVisible= */ false);
+ mUiBgExecutor.runNextReady();
+
+ verify(mBarService).onNotificationVisibilityChanged(
+ newlyVisibleCaptor.capture(), previouslyVisibleCaptor.capture());
+ assertThat(previouslyVisibleCaptor.getValue().length).isEqualTo(1);
+ assertThat(newlyVisibleCaptor.getValue().length).isEqualTo(0);
+ }
+}