diff options
3 files changed, 926 insertions, 1219 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java deleted file mode 100644 index a64339e20f7c..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java +++ /dev/null @@ -1,1189 +0,0 @@ -/* - * Copyright (C) 2016 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 static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO; -import static android.app.NotificationChannel.SOCIAL_MEDIA_ID; -import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE; -import static android.app.NotificationManager.IMPORTANCE_DEFAULT; -import static android.app.NotificationManager.IMPORTANCE_LOW; -import static android.app.NotificationManager.IMPORTANCE_MIN; -import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; -import static android.print.PrintManager.PRINT_SPOOLER_PACKAGE_NAME; -import static android.view.View.GONE; -import static android.view.View.VISIBLE; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - -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.eq; -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.Flags; -import android.app.INotificationManager; -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationChannelGroup; -import android.app.PendingIntent; -import android.app.Person; -import android.content.ComponentName; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.graphics.drawable.Drawable; -import android.os.RemoteException; -import android.os.UserHandle; -import android.platform.test.annotations.DisableFlags; -import android.platform.test.annotations.EnableFlags; -import android.platform.test.flag.junit.SetFlagsRule; -import android.service.notification.StatusBarNotification; -import android.telecom.TelecomManager; -import android.testing.TestableLooper; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.testing.UiEventLoggerFake; -import com.android.systemui.Dependency; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.res.R; -import com.android.systemui.statusbar.RankingBuilder; -import com.android.systemui.statusbar.notification.AssistantFeedbackController; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - -import java.util.List; -import java.util.Optional; -import java.util.concurrent.CountDownLatch; - -@SmallTest -@RunWith(AndroidJUnit4.class) -@TestableLooper.RunWithLooper -public class NotificationInfoTest extends SysuiTestCase { - private static final String TEST_PACKAGE_NAME = "test_package"; - private static final String TEST_SYSTEM_PACKAGE_NAME = PRINT_SPOOLER_PACKAGE_NAME; - private static final int TEST_UID = 1; - private static final String TEST_CHANNEL = "test_channel"; - private static final String TEST_CHANNEL_NAME = "TEST CHANNEL NAME"; - - private TestableLooper mTestableLooper; - private NotificationInfo mNotificationInfo; - private NotificationChannel mNotificationChannel; - private NotificationChannel mDefaultNotificationChannel; - private NotificationChannel mClassifiedNotificationChannel; - private StatusBarNotification mSbn; - private NotificationEntry mEntry; - private UiEventLoggerFake mUiEventLogger = new UiEventLoggerFake(); - - @Rule - public MockitoRule mockito = MockitoJUnit.rule(); - @Mock - private MetricsLogger mMetricsLogger; - @Mock - private INotificationManager mMockINotificationManager; - @Mock - private PackageManager mMockPackageManager; - @Mock - private OnUserInteractionCallback mOnUserInteractionCallback; - @Mock - private ChannelEditorDialogController mChannelEditorDialogController; - @Mock - private AssistantFeedbackController mAssistantFeedbackController; - @Mock - private TelecomManager mTelecomManager; - - @Rule - public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); - - @Before - public void setUp() throws Exception { - mTestableLooper = TestableLooper.get(this); - - mContext.addMockSystemService(TelecomManager.class, mTelecomManager); - - mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper()); - // Inflate the layout - final LayoutInflater layoutInflater = LayoutInflater.from(mContext); - mNotificationInfo = (NotificationInfo) layoutInflater.inflate(R.layout.notification_info, - null); - mNotificationInfo.setGutsParent(mock(NotificationGuts.class)); - // Our view is never attached to a window so the View#post methods in NotificationInfo never - // get called. Setting this will skip the post and do the action immediately. - mNotificationInfo.mSkipPost = true; - - // PackageManager must return a packageInfo and applicationInfo. - final PackageInfo packageInfo = new PackageInfo(); - packageInfo.packageName = TEST_PACKAGE_NAME; - when(mMockPackageManager.getPackageInfo(eq(TEST_PACKAGE_NAME), anyInt())) - .thenReturn(packageInfo); - final ApplicationInfo applicationInfo = new ApplicationInfo(); - applicationInfo.uid = TEST_UID; // non-zero - final PackageInfo systemPackageInfo = new PackageInfo(); - systemPackageInfo.packageName = TEST_SYSTEM_PACKAGE_NAME; - when(mMockPackageManager.getPackageInfo(eq(TEST_SYSTEM_PACKAGE_NAME), anyInt())) - .thenReturn(systemPackageInfo); - when(mMockPackageManager.getPackageInfo(eq("android"), anyInt())) - .thenReturn(packageInfo); - - ComponentName assistant = new ComponentName("package", "service"); - when(mMockINotificationManager.getAllowedNotificationAssistant()).thenReturn(assistant); - ResolveInfo ri = new ResolveInfo(); - ri.activityInfo = new ActivityInfo(); - ri.activityInfo.packageName = assistant.getPackageName(); - ri.activityInfo.name = "activity"; - when(mMockPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(List.of(ri)); - - // Package has one channel by default. - when(mMockINotificationManager.getNumNotificationChannelsForPackage( - eq(TEST_PACKAGE_NAME), eq(TEST_UID), anyBoolean())).thenReturn(1); - - // Some test channels. - mNotificationChannel = new NotificationChannel( - TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_LOW); - mDefaultNotificationChannel = new NotificationChannel( - NotificationChannel.DEFAULT_CHANNEL_ID, TEST_CHANNEL_NAME, - IMPORTANCE_LOW); - mClassifiedNotificationChannel = - new NotificationChannel(SOCIAL_MEDIA_ID, "social", IMPORTANCE_LOW); - - Notification notification = new Notification(); - notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, applicationInfo); - mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, - notification, UserHandle.getUserHandleForUid(TEST_UID), null, 0); - mEntry = new NotificationEntryBuilder().setSbn(mSbn).build(); - when(mAssistantFeedbackController.isFeedbackEnabled()).thenReturn(false); - when(mAssistantFeedbackController.getInlineDescriptionResource(any())) - .thenReturn(R.string.notification_channel_summary_automatic); - } - - private void doStandardBind() throws Exception { - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - null, - null, - null, - mUiEventLogger, - true, - false, - true, - mAssistantFeedbackController, - mMetricsLogger, null); - } - - @Test - public void testBindNotification_SetsTextApplicationName() throws Exception { - when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name"); - doStandardBind(); - final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name); - assertTrue(textView.getText().toString().contains("App Name")); - assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility()); - } - - @Test - public void testBindNotification_SetsPackageIcon() throws Exception { - final Drawable iconDrawable = mock(Drawable.class); - when(mMockPackageManager.getApplicationIcon(any(ApplicationInfo.class))) - .thenReturn(iconDrawable); - doStandardBind(); - final ImageView iconView = mNotificationInfo.findViewById(R.id.pkg_icon); - assertEquals(iconDrawable, iconView.getDrawable()); - } - - @Test - public void testBindNotification_noDelegate() throws Exception { - doStandardBind(); - final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); - assertEquals(GONE, nameView.getVisibility()); - } - - @Test - public void testBindNotification_delegate() throws Exception { - mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, "other", 0, null, TEST_UID, 0, - new Notification(), UserHandle.CURRENT, null, 0); - final ApplicationInfo applicationInfo = new ApplicationInfo(); - applicationInfo.uid = 7; // non-zero - when(mMockPackageManager.getApplicationInfo(eq("other"), anyInt())).thenReturn( - applicationInfo); - when(mMockPackageManager.getApplicationLabel(any())).thenReturn("Other"); - - NotificationEntry entry = new NotificationEntryBuilder().setSbn(mSbn).build(); - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - entry, - null, - null, - null, - mUiEventLogger, - true, - false, - true, - mAssistantFeedbackController, - mMetricsLogger, null); - final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); - assertEquals(VISIBLE, nameView.getVisibility()); - assertTrue(nameView.getText().toString().contains("Proxied")); - } - - @Test - public void testBindNotification_GroupNameHiddenIfNoGroup() throws Exception { - doStandardBind(); - final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name); - assertEquals(GONE, groupNameView.getVisibility()); - } - - @Test - public void testBindNotification_SetsGroupNameIfNonNull() throws Exception { - mNotificationChannel.setGroup("test_group_id"); - final NotificationChannelGroup notificationChannelGroup = - new NotificationChannelGroup("test_group_id", "Test Group Name"); - when(mMockINotificationManager.getNotificationChannelGroupForPackage( - eq("test_group_id"), eq(TEST_PACKAGE_NAME), eq(TEST_UID))) - .thenReturn(notificationChannelGroup); - doStandardBind(); - final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name); - assertEquals(View.VISIBLE, groupNameView.getVisibility()); - assertEquals("Test Group Name", groupNameView.getText()); - } - - @Test - public void testBindNotification_SetsTextChannelName() throws Exception { - doStandardBind(); - final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); - assertEquals(TEST_CHANNEL_NAME, textView.getText()); - } - - @Test - public void testBindNotification_DefaultChannelDoesNotUseChannelName() throws Exception { - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mDefaultNotificationChannel, - mEntry, - null, - null, - null, - mUiEventLogger, - true, - false, - true, - mAssistantFeedbackController, - mMetricsLogger, null); - final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); - assertEquals(GONE, textView.getVisibility()); - } - - @Test - public void testBindNotification_DefaultChannelUsesChannelNameIfMoreChannelsExist() - throws Exception { - // Package has more than one channel by default. - when(mMockINotificationManager.getNumNotificationChannelsForPackage( - eq(TEST_PACKAGE_NAME), eq(TEST_UID), anyBoolean())).thenReturn(10); - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mDefaultNotificationChannel, - mEntry, - null, - null, - null, - mUiEventLogger, - true, - false, - true, - mAssistantFeedbackController, - mMetricsLogger, - null); - final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); - assertEquals(VISIBLE, textView.getVisibility()); - } - - @Test - public void testBindNotification_UnblockablePackageUsesChannelName() throws Exception { - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - null, - null, - null, - mUiEventLogger, - true, - true, - true, - mAssistantFeedbackController, - mMetricsLogger, null); - final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); - assertEquals(VISIBLE, textView.getVisibility()); - } - - @Test - public void testBindNotification_SetsOnClickListenerForSettings() throws Exception { - final CountDownLatch latch = new CountDownLatch(1); - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - (View v, NotificationChannel c, int appUid) -> { - assertEquals(mNotificationChannel, c); - latch.countDown(); - }, - null, - null, - mUiEventLogger, - true, - false, - true, - mAssistantFeedbackController, - mMetricsLogger, - null); - - final View settingsButton = mNotificationInfo.findViewById(R.id.info); - settingsButton.performClick(); - // Verify that listener was triggered. - assertEquals(0, latch.getCount()); - } - - @Test - public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() throws Exception { - doStandardBind(); - final View settingsButton = mNotificationInfo.findViewById(R.id.info); - assertTrue(settingsButton.getVisibility() != View.VISIBLE); - } - - @Test - public void testBindNotification_SettingsButtonInvisibleWhenDeviceUnprovisioned() - throws Exception { - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - (View v, NotificationChannel c, int appUid) -> { - assertEquals(mNotificationChannel, c); - }, - null, - null, - mUiEventLogger, - false, - false, - true, - mAssistantFeedbackController, - mMetricsLogger, - null); - final View settingsButton = mNotificationInfo.findViewById(R.id.info); - assertTrue(settingsButton.getVisibility() != View.VISIBLE); - } - - @Test - public void testBindNotification_SettingsButtonReappearsAfterSecondBind() throws Exception { - doStandardBind(); - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - (View v, NotificationChannel c, int appUid) -> { }, - null, - null, - mUiEventLogger, - true, - false, - true, - mAssistantFeedbackController, - mMetricsLogger, - null); - final View settingsButton = mNotificationInfo.findViewById(R.id.info); - assertEquals(View.VISIBLE, settingsButton.getVisibility()); - } - - @Test - public void testBindNotification_whenAppUnblockable() throws Exception { - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - null, - null, - null, - mUiEventLogger, - true, - true, - true, - mAssistantFeedbackController, - mMetricsLogger, null); - final TextView view = mNotificationInfo.findViewById(R.id.non_configurable_text); - assertEquals(View.VISIBLE, view.getVisibility()); - assertEquals(mContext.getString(R.string.notification_unblockable_desc), - view.getText()); - assertEquals(GONE, - mNotificationInfo.findViewById(R.id.interruptiveness_settings).getVisibility()); - } - - @Test - public void testBindNotification_whenCurrentlyInCall() throws Exception { - when(mMockINotificationManager.isInCall(anyString(), anyInt())).thenReturn(true); - - Person person = new Person.Builder() - .setName("caller") - .build(); - Notification.Builder nb = new Notification.Builder( - mContext, mNotificationChannel.getId()) - .setContentTitle("foo") - .setSmallIcon(android.R.drawable.sym_def_app_icon) - .setStyle(Notification.CallStyle.forOngoingCall( - person, mock(PendingIntent.class))) - .setFullScreenIntent(mock(PendingIntent.class), true) - .addAction(new Notification.Action.Builder(null, "test", null).build()); - - mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, - nb.build(), UserHandle.getUserHandleForUid(TEST_UID), null, 0); - mEntry.setSbn(mSbn); - doStandardBind(); - final TextView view = mNotificationInfo.findViewById(R.id.non_configurable_call_text); - assertEquals(View.VISIBLE, view.getVisibility()); - assertEquals(mContext.getString(R.string.notification_unblockable_call_desc), - view.getText()); - assertEquals(GONE, - mNotificationInfo.findViewById(R.id.interruptiveness_settings).getVisibility()); - assertEquals(GONE, - mNotificationInfo.findViewById(R.id.non_configurable_text).getVisibility()); - } - - @Test - public void testBindNotification_whenCurrentlyInCall_notCall() throws Exception { - when(mMockINotificationManager.isInCall(anyString(), anyInt())).thenReturn(true); - - Notification.Builder nb = new Notification.Builder( - mContext, mNotificationChannel.getId()) - .setContentTitle("foo") - .setSmallIcon(android.R.drawable.sym_def_app_icon) - .setFullScreenIntent(mock(PendingIntent.class), true) - .addAction(new Notification.Action.Builder(null, "test", null).build()); - - mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, - nb.build(), UserHandle.getUserHandleForUid(TEST_UID), null, 0); - mEntry.setSbn(mSbn); - doStandardBind(); - assertEquals(GONE, - mNotificationInfo.findViewById(R.id.non_configurable_call_text).getVisibility()); - assertEquals(VISIBLE, - mNotificationInfo.findViewById(R.id.interruptiveness_settings).getVisibility()); - assertEquals(GONE, - mNotificationInfo.findViewById(R.id.non_configurable_text).getVisibility()); - } - - @Test - public void testBindNotification_automaticIsVisible() throws Exception { - when(mAssistantFeedbackController.isFeedbackEnabled()).thenReturn(true); - doStandardBind(); - assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.automatic).getVisibility()); - assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.automatic_summary).getVisibility()); - } - - @Test - public void testBindNotification_automaticIsGone() throws Exception { - doStandardBind(); - assertEquals(GONE, mNotificationInfo.findViewById(R.id.automatic).getVisibility()); - assertEquals(GONE, mNotificationInfo.findViewById(R.id.automatic_summary).getVisibility()); - } - - @Test - public void testBindNotification_automaticIsSelected() throws Exception { - when(mAssistantFeedbackController.isFeedbackEnabled()).thenReturn(true); - mNotificationChannel.unlockFields(USER_LOCKED_IMPORTANCE); - doStandardBind(); - assertTrue(mNotificationInfo.findViewById(R.id.automatic).isSelected()); - } - - @Test - public void testBindNotification_alertIsSelected() throws Exception { - doStandardBind(); - assertTrue(mNotificationInfo.findViewById(R.id.alert).isSelected()); - } - - @Test - public void testBindNotification_silenceIsSelected() throws Exception { - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - null, - null, - null, - mUiEventLogger, - true, - false, - false, - mAssistantFeedbackController, - mMetricsLogger, - null); - assertTrue(mNotificationInfo.findViewById(R.id.silence).isSelected()); - } - - @Test - public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception { - doStandardBind(); - mTestableLooper.processAllMessages(); - verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( - anyString(), eq(TEST_UID), any()); - } - - @Test - public void testBindNotification_LogsOpen() throws Exception { - doStandardBind(); - 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( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - null, - null, - null, - mUiEventLogger, - true, - false, - false, - mAssistantFeedbackController, - mMetricsLogger, - null); - - mNotificationInfo.findViewById(R.id.alert).performClick(); - mTestableLooper.processAllMessages(); - verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( - anyString(), eq(TEST_UID), any()); - } - - @Test - public void testDoesNotUpdateNotificationChannelAfterImportanceChangedSilenced() - throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); - doStandardBind(); - - mNotificationInfo.findViewById(R.id.silence).performClick(); - mTestableLooper.processAllMessages(); - verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( - anyString(), eq(TEST_UID), any()); - } - - @Test - public void testDoesNotUpdateNotificationChannelAfterImportanceChangedAutomatic() - throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); - doStandardBind(); - - mNotificationInfo.findViewById(R.id.automatic).performClick(); - mTestableLooper.processAllMessages(); - verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( - anyString(), eq(TEST_UID), any()); - } - - @Test - public void testHandleCloseControls_persistAutomatic() - throws Exception { - when(mAssistantFeedbackController.isFeedbackEnabled()).thenReturn(true); - mNotificationChannel.unlockFields(USER_LOCKED_IMPORTANCE); - doStandardBind(); - - mNotificationInfo.handleCloseControls(true, false); - mTestableLooper.processAllMessages(); - verify(mMockINotificationManager, times(1)).unlockNotificationChannel( - anyString(), eq(TEST_UID), any()); - } - - @Test - public void testHandleCloseControls_DoesNotUpdateNotificationChannelIfUnchanged() - throws Exception { - int originalImportance = mNotificationChannel.getImportance(); - doStandardBind(); - - mNotificationInfo.handleCloseControls(true, false); - mTestableLooper.processAllMessages(); - 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 - public void testHandleCloseControls_DoesNotUpdateNotificationChannelIfUnspecified() - throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED); - doStandardBind(); - - mNotificationInfo.handleCloseControls(true, false); - - mTestableLooper.processAllMessages(); - verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( - anyString(), eq(TEST_UID), any()); - assertEquals(IMPORTANCE_UNSPECIFIED, mNotificationChannel.getImportance()); - } - - @Test - public void testSilenceCallsUpdateNotificationChannel() throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); - doStandardBind(); - - mNotificationInfo.findViewById(R.id.silence).performClick(); - mNotificationInfo.findViewById(R.id.done).performClick(); - mNotificationInfo.handleCloseControls(true, false); - - mTestableLooper.processAllMessages(); - ArgumentCaptor<NotificationChannel> updated = - ArgumentCaptor.forClass(NotificationChannel.class); - verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( - anyString(), eq(TEST_UID), updated.capture()); - 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)); - assertFalse(mNotificationInfo.shouldBeSavedOnClose()); - } - - @Test - public void testUnSilenceCallsUpdateNotificationChannel() throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_LOW); - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - null, - null, - null, - mUiEventLogger, - true, - false, - false, - mAssistantFeedbackController, - mMetricsLogger, - null); - - mNotificationInfo.findViewById(R.id.alert).performClick(); - mNotificationInfo.findViewById(R.id.done).performClick(); - mNotificationInfo.handleCloseControls(true, false); - - mTestableLooper.processAllMessages(); - ArgumentCaptor<NotificationChannel> updated = - ArgumentCaptor.forClass(NotificationChannel.class); - verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( - anyString(), eq(TEST_UID), updated.capture()); - assertTrue((updated.getValue().getUserLockedFields() - & USER_LOCKED_IMPORTANCE) != 0); - assertEquals(IMPORTANCE_DEFAULT, updated.getValue().getImportance()); - assertFalse(mNotificationInfo.shouldBeSavedOnClose()); - } - - @Test - public void testAutomaticUnlocksUserImportance() throws Exception { - when(mAssistantFeedbackController.isFeedbackEnabled()).thenReturn(true); - mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); - mNotificationChannel.lockFields(USER_LOCKED_IMPORTANCE); - doStandardBind(); - - mNotificationInfo.findViewById(R.id.automatic).performClick(); - mNotificationInfo.findViewById(R.id.done).performClick(); - mNotificationInfo.handleCloseControls(true, false); - - mTestableLooper.processAllMessages(); - verify(mMockINotificationManager, times(1)).unlockNotificationChannel( - anyString(), eq(TEST_UID), any()); - assertEquals(IMPORTANCE_DEFAULT, mNotificationChannel.getImportance()); - assertFalse(mNotificationInfo.shouldBeSavedOnClose()); - } - - @Test - public void testSilenceCallsUpdateNotificationChannel_channelImportanceUnspecified() - throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED); - doStandardBind(); - - mNotificationInfo.findViewById(R.id.silence).performClick(); - mNotificationInfo.findViewById(R.id.done).performClick(); - mNotificationInfo.handleCloseControls(true, false); - - mTestableLooper.processAllMessages(); - ArgumentCaptor<NotificationChannel> updated = - ArgumentCaptor.forClass(NotificationChannel.class); - verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( - anyString(), eq(TEST_UID), updated.capture()); - assertTrue((updated.getValue().getUserLockedFields() - & USER_LOCKED_IMPORTANCE) != 0); - assertEquals(IMPORTANCE_LOW, updated.getValue().getImportance()); - assertFalse(mNotificationInfo.shouldBeSavedOnClose()); - } - - @Test - public void testSilenceCallsUpdateNotificationChannel_channelImportanceMin() - throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_MIN); - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - null, - null, - null, - mUiEventLogger, - true, - false, - false, - mAssistantFeedbackController, - mMetricsLogger, - null); - - assertEquals(mContext.getString(R.string.inline_done_button), - ((TextView) mNotificationInfo.findViewById(R.id.done)).getText()); - mNotificationInfo.findViewById(R.id.silence).performClick(); - assertEquals(mContext.getString(R.string.inline_done_button), - ((TextView) mNotificationInfo.findViewById(R.id.done)).getText()); - mNotificationInfo.findViewById(R.id.done).performClick(); - mNotificationInfo.handleCloseControls(true, false); - - mTestableLooper.processAllMessages(); - ArgumentCaptor<NotificationChannel> updated = - ArgumentCaptor.forClass(NotificationChannel.class); - verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( - anyString(), eq(TEST_UID), updated.capture()); - assertTrue((updated.getValue().getUserLockedFields() & USER_LOCKED_IMPORTANCE) != 0); - assertEquals(IMPORTANCE_MIN, updated.getValue().getImportance()); - assertFalse(mNotificationInfo.shouldBeSavedOnClose()); - } - - @Test - public void testSilence_closeGutsThenTryToSave() throws RemoteException { - mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - null, - null, - null, - mUiEventLogger, - true, - false, - false, - mAssistantFeedbackController, - mMetricsLogger, - null); - - mNotificationInfo.findViewById(R.id.silence).performClick(); - mNotificationInfo.handleCloseControls(false, false); - mNotificationInfo.handleCloseControls(true, false); - - mTestableLooper.processAllMessages(); - - assertEquals(IMPORTANCE_DEFAULT, mNotificationChannel.getImportance()); - assertFalse(mNotificationInfo.shouldBeSavedOnClose()); - } - - @Test - public void testAlertCallsUpdateNotificationChannel_channelImportanceMin() - throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_MIN); - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - null, - null, - null, - mUiEventLogger, - true, - false, - false, - mAssistantFeedbackController, - mMetricsLogger, - null); - - assertEquals(mContext.getString(R.string.inline_done_button), - ((TextView) mNotificationInfo.findViewById(R.id.done)).getText()); - mNotificationInfo.findViewById(R.id.alert).performClick(); - assertEquals(mContext.getString(R.string.inline_ok_button), - ((TextView) mNotificationInfo.findViewById(R.id.done)).getText()); - mNotificationInfo.findViewById(R.id.done).performClick(); - mNotificationInfo.handleCloseControls(true, false); - - mTestableLooper.processAllMessages(); - ArgumentCaptor<NotificationChannel> updated = - ArgumentCaptor.forClass(NotificationChannel.class); - verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( - anyString(), eq(TEST_UID), updated.capture()); - assertTrue((updated.getValue().getUserLockedFields() & USER_LOCKED_IMPORTANCE) != 0); - assertEquals(IMPORTANCE_DEFAULT, updated.getValue().getImportance()); - assertFalse(mNotificationInfo.shouldBeSavedOnClose()); - } - - @Test - public void testAdjustImportanceTemporarilyAllowsReordering() throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); - doStandardBind(); - - mNotificationInfo.findViewById(R.id.silence).performClick(); - mNotificationInfo.findViewById(R.id.done).performClick(); - mNotificationInfo.handleCloseControls(true, false); - - verify(mOnUserInteractionCallback).onImportanceChanged(mEntry); - assertFalse(mNotificationInfo.shouldBeSavedOnClose()); - } - - @Test - public void testDoneText() - throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_LOW); - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - null, - null, - null, - mUiEventLogger, - true, - false, - false, - mAssistantFeedbackController, - mMetricsLogger, - null); - - assertEquals(mContext.getString(R.string.inline_done_button), - ((TextView) mNotificationInfo.findViewById(R.id.done)).getText()); - mNotificationInfo.findViewById(R.id.alert).performClick(); - assertEquals(mContext.getString(R.string.inline_ok_button), - ((TextView) mNotificationInfo.findViewById(R.id.done)).getText()); - mNotificationInfo.findViewById(R.id.silence).performClick(); - assertEquals(mContext.getString(R.string.inline_done_button), - ((TextView) mNotificationInfo.findViewById(R.id.done)).getText()); - } - - @Test - public void testUnSilenceCallsUpdateNotificationChannel_channelImportanceUnspecified() - throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_LOW); - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - null, - null, - null, - mUiEventLogger, - true, - false, - false, - mAssistantFeedbackController, - mMetricsLogger, - null); - - mNotificationInfo.findViewById(R.id.alert).performClick(); - mNotificationInfo.findViewById(R.id.done).performClick(); - mNotificationInfo.handleCloseControls(true, false); - - mTestableLooper.processAllMessages(); - ArgumentCaptor<NotificationChannel> updated = - ArgumentCaptor.forClass(NotificationChannel.class); - verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( - anyString(), eq(TEST_UID), updated.capture()); - assertTrue((updated.getValue().getUserLockedFields() - & USER_LOCKED_IMPORTANCE) != 0); - assertEquals(IMPORTANCE_DEFAULT, updated.getValue().getImportance()); - assertFalse(mNotificationInfo.shouldBeSavedOnClose()); - } - - @Test - public void testCloseControlsDoesNotUpdateIfSaveIsFalse() throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_LOW); - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - null, - null, - null, - mUiEventLogger, - true, - false, - false, - mAssistantFeedbackController, - mMetricsLogger, - null); - - mNotificationInfo.findViewById(R.id.alert).performClick(); - mNotificationInfo.findViewById(R.id.done).performClick(); - mNotificationInfo.handleCloseControls(false, false); - - 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 - public void testCloseControlsUpdatesWhenCheckSaveListenerUsesCallback() throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_LOW); - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - null, - null, - null, - mUiEventLogger, - true, - false, - false, - mAssistantFeedbackController, - mMetricsLogger, - null); - - mNotificationInfo.findViewById(R.id.alert).performClick(); - mNotificationInfo.findViewById(R.id.done).performClick(); - mTestableLooper.processAllMessages(); - verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( - eq(TEST_PACKAGE_NAME), eq(TEST_UID), eq(mNotificationChannel)); - - mNotificationInfo.handleCloseControls(true, false); - - mTestableLooper.processAllMessages(); - verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( - eq(TEST_PACKAGE_NAME), eq(TEST_UID), eq(mNotificationChannel)); - } - - @Test - public void testCloseControls_withoutHittingApply() throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_LOW); - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - null, - null, - null, - mUiEventLogger, - true, - false, - false, - mAssistantFeedbackController, - mMetricsLogger, - null); - - mNotificationInfo.findViewById(R.id.alert).performClick(); - - assertFalse(mNotificationInfo.shouldBeSavedOnClose()); - } - - @Test - public void testWillBeRemovedReturnsFalse() throws Exception { - assertFalse(mNotificationInfo.willBeRemoved()); - - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - null, - null, - null, - mUiEventLogger, - true, - false, - false, - mAssistantFeedbackController, - mMetricsLogger, - null); - - assertFalse(mNotificationInfo.willBeRemoved()); - } - - - @Test - @DisableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - public void testBindNotification_HidesFeedbackLink_flagOff() throws Exception { - doStandardBind(); - assertEquals(GONE, mNotificationInfo.findViewById(R.id.feedback).getVisibility()); - } - - @Test - @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - public void testBindNotification_SetsFeedbackLink_isReservedChannel() throws RemoteException { - mEntry.setRanking(new RankingBuilder(mEntry.getRanking()) - .setSummarization("something").build()); - final CountDownLatch latch = new CountDownLatch(1); - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mClassifiedNotificationChannel, - mEntry, - null, - null, - (View v, Intent intent) -> { - latch.countDown(); - }, - mUiEventLogger, - true, - false, - false, - mAssistantFeedbackController, - mMetricsLogger, - null); - - final View feedback = mNotificationInfo.findViewById(R.id.feedback); - assertEquals(VISIBLE, feedback.getVisibility()); - feedback.performClick(); - // Verify that listener was triggered. - assertEquals(0, latch.getCount()); - } - - @Test - @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - public void testBindNotification_hidesFeedbackLink_notReservedChannel() throws Exception { - doStandardBind(); - - assertEquals(GONE, mNotificationInfo.findViewById(R.id.feedback).getVisibility()); - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt new file mode 100644 index 000000000000..2945fa98caad --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt @@ -0,0 +1,910 @@ +/* + * Copyright (C) 2025 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 android.app.Flags +import android.app.INotificationManager +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationChannel.SOCIAL_MEDIA_ID +import android.app.NotificationChannelGroup +import android.app.NotificationManager +import android.app.NotificationManager.IMPORTANCE_LOW +import android.app.PendingIntent +import android.app.Person +import android.content.ComponentName +import android.content.Intent +import android.content.mockPackageManager +import android.content.pm.ActivityInfo +import android.content.pm.ApplicationInfo +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.content.pm.ResolveInfo +import android.graphics.drawable.Drawable +import android.os.RemoteException +import android.os.UserHandle +import android.os.testableLooper +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.print.PrintManager +import android.service.notification.StatusBarNotification +import android.telecom.TelecomManager +import android.testing.TestableLooper.RunWithLooper +import android.view.LayoutInflater +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import android.widget.ImageView +import android.widget.TextView +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.internal.logging.MetricsLogger +import com.android.internal.logging.UiEventLogger +import com.android.internal.logging.metricsLogger +import com.android.internal.logging.uiEventLoggerFake +import com.android.systemui.Dependency +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase +import com.android.systemui.res.R +import com.android.systemui.statusbar.RankingBuilder +import com.android.systemui.statusbar.notification.AssistantFeedbackController +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.telecom.telecomManager +import com.google.common.truth.Truth.assertThat +import java.util.concurrent.CountDownLatch +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +@RunWithLooper +class NotificationInfoTest : SysuiTestCase() { + private val kosmos = Kosmos().also { it.testCase = this } + + private lateinit var underTest: NotificationInfo + private lateinit var notificationChannel: NotificationChannel + private lateinit var defaultNotificationChannel: NotificationChannel + private lateinit var classifiedNotificationChannel: NotificationChannel + private lateinit var sbn: StatusBarNotification + private lateinit var entry: NotificationEntry + + private val mockPackageManager = kosmos.mockPackageManager + private val uiEventLogger = kosmos.uiEventLoggerFake + private val testableLooper by lazy { kosmos.testableLooper } + + private val onUserInteractionCallback = mock<OnUserInteractionCallback>() + private val mockINotificationManager = mock<INotificationManager>() + private val channelEditorDialogController = mock<ChannelEditorDialogController>() + private val assistantFeedbackController = mock<AssistantFeedbackController>() + + @Before + fun setUp() { + mContext.addMockSystemService(TelecomManager::class.java, kosmos.telecomManager) + + mDependency.injectTestDependency(Dependency.BG_LOOPER, testableLooper.looper) + + // Inflate the layout + val inflater = LayoutInflater.from(mContext) + underTest = inflater.inflate(R.layout.notification_info, null) as NotificationInfo + + underTest.setGutsParent(mock<NotificationGuts>()) + + // Our view is never attached to a window so the View#post methods in NotificationInfo never + // get called. Setting this will skip the post and do the action immediately. + underTest.mSkipPost = true + + // PackageManager must return a packageInfo and applicationInfo. + val packageInfo = PackageInfo() + packageInfo.packageName = TEST_PACKAGE_NAME + whenever(mockPackageManager.getPackageInfo(eq(TEST_PACKAGE_NAME), anyInt())) + .thenReturn(packageInfo) + val applicationInfo = ApplicationInfo() + applicationInfo.uid = TEST_UID // non-zero + val systemPackageInfo = PackageInfo() + systemPackageInfo.packageName = TEST_SYSTEM_PACKAGE_NAME + whenever(mockPackageManager.getPackageInfo(eq(TEST_SYSTEM_PACKAGE_NAME), anyInt())) + .thenReturn(systemPackageInfo) + whenever(mockPackageManager.getPackageInfo(eq("android"), anyInt())).thenReturn(packageInfo) + + val assistant = ComponentName("package", "service") + whenever(mockINotificationManager.allowedNotificationAssistant).thenReturn(assistant) + val ri = ResolveInfo() + ri.activityInfo = ActivityInfo() + ri.activityInfo.packageName = assistant.packageName + ri.activityInfo.name = "activity" + whenever(mockPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(listOf(ri)) + + // Package has one channel by default. + whenever( + mockINotificationManager.getNumNotificationChannelsForPackage( + eq(TEST_PACKAGE_NAME), + eq(TEST_UID), + anyBoolean(), + ) + ) + .thenReturn(1) + + // Some test channels. + notificationChannel = NotificationChannel(TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_LOW) + defaultNotificationChannel = + NotificationChannel( + NotificationChannel.DEFAULT_CHANNEL_ID, + TEST_CHANNEL_NAME, + IMPORTANCE_LOW, + ) + classifiedNotificationChannel = + NotificationChannel(SOCIAL_MEDIA_ID, "social", IMPORTANCE_LOW) + + val notification = Notification() + notification.extras.putParcelable( + Notification.EXTRA_BUILDER_APPLICATION_INFO, + applicationInfo, + ) + sbn = + StatusBarNotification( + TEST_PACKAGE_NAME, + TEST_PACKAGE_NAME, + 0, + null, + TEST_UID, + 0, + notification, + UserHandle.getUserHandleForUid(TEST_UID), + null, + 0, + ) + entry = NotificationEntryBuilder().setSbn(sbn).build() + whenever(assistantFeedbackController.isFeedbackEnabled).thenReturn(false) + whenever(assistantFeedbackController.getInlineDescriptionResource(any())) + .thenReturn(R.string.notification_channel_summary_automatic) + } + + @Test + fun testBindNotification_SetsTextApplicationName() { + whenever(mockPackageManager.getApplicationLabel(any())).thenReturn("App Name") + bindNotification() + val textView = underTest.findViewById<TextView>(R.id.pkg_name) + assertThat(textView.text.toString()).contains("App Name") + assertThat(underTest.findViewById<View>(R.id.header).visibility).isEqualTo(VISIBLE) + } + + @Test + fun testBindNotification_SetsPackageIcon() { + val iconDrawable = mock<Drawable>() + whenever(mockPackageManager.getApplicationIcon(any<ApplicationInfo>())) + .thenReturn(iconDrawable) + bindNotification() + val iconView = underTest.findViewById<ImageView>(R.id.pkg_icon) + assertThat(iconView.drawable).isEqualTo(iconDrawable) + } + + @Test + fun testBindNotification_noDelegate() { + bindNotification() + val nameView = underTest.findViewById<TextView>(R.id.delegate_name) + assertThat(nameView.visibility).isEqualTo(GONE) + } + + @Test + fun testBindNotification_delegate() { + sbn = + StatusBarNotification( + TEST_PACKAGE_NAME, + "other", + 0, + null, + TEST_UID, + 0, + Notification(), + UserHandle.CURRENT, + null, + 0, + ) + val applicationInfo = ApplicationInfo() + applicationInfo.uid = 7 // non-zero + whenever(mockPackageManager.getApplicationInfo(eq("other"), anyInt())) + .thenReturn(applicationInfo) + whenever(mockPackageManager.getApplicationLabel(any())).thenReturn("Other") + + val entry = NotificationEntryBuilder().setSbn(sbn).build() + bindNotification(entry = entry) + val nameView = underTest.findViewById<TextView>(R.id.delegate_name) + assertThat(nameView.visibility).isEqualTo(VISIBLE) + assertThat(nameView.text.toString()).contains("Proxied") + } + + @Test + fun testBindNotification_GroupNameHiddenIfNoGroup() { + bindNotification() + val groupNameView = underTest.findViewById<TextView>(R.id.group_name) + assertThat(groupNameView.visibility).isEqualTo(GONE) + } + + @Test + fun testBindNotification_SetsGroupNameIfNonNull() { + notificationChannel.group = "test_group_id" + val notificationChannelGroup = NotificationChannelGroup("test_group_id", "Test Group Name") + whenever( + mockINotificationManager.getNotificationChannelGroupForPackage( + eq("test_group_id"), + eq(TEST_PACKAGE_NAME), + eq(TEST_UID), + ) + ) + .thenReturn(notificationChannelGroup) + bindNotification() + val groupNameView = underTest.findViewById<TextView>(R.id.group_name) + assertThat(groupNameView.visibility).isEqualTo(VISIBLE) + assertThat(groupNameView.text).isEqualTo("Test Group Name") + } + + @Test + fun testBindNotification_SetsTextChannelName() { + bindNotification() + val textView = underTest.findViewById<TextView>(R.id.channel_name) + assertThat(textView.text).isEqualTo(TEST_CHANNEL_NAME) + } + + @Test + fun testBindNotification_DefaultChannelDoesNotUseChannelName() { + bindNotification(notificationChannel = defaultNotificationChannel) + val textView = underTest.findViewById<TextView>(R.id.channel_name) + assertThat(textView.visibility).isEqualTo(GONE) + } + + @Test + fun testBindNotification_DefaultChannelUsesChannelNameIfMoreChannelsExist() { + // Package has more than one channel by default. + whenever( + mockINotificationManager.getNumNotificationChannelsForPackage( + eq(TEST_PACKAGE_NAME), + eq(TEST_UID), + anyBoolean(), + ) + ) + .thenReturn(10) + bindNotification(notificationChannel = defaultNotificationChannel) + val textView = underTest.findViewById<TextView>(R.id.channel_name) + assertThat(textView.visibility).isEqualTo(VISIBLE) + } + + @Test + fun testBindNotification_UnblockablePackageUsesChannelName() { + bindNotification(isNonblockable = true) + val textView = underTest.findViewById<TextView>(R.id.channel_name) + assertThat(textView.visibility).isEqualTo(VISIBLE) + } + + @Test + fun testBindNotification_SetsOnClickListenerForSettings() { + val latch = CountDownLatch(1) + bindNotification( + onSettingsClick = { _: View?, c: NotificationChannel?, _: Int -> + assertThat(c).isEqualTo(notificationChannel) + latch.countDown() + } + ) + + val settingsButton = underTest.findViewById<View>(R.id.info) + settingsButton.performClick() + // Verify that listener was triggered. + assertThat(latch.count).isEqualTo(0) + } + + @Test + fun testBindNotification_SettingsButtonInvisibleWhenNoClickListener() { + bindNotification() + val settingsButton = underTest.findViewById<View>(R.id.info) + assertThat(settingsButton.visibility != VISIBLE).isTrue() + } + + @Test + fun testBindNotification_SettingsButtonInvisibleWhenDeviceUnprovisioned() { + bindNotification( + onSettingsClick = { _: View?, c: NotificationChannel?, _: Int -> + assertThat(c).isEqualTo(notificationChannel) + }, + isDeviceProvisioned = false, + ) + val settingsButton = underTest.findViewById<View>(R.id.info) + assertThat(settingsButton.visibility != VISIBLE).isTrue() + } + + @Test + fun testBindNotification_SettingsButtonReappearsAfterSecondBind() { + bindNotification() + bindNotification(onSettingsClick = { _: View?, _: NotificationChannel?, _: Int -> }) + val settingsButton = underTest.findViewById<View>(R.id.info) + assertThat(settingsButton.visibility).isEqualTo(VISIBLE) + } + + @Test + fun testBindNotification_whenAppUnblockable() { + bindNotification(isNonblockable = true) + val view = underTest.findViewById<TextView>(R.id.non_configurable_text) + assertThat(view.visibility).isEqualTo(VISIBLE) + assertThat(view.text).isEqualTo(mContext.getString(R.string.notification_unblockable_desc)) + assertThat(underTest.findViewById<View>(R.id.interruptiveness_settings).visibility) + .isEqualTo(GONE) + } + + @Test + fun testBindNotification_whenCurrentlyInCall() { + whenever(mockINotificationManager.isInCall(anyString(), anyInt())).thenReturn(true) + + val person = Person.Builder().setName("caller").build() + val nb = + Notification.Builder(mContext, notificationChannel.id) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(Notification.CallStyle.forOngoingCall(person, mock<PendingIntent>())) + .setFullScreenIntent(mock<PendingIntent>(), true) + .addAction(Notification.Action.Builder(null, "test", null).build()) + + sbn = + StatusBarNotification( + TEST_PACKAGE_NAME, + TEST_PACKAGE_NAME, + 0, + null, + TEST_UID, + 0, + nb.build(), + UserHandle.getUserHandleForUid(TEST_UID), + null, + 0, + ) + entry.sbn = sbn + bindNotification() + val view = underTest.findViewById<TextView>(R.id.non_configurable_call_text) + assertThat(view.visibility).isEqualTo(VISIBLE) + assertThat(view.text) + .isEqualTo(mContext.getString(R.string.notification_unblockable_call_desc)) + assertThat(underTest.findViewById<View>(R.id.interruptiveness_settings).visibility) + .isEqualTo(GONE) + assertThat(underTest.findViewById<View>(R.id.non_configurable_text).visibility) + .isEqualTo(GONE) + } + + @Test + fun testBindNotification_whenCurrentlyInCall_notCall() { + whenever(mockINotificationManager.isInCall(anyString(), anyInt())).thenReturn(true) + + val nb = + Notification.Builder(mContext, notificationChannel.id) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setFullScreenIntent(mock<PendingIntent>(), true) + .addAction(Notification.Action.Builder(null, "test", null).build()) + + sbn = + StatusBarNotification( + TEST_PACKAGE_NAME, + TEST_PACKAGE_NAME, + 0, + null, + TEST_UID, + 0, + nb.build(), + UserHandle.getUserHandleForUid(TEST_UID), + null, + 0, + ) + entry.sbn = sbn + bindNotification() + assertThat(underTest.findViewById<View>(R.id.non_configurable_call_text).visibility) + .isEqualTo(GONE) + assertThat(underTest.findViewById<View>(R.id.interruptiveness_settings).visibility) + .isEqualTo(VISIBLE) + assertThat(underTest.findViewById<View>(R.id.non_configurable_text).visibility) + .isEqualTo(GONE) + } + + @Test + fun testBindNotification_automaticIsVisible() { + whenever(assistantFeedbackController.isFeedbackEnabled).thenReturn(true) + bindNotification() + assertThat(underTest.findViewById<View>(R.id.automatic).visibility).isEqualTo(VISIBLE) + assertThat(underTest.findViewById<View>(R.id.automatic_summary).visibility) + .isEqualTo(VISIBLE) + } + + @Test + fun testBindNotification_automaticIsGone() { + bindNotification() + assertThat(underTest.findViewById<View>(R.id.automatic).visibility).isEqualTo(GONE) + assertThat(underTest.findViewById<View>(R.id.automatic_summary).visibility).isEqualTo(GONE) + } + + @Test + fun testBindNotification_automaticIsSelected() { + whenever(assistantFeedbackController.isFeedbackEnabled).thenReturn(true) + notificationChannel.unlockFields(NotificationChannel.USER_LOCKED_IMPORTANCE) + bindNotification() + assertThat(underTest.findViewById<View>(R.id.automatic).isSelected).isTrue() + } + + @Test + fun testBindNotification_alertIsSelected() { + bindNotification() + assertThat(underTest.findViewById<View>(R.id.alert).isSelected).isTrue() + } + + @Test + fun testBindNotification_silenceIsSelected() { + bindNotification(wasShownHighPriority = false) + assertThat(underTest.findViewById<View>(R.id.silence).isSelected).isTrue() + } + + @Test + fun testBindNotification_DoesNotUpdateNotificationChannel() { + bindNotification() + testableLooper.processAllMessages() + verify(mockINotificationManager, never()) + .updateNotificationChannelForPackage(anyString(), eq(TEST_UID), any()) + } + + @Test + fun testBindNotification_LogsOpen() { + bindNotification() + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + assertThat(uiEventLogger.eventId(0)) + .isEqualTo(NotificationControlsEvent.NOTIFICATION_CONTROLS_OPEN.id) + } + + @Test + fun testDoesNotUpdateNotificationChannelAfterImportanceChanged() { + notificationChannel.importance = IMPORTANCE_LOW + bindNotification(wasShownHighPriority = false) + + underTest.findViewById<View>(R.id.alert).performClick() + testableLooper.processAllMessages() + verify(mockINotificationManager, never()) + .updateNotificationChannelForPackage(anyString(), eq(TEST_UID), any()) + } + + @Test + fun testDoesNotUpdateNotificationChannelAfterImportanceChangedSilenced() { + notificationChannel.importance = NotificationManager.IMPORTANCE_DEFAULT + bindNotification() + + underTest.findViewById<View>(R.id.silence).performClick() + testableLooper.processAllMessages() + verify(mockINotificationManager, never()) + .updateNotificationChannelForPackage(anyString(), eq(TEST_UID), any()) + } + + @Test + fun testDoesNotUpdateNotificationChannelAfterImportanceChangedAutomatic() { + notificationChannel.importance = NotificationManager.IMPORTANCE_DEFAULT + bindNotification() + + underTest.findViewById<View>(R.id.automatic).performClick() + testableLooper.processAllMessages() + verify(mockINotificationManager, never()) + .updateNotificationChannelForPackage(anyString(), eq(TEST_UID), any()) + } + + @Test + fun testHandleCloseControls_persistAutomatic() { + whenever(assistantFeedbackController.isFeedbackEnabled).thenReturn(true) + notificationChannel.unlockFields(NotificationChannel.USER_LOCKED_IMPORTANCE) + bindNotification() + + underTest.handleCloseControls(true, false) + testableLooper.processAllMessages() + verify(mockINotificationManager).unlockNotificationChannel(anyString(), eq(TEST_UID), any()) + } + + @Test + fun testHandleCloseControls_DoesNotUpdateNotificationChannelIfUnchanged() { + val originalImportance = notificationChannel.importance + bindNotification() + + underTest.handleCloseControls(true, false) + testableLooper.processAllMessages() + verify(mockINotificationManager) + .updateNotificationChannelForPackage(anyString(), eq(TEST_UID), any()) + assertThat(notificationChannel.importance).isEqualTo(originalImportance) + + assertThat(uiEventLogger.numLogs()).isEqualTo(2) + assertThat(uiEventLogger.eventId(0)) + .isEqualTo(NotificationControlsEvent.NOTIFICATION_CONTROLS_OPEN.id) + // The SAVE_IMPORTANCE event is logged whenever importance is saved, even if unchanged. + assertThat(uiEventLogger.eventId(1)) + .isEqualTo(NotificationControlsEvent.NOTIFICATION_CONTROLS_SAVE_IMPORTANCE.id) + } + + @Test + fun testHandleCloseControls_DoesNotUpdateNotificationChannelIfUnspecified() { + notificationChannel.importance = NotificationManager.IMPORTANCE_UNSPECIFIED + bindNotification() + + underTest.handleCloseControls(true, false) + + testableLooper.processAllMessages() + verify(mockINotificationManager) + .updateNotificationChannelForPackage(anyString(), eq(TEST_UID), any()) + assertThat(notificationChannel.importance) + .isEqualTo(NotificationManager.IMPORTANCE_UNSPECIFIED) + } + + @Test + fun testSilenceCallsUpdateNotificationChannel() { + notificationChannel.importance = NotificationManager.IMPORTANCE_DEFAULT + bindNotification() + + underTest.findViewById<View>(R.id.silence).performClick() + underTest.findViewById<View>(R.id.done).performClick() + underTest.handleCloseControls(true, false) + + testableLooper.processAllMessages() + val updated = argumentCaptor<NotificationChannel>() + verify(mockINotificationManager) + .updateNotificationChannelForPackage(anyString(), eq(TEST_UID), updated.capture()) + assertThat( + updated.firstValue.userLockedFields and NotificationChannel.USER_LOCKED_IMPORTANCE + ) + .isNotEqualTo(0) + assertThat(updated.firstValue.importance).isEqualTo(IMPORTANCE_LOW) + + assertThat(uiEventLogger.numLogs()).isEqualTo(2) + assertThat(uiEventLogger.eventId(0)) + .isEqualTo(NotificationControlsEvent.NOTIFICATION_CONTROLS_OPEN.id) + assertThat(uiEventLogger.eventId(1)) + .isEqualTo(NotificationControlsEvent.NOTIFICATION_CONTROLS_SAVE_IMPORTANCE.id) + assertThat(underTest.shouldBeSavedOnClose()).isFalse() + } + + @Test + fun testUnSilenceCallsUpdateNotificationChannel() { + notificationChannel.importance = IMPORTANCE_LOW + bindNotification(wasShownHighPriority = false) + + underTest.findViewById<View>(R.id.alert).performClick() + underTest.findViewById<View>(R.id.done).performClick() + underTest.handleCloseControls(true, false) + + testableLooper.processAllMessages() + val updated = argumentCaptor<NotificationChannel>() + verify(mockINotificationManager) + .updateNotificationChannelForPackage(anyString(), eq(TEST_UID), updated.capture()) + assertThat( + updated.firstValue.userLockedFields and NotificationChannel.USER_LOCKED_IMPORTANCE + ) + .isNotEqualTo(0) + assertThat(updated.firstValue.importance).isEqualTo(NotificationManager.IMPORTANCE_DEFAULT) + assertThat(underTest.shouldBeSavedOnClose()).isFalse() + } + + @Test + fun testAutomaticUnlocksUserImportance() { + whenever(assistantFeedbackController.isFeedbackEnabled).thenReturn(true) + notificationChannel.importance = NotificationManager.IMPORTANCE_DEFAULT + notificationChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE) + bindNotification() + + underTest.findViewById<View>(R.id.automatic).performClick() + underTest.findViewById<View>(R.id.done).performClick() + underTest.handleCloseControls(true, false) + + testableLooper.processAllMessages() + verify(mockINotificationManager).unlockNotificationChannel(anyString(), eq(TEST_UID), any()) + assertThat(notificationChannel.importance).isEqualTo(NotificationManager.IMPORTANCE_DEFAULT) + assertThat(underTest.shouldBeSavedOnClose()).isFalse() + } + + @Test + fun testSilenceCallsUpdateNotificationChannel_channelImportanceUnspecified() { + notificationChannel.importance = NotificationManager.IMPORTANCE_UNSPECIFIED + bindNotification() + + underTest.findViewById<View>(R.id.silence).performClick() + underTest.findViewById<View>(R.id.done).performClick() + underTest.handleCloseControls(true, false) + + testableLooper.processAllMessages() + val updated = argumentCaptor<NotificationChannel>() + verify(mockINotificationManager) + .updateNotificationChannelForPackage(anyString(), eq(TEST_UID), updated.capture()) + assertThat( + updated.firstValue.userLockedFields and NotificationChannel.USER_LOCKED_IMPORTANCE + ) + .isNotEqualTo(0) + assertThat(updated.firstValue.importance).isEqualTo(IMPORTANCE_LOW) + assertThat(underTest.shouldBeSavedOnClose()).isFalse() + } + + @Test + fun testSilenceCallsUpdateNotificationChannel_channelImportanceMin() { + notificationChannel.importance = NotificationManager.IMPORTANCE_MIN + bindNotification(wasShownHighPriority = false) + + assertThat((underTest.findViewById<View>(R.id.done) as TextView).text) + .isEqualTo(mContext.getString(R.string.inline_done_button)) + underTest.findViewById<View>(R.id.silence).performClick() + assertThat((underTest.findViewById<View>(R.id.done) as TextView).text) + .isEqualTo(mContext.getString(R.string.inline_done_button)) + underTest.findViewById<View>(R.id.done).performClick() + underTest.handleCloseControls(true, false) + + testableLooper.processAllMessages() + val updated = argumentCaptor<NotificationChannel>() + verify(mockINotificationManager) + .updateNotificationChannelForPackage(anyString(), eq(TEST_UID), updated.capture()) + assertThat( + updated.firstValue.userLockedFields and NotificationChannel.USER_LOCKED_IMPORTANCE + ) + .isNotEqualTo(0) + assertThat(updated.firstValue.importance).isEqualTo(NotificationManager.IMPORTANCE_MIN) + assertThat(underTest.shouldBeSavedOnClose()).isFalse() + } + + @Test + @Throws(RemoteException::class) + fun testSilence_closeGutsThenTryToSave() { + notificationChannel.importance = NotificationManager.IMPORTANCE_DEFAULT + bindNotification(wasShownHighPriority = false) + + underTest.findViewById<View>(R.id.silence).performClick() + underTest.handleCloseControls(false, false) + underTest.handleCloseControls(true, false) + + testableLooper.processAllMessages() + + assertThat(notificationChannel.importance).isEqualTo(NotificationManager.IMPORTANCE_DEFAULT) + assertThat(underTest.shouldBeSavedOnClose()).isFalse() + } + + @Test + fun testAlertCallsUpdateNotificationChannel_channelImportanceMin() { + notificationChannel.importance = NotificationManager.IMPORTANCE_MIN + bindNotification(wasShownHighPriority = false) + + assertThat((underTest.findViewById<View>(R.id.done) as TextView).text) + .isEqualTo(mContext.getString(R.string.inline_done_button)) + underTest.findViewById<View>(R.id.alert).performClick() + assertThat((underTest.findViewById<View>(R.id.done) as TextView).text) + .isEqualTo(mContext.getString(R.string.inline_ok_button)) + underTest.findViewById<View>(R.id.done).performClick() + underTest.handleCloseControls(true, false) + + testableLooper.processAllMessages() + val updated = argumentCaptor<NotificationChannel>() + verify(mockINotificationManager) + .updateNotificationChannelForPackage(anyString(), eq(TEST_UID), updated.capture()) + assertThat( + updated.firstValue.userLockedFields and NotificationChannel.USER_LOCKED_IMPORTANCE + ) + .isNotEqualTo(0) + assertThat(updated.firstValue.importance).isEqualTo(NotificationManager.IMPORTANCE_DEFAULT) + assertThat(underTest.shouldBeSavedOnClose()).isFalse() + } + + @Test + fun testAdjustImportanceTemporarilyAllowsReordering() { + notificationChannel.importance = NotificationManager.IMPORTANCE_DEFAULT + bindNotification() + + underTest.findViewById<View>(R.id.silence).performClick() + underTest.findViewById<View>(R.id.done).performClick() + underTest.handleCloseControls(true, false) + + verify(onUserInteractionCallback).onImportanceChanged(entry) + assertThat(underTest.shouldBeSavedOnClose()).isFalse() + } + + @Test + fun testDoneText() { + notificationChannel.importance = IMPORTANCE_LOW + bindNotification(wasShownHighPriority = false) + + assertThat((underTest.findViewById<View>(R.id.done) as TextView).text) + .isEqualTo(mContext.getString(R.string.inline_done_button)) + underTest.findViewById<View>(R.id.alert).performClick() + assertThat((underTest.findViewById<View>(R.id.done) as TextView).text) + .isEqualTo(mContext.getString(R.string.inline_ok_button)) + underTest.findViewById<View>(R.id.silence).performClick() + assertThat((underTest.findViewById<View>(R.id.done) as TextView).text) + .isEqualTo(mContext.getString(R.string.inline_done_button)) + } + + @Test + fun testUnSilenceCallsUpdateNotificationChannel_channelImportanceUnspecified() { + notificationChannel.importance = IMPORTANCE_LOW + bindNotification(wasShownHighPriority = false) + + underTest.findViewById<View>(R.id.alert).performClick() + underTest.findViewById<View>(R.id.done).performClick() + underTest.handleCloseControls(true, false) + + testableLooper.processAllMessages() + val updated = argumentCaptor<NotificationChannel>() + verify(mockINotificationManager) + .updateNotificationChannelForPackage(anyString(), eq(TEST_UID), updated.capture()) + assertThat( + updated.firstValue.userLockedFields and NotificationChannel.USER_LOCKED_IMPORTANCE + ) + .isNotEqualTo(0) + assertThat(updated.firstValue.importance).isEqualTo(NotificationManager.IMPORTANCE_DEFAULT) + assertThat(underTest.shouldBeSavedOnClose()).isFalse() + } + + @Test + fun testCloseControlsDoesNotUpdateIfSaveIsFalse() { + notificationChannel.importance = IMPORTANCE_LOW + bindNotification(wasShownHighPriority = false) + + underTest.findViewById<View>(R.id.alert).performClick() + underTest.findViewById<View>(R.id.done).performClick() + underTest.handleCloseControls(false, false) + + testableLooper.processAllMessages() + verify(mockINotificationManager, never()) + .updateNotificationChannelForPackage( + eq(TEST_PACKAGE_NAME), + eq(TEST_UID), + eq(notificationChannel), + ) + + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + assertThat(uiEventLogger.eventId(0)) + .isEqualTo(NotificationControlsEvent.NOTIFICATION_CONTROLS_OPEN.id) + } + + @Test + fun testCloseControlsUpdatesWhenCheckSaveListenerUsesCallback() { + notificationChannel.importance = IMPORTANCE_LOW + bindNotification(wasShownHighPriority = false) + + underTest.findViewById<View>(R.id.alert).performClick() + underTest.findViewById<View>(R.id.done).performClick() + testableLooper.processAllMessages() + verify(mockINotificationManager, never()) + .updateNotificationChannelForPackage( + eq(TEST_PACKAGE_NAME), + eq(TEST_UID), + eq(notificationChannel), + ) + + underTest.handleCloseControls(true, false) + + testableLooper.processAllMessages() + verify(mockINotificationManager) + .updateNotificationChannelForPackage( + eq(TEST_PACKAGE_NAME), + eq(TEST_UID), + eq(notificationChannel), + ) + } + + @Test + fun testCloseControls_withoutHittingApply() { + notificationChannel.importance = IMPORTANCE_LOW + bindNotification(wasShownHighPriority = false) + + underTest.findViewById<View>(R.id.alert).performClick() + + assertThat(underTest.shouldBeSavedOnClose()).isFalse() + } + + @Test + fun testWillBeRemovedReturnsFalse() { + assertThat(underTest.willBeRemoved()).isFalse() + + bindNotification(wasShownHighPriority = false) + + assertThat(underTest.willBeRemoved()).isFalse() + } + + @Test + @DisableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) + @Throws(Exception::class) + fun testBindNotification_HidesFeedbackLink_flagOff() { + bindNotification() + assertThat(underTest.findViewById<View>(R.id.feedback).visibility).isEqualTo(GONE) + } + + @Test + @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) + @Throws(RemoteException::class) + fun testBindNotification_SetsFeedbackLink_isReservedChannel() { + entry.setRanking(RankingBuilder(entry.ranking).setSummarization("something").build()) + val latch = CountDownLatch(1) + bindNotification( + notificationChannel = classifiedNotificationChannel, + onFeedbackClickListener = { _: View?, _: Intent? -> latch.countDown() }, + wasShownHighPriority = false, + ) + + val feedback: View = underTest.findViewById(R.id.feedback) + assertThat(feedback.visibility).isEqualTo(VISIBLE) + feedback.performClick() + // Verify that listener was triggered. + assertThat(latch.count).isEqualTo(0) + } + + @Test + @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) + @Throws(Exception::class) + fun testBindNotification_hidesFeedbackLink_notReservedChannel() { + bindNotification() + + assertThat(underTest.findViewById<View>(R.id.feedback).visibility).isEqualTo(GONE) + } + + private fun bindNotification( + pm: PackageManager = this.mockPackageManager, + iNotificationManager: INotificationManager = this.mockINotificationManager, + onUserInteractionCallback: OnUserInteractionCallback = this.onUserInteractionCallback, + channelEditorDialogController: ChannelEditorDialogController = + this.channelEditorDialogController, + pkg: String = TEST_PACKAGE_NAME, + notificationChannel: NotificationChannel = this.notificationChannel, + entry: NotificationEntry = this.entry, + onSettingsClick: NotificationInfo.OnSettingsClickListener? = null, + onAppSettingsClick: NotificationInfo.OnAppSettingsClickListener? = null, + onFeedbackClickListener: NotificationInfo.OnFeedbackClickListener? = null, + uiEventLogger: UiEventLogger = this.uiEventLogger, + isDeviceProvisioned: Boolean = true, + isNonblockable: Boolean = false, + wasShownHighPriority: Boolean = true, + assistantFeedbackController: AssistantFeedbackController = this.assistantFeedbackController, + metricsLogger: MetricsLogger = kosmos.metricsLogger, + onCloseClick: View.OnClickListener? = null, + ) { + underTest.bindNotification( + pm, + iNotificationManager, + onUserInteractionCallback, + channelEditorDialogController, + pkg, + notificationChannel, + entry, + onSettingsClick, + onAppSettingsClick, + onFeedbackClickListener, + uiEventLogger, + isDeviceProvisioned, + isNonblockable, + wasShownHighPriority, + assistantFeedbackController, + metricsLogger, + onCloseClick, + ) + } + + companion object { + private const val TEST_PACKAGE_NAME = "test_package" + private const val TEST_SYSTEM_PACKAGE_NAME = PrintManager.PRINT_SPOOLER_PACKAGE_NAME + private const val TEST_UID = 1 + private const val TEST_CHANNEL = "test_channel" + private const val TEST_CHANNEL_NAME = "TEST CHANNEL NAME" + } +} 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 bea14b2c003f..49b682d0a5d2 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 @@ -83,20 +83,6 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G private static final String TAG = "InfoGuts"; private int mActualHeight; - @IntDef(prefix = { "ACTION_" }, value = { - ACTION_NONE, - ACTION_TOGGLE_ALERT, - ACTION_TOGGLE_SILENT, - }) - public @interface NotificationInfoAction { - } - - public static final int ACTION_NONE = 0; - // standard controls - static final int ACTION_TOGGLE_SILENT = 2; - // standard controls - private static final int ACTION_TOGGLE_ALERT = 5; - private TextView mPriorityDescriptionView; private TextView mSilentDescriptionView; private TextView mAutomaticDescriptionView; @@ -123,7 +109,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G * The last importance level chosen by the user. Null if the user has not chosen an importance * level; non-null once the user takes an action which indicates an explicit preference. */ - @Nullable private Integer mChosenImportance; + @Nullable + private Integer mChosenImportance; private boolean mIsAutomaticChosen; private boolean mIsSingleDefaultChannel; private boolean mIsNonblockable; @@ -143,27 +130,27 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G boolean mSkipPost = false; // used by standard ui - private OnClickListener mOnAutomatic = v -> { + private final OnClickListener mOnAutomatic = v -> { mIsAutomaticChosen = true; applyAlertingBehavior(BEHAVIOR_AUTOMATIC, true /* userTriggered */); }; // used by standard ui - private OnClickListener mOnAlert = v -> { + private final OnClickListener mOnAlert = v -> { mChosenImportance = IMPORTANCE_DEFAULT; mIsAutomaticChosen = false; applyAlertingBehavior(BEHAVIOR_ALERTING, true /* userTriggered */); }; // used by standard ui - private OnClickListener mOnSilent = v -> { + private final OnClickListener mOnSilent = v -> { mChosenImportance = IMPORTANCE_LOW; mIsAutomaticChosen = false; applyAlertingBehavior(BEHAVIOR_SILENT, true /* userTriggered */); }; // used by standard ui - private OnClickListener mOnDismissSettings = v -> { + private final OnClickListener mOnDismissSettings = v -> { mPressedApply = true; mGutsContainer.closeControls(v, /* save= */ true); }; @@ -181,13 +168,6 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G mAutomaticDescriptionView = findViewById(R.id.automatic_summary); } - // Specify a CheckSaveListener to override when/if the user's changes are committed. - public interface CheckSaveListener { - // Invoked when importance has changed and the NotificationInfo wants to try to save it. - // Listener should run saveImportance unless the change should be canceled. - void checkSave(Runnable saveImportance, StatusBarNotification sbn); - } - public interface OnSettingsClickListener { void onClick(View v, NotificationChannel channel, int appUid); } @@ -216,7 +196,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G boolean isNonblockable, boolean wasShownHighPriority, AssistantFeedbackController assistantFeedbackController, - MetricsLogger metricsLogger, OnClickListener onCloseClick) + MetricsLogger metricsLogger, + OnClickListener onCloseClick) throws RemoteException { mINotificationManager = iNotificationManager; mMetricsLogger = metricsLogger; @@ -623,7 +604,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G intent, PackageManager.MATCH_DEFAULT_ONLY ); - if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) { + if (resolveInfos == null || resolveInfos.isEmpty() || resolveInfos.get(0) == null) { return null; } final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo; @@ -758,6 +739,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G /** * Returns a LogMaker with all available notification information. * Caller should set category, type, and maybe subtype, before passing it to mMetricsLogger. + * * @return LogMaker */ private LogMaker getLogMaker() { @@ -769,10 +751,11 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G /** * Returns an initialized LogMaker for logging importance changes. * The caller may override the type before passing it to mMetricsLogger. + * * @return LogMaker */ private LogMaker importanceChangeLogMaker() { - Integer chosenImportance = + int chosenImportance = mChosenImportance != null ? mChosenImportance : mStartingChannelImportance; return getLogMaker().setCategory(MetricsEvent.ACTION_SAVE_IMPORTANCE) .setType(MetricsEvent.TYPE_ACTION) @@ -782,6 +765,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G /** * Returns an initialized LogMaker for logging open/close of the info display. * The caller may override the type before passing it to mMetricsLogger. + * * @return LogMaker */ private LogMaker notificationControlsLogMaker() { @@ -799,7 +783,9 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G @Retention(SOURCE) @IntDef({BEHAVIOR_ALERTING, BEHAVIOR_SILENT, BEHAVIOR_AUTOMATIC}) - private @interface AlertingBehavior {} + private @interface AlertingBehavior { + } + private static final int BEHAVIOR_ALERTING = 0; private static final int BEHAVIOR_SILENT = 1; private static final int BEHAVIOR_AUTOMATIC = 2; |