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