Tag foreground notis that use certain services

- Draw over other apps
- Camera
- Microphone

The icons are not yet clickable, and the system 'drawing over
other apps' notification still appears even when the app's
notification is tagged.

Test: runtest systemui
Bug: 64085448
Change-Id: Ib3b0cdd9adced82f562f256cb81af80dc395440d
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
index 943020c..18dd3c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
@@ -16,6 +16,14 @@
 
 package com.android.systemui;
 
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 import android.annotation.UserIdInt;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -24,17 +32,14 @@
 import android.service.notification.StatusBarNotification;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.widget.RemoteViews;
+
 import com.android.internal.messages.nano.SystemMessageProto;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class ForegroundServiceControllerTest extends SysuiTestCase {
@@ -49,7 +54,7 @@
     }
 
     @Test
-    public void testNotificationCRUD() {
+    public void testNotificationCRUD_dungeon() {
         StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, "com.example.app1");
         StatusBarNotification sbn_user2_app2_fg = makeMockFgSBN(USERID_TWO, "com.example.app2");
         StatusBarNotification sbn_user1_app3_fg = makeMockFgSBN(USERID_ONE, "com.example.app3");
@@ -98,6 +103,101 @@
     }
 
     @Test
+    public void testNotificationCRUD_stdLayout() {
+        StatusBarNotification sbn_user1_app1_fg =
+                makeMockFgSBN(USERID_ONE, "com.example.app1", 0, true);
+        StatusBarNotification sbn_user2_app2_fg =
+                makeMockFgSBN(USERID_TWO, "com.example.app2", 1, true);
+        StatusBarNotification sbn_user1_app3_fg =
+                makeMockFgSBN(USERID_ONE, "com.example.app3", 2, true);
+        StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1",
+                5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
+        StatusBarNotification sbn_user2_app1 = makeMockSBN(USERID_TWO, "com.example.app1",
+                5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
+
+        assertFalse(fsc.removeNotification(sbn_user1_app3_fg));
+        assertFalse(fsc.removeNotification(sbn_user2_app2_fg));
+        assertFalse(fsc.removeNotification(sbn_user1_app1_fg));
+        assertFalse(fsc.removeNotification(sbn_user1_app1));
+        assertFalse(fsc.removeNotification(sbn_user2_app1));
+
+        fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN);
+        fsc.addNotification(sbn_user2_app2_fg, NotificationManager.IMPORTANCE_MIN);
+        fsc.addNotification(sbn_user1_app3_fg, NotificationManager.IMPORTANCE_MIN);
+        fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN);
+        fsc.addNotification(sbn_user2_app1, NotificationManager.IMPORTANCE_MIN);
+
+        // these are never added to the tracker
+        assertFalse(fsc.removeNotification(sbn_user1_app1));
+        assertFalse(fsc.removeNotification(sbn_user2_app1));
+
+        fsc.updateNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN);
+        fsc.updateNotification(sbn_user2_app1, NotificationManager.IMPORTANCE_MIN);
+        // should still not be there
+        assertFalse(fsc.removeNotification(sbn_user1_app1));
+        assertFalse(fsc.removeNotification(sbn_user2_app1));
+
+        fsc.updateNotification(sbn_user2_app2_fg, NotificationManager.IMPORTANCE_MIN);
+        fsc.updateNotification(sbn_user1_app3_fg, NotificationManager.IMPORTANCE_MIN);
+        fsc.updateNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN);
+
+        assertTrue(fsc.removeNotification(sbn_user1_app3_fg));
+        assertFalse(fsc.removeNotification(sbn_user1_app3_fg));
+
+        assertTrue(fsc.removeNotification(sbn_user2_app2_fg));
+        assertFalse(fsc.removeNotification(sbn_user2_app2_fg));
+
+        assertTrue(fsc.removeNotification(sbn_user1_app1_fg));
+        assertFalse(fsc.removeNotification(sbn_user1_app1_fg));
+
+        assertFalse(fsc.removeNotification(sbn_user1_app1));
+        assertFalse(fsc.removeNotification(sbn_user2_app1));
+    }
+
+    @Test
+    public void testAppOpsCRUD() {
+        // no crash on remove that doesn't exist
+        fsc.onAppOpChanged(9, 1000, "pkg1", false);
+        assertNull(fsc.getAppOps(0, "pkg1"));
+
+        // multiuser & multipackage
+        fsc.onAppOpChanged(8, 50, "pkg1", true);
+        fsc.onAppOpChanged(1, 60, "pkg3", true);
+        fsc.onAppOpChanged(7, 500000, "pkg2", true);
+
+        assertEquals(1, fsc.getAppOps(0, "pkg1").size());
+        assertTrue(fsc.getAppOps(0, "pkg1").contains(8));
+
+        assertEquals(1, fsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size());
+        assertTrue(fsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7));
+
+        assertEquals(1, fsc.getAppOps(0, "pkg3").size());
+        assertTrue(fsc.getAppOps(0, "pkg3").contains(1));
+
+        // multiple ops for the same package
+        fsc.onAppOpChanged(9, 50, "pkg1", true);
+        fsc.onAppOpChanged(5, 50, "pkg1", true);
+
+        assertEquals(3, fsc.getAppOps(0, "pkg1").size());
+        assertTrue(fsc.getAppOps(0, "pkg1").contains(8));
+        assertTrue(fsc.getAppOps(0, "pkg1").contains(9));
+        assertTrue(fsc.getAppOps(0, "pkg1").contains(5));
+
+        assertEquals(1, fsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size());
+        assertTrue(fsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7));
+
+        // remove one of the multiples
+        fsc.onAppOpChanged(9, 50, "pkg1", false);
+        assertEquals(2, fsc.getAppOps(0, "pkg1").size());
+        assertTrue(fsc.getAppOps(0, "pkg1").contains(8));
+        assertTrue(fsc.getAppOps(0, "pkg1").contains(5));
+
+        // remove last op
+        fsc.onAppOpChanged(1, 60, "pkg3", false);
+        assertNull(fsc.getAppOps(0, "pkg3"));
+    }
+
+    @Test
     public void testDungeonPredicate() {
         StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1",
                 5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
@@ -252,6 +352,14 @@
         assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
         assertTrue(fsc.isDungeonNeededForUser(USERID_ONE));
 
+        // importance upgrade
+        fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN);
+        assertTrue(fsc.isDungeonNeededForUser(USERID_ONE));
+        assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
+        sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
+        fsc.updateNotification(sbn_user1_app1_fg,
+                NotificationManager.IMPORTANCE_DEFAULT); // this is now a fg notification
+
         // finally, let's turn off the service
         fsc.addNotification(makeMockDungeon(USERID_ONE, null),
                 NotificationManager.IMPORTANCE_DEFAULT);
@@ -260,12 +368,71 @@
         assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
     }
 
+    @Test
+    public void testStdLayoutBasic() {
+        final String PKG1 = "com.example.app0";
+
+        StatusBarNotification sbn_user1_app1 = makeMockFgSBN(USERID_ONE, PKG1, 0, true);
+        sbn_user1_app1.getNotification().flags = 0;
+        StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1, 1, true);
+        fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN); // not fg
+        assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required!
+        fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN);
+        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // app1 has got it covered
+        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "otherpkg"));
+        // let's take out the non-fg notification and see what happens.
+        fsc.removeNotification(sbn_user1_app1);
+        // still covered by sbn_user1_app1_fg
+        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1));
+        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "anyPkg"));
+
+        // let's attempt to downgrade the notification from FLAG_FOREGROUND and see what we get
+        StatusBarNotification sbn_user1_app1_fg_sneaky = makeMockFgSBN(USERID_ONE, PKG1, 1, true);
+        sbn_user1_app1_fg_sneaky.getNotification().flags = 0;
+        fsc.updateNotification(sbn_user1_app1_fg_sneaky, NotificationManager.IMPORTANCE_MIN);
+        assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required!
+        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "anything"));
+        // ok, ok, we'll put it back
+        sbn_user1_app1_fg_sneaky.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
+        fsc.updateNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN);
+        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1));
+        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "whatever"));
+
+        assertTrue(fsc.removeNotification(sbn_user1_app1_fg_sneaky));
+        assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required!
+        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "a"));
+
+        // let's try a custom layout
+        sbn_user1_app1_fg_sneaky = makeMockFgSBN(USERID_ONE, PKG1, 1, false);
+        fsc.updateNotification(sbn_user1_app1_fg_sneaky, NotificationManager.IMPORTANCE_MIN);
+        assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required!
+        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "anything"));
+        // now let's test an upgrade (non fg to fg)
+        fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN);
+        assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1));
+        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "b"));
+        sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
+        fsc.updateNotification(sbn_user1_app1,
+                NotificationManager.IMPORTANCE_MIN); // this is now a fg notification
+
+        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, PKG1));
+        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1));
+
+        // remove it, make sure we're out of compliance again
+        assertTrue(fsc.removeNotification(sbn_user1_app1)); // was fg, should return true
+        assertFalse(fsc.removeNotification(sbn_user1_app1));
+        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, PKG1));
+        assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1));
+    }
+
     private StatusBarNotification makeMockSBN(int userid, String pkg, int id, String tag,
             int flags) {
         final Notification n = mock(Notification.class);
+        n.extras = new Bundle();
         n.flags = flags;
         return makeMockSBN(userid, pkg, id, tag, n);
     }
+
     private StatusBarNotification makeMockSBN(int userid, String pkg, int id, String tag,
             Notification n) {
         final StatusBarNotification sbn = mock(StatusBarNotification.class);
@@ -278,9 +445,25 @@
         when(sbn.getKey()).thenReturn("MOCK:"+userid+"|"+pkg+"|"+id+"|"+tag);
         return sbn;
     }
+
+    private StatusBarNotification makeMockFgSBN(int userid, String pkg, int id,
+            boolean usesStdLayout) {
+        StatusBarNotification sbn =
+                makeMockSBN(userid, pkg, id, "foo", Notification.FLAG_FOREGROUND_SERVICE);
+        if (usesStdLayout) {
+            sbn.getNotification().contentView = null;
+            sbn.getNotification().headsUpContentView = null;
+            sbn.getNotification().bigContentView = null;
+        } else {
+            sbn.getNotification().contentView = mock(RemoteViews.class);
+        }
+        return sbn;
+    }
+
     private StatusBarNotification makeMockFgSBN(int userid, String pkg) {
         return makeMockSBN(userid, pkg, 1000, "foo", Notification.FLAG_FOREGROUND_SERVICE);
     }
+
     private StatusBarNotification makeMockDungeon(int userid, String[] pkgs) {
         final Notification n = mock(Notification.class);
         n.flags = Notification.FLAG_ONGOING_EVENT;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AppOpsListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AppOpsListenerTest.java
new file mode 100644
index 0000000..2a48c4b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AppOpsListenerTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AppOpsManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.systemui.ForegroundServiceController;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class AppOpsListenerTest extends SysuiTestCase {
+    private static final String TEST_PACKAGE_NAME = "test";
+    private static final int TEST_UID = 0;
+
+    @Mock private NotificationPresenter mPresenter;
+    @Mock private AppOpsManager mAppOpsManager;
+
+    // Dependency mocks:
+    @Mock private NotificationEntryManager mEntryManager;
+    @Mock private ForegroundServiceController mFsc;
+
+    private AppOpsListener mListener;
+    private Handler mHandler;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
+        mDependency.injectTestDependency(ForegroundServiceController.class, mFsc);
+        getContext().addMockSystemService(AppOpsManager.class, mAppOpsManager);
+        mHandler = new Handler(Looper.getMainLooper());
+        when(mPresenter.getHandler()).thenReturn(mHandler);
+
+        mListener = new AppOpsListener(mContext);
+    }
+
+    @Test
+    public void testOnlyListenForFewOps() {
+        mListener.setUpWithPresenter(mPresenter, mEntryManager);
+
+        verify(mAppOpsManager, times(1)).startWatchingActive(AppOpsListener.OPS, mListener);
+    }
+
+    @Test
+    public void testStopListening() {
+        mListener.destroy();
+        verify(mAppOpsManager, times(1)).stopWatchingActive(mListener);
+    }
+
+    @Test
+    public void testInformEntryMgrOnAppOpsChange() {
+        mListener.setUpWithPresenter(mPresenter, mEntryManager);
+        mListener.onOpActiveChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+        waitForIdleSync(mHandler);
+        verify(mEntryManager, times(1)).updateNotificationsForAppOps(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+    }
+
+    @Test
+    public void testInformFscOnAppOpsChange() {
+        mListener.setUpWithPresenter(mPresenter, mEntryManager);
+        mListener.onOpActiveChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+        waitForIdleSync(mHandler);
+        verify(mFsc, times(1)).onAppOpChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java
index 544585a..ce629bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java
@@ -19,10 +19,15 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.app.AppOpsManager;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.util.ArraySet;
+import android.view.NotificationHeaderView;
 import android.view.View;
 
 import com.android.systemui.SysuiTestCase;
@@ -146,4 +151,34 @@
         Assert.assertTrue("Should always play sounds when not trusted.",
                 mGroup.isSoundEffectsEnabled());
     }
+
+    @Test
+    public void testShowAppOpsIcons_noHeader() {
+        // public notification is custom layout - no header
+        mGroup.setSensitive(true, true);
+        mGroup.showAppOpsIcons(new ArraySet<>());
+    }
+
+    @Test
+    public void testShowAppOpsIcons_header() throws Exception {
+        NotificationHeaderView mockHeader = mock(NotificationHeaderView.class);
+
+        NotificationContentView publicLayout = mock(NotificationContentView.class);
+        mGroup.setPublicLayout(publicLayout);
+        NotificationContentView privateLayout = mock(NotificationContentView.class);
+        mGroup.setPrivateLayout(privateLayout);
+        NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
+        when(mockContainer.getNotificationChildCount()).thenReturn(1);
+        when(mockContainer.getHeaderView()).thenReturn(mockHeader);
+        mGroup.setChildrenContainer(mockContainer);
+
+        ArraySet<Integer> ops = new ArraySet<>();
+        ops.add(AppOpsManager.OP_ANSWER_PHONE_CALLS);
+        mGroup.showAppOpsIcons(ops);
+
+        verify(mockHeader, times(1)).showAppOpsIcons(ops);
+        verify(privateLayout, times(1)).showAppOpsIcons(ops);
+        verify(publicLayout, times(1)).showAppOpsIcons(ops);
+
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java
index 436849c..1fb4c37 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java
@@ -16,14 +16,23 @@
 
 package com.android.systemui.statusbar;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+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;
 
+import android.app.AppOpsManager;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.util.ArraySet;
+import android.view.NotificationHeaderView;
 import android.view.View;
 
 import com.android.systemui.SysuiTestCase;
@@ -75,4 +84,35 @@
         mView.setHeadsUpAnimatingAway(true);
         Assert.assertFalse(mView.isAnimatingVisibleType());
     }
+
+    @Test
+    @UiThreadTest
+    public void testShowAppOpsIcons() {
+        NotificationHeaderView mockContracted = mock(NotificationHeaderView.class);
+        when(mockContracted.findViewById(com.android.internal.R.id.notification_header))
+                .thenReturn(mockContracted);
+        NotificationHeaderView mockExpanded = mock(NotificationHeaderView.class);
+        when(mockExpanded.findViewById(com.android.internal.R.id.notification_header))
+                .thenReturn(mockExpanded);
+        NotificationHeaderView mockHeadsUp = mock(NotificationHeaderView.class);
+        when(mockHeadsUp.findViewById(com.android.internal.R.id.notification_header))
+                .thenReturn(mockHeadsUp);
+        NotificationHeaderView mockAmbient = mock(NotificationHeaderView.class);
+        when(mockAmbient.findViewById(com.android.internal.R.id.notification_header))
+                .thenReturn(mockAmbient);
+
+        mView.setContractedChild(mockContracted);
+        mView.setExpandedChild(mockExpanded);
+        mView.setHeadsUpChild(mockHeadsUp);
+        mView.setAmbientChild(mockAmbient);
+
+        ArraySet<Integer> ops = new ArraySet<>();
+        ops.add(AppOpsManager.OP_ANSWER_PHONE_CALLS);
+        mView.showAppOpsIcons(ops);
+
+        verify(mockContracted, times(1)).showAppOpsIcons(ops);
+        verify(mockExpanded, times(1)).showAppOpsIcons(ops);
+        verify(mockAmbient, never()).showAppOpsIcons(ops);
+        verify(mockHeadsUp, times(1)).showAppOpsIcons(any());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java
index 972eddb..b1e1c02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java
@@ -16,8 +16,16 @@
 
 package com.android.systemui.statusbar;
 
+import static android.app.AppOpsManager.OP_ACCEPT_HANDOVER;
+import static android.app.AppOpsManager.OP_CAMERA;
+
+import static junit.framework.Assert.assertEquals;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -33,7 +41,9 @@
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.util.ArraySet;
 
+import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 
@@ -41,6 +51,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -51,6 +63,10 @@
 
     private final StatusBarNotification mMockStatusBarNotification =
             mock(StatusBarNotification.class);
+    @Mock
+    ForegroundServiceController mFsc;
+    @Mock
+    NotificationData.Environment mEnvironment;
 
     private final IPackageManager mMockPackageManager = mock(IPackageManager.class);
     private NotificationData mNotificationData;
@@ -58,6 +74,7 @@
 
     @Before
     public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
         when(mMockStatusBarNotification.getUid()).thenReturn(UID_NORMAL);
 
         when(mMockPackageManager.checkUidPermission(
@@ -69,9 +86,11 @@
                 eq(UID_ALLOW_DURING_SETUP)))
                 .thenReturn(PackageManager.PERMISSION_GRANTED);
 
-        NotificationData.Environment mock = mock(NotificationData.Environment.class);
-        when(mock.getGroupManager()).thenReturn(new NotificationGroupManager());
-        mNotificationData = new TestableNotificationData(mock);
+        mDependency.injectTestDependency(ForegroundServiceController.class, mFsc);
+        when(mEnvironment.getGroupManager()).thenReturn(new NotificationGroupManager());
+        when(mEnvironment.isDeviceProvisioned()).thenReturn(true);
+        when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
+        mNotificationData = new TestableNotificationData(mEnvironment);
         mNotificationData.updateRanking(mock(NotificationListenerService.RankingMap.class));
         mRow = new NotificationTestHelper(getContext()).createRow();
     }
@@ -117,6 +136,117 @@
         Assert.assertTrue(mRow.getEntry().channel != null);
     }
 
+    @Test
+    public void testAdd_appOpsAdded() {
+        ArraySet<Integer> expected = new ArraySet<>();
+        expected.add(3);
+        expected.add(235);
+        expected.add(1);
+        when(mFsc.getAppOps(mRow.getEntry().notification.getUserId(),
+                mRow.getEntry().notification.getPackageName())).thenReturn(expected);
+
+        mNotificationData.add(mRow.getEntry());
+        assertEquals(expected.size(),
+                mNotificationData.get(mRow.getEntry().key).mActiveAppOps.size());
+        for (int op : expected) {
+            assertTrue(" entry missing op " + op,
+                    mNotificationData.get(mRow.getEntry().key).mActiveAppOps.contains(op));
+        }
+    }
+
+    @Test
+    public void testAdd_noExistingAppOps() {
+        when(mFsc.getAppOps(mRow.getEntry().notification.getUserId(),
+                mRow.getEntry().notification.getPackageName())).thenReturn(null);
+
+        mNotificationData.add(mRow.getEntry());
+        assertEquals(0, mNotificationData.get(mRow.getEntry().key).mActiveAppOps.size());
+    }
+
+    @Test
+    public void testAllRelevantNotisTaggedWithAppOps() throws Exception {
+        mNotificationData.add(mRow.getEntry());
+        ExpandableNotificationRow row2 = new NotificationTestHelper(getContext()).createRow();
+        mNotificationData.add(row2.getEntry());
+        ExpandableNotificationRow diffPkg =
+                new NotificationTestHelper(getContext()).createRow("pkg", 4000);
+        mNotificationData.add(diffPkg.getEntry());
+
+        ArraySet<Integer> expectedOps = new ArraySet<>();
+        expectedOps.add(OP_CAMERA);
+        expectedOps.add(OP_ACCEPT_HANDOVER);
+
+        for (int op : expectedOps) {
+            mNotificationData.updateAppOp(op, NotificationTestHelper.UID,
+                    NotificationTestHelper.PKG, true);
+        }
+        for (int op : expectedOps) {
+            assertTrue(mRow.getEntry().key + " doesn't have op " + op,
+                    mNotificationData.get(mRow.getEntry().key).mActiveAppOps.contains(op));
+            assertTrue(row2.getEntry().key + " doesn't have op " + op,
+                    mNotificationData.get(row2.getEntry().key).mActiveAppOps.contains(op));
+            assertFalse(diffPkg.getEntry().key + " has op " + op,
+                    mNotificationData.get(diffPkg.getEntry().key).mActiveAppOps.contains(op));
+        }
+    }
+
+    @Test
+    public void testAppOpsRemoval() throws Exception {
+        mNotificationData.add(mRow.getEntry());
+        ExpandableNotificationRow row2 = new NotificationTestHelper(getContext()).createRow();
+        mNotificationData.add(row2.getEntry());
+
+        ArraySet<Integer> expectedOps = new ArraySet<>();
+        expectedOps.add(OP_CAMERA);
+        expectedOps.add(OP_ACCEPT_HANDOVER);
+
+        for (int op : expectedOps) {
+            mNotificationData.updateAppOp(op, NotificationTestHelper.UID,
+                    NotificationTestHelper.PKG, true);
+        }
+
+        expectedOps.remove(OP_ACCEPT_HANDOVER);
+        mNotificationData.updateAppOp(OP_ACCEPT_HANDOVER, NotificationTestHelper.UID,
+                NotificationTestHelper.PKG, false);
+
+        assertTrue(mRow.getEntry().key + " doesn't have op " + OP_CAMERA,
+                mNotificationData.get(mRow.getEntry().key).mActiveAppOps.contains(OP_CAMERA));
+        assertTrue(row2.getEntry().key + " doesn't have op " + OP_CAMERA,
+                mNotificationData.get(row2.getEntry().key).mActiveAppOps.contains(OP_CAMERA));
+        assertFalse(mRow.getEntry().key + " has op " + OP_ACCEPT_HANDOVER,
+                mNotificationData.get(mRow.getEntry().key)
+                        .mActiveAppOps.contains(OP_ACCEPT_HANDOVER));
+        assertFalse(row2.getEntry().key + " has op " + OP_ACCEPT_HANDOVER,
+                mNotificationData.get(row2.getEntry().key)
+                        .mActiveAppOps.contains(OP_ACCEPT_HANDOVER));
+    }
+
+    @Test
+    public void testSuppressSystemAlertNotification() {
+        when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false);
+        when(mFsc.isSystemAlertNotification(any())).thenReturn(true);
+
+        assertTrue(mNotificationData.shouldFilterOut(mRow.getEntry().notification));
+    }
+
+    @Test
+    public void testDoNotSuppressSystemAlertNotification() {
+        when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(true);
+        when(mFsc.isSystemAlertNotification(any())).thenReturn(true);
+
+        assertFalse(mNotificationData.shouldFilterOut(mRow.getEntry().notification));
+
+        when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(true);
+        when(mFsc.isSystemAlertNotification(any())).thenReturn(false);
+
+        assertFalse(mNotificationData.shouldFilterOut(mRow.getEntry().notification));
+
+        when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false);
+        when(mFsc.isSystemAlertNotification(any())).thenReturn(false);
+
+        assertFalse(mNotificationData.shouldFilterOut(mRow.getEntry().notification));
+    }
+
     private void initStatusBarNotification(boolean allowDuringSetup) {
         Bundle bundle = new Bundle();
         bundle.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, allowDuringSetup);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
index f9ec3f92..37dd939 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
@@ -23,14 +23,17 @@
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
+import android.app.AppOpsManager;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.Context;
@@ -274,4 +277,40 @@
 
         assertNull(mEntryManager.getNotificationData().get(mSbn.getKey()));
     }
+
+    @Test
+    public void testUpdateAppOps_foregroundNoti() {
+        com.android.systemui.util.Assert.isNotMainThread();
+
+        when(mForegroundServiceController.getStandardLayoutKey(anyInt(), anyString()))
+                .thenReturn("something");
+        mEntry.row = mRow;
+        mEntryManager.getNotificationData().add(mEntry);
+
+
+        mHandler.post(() -> {
+            mEntryManager.updateNotificationsForAppOps(
+                    AppOpsManager.OP_CAMERA, mEntry.notification.getUid(),
+                    mEntry.notification.getPackageName(), true);
+        });
+        waitForIdleSync(mHandler);
+
+        verify(mPresenter, times(1)).updateNotificationViews();
+        assertTrue(mEntryManager.getNotificationData().get(mEntry.key).mActiveAppOps.contains(
+                AppOpsManager.OP_CAMERA));
+    }
+
+    @Test
+    public void testUpdateAppOps_otherNoti() {
+        com.android.systemui.util.Assert.isNotMainThread();
+
+        when(mForegroundServiceController.getStandardLayoutKey(anyInt(), anyString()))
+                .thenReturn(null);
+        mHandler.post(() -> {
+            mEntryManager.updateNotificationsForAppOps(AppOpsManager.OP_CAMERA, 1000, "pkg", true);
+        });
+        waitForIdleSync(mHandler);
+
+        verify(mPresenter, never()).updateNotificationViews();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index f3c1171..2764254 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -48,6 +48,8 @@
     private ExpandableNotificationRow mRow;
     private InflationException mException;
     private HeadsUpManager mHeadsUpManager;
+    protected static final String PKG = "com.android.systemui";
+    protected static final int UID = 1000;
 
     public NotificationTestHelper(Context context) {
         mContext = context;
@@ -55,7 +57,7 @@
         mHeadsUpManager = new HeadsUpManagerPhone(mContext, null, mGroupManager, null, null);
     }
 
-    public ExpandableNotificationRow createRow() throws Exception {
+    public ExpandableNotificationRow createRow(String pkg, int uid) throws Exception {
         Notification publicVersion = new Notification.Builder(mContext).setSmallIcon(
                 R.drawable.ic_person)
                 .setCustomContentView(new RemoteViews(mContext.getPackageName(),
@@ -67,10 +69,19 @@
                 .setContentText("Text")
                 .setPublicVersion(publicVersion)
                 .build();
-        return createRow(notification);
+        return createRow(notification, pkg, uid);
+    }
+
+    public ExpandableNotificationRow createRow() throws Exception {
+        return createRow(PKG, UID);
     }
 
     public ExpandableNotificationRow createRow(Notification notification) throws Exception {
+        return createRow(notification, PKG, UID);
+    }
+
+    public ExpandableNotificationRow createRow(Notification notification, String pkg, int uid)
+            throws Exception {
         LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
                 mContext.LAYOUT_INFLATER_SERVICE);
         mInstrumentation.runOnMainSync(() -> {
@@ -83,8 +94,7 @@
         row.setHeadsUpManager(mHeadsUpManager);
         row.setAboveShelfChangedListener(aboveShelf -> {});
         UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
-        StatusBarNotification sbn = new StatusBarNotification("com.android.systemui",
-                "com.android.systemui", mId++, null, 1000,
+        StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, mId++, null, uid,
                 2000, notification, mUser, null, System.currentTimeMillis());
         NotificationData.Entry entry = new NotificationData.Entry(sbn);
         entry.row = row;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index fbe730a..76ed452 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -19,6 +19,9 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -170,6 +173,19 @@
         assertEquals(View.VISIBLE, entry1.row.getVisibility());
     }
 
+    @Test
+    public void testUpdateNotificationViews_appOps() throws Exception {
+        NotificationData.Entry entry0 = createEntry();
+        entry0.row = spy(entry0.row);
+        when(mNotificationData.getActiveNotifications()).thenReturn(
+                Lists.newArrayList(entry0));
+        mListContainer.addContainerView(entry0.row);
+
+        mViewHierarchyManager.updateNotificationViews();
+
+        verify(entry0.row, times(1)).showAppOpsIcons(any());
+    }
+
     private class FakeListContainer implements NotificationListContainer {
         final LinearLayout mLayout = new LinearLayout(mContext);
         final List<View> mRows = Lists.newArrayList();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 31442af..ff545f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -69,6 +69,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.statusbar.ActivatableNotificationView;
+import com.android.systemui.statusbar.AppOpsListener;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationData;
@@ -145,6 +146,7 @@
         mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
         mDependency.injectTestDependency(NotificationListener.class, mNotificationListener);
         mDependency.injectTestDependency(KeyguardMonitor.class, mock(KeyguardMonitorImpl.class));
+        mDependency.injectTestDependency(AppOpsListener.class, mock(AppOpsListener.class));
 
         mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
         mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class));