Merge "Statsd logging: notification controls." into rvc-dev
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 5c802bd..df1de63 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -24,6 +24,7 @@
 import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -116,7 +117,8 @@
             ChannelEditorDialogController channelEditorDialogController,
             CurrentUserContextTracker contextTracker,
             Provider<PriorityOnboardingDialogController.Builder> builderProvider,
-            BubbleController bubbleController) {
+            BubbleController bubbleController,
+            UiEventLogger uiEventLogger) {
         return new NotificationGutsManager(
                 context,
                 visualStabilityManager,
@@ -131,7 +133,8 @@
                 channelEditorDialogController,
                 contextTracker,
                 builderProvider,
-                bubbleController);
+                bubbleController,
+                uiEventLogger);
     }
 
     /** Provides an instance of {@link VisualStabilityManager} */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java
index e445c9d..28c53dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java
@@ -31,6 +31,7 @@
 import android.widget.TextView;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.R;
 
@@ -50,6 +51,7 @@
     private MetricsLogger mMetricsLogger;
     private OnSettingsClickListener mOnSettingsClickListener;
     private NotificationGuts mGutsContainer;
+    private UiEventLogger mUiEventLogger;
 
     private OnClickListener mOnOk = v -> {
         mGutsContainer.closeControls(v, false);
@@ -66,6 +68,7 @@
     public void bindGuts(final PackageManager pm,
             final OnSettingsClickListener onSettingsClick,
             final StatusBarNotification sbn,
+            final UiEventLogger uiEventLogger,
             ArraySet<Integer> activeOps) {
         mPkg = sbn.getPackageName();
         mSbn = sbn;
@@ -73,11 +76,13 @@
         mAppName = mPkg;
         mOnSettingsClickListener = onSettingsClick;
         mAppOps = activeOps;
+        mUiEventLogger = uiEventLogger;
 
         bindHeader();
         bindPrompt();
         bindButtons();
 
+        logUiEvent(NotificationAppOpsEvent.NOTIFICATION_APP_OPS_OPEN);
         mMetricsLogger = new MetricsLogger();
         mMetricsLogger.visibility(MetricsEvent.APP_OPS_GUTS, true);
     }
@@ -188,6 +193,7 @@
 
     @Override
     public boolean handleCloseControls(boolean save, boolean force) {
+        logUiEvent(NotificationAppOpsEvent.NOTIFICATION_APP_OPS_CLOSE);
         if (mMetricsLogger != null) {
             mMetricsLogger.visibility(MetricsEvent.APP_OPS_GUTS, false);
         }
@@ -198,4 +204,11 @@
     public int getActualHeight() {
         return getHeight();
     }
+
+    private void logUiEvent(NotificationAppOpsEvent event) {
+        if (mSbn != null) {
+            mUiEventLogger.logWithInstanceId(event,
+                    mSbn.getUid(), mSbn.getPackageName(), mSbn.getInstanceId());
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationAppOpsEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationAppOpsEvent.java
new file mode 100644
index 0000000..c856245
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationAppOpsEvent.java
@@ -0,0 +1,41 @@
+/*
+ * 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.statusbar.notification.row;
+
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+
+enum NotificationAppOpsEvent implements UiEventLogger.UiEventEnum {
+    @UiEvent(doc = "User opened app ops controls on a notification (for active "
+            + "privacy-sensitive permissions usage)")
+    NOTIFICATION_APP_OPS_OPEN(597),
+
+    @UiEvent(doc = "User closed app ops controls")
+    NOTIFICATION_APP_OPS_CLOSE(598),
+
+    @UiEvent(doc = "User clicked through to settings in app ops controls")
+    NOTIFICATION_APP_OPS_SETTINGS_CLICK(599);
+
+    private final int mId;
+    NotificationAppOpsEvent(int id) {
+        mId = id;
+    }
+    @Override public int getId() {
+        return mId;
+    }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationControlsEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationControlsEvent.java
new file mode 100644
index 0000000..6833326
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationControlsEvent.java
@@ -0,0 +1,40 @@
+/*
+ * 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.statusbar.notification.row;
+
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+
+enum NotificationControlsEvent implements UiEventLogger.UiEventEnum {
+    @UiEvent(doc = "The user opened the notification inline controls.")
+    NOTIFICATION_CONTROLS_OPEN(594),
+
+    @UiEvent(doc = "In notification inline controls, the user saved a notification channel "
+            + "importance change.")
+    NOTIFICATION_CONTROLS_SAVE_IMPORTANCE(595),
+
+    @UiEvent(doc = "The user closed the notification inline controls.")
+    NOTIFICATION_CONTROLS_CLOSE(596);
+
+    private final int mId;
+    NotificationControlsEvent(int id) {
+        mId = id;
+    }
+    @Override public int getId() {
+        return mId;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 8337cbe4..24883f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -41,6 +41,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.settingslib.notification.ConversationIconFactory;
 import com.android.systemui.Dependency;
@@ -121,6 +122,7 @@
     private final ShortcutManager mShortcutManager;
     private final CurrentUserContextTracker mContextTracker;
     private final Provider<PriorityOnboardingDialogController.Builder> mBuilderProvider;
+    private final UiEventLogger mUiEventLogger;
 
     /**
      * Injected constructor. See {@link NotificationsModule}.
@@ -135,7 +137,8 @@
             ChannelEditorDialogController channelEditorDialogController,
             CurrentUserContextTracker contextTracker,
             Provider<PriorityOnboardingDialogController.Builder> builderProvider,
-            BubbleController bubbleController) {
+            BubbleController bubbleController,
+            UiEventLogger uiEventLogger) {
         mContext = context;
         mVisualStabilityManager = visualStabilityManager;
         mStatusBarLazy = statusBarLazy;
@@ -150,6 +153,7 @@
         mBuilderProvider = builderProvider;
         mChannelEditorDialogController = channelEditorDialogController;
         mBubbleController = bubbleController;
+        mUiEventLogger = uiEventLogger;
     }
 
     public void setUpWithPresenter(NotificationPresenter presenter,
@@ -315,12 +319,16 @@
 
         AppOpsInfo.OnSettingsClickListener onSettingsClick =
                 (View v, String pkg, int uid, ArraySet<Integer> ops) -> {
-            mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_OPS_GUTS_SETTINGS);
-            guts.resetFalsingCheck();
-            startAppOpsSettingsActivity(pkg, uid, ops, row);
+                    mUiEventLogger.logWithInstanceId(
+                            NotificationAppOpsEvent.NOTIFICATION_APP_OPS_SETTINGS_CLICK,
+                            sbn.getUid(), sbn.getPackageName(), sbn.getInstanceId());
+                    mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_OPS_GUTS_SETTINGS);
+                    guts.resetFalsingCheck();
+                    startAppOpsSettingsActivity(pkg, uid, ops, row);
         };
         if (!row.getEntry().mActiveAppOps.isEmpty()) {
-            appOpsInfoView.bindGuts(pmUser, onSettingsClick, sbn, row.getEntry().mActiveAppOps);
+            appOpsInfoView.bindGuts(pmUser, onSettingsClick, sbn, mUiEventLogger,
+                    row.getEntry().mActiveAppOps);
         }
     }
 
@@ -370,6 +378,7 @@
                 row.getEntry(),
                 onSettingsClick,
                 onAppSettingsClick,
+                mUiEventLogger,
                 mDeviceProvisionedController.isDeviceProvisioned(),
                 row.getIsNonblockable(),
                 mHighPriorityProvider.isHighPriority(row.getEntry()));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index a131ebe..f0c93b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -56,6 +56,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
@@ -122,6 +123,7 @@
     private OnAppSettingsClickListener mAppSettingsClickListener;
     private NotificationGuts mGutsContainer;
     private Drawable mPkgIcon;
+    private UiEventLogger mUiEventLogger;
 
     @VisibleForTesting
     boolean mSkipPost = false;
@@ -182,6 +184,7 @@
             NotificationEntry entry,
             OnSettingsClickListener onSettingsClick,
             OnAppSettingsClickListener onAppSettingsClick,
+            UiEventLogger uiEventLogger,
             boolean isDeviceProvisioned,
             boolean isNonblockable,
             boolean wasShownHighPriority)
@@ -205,6 +208,7 @@
         mAppUid = mSbn.getUid();
         mDelegatePkg = mSbn.getOpPkg();
         mIsDeviceProvisioned = isDeviceProvisioned;
+        mUiEventLogger = uiEventLogger;
 
         int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage(
                 pkg, mAppUid, false /* includeDeleted */);
@@ -223,6 +227,7 @@
 
         bindInlineControls();
 
+        logUiEvent(NotificationControlsEvent.NOTIFICATION_CONTROLS_OPEN);
         mMetricsLogger.write(notificationControlsLogMaker());
     }
 
@@ -397,6 +402,7 @@
      */
     private void updateImportance() {
         if (mChosenImportance != null) {
+            logUiEvent(NotificationControlsEvent.NOTIFICATION_CONTROLS_SAVE_IMPORTANCE);
             mMetricsLogger.write(importanceChangeLogMaker());
 
             int newImportance = mChosenImportance;
@@ -483,6 +489,7 @@
 
         bindInlineControls();
 
+        logUiEvent(NotificationControlsEvent.NOTIFICATION_CONTROLS_CLOSE);
         mMetricsLogger.write(notificationControlsLogMaker().setType(MetricsEvent.TYPE_CLOSE));
     }
 
@@ -627,6 +634,13 @@
         }
     }
 
+    private void logUiEvent(NotificationControlsEvent event) {
+        if (mSbn != null) {
+            mUiEventLogger.logWithInstanceId(event,
+                    mSbn.getUid(), mSbn.getPackageName(), mSbn.getInstanceId());
+        }
+    }
+
     /**
      * Returns a LogMaker with all available notification information.
      * Caller should set category, type, and maybe subtype, before passing it to mMetricsLogger.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/AppOpsInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/AppOpsInfoTest.java
index ec73a75..43d8b50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/AppOpsInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/AppOpsInfoTest.java
@@ -49,6 +49,7 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 
@@ -69,6 +70,7 @@
     private final PackageManager mMockPackageManager = mock(PackageManager.class);
     private final NotificationGuts mGutsParent = mock(NotificationGuts.class);
     private StatusBarNotification mSbn;
+    private UiEventLoggerFake mUiEventLogger = new UiEventLoggerFake();
 
     @Before
     public void setUp() throws Exception {
@@ -94,7 +96,7 @@
     @Test
     public void testBindNotification_SetsTextApplicationName() {
         when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, new ArraySet<>());
+        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, new ArraySet<>());
         final TextView textView = mAppOpsInfo.findViewById(R.id.pkgname);
         assertTrue(textView.getText().toString().contains("App Name"));
     }
@@ -104,7 +106,7 @@
         final Drawable iconDrawable = mock(Drawable.class);
         when(mMockPackageManager.getApplicationIcon(any(ApplicationInfo.class)))
                 .thenReturn(iconDrawable);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, new ArraySet<>());
+        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, new ArraySet<>());
         final ImageView iconView = mAppOpsInfo.findViewById(R.id.pkgicon);
         assertEquals(iconDrawable, iconView.getDrawable());
     }
@@ -120,7 +122,7 @@
             assertEquals(expectedOps, ops);
             assertEquals(TEST_UID, uid);
             latch.countDown();
-        }, mSbn, expectedOps);
+        }, mSbn, mUiEventLogger, expectedOps);
 
         final View settingsButton = mAppOpsInfo.findViewById(R.id.settings);
         settingsButton.performClick();
@@ -129,6 +131,14 @@
     }
 
     @Test
+    public void testBindNotification_LogsOpen() throws Exception {
+        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, new ArraySet<>());
+        assertEquals(1, mUiEventLogger.numLogs());
+        assertEquals(NotificationAppOpsEvent.NOTIFICATION_APP_OPS_OPEN.getId(),
+                mUiEventLogger.eventId(0));
+    }
+
+    @Test
     public void testOk() {
         ArraySet<Integer> expectedOps = new ArraySet<>();
         expectedOps.add(OP_CAMERA);
@@ -139,7 +149,7 @@
             assertEquals(expectedOps, ops);
             assertEquals(TEST_UID, uid);
             latch.countDown();
-        }, mSbn, expectedOps);
+        }, mSbn, mUiEventLogger, expectedOps);
 
         final View okButton = mAppOpsInfo.findViewById(R.id.ok);
         okButton.performClick();
@@ -151,7 +161,7 @@
     public void testPrompt_camera() {
         ArraySet<Integer> expectedOps = new ArraySet<>();
         expectedOps.add(OP_CAMERA);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, expectedOps);
+        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps);
         TextView prompt = mAppOpsInfo.findViewById(R.id.prompt);
         assertEquals("This app is using the camera.", prompt.getText());
     }
@@ -160,7 +170,7 @@
     public void testPrompt_mic() {
         ArraySet<Integer> expectedOps = new ArraySet<>();
         expectedOps.add(OP_RECORD_AUDIO);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, expectedOps);
+        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps);
         TextView prompt = mAppOpsInfo.findViewById(R.id.prompt);
         assertEquals("This app is using the microphone.", prompt.getText());
     }
@@ -169,7 +179,7 @@
     public void testPrompt_overlay() {
         ArraySet<Integer> expectedOps = new ArraySet<>();
         expectedOps.add(OP_SYSTEM_ALERT_WINDOW);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, expectedOps);
+        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps);
         TextView prompt = mAppOpsInfo.findViewById(R.id.prompt);
         assertEquals("This app is displaying over other apps on your screen.", prompt.getText());
     }
@@ -179,7 +189,7 @@
         ArraySet<Integer> expectedOps = new ArraySet<>();
         expectedOps.add(OP_CAMERA);
         expectedOps.add(OP_RECORD_AUDIO);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, expectedOps);
+        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps);
         TextView prompt = mAppOpsInfo.findViewById(R.id.prompt);
         assertEquals("This app is using the microphone and camera.", prompt.getText());
     }
@@ -190,7 +200,7 @@
         expectedOps.add(OP_CAMERA);
         expectedOps.add(OP_RECORD_AUDIO);
         expectedOps.add(OP_SYSTEM_ALERT_WINDOW);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, expectedOps);
+        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps);
         TextView prompt = mAppOpsInfo.findViewById(R.id.prompt);
         assertEquals("This app is displaying over other apps on your screen and using"
                 + " the microphone and camera.", prompt.getText());
@@ -201,7 +211,7 @@
         ArraySet<Integer> expectedOps = new ArraySet<>();
         expectedOps.add(OP_CAMERA);
         expectedOps.add(OP_SYSTEM_ALERT_WINDOW);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, expectedOps);
+        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps);
         TextView prompt = mAppOpsInfo.findViewById(R.id.prompt);
         assertEquals("This app is displaying over other apps on your screen and using"
                 + " the camera.", prompt.getText());
@@ -212,7 +222,7 @@
         ArraySet<Integer> expectedOps = new ArraySet<>();
         expectedOps.add(OP_RECORD_AUDIO);
         expectedOps.add(OP_SYSTEM_ALERT_WINDOW);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, expectedOps);
+        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps);
         TextView prompt = mAppOpsInfo.findViewById(R.id.prompt);
         assertEquals("This app is displaying over other apps on your screen and using"
                 + " the microphone.", prompt.getText());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 5b5873b..9dee843 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -64,6 +64,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -149,7 +151,8 @@
         mGutsManager = new NotificationGutsManager(mContext, mVisualStabilityManager,
                 () -> mStatusBar, mHandler, mHandler, mAccessibilityManager, mHighPriorityProvider,
                 mINotificationManager, mLauncherApps, mShortcutManager,
-                mChannelEditorDialogController, mContextTracker, mProvider, mBubbleController);
+                mChannelEditorDialogController, mContextTracker, mProvider, mBubbleController,
+                new UiEventLoggerFake());
         mGutsManager.setUpWithPresenter(mPresenter, mStackScroller,
                 mCheckSaveListener, mOnSettingsClickListener);
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
@@ -362,6 +365,7 @@
                 eq(entry),
                 any(NotificationInfo.OnSettingsClickListener.class),
                 any(NotificationInfo.OnAppSettingsClickListener.class),
+                any(UiEventLogger.class),
                 eq(false),
                 eq(false),
                 eq(true) /* wasShownHighPriority */);
@@ -394,6 +398,7 @@
                 eq(entry),
                 any(NotificationInfo.OnSettingsClickListener.class),
                 any(NotificationInfo.OnAppSettingsClickListener.class),
+                any(UiEventLogger.class),
                 eq(true),
                 eq(false),
                 eq(false) /* wasShownHighPriority */);
@@ -424,6 +429,7 @@
                 eq(entry),
                 any(NotificationInfo.OnSettingsClickListener.class),
                 any(NotificationInfo.OnAppSettingsClickListener.class),
+                any(UiEventLogger.class),
                 eq(false),
                 eq(false),
                 eq(false) /* wasShownHighPriority */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index 6bf6072..ed982ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -62,6 +62,7 @@
 import android.widget.TextView;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
@@ -102,6 +103,7 @@
     private Set<NotificationChannel> mDefaultNotificationChannelSet = new HashSet<>();
     private StatusBarNotification mSbn;
     private NotificationEntry mEntry;
+    private UiEventLoggerFake mUiEventLogger = new UiEventLoggerFake();
 
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
@@ -187,6 +189,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -211,6 +214,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -231,6 +235,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -260,6 +265,7 @@
                 entry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -281,6 +287,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -307,6 +314,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -328,6 +336,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -348,6 +357,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -372,6 +382,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -392,6 +403,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 true,
                 true);
@@ -416,6 +428,7 @@
                     latch.countDown();
                 },
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -439,6 +452,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -462,6 +476,7 @@
                     assertEquals(mNotificationChannel, c);
                 },
                 null,
+                mUiEventLogger,
                 false,
                 false,
                 true);
@@ -482,6 +497,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -496,6 +512,7 @@
                 mEntry,
                 (View v, NotificationChannel c, int appUid) -> { },
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -519,6 +536,7 @@
                     latch.countDown();
                 },
                 null,
+                mUiEventLogger,
                 true,
                 true,
                 true);
@@ -543,6 +561,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -565,6 +584,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -587,6 +607,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 true,
                 true);
@@ -611,6 +632,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -630,6 +652,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 false);
@@ -649,6 +672,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -658,6 +682,28 @@
     }
 
     @Test
+    public void testBindNotification_LogsOpen() throws Exception {
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                mChannelEditorDialogController,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mEntry,
+                null,
+                null,
+                mUiEventLogger,
+                true,
+                false,
+                true);
+        assertEquals(1, mUiEventLogger.numLogs());
+        assertEquals(NotificationControlsEvent.NOTIFICATION_CONTROLS_OPEN.getId(),
+                mUiEventLogger.eventId(0));
+    }
+
+    @Test
     public void testDoesNotUpdateNotificationChannelAfterImportanceChanged() throws Exception {
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(
@@ -671,6 +717,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 false);
@@ -696,6 +743,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -721,6 +769,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -730,6 +779,13 @@
         verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
                 anyString(), eq(TEST_UID), any());
         assertEquals(originalImportance, mNotificationChannel.getImportance());
+
+        assertEquals(2, mUiEventLogger.numLogs());
+        assertEquals(NotificationControlsEvent.NOTIFICATION_CONTROLS_OPEN.getId(),
+                mUiEventLogger.eventId(0));
+        // The SAVE_IMPORTANCE event is logged whenever importance is saved, even if unchanged.
+        assertEquals(NotificationControlsEvent.NOTIFICATION_CONTROLS_SAVE_IMPORTANCE.getId(),
+                mUiEventLogger.eventId(1));
     }
 
     @Test
@@ -747,6 +803,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -773,6 +830,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -789,6 +847,12 @@
         assertTrue((updated.getValue().getUserLockedFields()
                 & USER_LOCKED_IMPORTANCE) != 0);
         assertEquals(IMPORTANCE_LOW, updated.getValue().getImportance());
+
+        assertEquals(2, mUiEventLogger.numLogs());
+        assertEquals(NotificationControlsEvent.NOTIFICATION_CONTROLS_OPEN.getId(),
+                mUiEventLogger.eventId(0));
+        assertEquals(NotificationControlsEvent.NOTIFICATION_CONTROLS_SAVE_IMPORTANCE.getId(),
+                mUiEventLogger.eventId(1));
     }
 
     @Test
@@ -805,6 +869,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 false);
@@ -838,6 +903,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -871,6 +937,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 false);
@@ -907,6 +974,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 false);
@@ -942,6 +1010,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 true);
@@ -968,6 +1037,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 false);
@@ -997,6 +1067,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 false);
@@ -1029,6 +1100,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 false);
@@ -1040,6 +1112,10 @@
         mTestableLooper.processAllMessages();
         verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
                 eq(TEST_PACKAGE_NAME), eq(TEST_UID), eq(mNotificationChannel));
+
+        assertEquals(1, mUiEventLogger.numLogs());
+        assertEquals(NotificationControlsEvent.NOTIFICATION_CONTROLS_OPEN.getId(),
+                mUiEventLogger.eventId(0));
     }
 
     @Test
@@ -1056,6 +1132,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 false
@@ -1088,6 +1165,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 false
@@ -1113,6 +1191,7 @@
                 mEntry,
                 null,
                 null,
+                mUiEventLogger,
                 true,
                 false,
                 false