1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.server.notification; 17 18 import static android.app.NotificationManager.IMPORTANCE_DEFAULT; 19 import static android.app.NotificationManager.IMPORTANCE_LOW; 20 21 import static junit.framework.TestCase.assertEquals; 22 23 import static org.junit.Assert.assertTrue; 24 import static org.mockito.ArgumentMatchers.any; 25 import static org.mockito.Matchers.anyInt; 26 import static org.mockito.Matchers.eq; 27 import static org.mockito.Mockito.mock; 28 import static org.mockito.Mockito.when; 29 30 import android.app.Notification; 31 import android.app.NotificationChannel; 32 import android.app.NotificationManager; 33 import android.content.ContentProvider; 34 import android.content.Context; 35 import android.content.IContentProvider; 36 import android.content.pm.ApplicationInfo; 37 import android.content.pm.PackageInfo; 38 import android.content.pm.PackageManager; 39 import android.content.pm.Signature; 40 import android.media.AudioAttributes; 41 import android.net.Uri; 42 import android.os.Build; 43 import android.os.UserHandle; 44 import android.os.Vibrator; 45 import android.service.notification.StatusBarNotification; 46 import android.test.suitebuilder.annotation.SmallTest; 47 import android.testing.TestableContentResolver; 48 49 import androidx.test.InstrumentationRegistry; 50 import androidx.test.runner.AndroidJUnit4; 51 52 import com.android.server.UiServiceTestCase; 53 54 import org.junit.Before; 55 import org.junit.Test; 56 import org.junit.runner.RunWith; 57 import org.mockito.Mock; 58 import org.mockito.MockitoAnnotations; 59 60 import java.util.ArrayList; 61 62 @SmallTest 63 @RunWith(AndroidJUnit4.class) 64 public class RankingHelperTest extends UiServiceTestCase { 65 private static final String PKG = "com.android.server.notification"; 66 private static final int UID = 0; 67 private static final UserHandle USER = UserHandle.of(0); 68 private static final String UPDATED_PKG = "updatedPkg"; 69 private static final int UID2 = 1111; 70 private static final String SYSTEM_PKG = "android"; 71 private static final int SYSTEM_UID= 1000; 72 private static final UserHandle USER2 = UserHandle.of(10); 73 private static final String TEST_CHANNEL_ID = "test_channel_id"; 74 private static final String TEST_AUTHORITY = "test"; 75 private static final Uri SOUND_URI = 76 Uri.parse("content://" + TEST_AUTHORITY + "/internal/audio/media/10"); 77 private static final Uri CANONICAL_SOUND_URI = 78 Uri.parse("content://" + TEST_AUTHORITY 79 + "/internal/audio/media/10?title=Test&canonical=1"); 80 81 @Mock NotificationUsageStats mUsageStats; 82 @Mock RankingHandler mHandler; 83 @Mock PackageManager mPm; 84 @Mock IContentProvider mTestIContentProvider; 85 @Mock Context mContext; 86 @Mock ZenModeHelper mMockZenModeHelper; 87 @Mock RankingConfig mConfig; 88 @Mock Vibrator mVibrator; 89 90 private NotificationManager.Policy mTestNotificationPolicy; 91 private Notification mNotiGroupGSortA; 92 private Notification mNotiGroupGSortB; 93 private Notification mNotiNoGroup; 94 private Notification mNotiNoGroup2; 95 private Notification mNotiNoGroupSortA; 96 private NotificationRecord mRecordGroupGSortA; 97 private NotificationRecord mRecordGroupGSortB; 98 private NotificationRecord mRecordNoGroup; 99 private NotificationRecord mRecordNoGroup2; 100 private NotificationRecord mRecordNoGroupSortA; 101 private RankingHelper mHelper; 102 private AudioAttributes mAudioAttributes; 103 104 @Before setUp()105 public void setUp() throws Exception { 106 MockitoAnnotations.initMocks(this); 107 UserHandle user = UserHandle.ALL; 108 109 final ApplicationInfo legacy = new ApplicationInfo(); 110 legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1; 111 final ApplicationInfo upgrade = new ApplicationInfo(); 112 upgrade.targetSdkVersion = Build.VERSION_CODES.O; 113 when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(legacy); 114 when(mPm.getApplicationInfoAsUser(eq(UPDATED_PKG), anyInt(), anyInt())).thenReturn(upgrade); 115 when(mPm.getApplicationInfoAsUser(eq(SYSTEM_PKG), anyInt(), anyInt())).thenReturn(upgrade); 116 when(mPm.getPackageUidAsUser(eq(PKG), anyInt())).thenReturn(UID); 117 when(mPm.getPackageUidAsUser(eq(UPDATED_PKG), anyInt())).thenReturn(UID2); 118 when(mPm.getPackageUidAsUser(eq(SYSTEM_PKG), anyInt())).thenReturn(SYSTEM_UID); 119 PackageInfo info = mock(PackageInfo.class); 120 info.signatures = new Signature[] {mock(Signature.class)}; 121 when(mPm.getPackageInfoAsUser(eq(SYSTEM_PKG), anyInt(), anyInt())).thenReturn(info); 122 when(mPm.getPackageInfoAsUser(eq(PKG), anyInt(), anyInt())) 123 .thenReturn(mock(PackageInfo.class)); 124 when(mContext.getResources()).thenReturn( 125 InstrumentationRegistry.getContext().getResources()); 126 when(mContext.getContentResolver()).thenReturn( 127 InstrumentationRegistry.getContext().getContentResolver()); 128 when(mContext.getPackageManager()).thenReturn(mPm); 129 when(mContext.getApplicationInfo()).thenReturn(legacy); 130 when(mContext.getSystemService(Vibrator.class)).thenReturn(mVibrator); 131 TestableContentResolver contentResolver = getContext().getContentResolver(); 132 contentResolver.setFallbackToExisting(false); 133 134 ContentProvider testContentProvider = mock(ContentProvider.class); 135 when(testContentProvider.getIContentProvider()).thenReturn(mTestIContentProvider); 136 contentResolver.addProvider(TEST_AUTHORITY, testContentProvider); 137 138 when(mTestIContentProvider.canonicalize(any(), eq(SOUND_URI))) 139 .thenReturn(CANONICAL_SOUND_URI); 140 when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI))) 141 .thenReturn(CANONICAL_SOUND_URI); 142 when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI))) 143 .thenReturn(SOUND_URI); 144 145 mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 146 NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); 147 when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); 148 mHelper = new RankingHelper(getContext(), mHandler, mConfig, mMockZenModeHelper, 149 mUsageStats, new String[] {ImportanceExtractor.class.getName()}); 150 151 mNotiGroupGSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID) 152 .setContentTitle("A") 153 .setGroup("G") 154 .setSortKey("A") 155 .setWhen(1205) 156 .build(); 157 mRecordGroupGSortA = new NotificationRecord(mContext, new StatusBarNotification( 158 PKG, PKG, 1, null, 0, 0, mNotiGroupGSortA, user, 159 null, System.currentTimeMillis()), getLowChannel()); 160 161 mNotiGroupGSortB = new Notification.Builder(mContext, TEST_CHANNEL_ID) 162 .setContentTitle("B") 163 .setGroup("G") 164 .setSortKey("B") 165 .setWhen(1200) 166 .build(); 167 mRecordGroupGSortB = new NotificationRecord(mContext, new StatusBarNotification( 168 PKG, PKG, 1, null, 0, 0, mNotiGroupGSortB, user, 169 null, System.currentTimeMillis()), getLowChannel()); 170 171 mNotiNoGroup = new Notification.Builder(mContext, TEST_CHANNEL_ID) 172 .setContentTitle("C") 173 .setWhen(1201) 174 .build(); 175 mRecordNoGroup = new NotificationRecord(mContext, new StatusBarNotification( 176 PKG, PKG, 1, null, 0, 0, mNotiNoGroup, user, 177 null, System.currentTimeMillis()), getLowChannel()); 178 179 mNotiNoGroup2 = new Notification.Builder(mContext, TEST_CHANNEL_ID) 180 .setContentTitle("D") 181 .setWhen(1202) 182 .build(); 183 mRecordNoGroup2 = new NotificationRecord(mContext, new StatusBarNotification( 184 PKG, PKG, 1, null, 0, 0, mNotiNoGroup2, user, 185 null, System.currentTimeMillis()), getLowChannel()); 186 187 mNotiNoGroupSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID) 188 .setContentTitle("E") 189 .setWhen(1201) 190 .setSortKey("A") 191 .build(); 192 mRecordNoGroupSortA = new NotificationRecord(mContext, new StatusBarNotification( 193 PKG, PKG, 1, null, 0, 0, mNotiNoGroupSortA, user, 194 null, System.currentTimeMillis()), getLowChannel()); 195 196 mAudioAttributes = new AudioAttributes.Builder() 197 .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN) 198 .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) 199 .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED) 200 .build(); 201 } 202 getLowChannel()203 private NotificationChannel getLowChannel() { 204 return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name", 205 IMPORTANCE_LOW); 206 } 207 getDefaultChannel()208 private NotificationChannel getDefaultChannel() { 209 return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name", 210 IMPORTANCE_DEFAULT); 211 } 212 213 @Test testSortShouldRespectCritical()214 public void testSortShouldRespectCritical() throws Exception { 215 ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(7); 216 NotificationRecord critical = generateRecord(0); 217 NotificationRecord critical_ish = generateRecord(1); 218 NotificationRecord critical_notAtAll = generateRecord(100); 219 220 notificationList.add(critical_ish); 221 notificationList.add(mRecordGroupGSortA); 222 notificationList.add(critical_notAtAll); 223 notificationList.add(mRecordGroupGSortB); 224 notificationList.add(mRecordNoGroup); 225 notificationList.add(mRecordNoGroupSortA); 226 notificationList.add(critical); 227 mHelper.sort(notificationList); 228 229 assertTrue(mHelper.indexOf(notificationList, critical) == 0); 230 assertTrue(mHelper.indexOf(notificationList, critical_ish) == 1); 231 assertTrue(mHelper.indexOf(notificationList, critical_notAtAll) == 6); 232 } 233 generateRecord(int criticality)234 private NotificationRecord generateRecord(int criticality) { 235 NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW); 236 final Notification.Builder builder = new Notification.Builder(getContext()) 237 .setContentTitle("foo") 238 .setSmallIcon(android.R.drawable.sym_def_app_icon); 239 Notification n = builder.build(); 240 StatusBarNotification sbn = new StatusBarNotification("", "", 0, "", 0, 241 0, n, UserHandle.ALL, null, System.currentTimeMillis()); 242 NotificationRecord notificationRecord = new NotificationRecord(getContext(), sbn, channel); 243 notificationRecord.setCriticality(criticality); 244 return notificationRecord; 245 } 246 247 @Test testFindAfterRankingWithASplitGroup()248 public void testFindAfterRankingWithASplitGroup() throws Exception { 249 ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(4); 250 notificationList.add(mRecordGroupGSortA); 251 notificationList.add(mRecordGroupGSortB); 252 notificationList.add(mRecordNoGroup); 253 notificationList.add(mRecordNoGroupSortA); 254 mHelper.sort(notificationList); 255 assertTrue(mHelper.indexOf(notificationList, mRecordGroupGSortA) >= 0); 256 assertTrue(mHelper.indexOf(notificationList, mRecordGroupGSortB) >= 0); 257 assertTrue(mHelper.indexOf(notificationList, mRecordNoGroup) >= 0); 258 assertTrue(mHelper.indexOf(notificationList, mRecordNoGroupSortA) >= 0); 259 } 260 261 @Test testSortShouldNotThrowWithPlainNotifications()262 public void testSortShouldNotThrowWithPlainNotifications() throws Exception { 263 ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(2); 264 notificationList.add(mRecordNoGroup); 265 notificationList.add(mRecordNoGroup2); 266 mHelper.sort(notificationList); 267 } 268 269 @Test testSortShouldNotThrowOneSorted()270 public void testSortShouldNotThrowOneSorted() throws Exception { 271 ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(2); 272 notificationList.add(mRecordNoGroup); 273 notificationList.add(mRecordNoGroupSortA); 274 mHelper.sort(notificationList); 275 } 276 277 @Test testSortShouldNotThrowOneNotification()278 public void testSortShouldNotThrowOneNotification() throws Exception { 279 ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(1); 280 notificationList.add(mRecordNoGroup); 281 mHelper.sort(notificationList); 282 } 283 284 @Test testSortShouldNotThrowOneSortKey()285 public void testSortShouldNotThrowOneSortKey() throws Exception { 286 ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(1); 287 notificationList.add(mRecordGroupGSortB); 288 mHelper.sort(notificationList); 289 } 290 291 @Test testSortShouldNotThrowOnEmptyList()292 public void testSortShouldNotThrowOnEmptyList() throws Exception { 293 ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(); 294 mHelper.sort(notificationList); 295 } 296 297 @Test testGroupNotifications_highestIsProxy()298 public void testGroupNotifications_highestIsProxy() { 299 ArrayList<NotificationRecord> notificationList = new ArrayList<>(); 300 // this should be the last in the list, except it's in a group with a high child 301 Notification lowSummaryN = new Notification.Builder(mContext, "") 302 .setGroup("group") 303 .setGroupSummary(true) 304 .build(); 305 NotificationRecord lowSummary = new NotificationRecord(mContext, new StatusBarNotification( 306 PKG, PKG, 1, "summary", 0, 0, lowSummaryN, USER, 307 null, System.currentTimeMillis()), getLowChannel()); 308 notificationList.add(lowSummary); 309 310 Notification lowN = new Notification.Builder(mContext, "").build(); 311 NotificationRecord low = new NotificationRecord(mContext, new StatusBarNotification( 312 PKG, PKG, 1, "low", 0, 0, lowN, USER, 313 null, System.currentTimeMillis()), getLowChannel()); 314 low.setContactAffinity(0.5f); 315 notificationList.add(low); 316 317 Notification highChildN = new Notification.Builder(mContext, "") 318 .setGroup("group") 319 .setGroupSummary(false) 320 .build(); 321 NotificationRecord highChild = new NotificationRecord(mContext, new StatusBarNotification( 322 PKG, PKG, 1, "child", 0, 0, highChildN, USER, 323 null, System.currentTimeMillis()), getDefaultChannel()); 324 notificationList.add(highChild); 325 326 mHelper.sort(notificationList); 327 328 assertEquals(lowSummary, notificationList.get(0)); 329 assertEquals(highChild, notificationList.get(1)); 330 assertEquals(low, notificationList.get(2)); 331 } 332 } 333