| /* |
| * 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.server.notification; |
| |
| import static android.app.Notification.EXTRA_SMALL_ICON; |
| import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; |
| import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; |
| import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE; |
| |
| import static org.junit.Assert.assertArrayEquals; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.anyInt; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.when; |
| |
| import android.app.INotificationManager; |
| import android.app.Notification; |
| import android.app.NotificationChannel; |
| import android.app.PendingIntent; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.ApplicationInfo; |
| import android.graphics.Bitmap; |
| import android.graphics.drawable.Icon; |
| import android.os.Binder; |
| import android.os.Build; |
| import android.os.IBinder; |
| import android.os.Parcel; |
| import android.service.notification.NotificationListenerService; |
| import android.service.notification.NotificationListenerService.Ranking; |
| import android.service.notification.NotificationListenerService.RankingMap; |
| import android.service.notification.NotificationRankingUpdate; |
| import android.service.notification.SnoozeCriterion; |
| import android.test.suitebuilder.annotation.SmallTest; |
| |
| import com.android.server.UiServiceTestCase; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import androidx.test.runner.AndroidJUnit4; |
| |
| @SmallTest |
| @RunWith(AndroidJUnit4.class) |
| public class NotificationListenerServiceTest extends UiServiceTestCase { |
| |
| int targetSdk = 0; |
| |
| @Before |
| public void setUp() { |
| targetSdk = mContext.getApplicationInfo().targetSdkVersion; |
| } |
| |
| @After |
| public void tearDown() { |
| mContext.getApplicationInfo().targetSdkVersion = targetSdk; |
| } |
| |
| @Test |
| public void testGetActiveNotifications_notNull() throws Exception { |
| TestListenerService service = new TestListenerService(); |
| INotificationManager noMan = service.getNoMan(); |
| when(noMan.getActiveNotificationsFromListener(any(), any(), anyInt())).thenReturn(null); |
| |
| assertNotNull(service.getActiveNotifications()); |
| assertNotNull(service.getActiveNotifications(NotificationListenerService.TRIM_FULL)); |
| assertNotNull(service.getActiveNotifications(new String[0])); |
| assertNotNull(service.getActiveNotifications( |
| new String[0], NotificationListenerService.TRIM_LIGHT)); |
| } |
| |
| @Test |
| public void testRanking() { |
| TestListenerService service = new TestListenerService(); |
| service.applyUpdateLocked(generateUpdate()); |
| for (int i = 0; i < mKeys.length; i++) { |
| String key = mKeys[i]; |
| Ranking ranking = new Ranking(); |
| service.getCurrentRanking().getRanking(key, ranking); |
| assertEquals(getVisibilityOverride(i), ranking.getVisibilityOverride()); |
| assertEquals(getOverrideGroupKey(key), ranking.getOverrideGroupKey()); |
| assertEquals(!isIntercepted(i), ranking.matchesInterruptionFilter()); |
| assertEquals(getSuppressedVisualEffects(i), ranking.getSuppressedVisualEffects()); |
| assertEquals(getImportance(i), ranking.getImportance()); |
| assertEquals(getExplanation(key), ranking.getImportanceExplanation()); |
| assertEquals(getChannel(key, i), ranking.getChannel()); |
| assertEquals(getPeople(key, i), ranking.getAdditionalPeople()); |
| assertEquals(getSnoozeCriteria(key, i), ranking.getSnoozeCriteria()); |
| assertEquals(getShowBadge(i), ranking.canShowBadge()); |
| assertEquals(getUserSentiment(i), ranking.getUserSentiment()); |
| assertEquals(getHidden(i), ranking.isSuspended()); |
| assertEquals(lastAudiblyAlerted(i), ranking.getLastAudiblyAlertedMillis()); |
| assertActionsEqual(getSmartActions(key, i), ranking.getSmartActions()); |
| assertEquals(getSmartReplies(key, i), ranking.getSmartReplies()); |
| assertEquals(canBubble(i), ranking.canBubble()); |
| } |
| } |
| |
| // Tests parceling of NotificationRankingUpdate, and by extension, RankingMap and Ranking. |
| @Test |
| public void testRankingUpdate_parcel() { |
| NotificationRankingUpdate nru = generateUpdate(); |
| Parcel parcel = Parcel.obtain(); |
| nru.writeToParcel(parcel, 0); |
| parcel.setDataPosition(0); |
| NotificationRankingUpdate nru1 = NotificationRankingUpdate.CREATOR.createFromParcel(parcel); |
| assertEquals(nru, nru1); |
| } |
| |
| // Tests parceling of RankingMap and RankingMap.equals |
| @Test |
| public void testRankingMap_parcel() { |
| RankingMap rmap = generateUpdate().getRankingMap(); |
| Parcel parcel = Parcel.obtain(); |
| rmap.writeToParcel(parcel, 0); |
| parcel.setDataPosition(0); |
| RankingMap rmap1 = RankingMap.CREATOR.createFromParcel(parcel); |
| |
| detailedAssertEquals(rmap, rmap1); |
| assertEquals(rmap, rmap1); |
| } |
| |
| // Tests parceling of Ranking and Ranking.equals |
| @Test |
| public void testRanking_parcel() { |
| Ranking ranking = generateUpdate().getRankingMap().getRawRankingObject(mKeys[0]); |
| Parcel parcel = Parcel.obtain(); |
| ranking.writeToParcel(parcel, 0); |
| parcel.setDataPosition(0); |
| Ranking ranking1 = new Ranking(parcel); |
| detailedAssertEquals("rankings differ: ", ranking, ranking1); |
| assertEquals(ranking, ranking1); |
| } |
| |
| // Tests NotificationRankingUpdate.equals(), and by extension, RankingMap and Ranking. |
| @Test |
| public void testRankingUpdate_equals() { |
| NotificationRankingUpdate nru = generateUpdate(); |
| NotificationRankingUpdate nru2 = generateUpdate(); |
| detailedAssertEquals(nru, nru2); |
| assertEquals(nru, nru2); |
| Ranking tweak = nru2.getRankingMap().getRawRankingObject(mKeys[0]); |
| tweak.populate( |
| tweak.getKey(), |
| tweak.getRank(), |
| !tweak.matchesInterruptionFilter(), // note the inversion here! |
| tweak.getVisibilityOverride(), |
| tweak.getSuppressedVisualEffects(), |
| tweak.getImportance(), |
| tweak.getImportanceExplanation(), |
| tweak.getOverrideGroupKey(), |
| tweak.getChannel(), |
| (ArrayList) tweak.getAdditionalPeople(), |
| (ArrayList) tweak.getSnoozeCriteria(), |
| tweak.canShowBadge(), |
| tweak.getUserSentiment(), |
| tweak.isSuspended(), |
| tweak.getLastAudiblyAlertedMillis(), |
| tweak.isNoisy(), |
| (ArrayList) tweak.getSmartActions(), |
| (ArrayList) tweak.getSmartReplies(), |
| tweak.canBubble() |
| ); |
| assertNotEquals(nru, nru2); |
| } |
| |
| @Test |
| public void testLegacyIcons_preM() { |
| TestListenerService service = new TestListenerService(); |
| service.attachBaseContext(mContext); |
| service.targetSdk = Build.VERSION_CODES.LOLLIPOP_MR1; |
| |
| Bitmap largeIcon = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565); |
| |
| Notification n = new Notification.Builder(mContext, "channel") |
| .setSmallIcon(android.R.drawable.star_on) |
| .setLargeIcon(Icon.createWithBitmap(largeIcon)) |
| .setContentTitle("test") |
| .build(); |
| |
| service.createLegacyIconExtras(n); |
| |
| assertEquals(android.R.drawable.star_on, n.extras.getInt(EXTRA_SMALL_ICON)); |
| assertEquals(android.R.drawable.star_on, n.icon); |
| assertNotNull(n.largeIcon); |
| assertNotNull(n.extras.getParcelable(Notification.EXTRA_LARGE_ICON)); |
| } |
| |
| @Test |
| public void testLegacyIcons_mPlus() { |
| TestListenerService service = new TestListenerService(); |
| service.attachBaseContext(mContext); |
| service.targetSdk = Build.VERSION_CODES.M; |
| |
| Bitmap largeIcon = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565); |
| |
| Notification n = new Notification.Builder(mContext, "channel") |
| .setSmallIcon(android.R.drawable.star_on) |
| .setLargeIcon(Icon.createWithBitmap(largeIcon)) |
| .setContentTitle("test") |
| .build(); |
| |
| service.createLegacyIconExtras(n); |
| |
| assertEquals(0, n.extras.getInt(EXTRA_SMALL_ICON)); |
| assertNull(n.largeIcon); |
| } |
| |
| |
| // Test data |
| |
| private String[] mKeys = new String[] { "key", "key1", "key2", "key3", "key4"}; |
| |
| private NotificationRankingUpdate generateUpdate() { |
| Ranking[] rankings = new Ranking[mKeys.length]; |
| for (int i = 0; i < mKeys.length; i++) { |
| final String key = mKeys[i]; |
| Ranking ranking = new Ranking(); |
| ranking.populate( |
| key, |
| i, |
| !isIntercepted(i), |
| getVisibilityOverride(i), |
| getSuppressedVisualEffects(i), |
| getImportance(i), |
| getExplanation(key), |
| getOverrideGroupKey(key), |
| getChannel(key, i), |
| getPeople(key, i), |
| getSnoozeCriteria(key, i), |
| getShowBadge(i), |
| getUserSentiment(i), |
| getHidden(i), |
| lastAudiblyAlerted(i), |
| getNoisy(i), |
| getSmartActions(key, i), |
| getSmartReplies(key, i), |
| canBubble(i) |
| ); |
| rankings[i] = ranking; |
| } |
| NotificationRankingUpdate update = new NotificationRankingUpdate(rankings); |
| return update; |
| } |
| |
| private int getVisibilityOverride(int index) { |
| return index * 9; |
| } |
| |
| private String getOverrideGroupKey(String key) { |
| return key + key; |
| } |
| |
| private boolean isIntercepted(int index) { |
| return index % 2 == 0; |
| } |
| |
| private int getSuppressedVisualEffects(int index) { |
| return index * 2; |
| } |
| |
| private int getImportance(int index) { |
| return index; |
| } |
| |
| private String getExplanation(String key) { |
| return key + "explain"; |
| } |
| |
| private NotificationChannel getChannel(String key, int index) { |
| return new NotificationChannel(key, key, getImportance(index)); |
| } |
| |
| private boolean getShowBadge(int index) { |
| return index % 3 == 0; |
| } |
| |
| private int getUserSentiment(int index) { |
| switch(index % 3) { |
| case 0: |
| return USER_SENTIMENT_NEGATIVE; |
| case 1: |
| return USER_SENTIMENT_NEUTRAL; |
| case 2: |
| return USER_SENTIMENT_POSITIVE; |
| } |
| return USER_SENTIMENT_NEUTRAL; |
| } |
| |
| private boolean getHidden(int index) { |
| return index % 2 == 0; |
| } |
| |
| private long lastAudiblyAlerted(int index) { |
| return index * 2000; |
| } |
| |
| private boolean getNoisy(int index) { |
| return index < 1; |
| } |
| |
| private ArrayList<String> getPeople(String key, int index) { |
| ArrayList<String> people = new ArrayList<>(); |
| for (int i = 0; i < index; i++) { |
| people.add(i + key); |
| } |
| return people; |
| } |
| |
| private ArrayList<SnoozeCriterion> getSnoozeCriteria(String key, int index) { |
| ArrayList<SnoozeCriterion> snooze = new ArrayList<>(); |
| for (int i = 0; i < index; i++) { |
| snooze.add(new SnoozeCriterion(key + i, getExplanation(key), key)); |
| } |
| return snooze; |
| } |
| |
| private ArrayList<Notification.Action> getSmartActions(String key, int index) { |
| ArrayList<Notification.Action> actions = new ArrayList<>(); |
| for (int i = 0; i < index; i++) { |
| PendingIntent intent = PendingIntent.getBroadcast( |
| getContext(), |
| index /*requestCode*/, |
| new Intent("ACTION_" + key), |
| 0 /*flags*/); |
| actions.add(new Notification.Action.Builder(null /*icon*/, key, intent).build()); |
| } |
| return actions; |
| } |
| |
| private ArrayList<CharSequence> getSmartReplies(String key, int index) { |
| ArrayList<CharSequence> choices = new ArrayList<>(); |
| for (int i = 0; i < index; i++) { |
| choices.add("choice_" + key + "_" + i); |
| } |
| return choices; |
| } |
| |
| private boolean canBubble(int index) { |
| return index % 4 == 0; |
| } |
| |
| private void assertActionsEqual( |
| List<Notification.Action> expecteds, List<Notification.Action> actuals) { |
| assertEquals(expecteds.size(), actuals.size()); |
| for (int i = 0; i < expecteds.size(); i++) { |
| Notification.Action expected = expecteds.get(i); |
| Notification.Action actual = actuals.get(i); |
| assertEquals(expected.title, actual.title); |
| } |
| } |
| |
| private void detailedAssertEquals(NotificationRankingUpdate a, NotificationRankingUpdate b) { |
| assertEquals(a.getRankingMap(), b.getRankingMap()); |
| } |
| |
| private void detailedAssertEquals(String comment, Ranking a, Ranking b) { |
| assertEquals(comment, a.getKey(), b.getKey()); |
| assertEquals(comment, a.getRank(), b.getRank()); |
| assertEquals(comment, a.matchesInterruptionFilter(), b.matchesInterruptionFilter()); |
| assertEquals(comment, a.getVisibilityOverride(), b.getVisibilityOverride()); |
| assertEquals(comment, a.getSuppressedVisualEffects(), b.getSuppressedVisualEffects()); |
| assertEquals(comment, a.getImportance(), b.getImportance()); |
| assertEquals(comment, a.getImportanceExplanation(), b.getImportanceExplanation()); |
| assertEquals(comment, a.getOverrideGroupKey(), b.getOverrideGroupKey()); |
| assertEquals(comment, a.getChannel(), b.getChannel()); |
| assertEquals(comment, a.getAdditionalPeople(), b.getAdditionalPeople()); |
| assertEquals(comment, a.getSnoozeCriteria(), b.getSnoozeCriteria()); |
| assertEquals(comment, a.canShowBadge(), b.canShowBadge()); |
| assertEquals(comment, a.getUserSentiment(), b.getUserSentiment()); |
| assertEquals(comment, a.isSuspended(), b.isSuspended()); |
| assertEquals(comment, a.getLastAudiblyAlertedMillis(), b.getLastAudiblyAlertedMillis()); |
| assertEquals(comment, a.isNoisy(), b.isNoisy()); |
| assertEquals(comment, a.getSmartReplies(), b.getSmartReplies()); |
| assertEquals(comment, a.canBubble(), b.canBubble()); |
| assertActionsEqual(a.getSmartActions(), b.getSmartActions()); |
| } |
| |
| private void detailedAssertEquals(RankingMap a, RankingMap b) { |
| Ranking arank = new Ranking(); |
| Ranking brank = new Ranking(); |
| assertArrayEquals(a.getOrderedKeys(), b.getOrderedKeys()); |
| for (String key : a.getOrderedKeys()) { |
| a.getRanking(key, arank); |
| b.getRanking(key, brank); |
| detailedAssertEquals("ranking for key <" + key + ">", arank, brank); |
| } |
| } |
| |
| public static class TestListenerService extends NotificationListenerService { |
| private final IBinder binder = new LocalBinder(); |
| public int targetSdk = 0; |
| |
| public TestListenerService() { |
| mWrapper = mock(NotificationListenerWrapper.class); |
| mNoMan = mock(INotificationManager.class); |
| } |
| |
| INotificationManager getNoMan() { |
| return mNoMan; |
| } |
| |
| @Override |
| public IBinder onBind(Intent intent) { |
| super.onBind(intent); |
| return binder; |
| } |
| |
| public class LocalBinder extends Binder { |
| TestListenerService getService() { |
| return TestListenerService.this; |
| } |
| } |
| |
| @Override |
| protected void attachBaseContext(Context base) { |
| super.attachBaseContext(base); |
| } |
| |
| @Override |
| public ApplicationInfo getApplicationInfo() { |
| ApplicationInfo info = super.getApplicationInfo(); |
| if (targetSdk != 0) { |
| info.targetSdkVersion = targetSdk; |
| } |
| return info; |
| } |
| } |
| } |