[Notif] Blocking helper basic metrics

Adding first pass of counters for basic actions taken with the blocking
helper. This includes:
- All notification dismissal events
- Shown event
- Dismissal via outside interaction
- Any button taps to dismiss
- Undo taps

Test: Via manual testing & updated tests (+added two new)
Bug: 74609669
Change-Id: I0adcfe03cfd10809ef7a2f13afbefcb420246a43
This commit is contained in:
Rohan Shah
2018-04-27 17:21:50 -07:00
parent 032ab15256
commit da5dcdd519
5 changed files with 125 additions and 27 deletions

View File

@@ -76,6 +76,7 @@ import com.android.systemui.statusbar.NotificationGuts.GutsContent;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.NotificationCounters;
import com.android.systemui.statusbar.notification.NotificationInflater;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.NotificationViewWrapper;
@@ -1252,6 +1253,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
Dependency.get(NotificationBlockingHelperManager.class);
boolean isBlockingHelperShown = manager.perhapsShowBlockingHelper(this, mMenuRow);
Dependency.get(MetricsLogger.class).count(NotificationCounters.NOTIFICATION_DISMISSED, 1);
// Continue with dismiss since we don't want the blocking helper to be directly associated
// with a certain notification.
performDismiss(fromAccessibility);

View File

@@ -30,6 +30,7 @@ import android.view.ViewAnimationUtils;
import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -270,7 +271,8 @@ public class NotificationGuts extends FrameLayout {
/** Animates out the guts view via either a fade or a circular reveal. */
private void animateClose(int x, int y, boolean shouldDoCircularReveal) {
@VisibleForTesting
void animateClose(int x, int y, boolean shouldDoCircularReveal) {
if (shouldDoCircularReveal) {
// Circular reveal originating at (x, y)
if (x == -1 || y == -1) {
@@ -340,7 +342,8 @@ public class NotificationGuts extends FrameLayout {
}
}
private void setExposed(boolean exposed, boolean needsFalsingProtection) {
@VisibleForTesting
void setExposed(boolean exposed, boolean needsFalsingProtection) {
final boolean wasExposed = mExposed;
mExposed = exposed;
mNeedsFalsingProtection = needsFalsingProtection;

View File

@@ -54,17 +54,20 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.NotificationCounters;
import java.util.List;
/**
* The guts of a notification revealed when performing a long press.
* The guts of a notification revealed when performing a long press. This also houses the blocking
* helper affordance that allows a user to keep/stop notifications after swiping one away.
*/
public class NotificationInfo extends LinearLayout implements NotificationGuts.GutsContent {
private static final String TAG = "InfoGuts";
private INotificationManager mINotificationManager;
private PackageManager mPm;
private MetricsLogger mMetricsLogger;
private String mPackageName;
private String mAppName;
@@ -84,17 +87,27 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
private OnAppSettingsClickListener mAppSettingsClickListener;
private NotificationGuts mGutsContainer;
/** Whether this view is being shown as part of the blocking helper */
/** Whether this view is being shown as part of the blocking helper. */
private boolean mIsForBlockingHelper;
private boolean mNegativeUserSentiment;
private OnClickListener mOnKeepShowing = this::closeControls;
/** Counter tag that describes how the user exit or quit out of this view. */
private String mExitReasonCounter = NotificationCounters.BLOCKING_HELPER_DISMISSED;
private OnClickListener mOnKeepShowing = v -> {
mExitReasonCounter = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
closeControls(v);
};
private OnClickListener mOnStopOrMinimizeNotifications = v -> {
mExitReasonCounter = NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS;
swapContent(false);
};
private OnClickListener mOnUndo = v -> {
// Reset exit counter that we'll log and record an undo event separately (not an exit event)
mExitReasonCounter = NotificationCounters.BLOCKING_HELPER_DISMISSED;
logBlockingHelperCounter(NotificationCounters.BLOCKING_HELPER_UNDO);
swapContent(true);
};
@@ -151,6 +164,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
boolean isUserSentimentNegative)
throws RemoteException {
mINotificationManager = iNotificationManager;
mMetricsLogger = Dependency.get(MetricsLogger.class);
mPackageName = pkg;
mNumUniqueChannelsInRow = numUniqueChannelsInRow;
mSbn = sbn;
@@ -183,6 +197,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
bindHeader();
bindPrompt();
bindButtons();
logBlockingHelperCounter(NotificationCounters.BLOCKING_HELPER_SHOWN);
}
private void bindHeader() throws RemoteException {
@@ -235,6 +251,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
final int appUidF = mAppUid;
settingsButton.setOnClickListener(
(View view) -> {
logBlockingHelperCounter(
NotificationCounters.BLOCKING_HELPER_NOTIF_SETTINGS);
mOnSettingsClickListener.onClick(view,
mNumUniqueChannelsInRow > 1 ? null : mSingleNotificationChannel,
appUidF);
@@ -269,6 +287,13 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
}
}
@VisibleForTesting
void logBlockingHelperCounter(String counterTag) {
if (mIsForBlockingHelper) {
mMetricsLogger.count(counterTag, 1);
}
}
private boolean hasImportanceChanged() {
return mSingleNotificationChannel != null && mStartingUserImportance != mChosenImportance;
}
@@ -437,25 +462,15 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
*/
@VisibleForTesting
void closeControls(View v) {
if (mIsForBlockingHelper) {
NotificationBlockingHelperManager manager =
Dependency.get(NotificationBlockingHelperManager.class);
manager.dismissCurrentBlockingHelper();
// Since this won't get a callback via gutsContainer.closeControls, save the new
// importance values immediately.
saveImportance();
} else {
int[] parentLoc = new int[2];
int[] targetLoc = new int[2];
mGutsContainer.getLocationOnScreen(parentLoc);
v.getLocationOnScreen(targetLoc);
final int centerX = v.getWidth() / 2;
final int centerY = v.getHeight() / 2;
final int x = targetLoc[0] - parentLoc[0] + centerX;
final int y = targetLoc[1] - parentLoc[1] + centerY;
mGutsContainer.closeControls(x, y, true /* save */, false /* force */);
}
int[] parentLoc = new int[2];
int[] targetLoc = new int[2];
mGutsContainer.getLocationOnScreen(parentLoc);
v.getLocationOnScreen(targetLoc);
final int centerX = v.getWidth() / 2;
final int centerY = v.getHeight() / 2;
final int x = targetLoc[0] - parentLoc[0] + centerX;
final int y = targetLoc[1] - parentLoc[1] + centerY;
mGutsContainer.closeControls(x, y, true /* save */, false /* force */);
}
@Override
@@ -480,6 +495,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
if (save) {
saveImportance();
}
logBlockingHelperCounter(mExitReasonCounter);
return false;
}

View File

@@ -0,0 +1,44 @@
/*
* 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 com.android.systemui.statusbar.notification;
/**
* Constants for counter tags for Notification-related actions/views.
*/
public class NotificationCounters {
/** Counter tag for notification dismissal. */
public static final String NOTIFICATION_DISMISSED = "notification_dismissed";
/** Counter tag for when the blocking helper is shown to the user. */
public static final String BLOCKING_HELPER_SHOWN = "blocking_helper_shown";
/** Counter tag for when the blocking helper is dismissed via a miscellaneous interaction. */
public static final String BLOCKING_HELPER_DISMISSED = "blocking_helper_dismissed";
/** Counter tag for when the user hits 'stop notifications' in the blocking helper. */
public static final String BLOCKING_HELPER_STOP_NOTIFICATIONS =
"blocking_helper_stop_notifications";
/** Counter tag for when the user hits 'keep showing' in the blocking helper. */
public static final String BLOCKING_HELPER_KEEP_SHOWING =
"blocking_helper_keep_showing";
/**
* Counter tag for when the user hits undo in context of the blocking helper - this can happen
* multiple times per view.
*/
public static final String BLOCKING_HELPER_UNDO = "blocking_helper_undo";
/** Counter tag for when the user hits the notification settings icon in the blocking helper. */
public static final String BLOCKING_HELPER_NOTIF_SETTINGS =
"blocking_helper_notif_settings";
}

View File

@@ -33,9 +33,12 @@ import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
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.when;
@@ -44,7 +47,6 @@ import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -52,7 +54,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.os.IBinder;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
@@ -65,6 +67,7 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
@@ -100,7 +103,7 @@ public class NotificationInfoTest extends SysuiTestCase {
private StatusBarNotification mSbn;
@Rule public MockitoRule mockito = MockitoJUnit.rule();
private Looper mLooper;
@Mock private MetricsLogger mMetricsLogger;
@Mock private INotificationManager mMockINotificationManager;
@Mock private PackageManager mMockPackageManager;
@Mock private NotificationBlockingHelperManager mBlockingHelperManager;
@@ -112,6 +115,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mBlockingHelperManager);
mTestableLooper = TestableLooper.get(this);
mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
// Inflate the layout
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
mNotificationInfo = (NotificationInfo) layoutInflater.inflate(R.layout.notification_info,
@@ -300,6 +304,24 @@ public class NotificationInfoTest extends SysuiTestCase {
assertEquals(View.VISIBLE, settingsButton.getVisibility());
}
@Test
public void testLogBlockingHelperCounter_doesntLogForNormalGutsView() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false);
mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent");
verify(mMetricsLogger, times(0)).count(anyString(), anyInt());
}
@Test
public void testLogBlockingHelperCounter_logsForBlockingHelper() throws Exception {
// Bind notification logs an event, so this counts as one invocation for the metrics logger.
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false, true,
true);
mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent");
verify(mMetricsLogger, times(2)).count(anyString(), anyInt());
}
@Test
public void testOnClickListenerPassesNullChannelForBundle() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
@@ -471,6 +493,13 @@ public class NotificationInfoTest extends SysuiTestCase {
false /* isNonblockable */, true /* isForBlockingHelper */,
true /* isUserSentimentNegative */);
NotificationGuts guts = spy(new NotificationGuts(mContext, null));
when(guts.getWindowToken()).thenReturn(mock(IBinder.class));
doNothing().when(guts).animateClose(anyInt(), anyInt(), anyBoolean());
doNothing().when(guts).setExposed(anyBoolean(), anyBoolean());
guts.setGutsContent(mNotificationInfo);
mNotificationInfo.setGutsParent(guts);
mNotificationInfo.findViewById(R.id.keep).performClick();
verify(mBlockingHelperManager).dismissCurrentBlockingHelper();
@@ -495,6 +524,9 @@ public class NotificationInfoTest extends SysuiTestCase {
false /* isNonblockable */,
true /* isForBlockingHelper */,
false /* isUserSentimentNegative */);
NotificationGuts guts = mock(NotificationGuts.class);
doCallRealMethod().when(guts).closeControls(anyInt(), anyInt(), anyBoolean(), anyBoolean());
mNotificationInfo.setGutsParent(guts);
mNotificationInfo.closeControls(mNotificationInfo);