| /* |
| * Copyright (C) 2013 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.cts.verifier.notifications; |
| |
| import static android.app.Notification.VISIBILITY_PRIVATE; |
| import static android.app.NotificationManager.IMPORTANCE_LOW; |
| import static android.app.NotificationManager.IMPORTANCE_MAX; |
| import static android.app.NotificationManager.IMPORTANCE_NONE; |
| import static android.provider.Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS; |
| import static android.provider.Settings.EXTRA_APP_PACKAGE; |
| import static android.provider.Settings.EXTRA_CHANNEL_ID; |
| |
| import static com.android.cts.verifier.notifications.MockListener.JSON_FLAGS; |
| import static com.android.cts.verifier.notifications.MockListener.JSON_ICON; |
| import static com.android.cts.verifier.notifications.MockListener.JSON_ID; |
| import static com.android.cts.verifier.notifications.MockListener.JSON_LAST_AUDIBLY_ALERTED; |
| import static com.android.cts.verifier.notifications.MockListener.JSON_PACKAGE; |
| import static com.android.cts.verifier.notifications.MockListener.JSON_REASON; |
| import static com.android.cts.verifier.notifications.MockListener.JSON_STATS; |
| import static com.android.cts.verifier.notifications.MockListener.JSON_TAG; |
| import static com.android.cts.verifier.notifications.MockListener.JSON_WHEN; |
| import static com.android.cts.verifier.notifications.MockListener.REASON_LISTENER_CANCEL; |
| |
| import android.annotation.SuppressLint; |
| import android.app.Notification; |
| import android.app.NotificationChannel; |
| import android.app.NotificationChannelGroup; |
| import android.app.PendingIntent; |
| import android.app.Person; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.SharedPreferences; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ShortcutInfo; |
| import android.content.pm.ShortcutManager; |
| import android.graphics.drawable.Icon; |
| import android.os.Bundle; |
| import android.os.SystemClock; |
| import android.provider.Settings; |
| import android.provider.Settings.Secure; |
| import android.service.notification.NotificationListenerService; |
| import android.service.notification.StatusBarNotification; |
| import android.util.ArraySet; |
| import android.util.Log; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.Button; |
| import android.widget.RemoteViews; |
| |
| import androidx.core.app.NotificationCompat; |
| |
| import com.android.cts.verifier.R; |
| |
| import org.json.JSONException; |
| import org.json.JSONObject; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.UUID; |
| |
| public class NotificationListenerVerifierActivity extends InteractiveVerifierActivity |
| implements Runnable { |
| static final String TAG = "NoListenerVerifier"; |
| private static final String NOTIFICATION_CHANNEL_ID = TAG; |
| private static final String NOISY_NOTIFICATION_CHANNEL_ID = TAG + "noisy"; |
| protected static final String PREFS = "listener_prefs"; |
| final int NUM_NOTIFICATIONS_SENT = 3; // # notifications sent by sendNotifications() |
| |
| private String mTag1; |
| private String mTag2; |
| private String mTag3; |
| private String mTag4; |
| private int mIcon1; |
| private int mIcon2; |
| private int mIcon3; |
| private int mIcon4; |
| private int mId1; |
| private int mId2; |
| private int mId3; |
| private int mId4; |
| private long mWhen1; |
| private long mWhen2; |
| private long mWhen3; |
| private long mWhen4; |
| private int mFlag1; |
| private int mFlag2; |
| private int mFlag3; |
| |
| @Override |
| protected int getTitleResource() { |
| return R.string.nls_test; |
| } |
| |
| @Override |
| protected int getInstructionsResource() { |
| return R.string.nls_info; |
| } |
| |
| // Test Setup |
| |
| @Override |
| protected List<InteractiveTestCase> createTestItems() { |
| boolean isAutomotive = getPackageManager().hasSystemFeature( |
| PackageManager.FEATURE_AUTOMOTIVE); |
| List<InteractiveTestCase> tests = new ArrayList<>(17); |
| tests.add(new IsEnabledTest()); |
| tests.add(new ServiceStartedTest()); |
| tests.add(new NotificationReceivedTest()); |
| /* |
| // TODO (b/200701618): re-enable tests if conditions in 3.8.3.1 change to MUST |
| if (!isAutomotive) { |
| tests.add(new SendUserToChangeFilter()); |
| tests.add(new AskIfFilterChanged()); |
| tests.add(new NotificationTypeFilterTest()); |
| tests.add(new ResetChangeFilter()); |
| }*/ |
| tests.add(new LongMessageTest()); |
| tests.add(new DataIntactTest()); |
| tests.add(new AudiblyAlertedTest()); |
| tests.add(new DismissOneTest()); |
| tests.add(new DismissOneWithReasonTest()); |
| tests.add(new DismissOneWithStatsTest()); |
| tests.add(new DismissAllTest()); |
| tests.add(new SnoozeNotificationForTimeTest()); |
| tests.add(new SnoozeNotificationForTimeCancelTest()); |
| tests.add(new GetSnoozedNotificationTest()); |
| tests.add(new EnableHintsTest()); |
| tests.add(new ReceiveAppBlockNoticeTest()); |
| tests.add(new ReceiveAppUnblockNoticeTest()); |
| if (!isAutomotive) { |
| tests.add(new ReceiveChannelBlockNoticeTest()); |
| tests.add(new ReceiveGroupBlockNoticeTest()); |
| } |
| tests.add(new RequestUnbindTest()); |
| tests.add(new RequestBindTest()); |
| tests.add(new MessageBundleTest()); |
| tests.add(new ConversationOrderingTest()); |
| tests.add(new HunDisplayTest()); |
| tests.add(new EnableHintsTest()); |
| tests.add(new IsDisabledTest()); |
| tests.add(new ServiceStoppedTest()); |
| tests.add(new NotificationNotReceivedTest()); |
| return tests; |
| } |
| |
| private void createChannels() { |
| NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, |
| NOTIFICATION_CHANNEL_ID, IMPORTANCE_LOW); |
| NotificationChannel noisyChannel = new NotificationChannel(NOISY_NOTIFICATION_CHANNEL_ID, |
| NOISY_NOTIFICATION_CHANNEL_ID, IMPORTANCE_MAX); |
| noisyChannel.setVibrationPattern(new long[]{100, 0, 100}); |
| mNm.createNotificationChannel(channel); |
| mNm.createNotificationChannel(noisyChannel); |
| } |
| |
| private void deleteChannels() { |
| mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID); |
| mNm.deleteNotificationChannel(NOISY_NOTIFICATION_CHANNEL_ID); |
| } |
| |
| @SuppressLint("NewApi") |
| private void sendNotifications() { |
| mTag1 = UUID.randomUUID().toString(); |
| Log.d(TAG, "Sending #1: " + mTag1); |
| mTag2 = UUID.randomUUID().toString(); |
| Log.d(TAG, "Sending #2: " + mTag2); |
| mTag3 = UUID.randomUUID().toString(); |
| Log.d(TAG, "Sending #3: " + mTag3); |
| |
| mWhen1 = System.currentTimeMillis() + 1; |
| mWhen2 = System.currentTimeMillis() + 2; |
| mWhen3 = System.currentTimeMillis() + 3; |
| |
| mIcon1 = R.drawable.ic_stat_alice; |
| mIcon2 = R.drawable.ic_stat_bob; |
| mIcon3 = R.drawable.ic_stat_charlie; |
| |
| mId1 = NOTIFICATION_ID + 1; |
| mId2 = NOTIFICATION_ID + 2; |
| mId3 = NOTIFICATION_ID + 3; |
| |
| mPackageString = "com.android.cts.verifier"; |
| |
| Notification n1 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) |
| .setContentTitle("ClearTest 1") |
| .setContentText(mTag1) |
| .setSmallIcon(mIcon1) |
| .setWhen(mWhen1) |
| .setDeleteIntent(makeIntent(1, mTag1)) |
| .setOnlyAlertOnce(true) |
| .build(); |
| mNm.notify(mTag1, mId1, n1); |
| mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE; |
| |
| Notification n2 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) |
| .setContentTitle("ClearTest 2") |
| .setContentText(mTag2) |
| .setSmallIcon(mIcon2) |
| .setWhen(mWhen2) |
| .setDeleteIntent(makeIntent(2, mTag2)) |
| .setAutoCancel(true) |
| .build(); |
| mNm.notify(mTag2, mId2, n2); |
| mFlag2 = Notification.FLAG_AUTO_CANCEL; |
| |
| Notification n3 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) |
| .setContentTitle("ClearTest 3") |
| .setContentText(mTag3) |
| .setSmallIcon(mIcon3) |
| .setWhen(mWhen3) |
| .setDeleteIntent(makeIntent(3, mTag3)) |
| .setAutoCancel(true) |
| .setOnlyAlertOnce(true) |
| .build(); |
| mNm.notify(mTag3, mId3, n3); |
| mFlag3 = Notification.FLAG_ONLY_ALERT_ONCE | Notification.FLAG_AUTO_CANCEL; |
| } |
| |
| private void sendNoisyNotification() { |
| mTag4 = UUID.randomUUID().toString(); |
| Log.d(TAG, "Sending noisy notif: " + mTag4); |
| |
| mWhen4 = System.currentTimeMillis() + 4; |
| mIcon4 = R.drawable.ic_stat_charlie; |
| mId4 = NOTIFICATION_ID + 4; |
| mPackageString = "com.android.cts.verifier"; |
| |
| Notification n1 = new Notification.Builder(mContext, NOISY_NOTIFICATION_CHANNEL_ID) |
| .setContentTitle("NoisyTest 1") |
| .setContentText(mTag4) |
| .setSmallIcon(mIcon4) |
| .setWhen(mWhen4) |
| .setDeleteIntent(makeIntent(4, mTag4)) |
| .setCategory(Notification.CATEGORY_REMINDER) |
| .build(); |
| mNm.notify(mTag4, mId4, n1); |
| } |
| |
| // Tests |
| private class NotificationReceivedTest extends InteractiveTestCase { |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createAutoItem(parent, R.string.nls_note_received); |
| |
| } |
| |
| @Override |
| protected void setUp() { |
| createChannels(); |
| sendNotifications(); |
| status = READY; |
| } |
| |
| @Override |
| protected void tearDown() { |
| mNm.cancelAll(); |
| MockListener.getInstance().resetData(); |
| deleteChannels(); |
| } |
| |
| @Override |
| protected void test() { |
| if (MockListener.getInstance().getPosted(mTag1) != null) { |
| status = PASS; |
| } else { |
| logFail(); |
| status = FAIL; |
| } |
| } |
| } |
| |
| private class LongMessageTest extends InteractiveTestCase { |
| private ViewGroup mParent; |
| @Override |
| protected View inflate(ViewGroup parent) { |
| mParent = createAutoItem(parent, R.string.nls_anr); |
| return mParent; |
| } |
| |
| @Override |
| protected void setUp() { |
| createChannels(); |
| StringBuilder sb = new StringBuilder(); |
| for (int i = 0; i < 20000; i++) { |
| sb.append("\u2009\u200a" + "\u200E\u200F" + "stuff"); |
| } |
| Notification.Builder builder = new Notification.Builder( |
| mContext, NOTIFICATION_CHANNEL_ID) |
| .setSmallIcon(R.drawable.ic_stat_alice) |
| .setContentTitle("This is an long notification") |
| .setContentText("Innocuous content") |
| .setStyle(new Notification.MessagingStyle("Fake person") |
| .addMessage("hey how is it goin", 0, "Person 1") |
| .addMessage("hey", 0, "Person 1") |
| .addMessage("u there", 0, "Person 1") |
| .addMessage("how you like tHIS", 0, "Person 1") |
| .addMessage(sb.toString(), 0, "Person 1") |
| ); |
| mTag1 = UUID.randomUUID().toString(); |
| mId1 = NOTIFICATION_ID + 1; |
| mPackageString = "com.android.cts.verifier"; |
| mNm.notify(mTag1, mId1, builder.build()); |
| status = READY; |
| } |
| |
| @Override |
| protected void tearDown() { |
| mNm.cancelAll(); |
| MockListener.getInstance().resetData(); |
| deleteChannels(); |
| } |
| |
| @Override |
| protected void test() { |
| StatusBarNotification sbn = MockListener.getInstance().getPosted(mTag1); |
| if (sbn == null) { |
| logFail(); |
| status = FAIL; |
| } else { |
| ViewGroup parent = mParent.findViewById(R.id.feedback); |
| parent.setVisibility(View.VISIBLE); |
| final Notification.Builder recoveredBuilder = Notification.Builder.recoverBuilder( |
| NotificationListenerVerifierActivity.this, |
| sbn.getNotification()); |
| RemoteViews rv = recoveredBuilder.createContentView(); |
| View v = rv.apply(NotificationListenerVerifierActivity.this, parent); |
| parent.addView(v); |
| } |
| if (MockListener.getInstance().getPosted(mTag1) != null) { |
| status = PASS; |
| } else { |
| logFail(); |
| status = FAIL; |
| } |
| } |
| } |
| |
| /** |
| * Creates a notification channel. Sends the user to settings to block the channel. Waits |
| * to receive the broadcast that the channel was blocked, and confirms that the broadcast |
| * contains the correct extras. |
| */ |
| protected class ReceiveChannelBlockNoticeTest extends InteractiveTestCase { |
| private String mChannelId; |
| private int mRetries = 2; |
| private View mView; |
| @Override |
| protected View inflate(ViewGroup parent) { |
| mView = createNlsSettingsItem(parent, R.string.nls_block_channel); |
| Button button = mView.findViewById(R.id.nls_action_button); |
| button.setEnabled(false); |
| return mView; |
| } |
| |
| @Override |
| protected void setUp() { |
| mChannelId = UUID.randomUUID().toString(); |
| NotificationChannel channel = new NotificationChannel( |
| mChannelId, "ReceiveChannelBlockNoticeTest", IMPORTANCE_LOW); |
| mNm.createNotificationChannel(channel); |
| status = READY; |
| Button button = mView.findViewById(R.id.nls_action_button); |
| button.setEnabled(true); |
| } |
| |
| @Override |
| boolean autoStart() { |
| return true; |
| } |
| |
| @Override |
| protected void test() { |
| NotificationChannel channel = mNm.getNotificationChannel(mChannelId); |
| SharedPreferences prefs = mContext.getSharedPreferences( |
| NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); |
| |
| if (channel.getImportance() == IMPORTANCE_NONE) { |
| if (prefs.contains(mChannelId) && prefs.getBoolean(mChannelId, false)) { |
| status = PASS; |
| } else { |
| if (mRetries > 0) { |
| mRetries--; |
| status = RETEST; |
| } else { |
| status = FAIL; |
| } |
| } |
| } else { |
| // user hasn't jumped to settings to block the channel yet |
| status = WAIT_FOR_USER; |
| } |
| |
| next(); |
| } |
| |
| protected void tearDown() { |
| MockListener.getInstance().resetData(); |
| mNm.deleteNotificationChannel(mChannelId); |
| SharedPreferences prefs = mContext.getSharedPreferences( |
| NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); |
| SharedPreferences.Editor editor = prefs.edit(); |
| editor.remove(mChannelId); |
| } |
| |
| @Override |
| protected Intent getIntent() { |
| return new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) |
| .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName()) |
| .putExtra(EXTRA_CHANNEL_ID, mChannelId); |
| } |
| } |
| |
| /** |
| * Creates a notification channel group. Sends the user to settings to block the group. Waits |
| * to receive the broadcast that the group was blocked, and confirms that the broadcast contains |
| * the correct extras. |
| */ |
| protected class ReceiveGroupBlockNoticeTest extends InteractiveTestCase { |
| private String mGroupId; |
| private int mRetries = 2; |
| private View mView; |
| @Override |
| protected View inflate(ViewGroup parent) { |
| mView = createNlsSettingsItem(parent, R.string.nls_block_group); |
| Button button = mView.findViewById(R.id.nls_action_button); |
| button.setEnabled(false); |
| return mView; |
| } |
| |
| @Override |
| protected void setUp() { |
| mGroupId = UUID.randomUUID().toString(); |
| NotificationChannelGroup group |
| = new NotificationChannelGroup(mGroupId, "ReceiveChannelGroupBlockNoticeTest"); |
| mNm.createNotificationChannelGroup(group); |
| NotificationChannel channel = new NotificationChannel( |
| mGroupId, "ReceiveChannelBlockNoticeTest", IMPORTANCE_LOW); |
| channel.setGroup(mGroupId); |
| mNm.createNotificationChannel(channel); |
| status = READY; |
| Button button = mView.findViewById(R.id.nls_action_button); |
| button.setEnabled(true); |
| } |
| |
| @Override |
| boolean autoStart() { |
| return true; |
| } |
| |
| @Override |
| protected void test() { |
| NotificationChannelGroup group = mNm.getNotificationChannelGroup(mGroupId); |
| SharedPreferences prefs = mContext.getSharedPreferences( |
| NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); |
| |
| if (group.isBlocked()) { |
| if (prefs.contains(mGroupId) && prefs.getBoolean(mGroupId, false)) { |
| status = PASS; |
| } else { |
| if (mRetries > 0) { |
| mRetries--; |
| status = RETEST; |
| } else { |
| status = FAIL; |
| } |
| } |
| } else { |
| // user hasn't jumped to settings to block the group yet |
| status = WAIT_FOR_USER; |
| } |
| |
| next(); |
| } |
| |
| protected void tearDown() { |
| MockListener.getInstance().resetData(); |
| mNm.deleteNotificationChannelGroup(mGroupId); |
| SharedPreferences prefs = mContext.getSharedPreferences( |
| NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); |
| SharedPreferences.Editor editor = prefs.edit(); |
| editor.remove(mGroupId); |
| } |
| |
| @Override |
| protected Intent getIntent() { |
| return new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) |
| .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) |
| .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName()); |
| } |
| } |
| |
| /** |
| * Sends the user to settings to block the app. Waits to receive the broadcast that the app was |
| * blocked, and confirms that the broadcast contains the correct extras. |
| */ |
| protected class ReceiveAppBlockNoticeTest extends InteractiveTestCase { |
| private int mRetries = 2; |
| private View mView; |
| @Override |
| protected View inflate(ViewGroup parent) { |
| mView = createNlsSettingsItem(parent, R.string.nls_block_app); |
| Button button = mView.findViewById(R.id.nls_action_button); |
| button.setEnabled(false); |
| return mView; |
| } |
| |
| @Override |
| protected void setUp() { |
| status = READY; |
| Button button = mView.findViewById(R.id.nls_action_button); |
| button.setEnabled(true); |
| } |
| |
| @Override |
| boolean autoStart() { |
| return true; |
| } |
| |
| @Override |
| protected void test() { |
| SharedPreferences prefs = mContext.getSharedPreferences( |
| NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); |
| |
| if (!mNm.areNotificationsEnabled()) { |
| Log.d(TAG, "Got broadcast " + prefs.contains(mContext.getPackageName())); |
| Log.d(TAG, "Broadcast contains correct data? " + |
| prefs.getBoolean(mContext.getPackageName(), false)); |
| if (prefs.contains(mContext.getPackageName()) |
| && prefs.getBoolean(mContext.getPackageName(), false)) { |
| status = PASS; |
| } else { |
| if (mRetries > 0) { |
| mRetries--; |
| status = RETEST; |
| } else { |
| status = FAIL; |
| } |
| } |
| } else { |
| Log.d(TAG, "Notifications still enabled"); |
| // user hasn't jumped to settings to block the app yet |
| status = WAIT_FOR_USER; |
| } |
| |
| next(); |
| } |
| |
| protected void tearDown() { |
| MockListener.getInstance().resetData(); |
| SharedPreferences prefs = mContext.getSharedPreferences( |
| NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); |
| SharedPreferences.Editor editor = prefs.edit(); |
| editor.remove(mContext.getPackageName()); |
| } |
| |
| @Override |
| protected Intent getIntent() { |
| return new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) |
| .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) |
| .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName()); |
| } |
| } |
| |
| /** |
| * Sends the user to settings to unblock the app. Waits to receive the broadcast that the app |
| * was unblocked, and confirms that the broadcast contains the correct extras. |
| */ |
| protected class ReceiveAppUnblockNoticeTest extends InteractiveTestCase { |
| private int mRetries = 2; |
| private View mView; |
| @Override |
| protected View inflate(ViewGroup parent) { |
| mView = createNlsSettingsItem(parent, R.string.nls_unblock_app); |
| Button button = mView.findViewById(R.id.nls_action_button); |
| button.setEnabled(false); |
| return mView; |
| } |
| |
| @Override |
| protected void setUp() { |
| status = READY; |
| Button button = mView.findViewById(R.id.nls_action_button); |
| button.setEnabled(true); |
| } |
| |
| @Override |
| boolean autoStart() { |
| return true; |
| } |
| |
| @Override |
| protected void test() { |
| SharedPreferences prefs = mContext.getSharedPreferences( |
| NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); |
| |
| if (mNm.areNotificationsEnabled()) { |
| if (prefs.contains(mContext.getPackageName()) |
| && !prefs.getBoolean(mContext.getPackageName(), true)) { |
| status = PASS; |
| } else { |
| if (mRetries > 0) { |
| mRetries--; |
| status = RETEST; |
| } else { |
| status = FAIL; |
| } |
| } |
| } else { |
| // user hasn't jumped to settings to block the app yet |
| status = WAIT_FOR_USER; |
| } |
| |
| next(); |
| } |
| |
| protected void tearDown() { |
| MockListener.getInstance().resetData(); |
| SharedPreferences prefs = mContext.getSharedPreferences( |
| NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); |
| SharedPreferences.Editor editor = prefs.edit(); |
| editor.remove(mContext.getPackageName()); |
| } |
| |
| @Override |
| protected Intent getIntent() { |
| return new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) |
| .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) |
| .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName()); |
| } |
| } |
| |
| private class DataIntactTest extends InteractiveTestCase { |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createAutoItem(parent, R.string.nls_payload_intact); |
| } |
| |
| @Override |
| protected void setUp() { |
| createChannels(); |
| sendNotifications(); |
| status = READY; |
| } |
| |
| @Override |
| protected void test() { |
| List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted()); |
| |
| Set<String> found = new HashSet<String>(); |
| if (result.size() == 0) { |
| status = FAIL; |
| return; |
| } |
| boolean pass = true; |
| for (JSONObject payload : result) { |
| try { |
| pass &= checkEquals(mPackageString, |
| payload.getString(JSON_PACKAGE), |
| "data integrity test: notification package (%s, %s)"); |
| String tag = payload.getString(JSON_TAG); |
| if (mTag1.equals(tag)) { |
| found.add(mTag1); |
| pass &= checkEquals(mIcon1, payload.getInt(JSON_ICON), |
| "data integrity test: notification icon (%d, %d)"); |
| pass &= checkFlagSet(mFlag1, payload.getInt(JSON_FLAGS), |
| "data integrity test: notification flags (%d, %d)"); |
| pass &= checkEquals(mId1, payload.getInt(JSON_ID), |
| "data integrity test: notification ID (%d, %d)"); |
| pass &= checkEquals(mWhen1, payload.getLong(JSON_WHEN), |
| "data integrity test: notification when (%d, %d)"); |
| } else if (mTag2.equals(tag)) { |
| found.add(mTag2); |
| pass &= checkEquals(mIcon2, payload.getInt(JSON_ICON), |
| "data integrity test: notification icon (%d, %d)"); |
| pass &= checkFlagSet(mFlag2, payload.getInt(JSON_FLAGS), |
| "data integrity test: notification flags (%d, %d)"); |
| pass &= checkEquals(mId2, payload.getInt(JSON_ID), |
| "data integrity test: notification ID (%d, %d)"); |
| pass &= checkEquals(mWhen2, payload.getLong(JSON_WHEN), |
| "data integrity test: notification when (%d, %d)"); |
| } else if (mTag3.equals(tag)) { |
| found.add(mTag3); |
| pass &= checkEquals(mIcon3, payload.getInt(JSON_ICON), |
| "data integrity test: notification icon (%d, %d)"); |
| pass &= checkFlagSet(mFlag3, payload.getInt(JSON_FLAGS), |
| "data integrity test: notification flags (%d, %d)"); |
| pass &= checkEquals(mId3, payload.getInt(JSON_ID), |
| "data integrity test: notification ID (%d, %d)"); |
| pass &= checkEquals(mWhen3, payload.getLong(JSON_WHEN), |
| "data integrity test: notification when (%d, %d)"); |
| } |
| } catch (JSONException e) { |
| pass = false; |
| Log.e(TAG, "failed to unpack data from mocklistener", e); |
| } |
| } |
| |
| pass &= found.size() >= 3; |
| status = pass ? PASS : FAIL; |
| } |
| |
| @Override |
| protected void tearDown() { |
| mNm.cancelAll(); |
| MockListener.getInstance().resetData(); |
| deleteChannels(); |
| } |
| } |
| |
| private class AudiblyAlertedTest extends InteractiveTestCase { |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createAutoItem(parent, R.string.nls_audibly_alerted); |
| } |
| |
| @Override |
| protected void setUp() { |
| createChannels(); |
| sendNotifications(); |
| sendNoisyNotification(); |
| status = READY; |
| } |
| |
| @Override |
| protected void test() { |
| List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted()); |
| |
| Set<String> found = new HashSet<>(); |
| if (result.size() == 0) { |
| status = FAIL; |
| return; |
| } |
| boolean pass = true; |
| for (JSONObject payload : result) { |
| try { |
| String tag = payload.getString(JSON_TAG); |
| if (mTag4.equals(tag)) { |
| found.add(mTag4); |
| boolean lastAudiblyAlertedSet |
| = payload.getLong(JSON_LAST_AUDIBLY_ALERTED) > -1; |
| if (!lastAudiblyAlertedSet) { |
| logWithStack( |
| "noisy notification test: getLastAudiblyAlertedMillis not set"); |
| } |
| pass &= lastAudiblyAlertedSet; |
| } else if (payload.getString(JSON_PACKAGE).equals(mPackageString)) { |
| found.add(tag); |
| boolean lastAudiblyAlertedSet |
| = payload.getLong(JSON_LAST_AUDIBLY_ALERTED) > 0; |
| if (lastAudiblyAlertedSet) { |
| logWithStack( |
| "noisy notification test: getLastAudiblyAlertedMillis set " |
| + "incorrectly"); |
| } |
| pass &= !lastAudiblyAlertedSet; |
| } |
| } catch (JSONException e) { |
| pass = false; |
| Log.e(TAG, "failed to unpack data from mocklistener", e); |
| } |
| } |
| |
| pass &= found.size() >= 4; |
| status = pass ? PASS : FAIL; |
| } |
| |
| @Override |
| protected void tearDown() { |
| mNm.cancelAll(); |
| MockListener.getInstance().resetData(); |
| deleteChannels(); |
| } |
| } |
| |
| private class DismissOneTest extends InteractiveTestCase { |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createAutoItem(parent, R.string.nls_clear_one); |
| } |
| |
| @Override |
| protected void setUp() { |
| createChannels(); |
| sendNotifications(); |
| status = READY; |
| } |
| |
| @Override |
| protected void test() { |
| if (status == READY) { |
| MockListener.getInstance().cancelNotification( |
| MockListener.getInstance().getKeyForTag(mTag1)); |
| status = RETEST; |
| } else { |
| List<String> result = new ArrayList<>(MockListener.getInstance().mRemoved); |
| if (result.size() != 0 |
| && result.contains(mTag1) |
| && !result.contains(mTag2) |
| && !result.contains(mTag3)) { |
| status = PASS; |
| } else { |
| logFail(); |
| status = FAIL; |
| } |
| } |
| } |
| |
| @Override |
| protected void tearDown() { |
| mNm.cancelAll(); |
| deleteChannels(); |
| MockListener.getInstance().resetData(); |
| } |
| } |
| |
| private class DismissOneWithReasonTest extends InteractiveTestCase { |
| int mRetries = 3; |
| |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createAutoItem(parent, R.string.nls_clear_one_reason); |
| } |
| |
| @Override |
| protected void setUp() { |
| createChannels(); |
| sendNotifications(); |
| status = READY; |
| } |
| |
| @Override |
| protected void test() { |
| if (status == READY) { |
| MockListener.getInstance().cancelNotification( |
| MockListener.getInstance().getKeyForTag(mTag1)); |
| status = RETEST; |
| } else { |
| List<JSONObject> result = |
| new ArrayList<>(MockListener.getInstance().mRemovedReason.values()); |
| boolean pass = false; |
| for (JSONObject payload : result) { |
| try { |
| pass |= (checkEquals(mTag1, |
| payload.getString(JSON_TAG), |
| "data dismissal test: notification tag (%s, %s)") |
| && checkEquals(REASON_LISTENER_CANCEL, |
| payload.getInt(JSON_REASON), |
| "data dismissal test: reason (%d, %d)")); |
| if(pass) { |
| break; |
| } |
| } catch (JSONException e) { |
| e.printStackTrace(); |
| } |
| } |
| if (pass) { |
| status = PASS; |
| } else { |
| if (--mRetries > 0) { |
| sleep(100); |
| status = RETEST; |
| } else { |
| status = FAIL; |
| } |
| } |
| } |
| } |
| |
| @Override |
| protected void tearDown() { |
| mNm.cancelAll(); |
| deleteChannels(); |
| MockListener.getInstance().resetData(); |
| } |
| } |
| |
| private class DismissOneWithStatsTest extends InteractiveTestCase { |
| int mRetries = 3; |
| |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createAutoItem(parent, R.string.nls_clear_one_stats); |
| } |
| |
| @Override |
| protected void setUp() { |
| createChannels(); |
| sendNotifications(); |
| status = READY; |
| } |
| |
| @Override |
| protected void test() { |
| if (status == READY) { |
| MockListener.getInstance().cancelNotification( |
| MockListener.getInstance().getKeyForTag(mTag1)); |
| status = RETEST; |
| } else { |
| List<JSONObject> result = |
| new ArrayList<>(MockListener.getInstance().mRemovedReason.values()); |
| boolean pass = true; |
| for (JSONObject payload : result) { |
| try { |
| pass &= (payload.getBoolean(JSON_STATS) == false); |
| } catch (JSONException e) { |
| e.printStackTrace(); |
| pass = false; |
| } |
| } |
| if (pass) { |
| status = PASS; |
| } else { |
| if (--mRetries > 0) { |
| sleep(100); |
| status = RETEST; |
| } else { |
| logFail("Notification listener got populated stats object."); |
| status = FAIL; |
| } |
| } |
| } |
| } |
| |
| @Override |
| protected void tearDown() { |
| mNm.cancelAll(); |
| deleteChannels(); |
| MockListener.getInstance().resetData(); |
| } |
| } |
| |
| private class DismissAllTest extends InteractiveTestCase { |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createAutoItem(parent, R.string.nls_clear_all); |
| } |
| |
| @Override |
| protected void setUp() { |
| createChannels(); |
| sendNotifications(); |
| status = READY; |
| } |
| |
| @Override |
| protected void test() { |
| if (status == READY) { |
| MockListener.getInstance().cancelAllNotifications(); |
| status = RETEST; |
| } else { |
| List<String> result = new ArrayList<>(MockListener.getInstance().mRemoved); |
| if (result.size() != 0 |
| && result.contains(mTag1) |
| && result.contains(mTag2) |
| && result.contains(mTag3)) { |
| status = PASS; |
| } else { |
| logFail(); |
| status = FAIL; |
| } |
| } |
| } |
| |
| @Override |
| protected void tearDown() { |
| mNm.cancelAll(); |
| deleteChannels(); |
| MockListener.getInstance().resetData(); |
| } |
| } |
| |
| private class IsDisabledTest extends InteractiveTestCase { |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createNlsSettingsItem(parent, R.string.nls_disable_service); |
| } |
| |
| @Override |
| boolean autoStart() { |
| return true; |
| } |
| |
| @Override |
| protected void test() { |
| String listeners = Secure.getString(getContentResolver(), |
| ENABLED_NOTIFICATION_LISTENERS); |
| if (listeners == null || !listeners.contains(LISTENER_PATH)) { |
| status = PASS; |
| } else { |
| status = WAIT_FOR_USER; |
| } |
| } |
| |
| @Override |
| protected void tearDown() { |
| MockListener.getInstance().resetData(); |
| } |
| |
| @Override |
| protected Intent getIntent() { |
| return new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS); |
| } |
| } |
| |
| private class ServiceStoppedTest extends InteractiveTestCase { |
| int mRetries = 3; |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createAutoItem(parent, R.string.nls_service_stopped); |
| } |
| |
| @Override |
| protected void test() { |
| if (mNm.getEffectsSuppressor() == null && (MockListener.getInstance() == null |
| || !MockListener.getInstance().isConnected)) { |
| status = PASS; |
| } else { |
| if (--mRetries > 0) { |
| sleep(100); |
| status = RETEST; |
| } else { |
| status = FAIL; |
| } |
| } |
| } |
| |
| @Override |
| protected Intent getIntent() { |
| return new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS); |
| } |
| } |
| |
| private class NotificationNotReceivedTest extends InteractiveTestCase { |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createAutoItem(parent, R.string.nls_note_missed); |
| |
| } |
| |
| @Override |
| protected void setUp() { |
| createChannels(); |
| sendNotifications(); |
| status = READY; |
| } |
| |
| @Override |
| protected void test() { |
| if (MockListener.getInstance() == null) { |
| status = PASS; |
| } else { |
| if (MockListener.getInstance().mPosted.size() == 0) { |
| status = PASS; |
| } else { |
| logFail(); |
| status = FAIL; |
| } |
| } |
| next(); |
| } |
| |
| @Override |
| protected void tearDown() { |
| mNm.cancelAll(); |
| deleteChannels(); |
| if (MockListener.getInstance() != null) { |
| MockListener.getInstance().resetData(); |
| } |
| } |
| } |
| |
| private class RequestUnbindTest extends InteractiveTestCase { |
| int mRetries = 5; |
| |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createAutoItem(parent, R.string.nls_snooze); |
| |
| } |
| |
| @Override |
| protected void setUp() { |
| status = READY; |
| MockListener.getInstance().requestListenerHints( |
| MockListener.HINT_HOST_DISABLE_CALL_EFFECTS); |
| } |
| |
| @Override |
| protected void test() { |
| if (status == READY) { |
| MockListener.getInstance().requestUnbind(); |
| status = RETEST; |
| } else { |
| if (mNm.getEffectsSuppressor() == null && !MockListener.getInstance().isConnected) { |
| status = PASS; |
| } else { |
| if (--mRetries > 0) { |
| status = RETEST; |
| } else { |
| logFail(); |
| status = FAIL; |
| } |
| } |
| next(); |
| } |
| } |
| } |
| |
| private class RequestBindTest extends InteractiveTestCase { |
| int mRetries = 5; |
| |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createAutoItem(parent, R.string.nls_unsnooze); |
| |
| } |
| |
| @Override |
| protected void test() { |
| if (status == READY) { |
| MockListener.requestRebind(MockListener.COMPONENT_NAME); |
| status = RETEST; |
| } else { |
| if (MockListener.getInstance().isConnected) { |
| status = PASS; |
| next(); |
| } else { |
| if (--mRetries > 0) { |
| status = RETEST; |
| next(); |
| } else { |
| logFail(); |
| status = FAIL; |
| } |
| } |
| } |
| } |
| } |
| |
| private class EnableHintsTest extends InteractiveTestCase { |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createAutoItem(parent, R.string.nls_hints); |
| |
| } |
| |
| @Override |
| protected void test() { |
| if (status == READY) { |
| MockListener.getInstance().requestListenerHints( |
| MockListener.HINT_HOST_DISABLE_CALL_EFFECTS); |
| status = RETEST; |
| } else { |
| int result = MockListener.getInstance().getCurrentListenerHints(); |
| if ((result & MockListener.HINT_HOST_DISABLE_CALL_EFFECTS) |
| == MockListener.HINT_HOST_DISABLE_CALL_EFFECTS) { |
| status = PASS; |
| next(); |
| } else { |
| logFail(); |
| status = FAIL; |
| } |
| } |
| } |
| } |
| |
| private class SnoozeNotificationForTimeTest extends InteractiveTestCase { |
| final static int READY_TO_SNOOZE = 0; |
| final static int SNOOZED = 1; |
| final static int READY_TO_CHECK_FOR_UNSNOOZE = 2; |
| int state = -1; |
| long snoozeTime = 3000; |
| |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createAutoItem(parent, R.string.nls_snooze_one_time); |
| } |
| |
| @Override |
| protected void setUp() { |
| createChannels(); |
| sendNotifications(); |
| status = READY; |
| state = READY_TO_SNOOZE; |
| delay(); |
| } |
| |
| @Override |
| protected void test() { |
| status = RETEST; |
| if (state == READY_TO_SNOOZE) { |
| MockListener.getInstance().snoozeNotification( |
| MockListener.getInstance().getKeyForTag(mTag1), snoozeTime); |
| state = SNOOZED; |
| } else if (state == SNOOZED) { |
| List<JSONObject> result = |
| new ArrayList<>(MockListener.getInstance().mRemovedReason.values()); |
| boolean pass = false; |
| for (JSONObject payload : result) { |
| try { |
| pass |= (checkEquals(mTag1, |
| payload.getString(JSON_TAG), |
| "data dismissal test: notification tag (%s, %s)") |
| && checkEquals(MockListener.REASON_SNOOZED, |
| payload.getInt(JSON_REASON), |
| "data dismissal test: reason (%d, %d)")); |
| if (pass) { |
| break; |
| } |
| } catch (JSONException e) { |
| e.printStackTrace(); |
| } |
| } |
| if (!pass) { |
| logFail(); |
| status = FAIL; |
| next(); |
| return; |
| } else { |
| state = READY_TO_CHECK_FOR_UNSNOOZE; |
| } |
| } else { |
| if (MockListener.getInstance().getPosted(mTag1) != null) { |
| status = PASS; |
| } else { |
| logFail(); |
| status = FAIL; |
| } |
| } |
| } |
| |
| @Override |
| protected void tearDown() { |
| mNm.cancelAll(); |
| deleteChannels(); |
| MockListener.getInstance().resetData(); |
| delay(); |
| } |
| } |
| |
| /** |
| * Posts notifications, snoozes one of them. Verifies that it is snoozed. Cancels all |
| * notifications and reposts them. Confirms that the original notification is still snoozed. |
| */ |
| private class SnoozeNotificationForTimeCancelTest extends InteractiveTestCase { |
| |
| final static int READY_TO_SNOOZE = 0; |
| final static int SNOOZED = 1; |
| final static int READY_TO_CHECK_FOR_SNOOZE = 2; |
| int state = -1; |
| long snoozeTime = 10000; |
| private String tag; |
| |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createAutoItem(parent, R.string.nls_snooze_one_time); |
| } |
| |
| @Override |
| protected void setUp() { |
| createChannels(); |
| sendNotifications(); |
| tag = mTag1; |
| status = READY; |
| state = READY_TO_SNOOZE; |
| delay(); |
| } |
| |
| @Override |
| protected void test() { |
| status = RETEST; |
| if (state == READY_TO_SNOOZE) { |
| MockListener.getInstance().snoozeNotification( |
| MockListener.getInstance().getKeyForTag(tag), snoozeTime); |
| state = SNOOZED; |
| } else if (state == SNOOZED) { |
| List<String> result = getSnoozed(); |
| if (result.size() >= 1 |
| && result.contains(tag)) { |
| // cancel and repost |
| sendNotifications(); |
| state = READY_TO_CHECK_FOR_SNOOZE; |
| } else { |
| logFail(); |
| status = FAIL; |
| } |
| } else { |
| List<String> result = getSnoozed(); |
| if (result.size() >= 1 |
| && result.contains(tag)) { |
| status = PASS; |
| } else { |
| logFail(); |
| status = FAIL; |
| } |
| } |
| } |
| |
| private List<String> getSnoozed() { |
| List<String> result = new ArrayList<>(); |
| StatusBarNotification[] snoozed = MockListener.getInstance().getSnoozedNotifications(); |
| for (StatusBarNotification sbn : snoozed) { |
| result.add(sbn.getTag()); |
| } |
| return result; |
| } |
| |
| @Override |
| protected void tearDown() { |
| mNm.cancelAll(); |
| deleteChannels(); |
| MockListener.getInstance().resetData(); |
| } |
| } |
| |
| private class GetSnoozedNotificationTest extends InteractiveTestCase { |
| final static int READY_TO_SNOOZE = 0; |
| final static int SNOOZED = 1; |
| final static int READY_TO_CHECK_FOR_GET_SNOOZE = 2; |
| int state = -1; |
| long snoozeTime = 30000; |
| |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createAutoItem(parent, R.string.nls_get_snoozed); |
| } |
| |
| @Override |
| protected void setUp() { |
| createChannels(); |
| sendNotifications(); |
| status = READY; |
| state = READY_TO_SNOOZE; |
| } |
| |
| @Override |
| protected void test() { |
| status = RETEST; |
| if (state == READY_TO_SNOOZE) { |
| MockListener.getInstance().snoozeNotification( |
| MockListener.getInstance().getKeyForTag(mTag1), snoozeTime); |
| MockListener.getInstance().snoozeNotification( |
| MockListener.getInstance().getKeyForTag(mTag2), snoozeTime); |
| state = SNOOZED; |
| } else if (state == SNOOZED) { |
| List<JSONObject> result = |
| new ArrayList<>(MockListener.getInstance().mRemovedReason.values()); |
| if (result.size() == 0) { |
| status = FAIL; |
| return; |
| } |
| boolean pass = false; |
| for (JSONObject payload : result) { |
| try { |
| pass |= (checkEquals(mTag1, |
| payload.getString(JSON_TAG), |
| "data dismissal test: notification tag (%s, %s)") |
| && checkEquals(MockListener.REASON_SNOOZED, |
| payload.getInt(JSON_REASON), |
| "data dismissal test: reason (%d, %d)")); |
| if (pass) { |
| break; |
| } |
| } catch (JSONException e) { |
| e.printStackTrace(); |
| } |
| } |
| if (!pass) { |
| logFail(); |
| status = FAIL; |
| } else { |
| state = READY_TO_CHECK_FOR_GET_SNOOZE; |
| } |
| } else { |
| List<String> result = new ArrayList<>(); |
| StatusBarNotification[] snoozed = |
| MockListener.getInstance().getSnoozedNotifications(); |
| for (StatusBarNotification sbn : snoozed) { |
| result.add(sbn.getTag()); |
| } |
| if (result.size() >= 2 |
| && result.contains(mTag1) |
| && result.contains(mTag2)) { |
| status = PASS; |
| } else { |
| logFail(); |
| status = FAIL; |
| } |
| } |
| } |
| |
| @Override |
| protected void tearDown() { |
| mNm.cancelAll(); |
| deleteChannels(); |
| MockListener.getInstance().resetData(); |
| delay(); |
| } |
| } |
| |
| /** Tests that the extras {@link Bundle} in a MessagingStyle#Message is preserved. */ |
| private class MessageBundleTest extends InteractiveTestCase { |
| private final String extrasKey1 = "extras_key_1"; |
| private final CharSequence extrasValue1 = "extras_value_1"; |
| private final String extrasKey2 = "extras_key_2"; |
| private final CharSequence extrasValue2 = "extras_value_2"; |
| |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createAutoItem(parent, R.string.msg_extras_preserved); |
| } |
| |
| @Override |
| protected void setUp() { |
| createChannels(); |
| sendMessagingNotification(); |
| status = READY; |
| } |
| |
| @Override |
| protected void tearDown() { |
| mNm.cancelAll(); |
| deleteChannels(); |
| delay(); |
| } |
| |
| private void sendMessagingNotification() { |
| mTag1 = UUID.randomUUID().toString(); |
| mNm.cancelAll(); |
| mWhen1 = System.currentTimeMillis() + 1; |
| mIcon1 = R.drawable.ic_stat_alice; |
| mId1 = NOTIFICATION_ID + 1; |
| |
| Notification.MessagingStyle.Message msg1 = |
| new Notification.MessagingStyle.Message("text1", 0 /* timestamp */, "sender1"); |
| msg1.getExtras().putCharSequence(extrasKey1, extrasValue1); |
| |
| Notification.MessagingStyle.Message msg2 = |
| new Notification.MessagingStyle.Message("text2", 1 /* timestamp */, "sender2"); |
| msg2.getExtras().putCharSequence(extrasKey2, extrasValue2); |
| |
| Notification.MessagingStyle style = new Notification.MessagingStyle("display_name"); |
| style.addMessage(msg1); |
| style.addMessage(msg2); |
| |
| Notification n1 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) |
| .setContentTitle("ClearTest 1") |
| .setContentText(mTag1.toString()) |
| .setPriority(Notification.PRIORITY_LOW) |
| .setSmallIcon(mIcon1) |
| .setWhen(mWhen1) |
| .setDeleteIntent(makeIntent(1, mTag1)) |
| .setOnlyAlertOnce(true) |
| .setStyle(style) |
| .build(); |
| mNm.notify(mTag1, mId1, n1); |
| mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE; |
| } |
| |
| // Returns true on success. |
| private boolean verifyMessage( |
| NotificationCompat.MessagingStyle.Message message, |
| String extrasKey, |
| CharSequence extrasValue) { |
| return message.getExtras() != null |
| && message.getExtras().getCharSequence(extrasKey) != null |
| && message.getExtras().getCharSequence(extrasKey).equals(extrasValue); |
| } |
| |
| @Override |
| protected void test() { |
| List<Notification> result = |
| new ArrayList<>(MockListener.getInstance().mPostedNotifications); |
| if (result.size() != 1 || result.get(0) == null) { |
| logFail(); |
| status = FAIL; |
| next(); |
| return; |
| } |
| // Can only read in MessagingStyle using the compat class. |
| NotificationCompat.MessagingStyle readStyle = |
| NotificationCompat.MessagingStyle |
| .extractMessagingStyleFromNotification( |
| result.get(0)); |
| if (readStyle == null || readStyle.getMessages().size() != 2) { |
| status = FAIL; |
| logFail(); |
| next(); |
| return; |
| } |
| |
| if (!verifyMessage(readStyle.getMessages().get(0), extrasKey1, |
| extrasValue1) |
| || !verifyMessage( |
| readStyle.getMessages().get(1), extrasKey2, extrasValue2)) { |
| status = FAIL; |
| logFail(); |
| next(); |
| return; |
| } |
| |
| status = PASS; |
| } |
| } |
| |
| /** |
| * Tests that conversation notifications appear at the top of the shade, if the device supports |
| * a separate conversation section |
| */ |
| private class ConversationOrderingTest extends InteractiveTestCase { |
| private static final String SHARE_SHORTCUT_ID = "shareShortcut"; |
| private static final String SHORTCUT_CATEGORY = |
| "com.android.cts.verifier.notifications.SHORTCUT_CATEGORY"; |
| |
| @Override |
| protected void setUp() { |
| createChannels(); |
| createDynamicShortcut(); |
| sendNotifications(); |
| status = READY; |
| } |
| |
| @Override |
| protected void tearDown() { |
| mNm.cancelAll(); |
| deleteChannels(); |
| delay(); |
| } |
| |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createPassFailItem(parent, R.string.conversation_section_ordering); |
| } |
| |
| private void createDynamicShortcut() { |
| Person person = new Person.Builder() |
| .setBot(false) |
| .setIcon(Icon.createWithResource(mContext, R.drawable.ic_stat_alice)) |
| .setName("Person A") |
| .setImportant(true) |
| .build(); |
| |
| Set<String> categorySet = new ArraySet<>(); |
| categorySet.add(SHORTCUT_CATEGORY); |
| Intent shortcutIntent = |
| new Intent(mContext, BubbleActivity.class); |
| shortcutIntent.setAction(Intent.ACTION_VIEW); |
| |
| ShortcutInfo shortcut = new ShortcutInfo.Builder(mContext, SHARE_SHORTCUT_ID) |
| .setShortLabel(SHARE_SHORTCUT_ID) |
| .setIcon(Icon.createWithResource(mContext, R.drawable.ic_stat_alice)) |
| .setIntent(shortcutIntent) |
| .setPerson(person) |
| .setCategories(categorySet) |
| .setLongLived(true) |
| .build(); |
| |
| ShortcutManager scManager = |
| (ShortcutManager) mContext.getSystemService(Context.SHORTCUT_SERVICE); |
| scManager.addDynamicShortcuts(Arrays.asList(shortcut)); |
| } |
| |
| private void sendNotifications() { |
| mTag1 = UUID.randomUUID().toString(); |
| mId1 = NOTIFICATION_ID + 1; |
| |
| Person person = new Person.Builder() |
| .setName("Person A") |
| .build(); |
| |
| Notification n1 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) |
| .setContentTitle("ConversationOrderingTest") |
| .setContentText(mTag1) |
| .setSmallIcon(R.drawable.ic_stat_alice) |
| .setShortcutId(SHARE_SHORTCUT_ID) |
| .setStyle(new Notification.MessagingStyle(person) |
| .setConversationTitle("Bubble Chat") |
| .addMessage("Hello?", |
| SystemClock.currentThreadTimeMillis() - 300000, person) |
| .addMessage("Is it me you're looking for?", |
| SystemClock.currentThreadTimeMillis(), person) |
| ) |
| .build(); |
| mNm.notify(mTag1, mId1, n1); |
| |
| mTag2 = UUID.randomUUID().toString(); |
| mId2 = mId1 + 1; |
| Notification n2 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) |
| .setContentTitle("Non-Person Notification") |
| .setContentText(mTag1) |
| .setSmallIcon(R.drawable.ic_stat_alice) |
| .build(); |
| mNm.notify(mTag2, mId2, n2); |
| } |
| |
| @Override |
| boolean autoStart() { |
| return true; |
| } |
| |
| @Override |
| protected void test() { |
| status = WAIT_FOR_USER; |
| next(); |
| } |
| } |
| |
| /** |
| * Tests that heads-up notifications appear with the view, resources, and actions provided |
| * in Notification.Builder. |
| */ |
| private class HunDisplayTest extends InteractiveTestCase { |
| |
| @Override |
| protected void setUp() { |
| createChannels(); |
| sendNotifications(); |
| status = READY; |
| } |
| |
| @Override |
| protected void tearDown() { |
| mNm.cancelAll(); |
| deleteChannels(); |
| delay(); |
| } |
| |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createPassFailItem(parent, R.string.hun_display); |
| } |
| |
| private void sendNotifications() { |
| mTag1 = UUID.randomUUID().toString(); |
| mId1 = NOTIFICATION_ID + 1; |
| |
| Notification n1 = new Notification.Builder(mContext, NOISY_NOTIFICATION_CHANNEL_ID) |
| .setContentTitle("HunDisplayTest") |
| .setContentText(mTag1) |
| .setSmallIcon(R.drawable.ic_stat_alice) |
| .setLargeIcon(Icon.createWithResource(mContext, R.drawable.test_pass_gradient)) |
| .addAction(generateAction(1)) |
| .addAction(generateAction(2)) |
| .build(); |
| mNm.notify(mTag1, mId1, n1); |
| } |
| |
| private Notification.Action generateAction(int num) { |
| PendingIntent pi = PendingIntent.getActivity(mContext, num, |
| new Intent(Settings.ACTION_ALL_APPS_NOTIFICATION_SETTINGS), |
| PendingIntent.FLAG_IMMUTABLE); |
| return new Notification.Action.Builder( |
| Icon.createWithResource(mContext, R.drawable.ic_android), |
| mContext.getString(R.string.action, num), pi) |
| .build(); |
| } |
| |
| @Override |
| boolean autoStart() { |
| return true; |
| } |
| |
| @Override |
| protected void test() { |
| status = WAIT_FOR_USER; |
| next(); |
| } |
| } |
| |
| /** |
| * Sends the user to settings filter out silent notifications for this notification listener. |
| * Sends silent and not silent notifs and makes sure only the non silent is received |
| */ |
| private class NotificationTypeFilterTest extends InteractiveTestCase { |
| int mRetries = 3; |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createAutoItem(parent, R.string.nls_filter_test); |
| |
| } |
| |
| @Override |
| protected void setUp() { |
| createChannels(); |
| sendNotifications(); |
| sendNoisyNotification(); |
| status = READY; |
| } |
| |
| @Override |
| protected void tearDown() { |
| mNm.cancelAll(); |
| MockListener.getInstance().resetData(); |
| deleteChannels(); |
| } |
| |
| @Override |
| protected void test() { |
| if (MockListener.getInstance().getPosted(mTag4) == null) { |
| Log.d(TAG, "Could not find " + mTag4); |
| if (--mRetries > 0) { |
| sleep(100); |
| status = RETEST; |
| } else { |
| status = FAIL; |
| } |
| } else if (MockListener.getInstance().getPosted(mTag2) != null) { |
| logFail("Found" + mTag2); |
| status = FAIL; |
| } else { |
| status = PASS; |
| } |
| } |
| } |
| |
| protected class SendUserToChangeFilter extends InteractiveTestCase { |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createUserItem( |
| parent, R.string.cp_start_settings, R.string.nls_change_type_filter); |
| } |
| |
| @Override |
| protected void setUp() { |
| // note: it's expected that the '0' type will be ignored since we've specified a |
| // type in the manifest |
| ArrayList<String> pkgs = new ArrayList<>(); |
| pkgs.add("com.android.settings"); |
| MockListener.getInstance().migrateNotificationFilter(0, pkgs); |
| status = READY; |
| } |
| |
| @Override |
| boolean autoStart() { |
| return true; |
| } |
| |
| @Override |
| protected void test() { |
| if (getIntent().resolveActivity(mPackageManager) == null) { |
| logFail("no settings activity"); |
| status = FAIL; |
| } else { |
| if (buttonPressed) { |
| status = PASS; |
| } else { |
| status = RETEST_AFTER_LONG_DELAY; |
| } |
| next(); |
| } |
| } |
| |
| protected void tearDown() { |
| // wait for the service to start |
| delay(); |
| } |
| |
| @Override |
| protected Intent getIntent() { |
| Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS); |
| intent.putExtra(Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME, |
| MockListener.COMPONENT_NAME.flattenToString()); |
| return intent; |
| } |
| } |
| |
| protected class ResetChangeFilter extends SendUserToChangeFilter { |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createUserItem( |
| parent, R.string.cp_start_settings, R.string.nls_reset_type_filter); |
| } |
| } |
| |
| protected class AskIfFilterChanged extends InteractiveTestCase { |
| @Override |
| protected View inflate(ViewGroup parent) { |
| return createPassFailItem(parent, R.string.nls_original_filter_verification); |
| } |
| |
| @Override |
| boolean autoStart() { |
| return true; |
| } |
| |
| @Override |
| protected void test() { |
| status = WAIT_FOR_USER; |
| next(); |
| } |
| } |
| } |