| /* |
| * 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.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.ActivityManager; |
| import android.app.Notification; |
| import android.app.NotificationChannel; |
| import android.app.NotificationChannelGroup; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.SharedPreferences; |
| import android.content.pm.PackageManager; |
| import android.os.Bundle; |
| import android.provider.Settings; |
| import android.provider.Settings.Secure; |
| import android.service.notification.StatusBarNotification; |
| import android.util.Log; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.Button; |
| |
| 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.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.UUID; |
| |
| public class NotificationListenerVerifierActivity extends InteractiveVerifierActivity |
| implements Runnable { |
| private 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() { |
| List<InteractiveTestCase> tests = new ArrayList<>(17); |
| ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); |
| if (am.isLowRamDevice()) { |
| tests.add(new CannotBeEnabledTest()); |
| tests.add(new ServiceStoppedTest()); |
| tests.add(new NotificationNotReceivedTest()); |
| } else { |
| tests.add(new IsEnabledTest()); |
| tests.add(new ServiceStartedTest()); |
| tests.add(new NotificationReceivedTest()); |
| 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 (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { |
| tests.add(new ReceiveChannelBlockNoticeTest()); |
| tests.add(new ReceiveGroupBlockNoticeTest()); |
| } |
| tests.add(new RequestUnbindTest()); |
| tests.add(new RequestBindTest()); |
| tests.add(new MessageBundleTest()); |
| 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 " + mTag1); |
| mTag2 = UUID.randomUUID().toString(); |
| Log.d(TAG, "Sending " + mTag2); |
| mTag3 = UUID.randomUUID().toString(); |
| Log.d(TAG, "Sending " + 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 " + 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() { |
| List<String> result = new ArrayList<>(MockListener.getInstance().mPosted); |
| if (result.size() > 0 && result.contains(mTag1)) { |
| 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) > -1; |
| 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 { |
| List<String> result = new ArrayList<>(MockListener.getInstance().mPosted); |
| if (result.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 { |
| List<String> result = new ArrayList<>(MockListener.getInstance().mPosted); |
| if (result.size() > 0 && result.contains(mTag1)) { |
| 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; |
| } |
| } |
| } |