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

Commit a9cf9c72 authored by Eyal Posener's avatar Eyal Posener
Browse files

Add blocking helper logging

The added logging events are: blocking helper view displayed or
dismissed, any button click within the view, undo clicks and
system suggests of blocking helper.

* Move some of the getLogMaker logic of server's NotificationRecord
  class to the common StatusBarNotification class.
* Use the StatusBarNotification.getLogMaker to produce blocking helper
  logging.
* Add logging in the NotificationInfo for interaction and display of
  the blocking helper view.
* Add logging in the NotificationBlockingHelperManager for system
  suggests of blocking helper.

Bug: 112482290
Test: unittests and manual - viewed produced logs.
Change-Id: I3a5267d55faba21f6668d35ff8aa12deb0dc5921
parent 6ec5ba9a
Loading
Loading
Loading
Loading
+55 −0
Original line number Diff line number Diff line
@@ -22,16 +22,21 @@ import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.metrics.LogMaker;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;

import com.android.internal.logging.nano.MetricsProto.MetricsEvent;

/**
 * Class encapsulating a Notification. Sent by the NotificationManagerService to clients including
 * the status bar and any {@link android.service.notification.NotificationListenerService}s.
 */
public class StatusBarNotification implements Parcelable {
    static final int MAX_LOG_TAG_LENGTH = 36;

    @UnsupportedAppUsage
    private final String pkg;
    @UnsupportedAppUsage
@@ -56,6 +61,9 @@ public class StatusBarNotification implements Parcelable {

    private Context mContext; // used for inflation & icon expansion

    // Contains the basic logging data of the notification.
    private LogMaker mLogMaker;

    /** @hide */
    public StatusBarNotification(String pkg, String opPkg, int id,
            String tag, int uid, int initialPid, Notification notification, UserHandle user,
@@ -381,4 +389,51 @@ public class StatusBarNotification implements Parcelable {
        }
        return mContext;
    }

    /**
     * Returns a LogMaker that contains all basic information of the notification.
     * @hide
     */
    public LogMaker getLogMaker() {
        if (mLogMaker == null) {
            // Initialize fields that only change on update (so a new record).
            mLogMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN)
                .setPackageName(getPackageName())
                .addTaggedData(MetricsEvent.NOTIFICATION_ID, getId())
                .addTaggedData(MetricsEvent.NOTIFICATION_TAG, getTag())
                .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID, getChannelIdLogTag());
        }
        // Reset fields that can change between updates, or are used by multiple logs.
        return mLogMaker
            .clearCategory()
            .clearType()
            .clearSubtype()
            .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID, getGroupLogTag())
            .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_SUMMARY,
                getNotification().isGroupSummary() ? 1 : 0);
    }

    private String getGroupLogTag() {
        return shortenTag(getGroup());
    }

    private String getChannelIdLogTag() {
        if (notification.getChannelId() == null) {
            return null;
        }
        return shortenTag(notification.getChannelId());
    }

    // Make logTag with max size MAX_LOG_TAG_LENGTH.
    // For shorter or equal tags, returns the tag.
    // For longer tags, truncate the tag and append a hash of the full tag to
    // fill the maximum size.
    private String shortenTag(String logTag) {
        if (logTag == null || logTag.length() <= MAX_LOG_TAG_LENGTH) {
            return logTag;
        }
        String hash = Integer.toHexString(logTag.hashCode());
        return logTag.substring(0, MAX_LOG_TAG_LENGTH - hash.length() - 1) + "-"
            + hash;
    }
}
+152 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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 android.service.notification;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import android.app.ActivityManager;
import android.app.Notification;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.metrics.LogMaker;
import android.os.UserHandle;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;

import com.android.internal.logging.nano.MetricsProto.MetricsEvent;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

@RunWith(AndroidJUnit4.class)
@SmallTest
public class StatusBarNotificationTest {

    private final Context mMockContext = mock(Context.class);
    @Mock
    private PackageManager mPm;

    private static final String PKG = "com.example.o";
    private static final int UID = 9583;
    private static final int ID = 1;
    private static final String TAG = "tag1";
    private static final String CHANNEL_ID = "channel";
    private static final String CHANNEL_ID_LONG =
            "give_a_developer_a_string_argument_and_who_knows_what_they_will_pass_in_there";
    private static final String GROUP_ID_1 = "group1";
    private static final String GROUP_ID_2 = "group2";
    private static final String GROUP_ID_LONG =
            "0|com.foo.bar|g:content://com.foo.bar.ui/account%3A-0000000/account/";
    private static final android.os.UserHandle USER =
            UserHandle.of(ActivityManager.getCurrentUser());

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        when(mMockContext.getResources()).thenReturn(
                InstrumentationRegistry.getContext().getResources());
        when(mMockContext.getPackageManager()).thenReturn(mPm);
        when(mMockContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
    }

    @Test
    public void testLogMaker() {
        final LogMaker logMaker = getNotification(PKG, GROUP_ID_1, CHANNEL_ID).getLogMaker();

        assertEquals(CHANNEL_ID,
                (String) logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID));
        assertEquals(PKG, logMaker.getPackageName());
        assertEquals(ID, logMaker.getTaggedData(MetricsEvent.NOTIFICATION_ID));
        assertEquals(TAG, logMaker.getTaggedData(MetricsEvent.NOTIFICATION_TAG));
        assertEquals(GROUP_ID_1,
                logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
    }

    @Test
    public void testLogMakerNoChannel() {
        final LogMaker logMaker = getNotification(PKG, GROUP_ID_1, null).getLogMaker();

        assertNull(logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID));
    }

    @Test
    public void testLogMakerLongChannel() {
        final LogMaker logMaker = getNotification(PKG, null, CHANNEL_ID_LONG).getLogMaker();
        final String loggedId = (String) logMaker
                .getTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID);
        assertEquals(StatusBarNotification.MAX_LOG_TAG_LENGTH, loggedId.length());
        assertEquals(CHANNEL_ID_LONG.substring(0, 10), loggedId.substring(0, 10));
    }

    @Test
    public void testLogMakerNoGroup() {
        final LogMaker logMaker = getNotification(PKG, null, CHANNEL_ID).getLogMaker();

        assertNull(
                logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
    }

    @Test
    public void testLogMakerLongGroup() {
        final LogMaker logMaker = getNotification(PKG, GROUP_ID_LONG, CHANNEL_ID)
                .getLogMaker();

        final String loggedId = (String)
                logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID);
        assertEquals(StatusBarNotification.MAX_LOG_TAG_LENGTH, loggedId.length());
        assertEquals(GROUP_ID_LONG.substring(0, 10), loggedId.substring(0, 10));
    }

    @Test
    public void testLogMakerOverrideGroup() {
        StatusBarNotification sbn = getNotification(PKG, GROUP_ID_1, CHANNEL_ID);
        assertEquals(GROUP_ID_1,
                sbn.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));

        sbn.setOverrideGroupKey(GROUP_ID_2);
        assertEquals(GROUP_ID_2,
                sbn.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));

        sbn.setOverrideGroupKey(null);
        assertEquals(GROUP_ID_1,
                sbn.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
    }

    private StatusBarNotification getNotification(String pkg, String group, String channelId) {
        final Notification.Builder builder = new Notification.Builder(mMockContext, channelId)
                .setContentTitle("foo")
                .setSmallIcon(android.R.drawable.sym_def_app_icon);

        if (group != null) {
            builder.setGroup(group);
        }

        Notification n = builder.build();
        return new StatusBarNotification(
                pkg, pkg, ID, TAG, UID, UID, n, USER, null, UID);
    }

}
+16 −0
Original line number Diff line number Diff line
@@ -20,11 +20,13 @@ import static android.service.notification.NotificationListenerService.Ranking
        .USER_SENTIMENT_NEGATIVE;

import android.content.Context;
import android.metrics.LogMaker;
import android.util.Log;

import androidx.annotation.VisibleForTesting;

import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -58,6 +60,8 @@ public class NotificationBlockingHelperManager {
     */
    private boolean mIsShadeExpanded;

    private MetricsLogger mMetricsLogger = new MetricsLogger();

    @Inject
    public NotificationBlockingHelperManager(Context context) {
        mContext = context;
@@ -100,6 +104,11 @@ public class NotificationBlockingHelperManager {
            mBlockingHelperRow = row;
            mBlockingHelperRow.setBlockingHelperShowing(true);

            // Log triggering of blocking helper by the system. This log line
            // should be emitted before the "display" log line.
            mMetricsLogger.write(
                    getLogMaker().setSubtype(MetricsEvent.BLOCKING_HELPER_TRIGGERED_BY_SYSTEM));

            // We don't care about the touch origin (x, y) since we're opening guts without any
            // explicit user interaction.
            manager.openGuts(mBlockingHelperRow, 0, 0, menuRow.getLongpressMenuItem(mContext));
@@ -153,6 +162,13 @@ public class NotificationBlockingHelperManager {
                || mNonBlockablePkgs.contains(makeChannelKey(packageName, channelName));
    }

    private LogMaker getLogMaker() {
        return mBlockingHelperRow.getStatusBarNotification()
            .getLogMaker()
            .setCategory(MetricsEvent.NOTIFICATION_ITEM)
            .setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER);
    }

    // Format must stay in sync with frameworks/base/core/res/res/values/config.xml
    // config_nonBlockableNotificationPackages
    private String makeChannelKey(String pkg, String channel) {
+17 −0
Original line number Diff line number Diff line
@@ -123,11 +123,15 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
    private OnClickListener mOnKeepShowing = v -> {
        mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
        closeControls(v);
        mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
                .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_STAY_SILENT));
    };

    private OnClickListener mOnToggleSilent = v -> {
        Runnable saveImportance = () -> {
            swapContent(ACTION_TOGGLE_SILENT, true /* animate */);
            mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
                    .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_ALERT_ME));
        };
        if (mCheckSaveListener != null) {
            mCheckSaveListener.checkSave(saveImportance, mSbn);
@@ -139,6 +143,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
    private OnClickListener mOnStopOrMinimizeNotifications = v -> {
        Runnable saveImportance = () -> {
            swapContent(ACTION_BLOCK, true /* animate */);
            mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
                    .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_BLOCKED));
        };
        if (mCheckSaveListener != null) {
            mCheckSaveListener.checkSave(saveImportance, mSbn);
@@ -153,6 +159,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
        logBlockingHelperCounter(NotificationCounters.BLOCKING_HELPER_UNDO);
        mMetricsLogger.write(importanceChangeLogMaker().setType(MetricsEvent.TYPE_DISMISS));
        swapContent(ACTION_UNDO, true /* animate */);
        mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
                .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_UNDO));
    };

    public NotificationInfo(Context context, AttributeSet attrs) {
@@ -251,6 +259,9 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
        bindHeader();
        bindPrompt();
        bindButtons();

        mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
                .setSubtype(MetricsEvent.BLOCKING_HELPER_DISPLAY));
    }

    private void bindHeader() throws RemoteException {
@@ -588,6 +599,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
        confirmation.setAlpha(1f);
        header.setVisibility(VISIBLE);
        header.setAlpha(1f);
        mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
                .setSubtype(MetricsEvent.BLOCKING_HELPER_DISMISS));
    }

    @Override
@@ -733,4 +746,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
            }
        }
    }

    private LogMaker getLogMaker() {
        return mSbn.getLogMaker().setCategory(MetricsEvent.NOTIFICATION_ITEM);
    }
}
+6 −3
Original line number Diff line number Diff line
@@ -45,7 +45,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;

import android.app.INotificationManager;
@@ -73,6 +72,7 @@ import android.widget.TextView;

import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
@@ -498,12 +498,15 @@ public class NotificationInfoTest extends SysuiTestCase {
    }

    @Test
    public void testLogBlockingHelperCounter_doesntLogForNormalGutsView() throws Exception {
    public void testLogBlockingHelperCounter_logGutsViewDisplayed() throws Exception {
        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
                IMPORTANCE_DEFAULT);
        mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent");
        verifyZeroInteractions(mMetricsLogger);
        verify(mMetricsLogger).write(argThat(logMaker ->
                logMaker.getType() == MetricsEvent.NOTIFICATION_BLOCKING_HELPER
                        && logMaker.getSubtype() == MetricsEvent.BLOCKING_HELPER_DISPLAY
        ));
    }

    @Test
Loading