diff options
3 files changed, 146 insertions, 45 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java index ad047889f29f..bd0d0b31e4dc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java @@ -29,6 +29,7 @@ import android.util.Log; import androidx.annotation.Nullable; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; @@ -36,6 +37,7 @@ import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.statusbar.NotificationListener; +import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -58,6 +60,7 @@ import javax.inject.Inject; */ public class NotificationLogger implements StateListener { private static final String TAG = "NotificationLogger"; + private static final boolean DEBUG = false; /** The minimum delay in ms between reports of notification visibility. */ private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500; @@ -79,7 +82,12 @@ public class NotificationLogger implements StateListener { private long mLastVisibilityReportUptimeMs; private NotificationListContainer mListContainer; private final Object mDozingLock = new Object(); - private boolean mDozing; + @GuardedBy("mDozingLock") + private Boolean mDozing = null; // Use null to indicate state is not yet known + @GuardedBy("mDozingLock") + private Boolean mLockscreen = null; // Use null to indicate state is not yet known + private Boolean mPanelExpanded = null; // Use null to indicate state is not yet known + private boolean mLogging = false; protected final OnChildLocationsChangedListener mNotificationLocationsChangedListener = new OnChildLocationsChangedListener() { @@ -247,33 +255,44 @@ public class NotificationLogger implements StateListener { } public void stopNotificationLogging() { - // Report all notifications as invisible and turn down the - // reporter. - if (!mCurrentlyVisibleNotifications.isEmpty()) { - logNotificationVisibilityChanges( - Collections.emptyList(), mCurrentlyVisibleNotifications); - recycleAllVisibilityObjects(mCurrentlyVisibleNotifications); + if (mLogging) { + mLogging = false; + if (DEBUG) { + Log.i(TAG, "stopNotificationLogging: log notifications invisible"); + } + // Report all notifications as invisible and turn down the + // reporter. + if (!mCurrentlyVisibleNotifications.isEmpty()) { + logNotificationVisibilityChanges( + Collections.emptyList(), mCurrentlyVisibleNotifications); + recycleAllVisibilityObjects(mCurrentlyVisibleNotifications); + } + mHandler.removeCallbacks(mVisibilityReporter); + mListContainer.setChildLocationsChangedListener(null); } - mHandler.removeCallbacks(mVisibilityReporter); - mListContainer.setChildLocationsChangedListener(null); } public void startNotificationLogging() { - mListContainer.setChildLocationsChangedListener(mNotificationLocationsChangedListener); - // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't - // cause the scroller to emit child location events. Hence generate - // one ourselves to guarantee that we're reporting visible - // notifications. - // (Note that in cases where the scroller does emit events, this - // additional event doesn't break anything.) - mNotificationLocationsChangedListener.onChildLocationsChanged(); - mNotificationPanelLogger.logPanelShown(mListContainer.hasPulsingNotifications(), - mEntryManager.getVisibleNotifications()); + if (!mLogging) { + mLogging = true; + if (DEBUG) { + Log.i(TAG, "startNotificationLogging"); + } + mListContainer.setChildLocationsChangedListener(mNotificationLocationsChangedListener); + // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't + // cause the scroller to emit child location events. Hence generate + // one ourselves to guarantee that we're reporting visible + // notifications. + // (Note that in cases where the scroller does emit events, this + // additional event doesn't break anything.) + mNotificationLocationsChangedListener.onChildLocationsChanged(); + } } private void setDozing(boolean dozing) { synchronized (mDozingLock) { mDozing = dozing; + maybeUpdateLoggingStatus(); } } @@ -343,19 +362,12 @@ public class NotificationLogger implements StateListener { for (int i = 0; i < N; i++) { newlyVisibleKeyAr[i] = newlyVisibleAr[i].key; } - - synchronized (mDozingLock) { - // setNotificationsShown should only be called if we are confident that - // the user has seen the notification, aka not when ambient display is on - if (!mDozing) { - // TODO: Call NotificationEntryManager to do this, once it exists. - // TODO: Consider not catching all runtime exceptions here. - try { - mNotificationListener.setNotificationsShown(newlyVisibleKeyAr); - } catch (RuntimeException e) { - Log.d(TAG, "failed setNotificationsShown: ", e); - } - } + // TODO: Call NotificationEntryManager to do this, once it exists. + // TODO: Consider not catching all runtime exceptions here. + try { + mNotificationListener.setNotificationsShown(newlyVisibleKeyAr); + } catch (RuntimeException e) { + Log.d(TAG, "failed setNotificationsShown: ", e); } } recycleAllVisibilityObjects(newlyVisibleAr); @@ -400,14 +412,64 @@ public class NotificationLogger implements StateListener { @Override public void onStateChanged(int newState) { - // don't care about state change + if (DEBUG) { + Log.i(TAG, "onStateChanged: new=" + newState); + } + synchronized (mDozingLock) { + mLockscreen = (newState == StatusBarState.KEYGUARD + || newState == StatusBarState.SHADE_LOCKED); + } } @Override public void onDozingChanged(boolean isDozing) { + if (DEBUG) { + Log.i(TAG, "onDozingChanged: new=" + isDozing); + } setDozing(isDozing); } + @GuardedBy("mDozingLock") + private void maybeUpdateLoggingStatus() { + if (mPanelExpanded == null || mDozing == null) { + if (DEBUG) { + Log.i(TAG, "Panel status unclear: panelExpandedKnown=" + + (mPanelExpanded == null) + " dozingKnown=" + (mDozing == null)); + } + return; + } + // Once we know panelExpanded and Dozing, turn logging on & off when appropriate + boolean lockscreen = mLockscreen == null ? false : mLockscreen; + if (mPanelExpanded && !mDozing) { + mNotificationPanelLogger.logPanelShown(lockscreen, + mEntryManager.getVisibleNotifications()); + if (DEBUG) { + Log.i(TAG, "Notification panel shown, lockscreen=" + lockscreen); + } + startNotificationLogging(); + } else { + if (DEBUG) { + Log.i(TAG, "Notification panel hidden, lockscreen=" + lockscreen); + } + stopNotificationLogging(); + } + } + + /** + * Called by StatusBar to notify the logger that the panel expansion has changed. + * The panel may be showing any of the normal notification panel, the AOD, or the bouncer. + * @param isExpanded True if the panel is expanded. + */ + public void onPanelExpandedChanged(boolean isExpanded) { + if (DEBUG) { + Log.i(TAG, "onPanelExpandedChanged: new=" + isExpanded); + } + mPanelExpanded = isExpanded; + synchronized (mDozingLock) { + maybeUpdateLoggingStatus(); + } + } + /** * Called when the notification is expanded / collapsed. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index e0e52001e740..2306aa4a05e6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1772,6 +1772,9 @@ public class StatusBar extends SystemUI implements DemoMode, } public void setPanelExpanded(boolean isExpanded) { + if (mPanelExpanded != isExpanded) { + mNotificationLogger.onPanelExpandedChanged(isExpanded); + } mPanelExpanded = isExpanded; updateHideIconsForBouncer(false /* animate */); mNotificationShadeWindowController.setPanelExpanded(isExpanded); @@ -2865,7 +2868,6 @@ public class StatusBar extends SystemUI implements DemoMode, } // Visibility reporting - protected void handleVisibleToUserChanged(boolean visibleToUser) { if (visibleToUser) { handleVisibleToUserChangedImpl(visibleToUser); @@ -2887,12 +2889,12 @@ public class StatusBar extends SystemUI implements DemoMode, } } - /** - * The LEDs are turned off when the notification panel is shown, even just a little bit. - * See also StatusBar.setPanelExpanded for another place where we attempt to do this. - */ - private void handleVisibleToUserChangedImpl(boolean visibleToUser) { + // Visibility reporting + + void handleVisibleToUserChangedImpl(boolean visibleToUser) { if (visibleToUser) { + /* The LEDs are turned off when the notification panel is shown, even just a little bit. + * See also StatusBar.setPanelExpanded for another place where we attempt to do this. */ boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp(); boolean clearNotificationEffects = !mPresenter.isPresenterFullyCollapsed() && diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java index a3a46f67ee40..06bad80d6f87 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java @@ -20,6 +20,8 @@ import static com.android.systemui.statusbar.notification.stack.NotificationSect import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; @@ -42,6 +44,7 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.NotificationListener; +import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; @@ -163,28 +166,61 @@ public class NotificationLoggerTest extends SysuiTestCase { mUiBgExecutor.runAllReady(); Mockito.reset(mBarService); - mLogger.stopNotificationLogging(); + setStateAsleep(); + mLogger.onDozingChanged(false); // Wake to lockscreen + mLogger.onDozingChanged(true); // And go back to sleep, turning off logging mUiBgExecutor.runAllReady(); // The visibility objects are recycled by NotificationLogger, so we can't use specific // matchers here. verify(mBarService, times(1)).onNotificationVisibilityChanged(any(), any()); } + private void setStateAsleep() { + mLogger.onPanelExpandedChanged(true); + mLogger.onDozingChanged(true); + mLogger.onStateChanged(StatusBarState.KEYGUARD); + } + + private void setStateAwake() { + mLogger.onPanelExpandedChanged(false); + mLogger.onDozingChanged(false); + mLogger.onStateChanged(StatusBarState.SHADE); + } + + @Test + public void testLogPanelShownOnWake() { + when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry)); + setStateAsleep(); + mLogger.onDozingChanged(false); // Wake to lockscreen + assertEquals(1, mNotificationPanelLoggerFake.getCalls().size()); + assertTrue(mNotificationPanelLoggerFake.get(0).isLockscreen); + assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length); + Notifications.Notification n = mNotificationPanelLoggerFake.get(0).list.notifications[0]; + assertEquals(TEST_PACKAGE_NAME, n.packageName); + assertEquals(TEST_UID, n.uid); + assertEquals(1, n.instanceId); + assertFalse(n.isGroupSummary); + assertEquals(1 + BUCKET_ALERTING, n.section); + } + @Test - public void testLogPanelShownOnLoggingStart() { + public void testLogPanelShownOnShadePull() { when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry)); - mLogger.startNotificationLogging(); + setStateAwake(); + // Now expand panel + mLogger.onPanelExpandedChanged(true); assertEquals(1, mNotificationPanelLoggerFake.getCalls().size()); - assertEquals(false, mNotificationPanelLoggerFake.get(0).isLockscreen); + assertFalse(mNotificationPanelLoggerFake.get(0).isLockscreen); assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length); Notifications.Notification n = mNotificationPanelLoggerFake.get(0).list.notifications[0]; assertEquals(TEST_PACKAGE_NAME, n.packageName); assertEquals(TEST_UID, n.uid); assertEquals(1, n.instanceId); - assertEquals(false, n.isGroupSummary); + assertFalse(n.isGroupSummary); assertEquals(1 + BUCKET_ALERTING, n.section); } + @Test public void testLogPanelShownHandlesNullInstanceIds() { // Construct a NotificationEntry like mEntry, but with a null instance id. @@ -198,7 +234,8 @@ public class NotificationLoggerTest extends SysuiTestCase { entry.setRow(mRow); when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(entry)); - mLogger.startNotificationLogging(); + setStateAsleep(); + mLogger.onDozingChanged(false); // Wake to lockscreen assertEquals(1, mNotificationPanelLoggerFake.getCalls().size()); assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length); Notifications.Notification n = mNotificationPanelLoggerFake.get(0).list.notifications[0]; |