| /* |
| * Copyright (C) 2019 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 android.app.notification.legacy29.cts; |
| |
| import static android.service.notification.NotificationAssistantService.FEEDBACK_RATING; |
| |
| import static junit.framework.Assert.assertEquals; |
| import static junit.framework.Assert.assertTrue; |
| import static junit.framework.TestCase.assertFalse; |
| import static junit.framework.TestCase.assertNotNull; |
| |
| import static org.junit.Assert.assertNotEquals; |
| import static org.junit.Assert.fail; |
| import static org.junit.Assume.assumeFalse; |
| |
| import android.app.Instrumentation; |
| import android.app.Notification; |
| import android.app.NotificationChannel; |
| import android.app.NotificationManager; |
| import android.app.PendingIntent; |
| import android.app.Person; |
| import android.app.StatusBarManager; |
| import android.app.UiAutomation; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| import android.os.Bundle; |
| import android.os.ParcelFileDescriptor; |
| import android.os.SystemClock; |
| import android.provider.Telephony; |
| import android.service.notification.Adjustment; |
| import android.service.notification.NotificationAssistantService; |
| import android.service.notification.NotificationListenerService; |
| import android.service.notification.StatusBarNotification; |
| |
| import androidx.test.InstrumentationRegistry; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import junit.framework.Assert; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.io.BufferedReader; |
| import java.io.FileReader; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.TimeUnit; |
| |
| @RunWith(AndroidJUnit4.class) |
| public class NotificationAssistantServiceTest { |
| |
| final String TAG = "NotAsstServiceTest"; |
| final String NOTIFICATION_CHANNEL_ID = "NotificationAssistantServiceTest"; |
| final int ICON_ID = android.R.drawable.sym_def_app_icon; |
| final long SLEEP_TIME = 1000; // milliseconds |
| |
| private TestNotificationAssistant mNotificationAssistantService; |
| private TestNotificationListener mNotificationListenerService; |
| private NotificationManager mNotificationManager; |
| private StatusBarManager mStatusBarManager; |
| private Context mContext; |
| private UiAutomation mUi; |
| |
| private boolean isWatch() { |
| return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); |
| } |
| |
| @Before |
| public void setUp() throws IOException { |
| mUi = InstrumentationRegistry.getInstrumentation().getUiAutomation(); |
| mContext = InstrumentationRegistry.getContext(); |
| mNotificationManager = (NotificationManager) mContext.getSystemService( |
| Context.NOTIFICATION_SERVICE); |
| mNotificationManager.createNotificationChannel(new NotificationChannel( |
| NOTIFICATION_CHANNEL_ID, "name", NotificationManager.IMPORTANCE_DEFAULT)); |
| mStatusBarManager = (StatusBarManager) mContext.getSystemService( |
| Context.STATUS_BAR_SERVICE); |
| } |
| |
| @After |
| public void tearDown() throws IOException { |
| if (mNotificationListenerService != null) mNotificationListenerService.resetData(); |
| |
| toggleListenerAccess(false); |
| toggleAssistantAccess(false); |
| mUi.dropShellPermissionIdentity(); |
| } |
| |
| @Test |
| public void testOnNotificationEnqueued() throws Exception { |
| toggleListenerAccess(true); |
| Thread.sleep(SLEEP_TIME); |
| |
| mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE"); |
| mNotificationManager.allowAssistantAdjustment(Adjustment.KEY_USER_SENTIMENT); |
| mUi.dropShellPermissionIdentity(); |
| |
| mNotificationListenerService = TestNotificationListener.getInstance(); |
| |
| sendNotification(1, ICON_ID); |
| StatusBarNotification sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG); |
| NotificationListenerService.Ranking out = new NotificationListenerService.Ranking(); |
| mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out); |
| |
| // No modification because the Notification Assistant is not enabled |
| assertEquals(NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL, |
| out.getUserSentiment()); |
| mNotificationListenerService.resetData(); |
| |
| toggleAssistantAccess(true); |
| Thread.sleep(SLEEP_TIME); // wait for listener and assistant to be allowed |
| mNotificationAssistantService = TestNotificationAssistant.getInstance(); |
| |
| sendNotification(1, ICON_ID); |
| sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG); |
| mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out); |
| |
| // Assistant gets correct rank |
| assertTrue(mNotificationAssistantService.notificationRank >= 0); |
| // Assistant modifies notification |
| assertEquals(NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE, |
| out.getUserSentiment()); |
| } |
| |
| @Test |
| public void testAdjustNotification_userSentimentKey() throws Exception { |
| setUpListeners(); |
| |
| mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE"); |
| mNotificationManager.allowAssistantAdjustment(Adjustment.KEY_USER_SENTIMENT); |
| mUi.dropShellPermissionIdentity(); |
| |
| sendNotification(1, ICON_ID); |
| StatusBarNotification sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG); |
| NotificationListenerService.Ranking out = new NotificationListenerService.Ranking(); |
| mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out); |
| |
| assertEquals(NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE, |
| out.getUserSentiment()); |
| |
| Bundle signals = new Bundle(); |
| signals.putInt(Adjustment.KEY_USER_SENTIMENT, |
| NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE); |
| Adjustment adjustment = new Adjustment(sbn.getPackageName(), sbn.getKey(), signals, "", |
| sbn.getUser()); |
| |
| mNotificationAssistantService.adjustNotification(adjustment); |
| Thread.sleep(SLEEP_TIME); // wait for adjustment to be processed |
| |
| mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out); |
| |
| assertEquals(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE, |
| out.getUserSentiment()); |
| } |
| |
| @Test |
| public void testAdjustNotification_importanceKey() throws Exception { |
| setUpListeners(); |
| |
| mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE"); |
| mNotificationManager.allowAssistantAdjustment(Adjustment.KEY_IMPORTANCE); |
| mUi.dropShellPermissionIdentity(); |
| |
| sendNotification(1, ICON_ID); |
| StatusBarNotification sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG); |
| NotificationListenerService.Ranking out = new NotificationListenerService.Ranking(); |
| mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out); |
| |
| int currentImportance = out.getImportance(); |
| int newImportance = currentImportance == NotificationManager.IMPORTANCE_DEFAULT |
| ? NotificationManager.IMPORTANCE_HIGH : NotificationManager.IMPORTANCE_DEFAULT; |
| |
| Bundle signals = new Bundle(); |
| signals.putInt(Adjustment.KEY_IMPORTANCE, newImportance); |
| Adjustment adjustment = new Adjustment(sbn.getPackageName(), sbn.getKey(), signals, "", |
| sbn.getUser()); |
| |
| mNotificationAssistantService.adjustNotification(adjustment); |
| Thread.sleep(SLEEP_TIME); // wait for adjustment to be processed |
| |
| mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out); |
| |
| assertEquals(newImportance, out.getImportance()); |
| } |
| |
| @Test |
| public void testAdjustNotifications_rankingScoreKey() throws Exception { |
| setUpListeners(); |
| |
| try { |
| mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE"); |
| mNotificationManager.allowAssistantAdjustment(Adjustment.KEY_RANKING_SCORE); |
| mUi.dropShellPermissionIdentity(); |
| |
| sendNotification(1, ICON_ID); |
| StatusBarNotification sbn1 = getFirstNotificationFromPackage( |
| TestNotificationListener.PKG); |
| NotificationListenerService.Ranking out1 = new NotificationListenerService.Ranking(); |
| |
| sendNotification(2, ICON_ID); |
| StatusBarNotification sbn2 = getFirstNotificationFromPackage( |
| TestNotificationListener.PKG); |
| NotificationListenerService.Ranking out2 = new NotificationListenerService.Ranking(); |
| |
| mNotificationListenerService.mRankingMap.getRanking(sbn1.getKey(), out1); |
| mNotificationListenerService.mRankingMap.getRanking(sbn2.getKey(), out2); |
| |
| int currentRank1 = out1.getRank(); |
| int currentRank2 = out2.getRank(); |
| |
| float rankingScore1 = (currentRank1 > currentRank2) ? 1f : 0; |
| float rankingScore2 = (currentRank1 > currentRank2) ? 0 : 1f; |
| |
| Bundle signals = new Bundle(); |
| signals.putFloat(Adjustment.KEY_RANKING_SCORE, rankingScore1); |
| Adjustment adjustment = new Adjustment(sbn1.getPackageName(), sbn1.getKey(), signals, "", |
| sbn1.getUser()); |
| Bundle signals2 = new Bundle(); |
| signals2.putFloat(Adjustment.KEY_RANKING_SCORE, rankingScore2); |
| Adjustment adjustment2 = new Adjustment(sbn2.getPackageName(), sbn2.getKey(), signals2, "", |
| sbn2.getUser()); |
| mNotificationAssistantService.adjustNotifications(List.of(adjustment, adjustment2)); |
| Thread.sleep(SLEEP_TIME); // wait for adjustments to be processed |
| |
| mNotificationListenerService.mRankingMap.getRanking(sbn1.getKey(), out1); |
| mNotificationListenerService.mRankingMap.getRanking(sbn2.getKey(), out2); |
| |
| // verify the relative ordering changed |
| int newRank1 = out1.getRank(); |
| int newRank2 = out2.getRank(); |
| if (currentRank1 > currentRank2) { |
| assertTrue(newRank1 < newRank2); |
| } else { |
| assertTrue(newRank1 > newRank2); |
| } |
| } finally { |
| mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE"); |
| mNotificationManager.disallowAssistantAdjustment(Adjustment.KEY_RANKING_SCORE); |
| mUi.dropShellPermissionIdentity(); |
| } |
| } |
| |
| @Test |
| public void testAdjustNotification_smartActionKey() throws Exception { |
| setUpListeners(); |
| |
| mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE"); |
| mNotificationManager.allowAssistantAdjustment(Adjustment.KEY_CONTEXTUAL_ACTIONS); |
| mUi.dropShellPermissionIdentity(); |
| |
| PendingIntent sendIntent = PendingIntent.getActivity(mContext, 0, |
| new Intent(Intent.ACTION_SEND), PendingIntent.FLAG_MUTABLE_UNAUDITED); |
| Notification.Action sendAction = new Notification.Action.Builder(ICON_ID, "SEND", |
| sendIntent).build(); |
| |
| sendNotification(1, ICON_ID); |
| StatusBarNotification sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG); |
| NotificationListenerService.Ranking out = new NotificationListenerService.Ranking(); |
| mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out); |
| |
| List<Notification.Action> smartActions = out.getSmartActions(); |
| if (smartActions != null) { |
| for (int i = 0; i < smartActions.size(); i++) { |
| Notification.Action action = smartActions.get(i); |
| assertNotEquals(sendIntent, action.actionIntent); |
| } |
| } |
| |
| ArrayList<Notification.Action> extraAction = new ArrayList<>(); |
| extraAction.add(sendAction); |
| Bundle signals = new Bundle(); |
| signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, extraAction); |
| Adjustment adjustment = new Adjustment(sbn.getPackageName(), sbn.getKey(), signals, "", |
| sbn.getUser()); |
| |
| mNotificationAssistantService.adjustNotification(adjustment); |
| Thread.sleep(SLEEP_TIME); //wait for adjustment to be processed |
| |
| mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out); |
| |
| boolean actionFound = false; |
| smartActions = out.getSmartActions(); |
| for (int i = 0; i < smartActions.size(); i++) { |
| Notification.Action action = smartActions.get(i); |
| actionFound = actionFound || action.actionIntent.equals(sendIntent); |
| } |
| assertTrue(actionFound); |
| } |
| |
| @Test |
| public void testAdjustNotification_smartReplyKey() throws Exception { |
| setUpListeners(); |
| CharSequence smartReply = "Smart Reply!"; |
| |
| mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE"); |
| mNotificationManager.allowAssistantAdjustment(Adjustment.KEY_TEXT_REPLIES); |
| mUi.dropShellPermissionIdentity(); |
| |
| sendNotification(1, ICON_ID); |
| StatusBarNotification sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG); |
| NotificationListenerService.Ranking out = new NotificationListenerService.Ranking(); |
| mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out); |
| |
| List<CharSequence> smartReplies = out.getSmartReplies(); |
| if (smartReplies != null) { |
| for (int i = 0; i < smartReplies.size(); i++) { |
| CharSequence reply = smartReplies.get(i); |
| assertNotEquals(smartReply, reply); |
| } |
| } |
| |
| ArrayList<CharSequence> extraReply = new ArrayList<>(); |
| extraReply.add(smartReply); |
| Bundle signals = new Bundle(); |
| signals.putCharSequenceArrayList(Adjustment.KEY_TEXT_REPLIES, extraReply); |
| Adjustment adjustment = new Adjustment(sbn.getPackageName(), sbn.getKey(), signals, "", |
| sbn.getUser()); |
| |
| mNotificationAssistantService.adjustNotification(adjustment); |
| Thread.sleep(SLEEP_TIME); //wait for adjustment to be processed |
| |
| mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out); |
| |
| boolean replyFound = false; |
| smartReplies = out.getSmartReplies(); |
| for (int i = 0; i < smartReplies.size(); i++) { |
| CharSequence reply = smartReplies.get(i); |
| replyFound = replyFound || reply.equals(smartReply); |
| } |
| assertTrue(replyFound); |
| } |
| |
| @Test |
| public void testAdjustNotification_importanceKey_notAllowed() throws Exception { |
| setUpListeners(); |
| |
| mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE"); |
| mNotificationManager.disallowAssistantAdjustment(Adjustment.KEY_IMPORTANCE); |
| mUi.dropShellPermissionIdentity(); |
| |
| sendNotification(1, ICON_ID); |
| StatusBarNotification sbn = getFirstNotificationFromPackage( |
| TestNotificationListener.PKG); |
| NotificationListenerService.Ranking out = new NotificationListenerService.Ranking(); |
| mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out); |
| |
| int currentImportance = out.getImportance(); |
| int newImportance = currentImportance == NotificationManager.IMPORTANCE_DEFAULT |
| ? NotificationManager.IMPORTANCE_HIGH : NotificationManager.IMPORTANCE_DEFAULT; |
| |
| Bundle signals = new Bundle(); |
| signals.putInt(Adjustment.KEY_IMPORTANCE, newImportance); |
| Adjustment adjustment = new Adjustment(sbn.getPackageName(), sbn.getKey(), signals, "", |
| sbn.getUser()); |
| |
| mNotificationAssistantService.adjustNotification(adjustment); |
| Thread.sleep(SLEEP_TIME); // wait for adjustment to be processed |
| |
| mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out); |
| |
| assertEquals(currentImportance, out.getImportance()); |
| |
| } |
| |
| @Test |
| public void testAdjustNotification_rankingScoreKey_notAllowed() throws Exception { |
| setUpListeners(); |
| |
| mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE"); |
| mNotificationManager.cancelAll(); |
| mNotificationManager.disallowAssistantAdjustment(Adjustment.KEY_RANKING_SCORE); |
| mUi.dropShellPermissionIdentity(); |
| |
| sendNotification(1, ICON_ID); |
| StatusBarNotification sbn1 = getFirstNotificationFromPackage(TestNotificationListener.PKG); |
| NotificationListenerService.Ranking out1 = new NotificationListenerService.Ranking(); |
| |
| sendNotification(2, ICON_ID); |
| StatusBarNotification sbn2 = getFirstNotificationFromPackage(TestNotificationListener.PKG); |
| NotificationListenerService.Ranking out2 = new NotificationListenerService.Ranking(); |
| |
| mNotificationListenerService.mRankingMap.getRanking(sbn1.getKey(), out1); |
| mNotificationListenerService.mRankingMap.getRanking(sbn2.getKey(), out2); |
| |
| int currentRank1 = out1.getRank(); |
| int currentRank2 = out2.getRank(); |
| |
| float rankingScore1 = (currentRank1 > currentRank2) ? 1f: 0; |
| float rankingScore2 = (currentRank1 > currentRank2) ? 0: 1f; |
| |
| Bundle signals = new Bundle(); |
| signals.putFloat(Adjustment.KEY_RANKING_SCORE, rankingScore1); |
| Adjustment adjustment = new Adjustment(sbn1.getPackageName(), sbn1.getKey(), signals, "", |
| sbn1.getUser()); |
| mNotificationAssistantService.adjustNotification(adjustment); |
| signals = new Bundle(); |
| signals.putFloat(Adjustment.KEY_RANKING_SCORE, rankingScore2); |
| adjustment = new Adjustment(sbn2.getPackageName(), sbn2.getKey(), signals, "", |
| sbn2.getUser()); |
| mNotificationAssistantService.adjustNotification(adjustment); |
| Thread.sleep(SLEEP_TIME); // wait for adjustments to be processed |
| |
| mNotificationListenerService.mRankingMap.getRanking(sbn1.getKey(), out1); |
| mNotificationListenerService.mRankingMap.getRanking(sbn2.getKey(), out2); |
| |
| // verify the relative ordering remains the same |
| int newRank1 = out1.getRank(); |
| int newRank2 = out2.getRank(); |
| if (currentRank1 > currentRank2) { |
| assertTrue(newRank1 > newRank2); |
| } else { |
| assertTrue(newRank1 < newRank2); |
| } |
| } |
| |
| @Test |
| public void testGetAllowedAssistantCapabilities_permission() throws Exception { |
| toggleAssistantAccess(false); |
| |
| try { |
| mNotificationManager.getAllowedAssistantAdjustments(); |
| fail(" Non assistants cannot call this method"); |
| } catch (SecurityException e) { |
| //pass |
| } |
| } |
| |
| @Test |
| public void testGetAllowedAssistantCapabilities() throws Exception { |
| toggleAssistantAccess(true); |
| Thread.sleep(SLEEP_TIME); // wait for assistant to be allowed |
| mNotificationAssistantService = TestNotificationAssistant.getInstance(); |
| mNotificationAssistantService.onAllowedAdjustmentsChanged(); |
| assertNotNull(mNotificationAssistantService.currentCapabilities); |
| |
| mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE"); |
| mNotificationManager.allowAssistantAdjustment(Adjustment.KEY_SNOOZE_CRITERIA); |
| |
| Thread.sleep(SLEEP_TIME); |
| assertTrue(mNotificationAssistantService.currentCapabilities.contains( |
| Adjustment.KEY_SNOOZE_CRITERIA)); |
| |
| mNotificationManager.disallowAssistantAdjustment(Adjustment.KEY_SNOOZE_CRITERIA); |
| Thread.sleep(SLEEP_TIME); |
| assertFalse(mNotificationAssistantService.currentCapabilities.contains( |
| Adjustment.KEY_SNOOZE_CRITERIA)); |
| |
| // just in case KEY_SNOOZE_CRITERIA was included in the original set, test adding again |
| mNotificationManager.allowAssistantAdjustment(Adjustment.KEY_SNOOZE_CRITERIA); |
| Thread.sleep(SLEEP_TIME); |
| assertTrue(mNotificationAssistantService.currentCapabilities.contains( |
| Adjustment.KEY_SNOOZE_CRITERIA)); |
| |
| mUi.dropShellPermissionIdentity(); |
| } |
| |
| @Test |
| public void testOnNotificationSnoozedUntilContext() throws Exception { |
| final String snoozeContext = "@SnoozeContext1@"; |
| |
| setUpListeners(); // also enables assistant |
| |
| sendNotification(1001, ICON_ID); |
| StatusBarNotification sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG); |
| |
| // simulate the user snoozing the notification |
| Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); |
| runCommand(String.format("cmd notification snooze --condition %s %s", snoozeContext, |
| sbn.getKey()), instrumentation); |
| |
| Thread.sleep(SLEEP_TIME); |
| |
| assertTrue(String.format("snoozed notification <%s> was not removed", sbn.getKey()), |
| mNotificationListenerService.checkRemovedKey(sbn.getKey())); |
| |
| assertEquals(String.format("snoozed notification <%s> was not observed by NAS", sbn.getKey()), |
| sbn.getKey(), mNotificationAssistantService.snoozedKey); |
| assertEquals(snoozeContext, mNotificationAssistantService.snoozedUntilContext); |
| } |
| |
| @Test |
| public void testUnsnoozeFromNAS() throws Exception { |
| final String snoozeContext = "@SnoozeContext2@"; |
| |
| setUpListeners(); // also enables assistant |
| |
| sendNotification(1002, ICON_ID); |
| StatusBarNotification sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG); |
| |
| // simulate the user snoozing the notification |
| Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); |
| runCommand(String.format("cmd notification snooze --context %s %s", snoozeContext, |
| sbn.getKey()), instrumentation); |
| |
| Thread.sleep(SLEEP_TIME); |
| |
| // unsnooze from listener |
| mNotificationAssistantService = TestNotificationAssistant.getInstance(); |
| android.util.Log.v(TAG, "unsnoozing from listener: " + sbn.getKey()); |
| mNotificationAssistantService.unsnoozeNotification(sbn.getKey()); |
| |
| Thread.sleep(SLEEP_TIME); |
| |
| NotificationListenerService.Ranking out = new NotificationListenerService.Ranking(); |
| boolean found = mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out); |
| assertTrue("notification <" + sbn.getKey() |
| + "> was not restored when unsnoozed from listener", |
| found); |
| } |
| |
| @Test |
| public void testOnActionInvoked_methodExists() throws Exception { |
| setUpListeners(); |
| final Intent intent = new Intent(Intent.ACTION_MAIN, Telephony.Threads.CONTENT_URI); |
| |
| intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP |
| | Intent.FLAG_ACTIVITY_CLEAR_TOP); |
| intent.setAction(Intent.ACTION_MAIN); |
| |
| final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED); |
| Notification.Action action = new Notification.Action.Builder(null, "", |
| pendingIntent).build(); |
| // This method has to exist and the call cannot fail |
| mNotificationAssistantService.onActionInvoked("", action, |
| NotificationAssistantService.SOURCE_FROM_APP); |
| } |
| |
| @Test |
| public void testOnNotificationDirectReplied_methodExists() throws Exception { |
| setUpListeners(); |
| // This method has to exist and the call cannot fail |
| mNotificationAssistantService.onNotificationDirectReplied(""); |
| } |
| |
| @Test |
| public void testOnNotificationExpansionChanged_methodExists() throws Exception { |
| setUpListeners(); |
| // This method has to exist and the call cannot fail |
| mNotificationAssistantService.onNotificationExpansionChanged("", true, true); |
| } |
| |
| @Test |
| public void testOnNotificationVisibilityChanged() throws Exception { |
| if (isTelevision()) { |
| return; |
| } |
| assumeFalse("Status bar service not supported", isWatch()); |
| setUpListeners(); |
| turnScreenOn(); |
| mUi.adoptShellPermissionIdentity("android.permission.EXPAND_STATUS_BAR"); |
| |
| sendConversationNotification(mNotificationAssistantService.notificationId); |
| Thread.sleep(SLEEP_TIME * 2); |
| |
| // Initialize as closed |
| mStatusBarManager.collapsePanels(); |
| Thread.sleep(SLEEP_TIME * 2); |
| |
| mStatusBarManager.expandNotificationsPanel(); |
| Thread.sleep(SLEEP_TIME * 2); |
| assertTrue(mNotificationAssistantService.notificationVisible); |
| |
| mStatusBarManager.collapsePanels(); |
| Thread.sleep(SLEEP_TIME * 2); |
| assertFalse(mNotificationAssistantService.notificationVisible); |
| |
| mUi.dropShellPermissionIdentity(); |
| } |
| |
| @Test |
| public void testOnNotificationsSeen() throws Exception { |
| if (isTelevision()) { |
| return; |
| } |
| assumeFalse("Status bar service not supported", isWatch()); |
| setUpListeners(); |
| turnScreenOn(); |
| mUi.adoptShellPermissionIdentity("android.permission.EXPAND_STATUS_BAR"); |
| |
| mNotificationAssistantService.resetNotificationVisibilityCounts(); |
| |
| // Initialize as closed |
| mStatusBarManager.collapsePanels(); |
| |
| sendNotification(1, ICON_ID); |
| assertEquals(0, mNotificationAssistantService.notificationSeenCount); |
| |
| mStatusBarManager.expandNotificationsPanel(); |
| Thread.sleep(SLEEP_TIME * 2); |
| assertTrue(mNotificationAssistantService.notificationSeenCount > 0); |
| |
| mStatusBarManager.collapsePanels(); |
| mUi.dropShellPermissionIdentity(); |
| } |
| |
| @Test |
| public void testOnPanelRevealedAndHidden() throws Exception { |
| if (isTelevision()) { |
| return; |
| } |
| assumeFalse("Status bar service not supported", isWatch()); |
| setUpListeners(); |
| turnScreenOn(); |
| mUi.adoptShellPermissionIdentity("android.permission.EXPAND_STATUS_BAR"); |
| |
| // Initialize as closed |
| mStatusBarManager.collapsePanels(); |
| assertFalse(mNotificationAssistantService.isPanelOpen); |
| |
| mStatusBarManager.expandNotificationsPanel(); |
| Thread.sleep(SLEEP_TIME * 2); |
| assertTrue(mNotificationAssistantService.isPanelOpen); |
| |
| mStatusBarManager.collapsePanels(); |
| Thread.sleep(SLEEP_TIME * 2); |
| assertFalse(mNotificationAssistantService.isPanelOpen); |
| |
| mUi.dropShellPermissionIdentity(); |
| } |
| |
| @Test |
| public void testOnSuggestedReplySent_methodExists() throws Exception { |
| setUpListeners(); |
| // This method has to exist and the call cannot fail |
| mNotificationAssistantService.onSuggestedReplySent("", "", |
| NotificationAssistantService.SOURCE_FROM_APP); |
| } |
| |
| @Test |
| public void testOnNotificationClicked() throws Exception { |
| if (isTelevision()) { |
| return; |
| } |
| |
| setUpListeners(); |
| turnScreenOn(); |
| mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE", "android.permission.EXPAND_STATUS_BAR"); |
| |
| mNotificationAssistantService.resetNotificationClickCount(); |
| |
| // Initialize as closed |
| mStatusBarManager.collapsePanels(); |
| sendNotification(1, ICON_ID); |
| StatusBarNotification sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG); |
| |
| mStatusBarManager.expandNotificationsPanel(); |
| Thread.sleep(SLEEP_TIME * 2); |
| mStatusBarManager.clickNotification(sbn.getKey(), 1, 1, true); |
| Thread.sleep(SLEEP_TIME * 2); |
| |
| assertEquals(1, mNotificationAssistantService.notificationClickCount); |
| |
| mStatusBarManager.collapsePanels(); |
| mUi.dropShellPermissionIdentity(); |
| |
| } |
| |
| @Test |
| public void testOnNotificationFeedbackReceived() throws Exception { |
| setUpListeners(); // also enables assistant |
| mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE", "android.permission.EXPAND_STATUS_BAR"); |
| |
| sendNotification(1, ICON_ID); |
| StatusBarNotification sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG); |
| |
| Bundle feedback = new Bundle(); |
| feedback.putInt(FEEDBACK_RATING, 1); |
| |
| mStatusBarManager.sendNotificationFeedback(sbn.getKey(), feedback); |
| Thread.sleep(SLEEP_TIME * 2); |
| assertEquals(1, mNotificationAssistantService.notificationFeedback); |
| |
| mUi.dropShellPermissionIdentity(); |
| } |
| |
| private StatusBarNotification getFirstNotificationFromPackage(String PKG) |
| throws InterruptedException { |
| StatusBarNotification sbn = mNotificationListenerService.mPosted.poll(SLEEP_TIME, |
| TimeUnit.MILLISECONDS); |
| assertNotNull(sbn); |
| while (!sbn.getPackageName().equals(PKG)) { |
| sbn = mNotificationListenerService.mPosted.poll(SLEEP_TIME, TimeUnit.MILLISECONDS); |
| } |
| assertNotNull(sbn); |
| return sbn; |
| } |
| |
| private void setUpListeners() throws Exception { |
| toggleListenerAccess(true); |
| toggleAssistantAccess(true); |
| Thread.sleep(2 * SLEEP_TIME); // wait for listener and assistant to be allowed |
| |
| mNotificationListenerService = TestNotificationListener.getInstance(); |
| mNotificationAssistantService = TestNotificationAssistant.getInstance(); |
| |
| assertNotNull(mNotificationListenerService); |
| assertNotNull(mNotificationAssistantService); |
| } |
| |
| private void sendNotification(final int id, final int icon) throws Exception { |
| sendNotification(id, null, icon); |
| } |
| |
| private void sendNotification(final int id, String groupKey, final int icon) throws Exception { |
| final Intent intent = new Intent(Intent.ACTION_MAIN, Telephony.Threads.CONTENT_URI); |
| |
| intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP |
| | Intent.FLAG_ACTIVITY_CLEAR_TOP); |
| intent.setAction(Intent.ACTION_MAIN); |
| |
| final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED); |
| final Notification notification = |
| new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) |
| .setSmallIcon(icon) |
| .setWhen(System.currentTimeMillis()) |
| .setContentTitle("notify#" + id) |
| .setContentText("This is #" + id + "notification ") |
| .setContentIntent(pendingIntent) |
| .setGroup(groupKey) |
| .build(); |
| mNotificationManager.notify(id, notification); |
| } |
| |
| private void sendConversationNotification(final int id) { |
| Person person = new Person.Builder() |
| .setName("test") |
| .build(); |
| final Notification notification = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) |
| .setContentTitle("foo") |
| .setShortcutId("shareShortcut") |
| .setStyle(new Notification.MessagingStyle(person) |
| .setConversationTitle("Test Chat") |
| .addMessage("Hello?", |
| SystemClock.currentThreadTimeMillis() - 300000, person) |
| .addMessage("Is it me you're looking for?", |
| SystemClock.currentThreadTimeMillis(), person) |
| ) |
| .setSmallIcon(ICON_ID) |
| .build(); |
| mNotificationManager.notify(id, notification); |
| } |
| |
| private void turnScreenOn() throws IOException { |
| Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); |
| runCommand("input keyevent KEYCODE_WAKEUP", instrumentation); |
| runCommand("wm dismiss-keyguard", instrumentation); |
| } |
| |
| private boolean isTelevision() { |
| PackageManager packageManager = mContext.getPackageManager(); |
| return packageManager != null |
| && (packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK) |
| || packageManager.hasSystemFeature(PackageManager.FEATURE_TELEVISION)); |
| } |
| |
| private void toggleListenerAccess(boolean on) throws IOException { |
| |
| Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); |
| String componentName = TestNotificationListener.getId(); |
| |
| String command = " cmd notification " + (on ? "allow_listener " : "disallow_listener ") |
| + componentName; |
| |
| runCommand(command, instrumentation); |
| |
| final ComponentName listenerComponent = TestNotificationListener.getComponentName(); |
| final NotificationManager nm = mContext.getSystemService(NotificationManager.class); |
| Assert.assertTrue(listenerComponent + " has not been " + (on ? "allowed" : "disallowed"), |
| nm.isNotificationListenerAccessGranted(listenerComponent) == on); |
| } |
| |
| private void toggleAssistantAccess(boolean on) { |
| final ComponentName assistantComponent = TestNotificationAssistant.getComponentName(); |
| |
| mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE", |
| "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE"); |
| mNotificationManager.setNotificationAssistantAccessGranted(assistantComponent, on); |
| |
| assertTrue(assistantComponent + " has not been " + (on ? "allowed" : "disallowed"), |
| mNotificationManager.isNotificationAssistantAccessGranted(assistantComponent) |
| == on); |
| if (on) { |
| assertEquals(assistantComponent, |
| mNotificationManager.getAllowedNotificationAssistant()); |
| } else { |
| assertNotEquals(assistantComponent, |
| mNotificationManager.getAllowedNotificationAssistant()); |
| } |
| |
| mUi.dropShellPermissionIdentity(); |
| } |
| |
| private void runCommand(String command, Instrumentation instrumentation) throws IOException { |
| UiAutomation uiAutomation = instrumentation.getUiAutomation(); |
| // Execute command |
| System.out.println("runCommand: <<<" + command + ">>>"); |
| try (ParcelFileDescriptor fd = uiAutomation.executeShellCommand(command)) { |
| assertNotNull("Failed to execute shell command: " + command, fd); |
| // Wait for the command to finish by reading until EOF |
| try (BufferedReader in = new BufferedReader(new FileReader(fd.getFileDescriptor()))) { |
| String line; |
| while (null != (line = in.readLine())) { |
| android.util.Log.v(TAG, "runCommand: output: " + line); |
| } |
| } catch (IOException e) { |
| throw new IOException("Could not read stdout of command: " + command, e); |
| } |
| } |
| } |
| } |