Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d93cb573 authored by Will Brockman's avatar Will Brockman
Browse files

More accurate notification panel logs.

Trigger notification panel logging based on panel expansion and
non-dozing, rather than visibility and interactivity. Turns out this
is more specific to the cases where the panel is actually shown, in my
testing.

Turns on/off notification visibility logging if the panel is believed
to be on, but also respect existing StatusBar hints about visibility logging.

Added new test for not-lockscreen panel log.

Bug: 155108241
Test: atest NotificationLoggerTest
Change-Id: Ic418417afc6cac073429938885773c56aa55fef8
parent 3bf4ec13
Loading
Loading
Loading
Loading
+95 −33
Original line number Diff line number Diff line
@@ -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,6 +255,11 @@ public class NotificationLogger implements StateListener {
    }

    public void stopNotificationLogging() {
        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()) {
@@ -257,8 +270,14 @@ public class NotificationLogger implements StateListener {
            mHandler.removeCallbacks(mVisibilityReporter);
            mListContainer.setChildLocationsChangedListener(null);
        }
    }

    public void startNotificationLogging() {
        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
@@ -267,13 +286,13 @@ public class NotificationLogger implements StateListener {
            // (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());
        }
    }

    private void setDozing(boolean dozing) {
        synchronized (mDozingLock) {
            mDozing = dozing;
            maybeUpdateLoggingStatus();
        }
    }

@@ -343,11 +362,6 @@ 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 {
@@ -356,8 +370,6 @@ public class NotificationLogger implements StateListener {
                    Log.d(TAG, "failed setNotificationsShown: ", e);
                }
            }
                }
            }
            recycleAllVisibilityObjects(newlyVisibleAr);
            recycleAllVisibilityObjects(noLongerVisibleAr);
        });
@@ -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.
     */
+8 −6
Original line number Diff line number Diff line
@@ -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() &&
+43 −6
Original line number Diff line number Diff line
@@ -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];