| /* |
| * Copyright (C) 2021 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.Notification.VISIBILITY_PUBLIC; |
| import static android.app.Notification.VISIBILITY_SECRET; |
| import static android.app.NotificationManager.IMPORTANCE_DEFAULT; |
| import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE; |
| import static android.provider.Settings.EXTRA_APP_PACKAGE; |
| import static android.provider.Settings.EXTRA_CHANNEL_ID; |
| |
| import android.annotation.SuppressLint; |
| import android.app.KeyguardManager; |
| import android.app.Notification; |
| import android.app.NotificationChannel; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| import android.os.Bundle; |
| import android.provider.Settings; |
| import android.util.Log; |
| import android.view.View; |
| import android.view.ViewGroup; |
| |
| import androidx.annotation.StringRes; |
| |
| import com.android.cts.verifier.R; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.UUID; |
| |
| /** |
| * A verifier test which validates the lockscreen behaviors of notifications under various settings |
| */ |
| public class NotificationPrivacyVerifierActivity extends InteractiveVerifierActivity |
| implements Runnable { |
| static final String TAG = "NotifPrivacyVerifier"; |
| private static final String NOTIFICATION_CHANNEL_ID = TAG; |
| |
| @Override |
| protected void onCreate(Bundle savedState) { |
| super.onCreate(savedState); |
| } |
| |
| @Override |
| protected int getTitleResource() { |
| return R.string.notif_privacy_test; |
| } |
| |
| @Override |
| protected int getInstructionsResource() { |
| return R.string.notif_privacy_info; |
| } |
| |
| private int getChannelVisibility() { |
| NotificationChannel channel = mNm.getNotificationChannel(NOTIFICATION_CHANNEL_ID); |
| int visibility = channel.getLockscreenVisibility(); |
| if (visibility == VISIBILITY_NO_OVERRIDE) { |
| visibility = getGlobalVisibility(); |
| } |
| if (visibility != VISIBILITY_SECRET |
| && visibility != VISIBILITY_PRIVATE |
| && visibility != VISIBILITY_PUBLIC) { |
| throw new RuntimeException("Unexpected visibility: " + visibility); |
| } |
| return visibility; |
| } |
| |
| private int getGlobalVisibility() { |
| if (!getLockscreenNotificationsEnabled()) { |
| return VISIBILITY_SECRET; |
| } else if (!getLockscreenAllowPrivateNotifications()) { |
| return VISIBILITY_PRIVATE; |
| } |
| return VISIBILITY_PUBLIC; |
| } |
| |
| private boolean getLockscreenNotificationsEnabled() { |
| return Settings.Secure.getInt(mContext.getContentResolver(), |
| Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0) != 0; |
| } |
| |
| private boolean getLockscreenAllowPrivateNotifications() { |
| return Settings.Secure.getInt(mContext.getContentResolver(), |
| Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0) != 0; |
| } |
| |
| |
| // Test Setup |
| |
| @Override |
| protected List<InteractiveTestCase> createTestItems() { |
| boolean isAutomotive = getPackageManager().hasSystemFeature( |
| PackageManager.FEATURE_AUTOMOTIVE); |
| List<InteractiveTestCase> tests = new ArrayList<>(); |
| if (!isAutomotive) { |
| // FIRST: set redaction settings |
| tests.add(new SetScreenLockEnabledStep()); |
| tests.add(new SetGlobalVisibilityPublicStep()); |
| tests.add(new SetChannelLockscreenVisibilityPrivateStep()); |
| // NOW TESTING: redacted by channel |
| tests.add(new NotificationWhenLockedShowsRedactedTest()); |
| // TODO: Test: notification CAN be dismissed on lockscreen |
| tests.add(new NotificationWhenOccludedShowsRedactedTest()); |
| |
| tests.add(new SetChannelLockscreenVisibilityPublicStep()); |
| // NOW TESTING: not redacted at all |
| tests.add(new SecureActionOnLockScreenTest()); |
| tests.add(new NotificationWhenLockedShowsPrivateTest()); |
| // TODO: Test: notification can NOT be dismissed on lockscreen |
| tests.add(new NotificationWhenOccludedShowsPrivateTest()); |
| |
| tests.add(new SetGlobalVisibilityPrivateStep()); |
| // NOW TESTING: redacted globally |
| tests.add(new NotificationWhenLockedShowsRedactedTest()); |
| // TODO: Test: notification CAN be dismissed on lockscreen |
| tests.add(new NotificationWhenOccludedShowsRedactedTest()); |
| |
| tests.add(new SetGlobalVisibilitySecretStep()); |
| // NOW TESTING: notifications do not appear |
| tests.add(new NotificationWhenLockedIsHiddenTest()); |
| tests.add(new NotificationWhenOccludedIsHiddenTest()); |
| |
| // FINALLY: restore device state |
| tests.add(new SetScreenLockDisabledStep()); |
| } |
| return tests; |
| } |
| |
| private void createChannels() { |
| NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, |
| NOTIFICATION_CHANNEL_ID, IMPORTANCE_DEFAULT); |
| mNm.createNotificationChannel(channel); |
| } |
| |
| private void deleteChannels() { |
| mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID); |
| } |
| |
| @SuppressLint("NewApi") |
| private void sendNotification() { |
| String tag = UUID.randomUUID().toString(); |
| long when = System.currentTimeMillis(); |
| Log.d(TAG, "Sending: tag=" + tag + " when=" + when); |
| |
| mPackageString = "com.android.cts.verifier"; |
| |
| Notification publicVersion = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) |
| .setContentTitle(getString(R.string.np_public_version_text)) |
| .setSmallIcon(R.drawable.ic_stat_bob) |
| .setWhen(when) |
| .build(); |
| Notification privateVersion = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) |
| .setContentTitle(getString(R.string.np_private_version_text)) |
| .setSmallIcon(R.drawable.ic_stat_alice) |
| .setWhen(when) |
| .setPublicVersion(publicVersion) |
| .build(); |
| mNm.notify(tag, NOTIFICATION_ID, privateVersion); |
| } |
| |
| /** |
| * Asks the user to set the lockscreen visibility of the channel to the given value |
| */ |
| private abstract class SetChannelLockscreenVisibilityBaseStep extends InteractiveTestCase { |
| @StringRes |
| private final int mInstructionRes; |
| private final int mExpectVisibility; |
| private View mView; |
| |
| SetChannelLockscreenVisibilityBaseStep(@StringRes int instructionRes, |
| int expectVisibility) { |
| mInstructionRes = instructionRes; |
| mExpectVisibility = expectVisibility; |
| } |
| |
| @Override |
| protected View inflate(ViewGroup parent) { |
| mView = createUserItem(parent, R.string.np_start_channel_settings, mInstructionRes); |
| setButtonsEnabled(mView, false); |
| return mView; |
| } |
| |
| @Override |
| protected void setUp() { |
| createChannels(); |
| status = READY; |
| setButtonsEnabled(mView, true); |
| next(); |
| } |
| |
| @Override |
| boolean autoStart() { |
| return true; |
| } |
| |
| @Override |
| protected void test() { |
| if (getChannelVisibility() == mExpectVisibility) { |
| status = PASS; |
| } else { |
| // user hasn't jumped to settings yet |
| status = WAIT_FOR_USER; |
| } |
| |
| next(); |
| } |
| |
| protected void tearDown() { |
| deleteChannels(); |
| next(); |
| } |
| |
| @Override |
| protected Intent getIntent() { |
| return new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) |
| .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName()) |
| .putExtra(EXTRA_CHANNEL_ID, NOTIFICATION_CHANNEL_ID); |
| } |
| } |
| |
| private class SetChannelLockscreenVisibilityPrivateStep extends |
| SetChannelLockscreenVisibilityBaseStep { |
| SetChannelLockscreenVisibilityPrivateStep() { |
| super(R.string.nls_visibility, VISIBILITY_PRIVATE); |
| } |
| } |
| |
| private class SetChannelLockscreenVisibilityPublicStep extends |
| SetChannelLockscreenVisibilityBaseStep { |
| SetChannelLockscreenVisibilityPublicStep() { |
| super(R.string.nls_restore_visibility, VISIBILITY_PUBLIC); |
| } |
| } |
| |
| private abstract class SetScreenLockBaseStep extends InteractiveTestCase { |
| @StringRes |
| private final int mInstructionRes; |
| private final boolean mExpectSecure; |
| private View mView; |
| |
| private SetScreenLockBaseStep(int instructionRes, boolean expectSecure) { |
| mInstructionRes = instructionRes; |
| mExpectSecure = expectSecure; |
| } |
| |
| @Override |
| protected View inflate(ViewGroup parent) { |
| mView = createUserItem(parent, R.string.np_start_security_settings, mInstructionRes); |
| setButtonsEnabled(mView, false); |
| return mView; |
| } |
| |
| @Override |
| protected void setUp() { |
| status = READY; |
| setButtonsEnabled(mView, true); |
| next(); |
| } |
| |
| @Override |
| boolean autoStart() { |
| return true; |
| } |
| |
| @Override |
| protected void test() { |
| KeyguardManager km = getSystemService(KeyguardManager.class); |
| if (km.isDeviceSecure() == mExpectSecure) { |
| status = PASS; |
| } else { |
| status = WAIT_FOR_USER; |
| } |
| |
| next(); |
| } |
| |
| @Override |
| protected Intent getIntent() { |
| return new Intent(Settings.ACTION_SECURITY_SETTINGS) |
| .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); |
| } |
| } |
| |
| private class SetScreenLockEnabledStep extends SetScreenLockBaseStep { |
| private SetScreenLockEnabledStep() { |
| super(R.string.add_screen_lock, true /* secure */); |
| } |
| } |
| |
| private class SetScreenLockDisabledStep extends SetScreenLockBaseStep { |
| private SetScreenLockDisabledStep() { |
| super(R.string.remove_screen_lock, false /* secure */); |
| } |
| } |
| |
| private abstract class SetGlobalVisibilityBaseStep extends InteractiveTestCase { |
| @StringRes |
| private final int mInstructionRes; |
| private final int mExpectVisibility; |
| private View mView; |
| |
| private SetGlobalVisibilityBaseStep(int instructionRes, int expectVisibility) { |
| mInstructionRes = instructionRes; |
| mExpectVisibility = expectVisibility; |
| } |
| |
| @Override |
| protected View inflate(ViewGroup parent) { |
| mView = createUserItem(parent, R.string.np_start_notif_settings, mInstructionRes); |
| setButtonsEnabled(mView, false); |
| return mView; |
| } |
| |
| @Override |
| protected void setUp() { |
| status = READY; |
| setButtonsEnabled(mView, true); |
| next(); |
| } |
| |
| @Override |
| boolean autoStart() { |
| return true; |
| } |
| |
| @Override |
| protected void test() { |
| KeyguardManager km = getSystemService(KeyguardManager.class); |
| if (!km.isDeviceSecure()) { |
| // if lockscreen itself not set, this setting won't be available. |
| status = FAIL; |
| } else if (getGlobalVisibility() == mExpectVisibility) { |
| status = PASS; |
| } else { |
| status = WAIT_FOR_USER; |
| } |
| |
| next(); |
| } |
| |
| @Override |
| protected Intent getIntent() { |
| return new Intent(Settings.ACTION_NOTIFICATION_SETTINGS) |
| .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); |
| } |
| } |
| |
| private class SetGlobalVisibilityPublicStep extends SetGlobalVisibilityBaseStep { |
| private SetGlobalVisibilityPublicStep() { |
| super(R.string.set_global_visibility_public, VISIBILITY_PUBLIC); |
| } |
| } |
| |
| private class SetGlobalVisibilityPrivateStep extends SetGlobalVisibilityBaseStep { |
| private SetGlobalVisibilityPrivateStep() { |
| super(R.string.set_global_visibility_private, VISIBILITY_PRIVATE); |
| } |
| } |
| |
| private class SetGlobalVisibilitySecretStep extends SetGlobalVisibilityBaseStep { |
| private SetGlobalVisibilitySecretStep() { |
| super(R.string.set_global_visibility_secret, VISIBILITY_SECRET); |
| } |
| } |
| |
| private class SecureActionOnLockScreenTest extends InteractiveTestCase { |
| private View mView; |
| |
| @Override |
| protected void setUp() { |
| createChannels(); |
| ActionTriggeredReceiver.sendNotification(mContext, true); |
| setButtonsEnabled(mView, true); |
| status = READY; |
| next(); |
| } |
| |
| @Override |
| protected void tearDown() { |
| mNm.cancelAll(); |
| deleteChannels(); |
| delay(); |
| } |
| |
| @Override |
| protected View inflate(ViewGroup parent) { |
| mView = createPassFailItem(parent, R.string.secure_action_lockscreen); |
| setButtonsEnabled(mView, false); |
| return mView; |
| } |
| |
| @Override |
| boolean autoStart() { |
| return true; |
| } |
| |
| @Override |
| protected void test() { |
| status = WAIT_FOR_USER; |
| next(); |
| } |
| } |
| |
| |
| private abstract class NotificationPrivacyBaseTest extends InteractiveTestCase { |
| private View mView; |
| @StringRes |
| private final int mInstructionRes; |
| |
| NotificationPrivacyBaseTest(@StringRes int instructionRes) { |
| mInstructionRes = instructionRes; |
| } |
| |
| @Override |
| protected void setUp() { |
| createChannels(); |
| sendNotification(); |
| setButtonsEnabled(mView, true); |
| status = READY; |
| next(); |
| } |
| |
| @Override |
| protected void tearDown() { |
| mNm.cancelAll(); |
| deleteChannels(); |
| delay(); |
| } |
| |
| @Override |
| protected View inflate(ViewGroup parent) { |
| mView = createPassFailItem(parent, mInstructionRes); |
| setButtonsEnabled(mView, false); |
| return mView; |
| } |
| |
| @Override |
| boolean autoStart() { |
| return true; |
| } |
| |
| @Override |
| protected void test() { |
| status = WAIT_FOR_USER; |
| next(); |
| } |
| } |
| |
| private class NotificationWhenLockedShowsRedactedTest extends NotificationPrivacyBaseTest { |
| NotificationWhenLockedShowsRedactedTest() { |
| super(R.string.np_when_locked_see_redacted); |
| } |
| } |
| |
| private class NotificationWhenLockedShowsPrivateTest extends NotificationPrivacyBaseTest { |
| NotificationWhenLockedShowsPrivateTest() { |
| super(R.string.np_when_locked_see_private); |
| } |
| } |
| |
| private class NotificationWhenLockedIsHiddenTest extends NotificationPrivacyBaseTest { |
| NotificationWhenLockedIsHiddenTest() { |
| super(R.string.np_when_locked_hidden); |
| } |
| } |
| |
| private abstract class NotificationWhenOccludedBaseTest extends InteractiveTestCase { |
| private View mView; |
| @StringRes |
| private final int mInstructionRes; |
| |
| NotificationWhenOccludedBaseTest(@StringRes int instructionRes) { |
| mInstructionRes = instructionRes; |
| } |
| |
| @Override |
| protected void setUp() { |
| createChannels(); |
| sendNotification(); |
| setButtonsEnabled(mView, true); |
| status = READY; |
| next(); |
| } |
| |
| @Override |
| protected void tearDown() { |
| mNm.cancelAll(); |
| deleteChannels(); |
| delay(); |
| } |
| |
| @Override |
| protected View inflate(ViewGroup parent) { |
| mView = createUserAndPassFailItem( |
| parent, R.string.np_start_occluding, R.string.np_occluding_instructions); |
| setButtonsEnabled(mView, false); |
| return mView; |
| } |
| |
| @Override |
| boolean autoStart() { |
| return true; |
| } |
| |
| @Override |
| protected void test() { |
| status = WAIT_FOR_USER; |
| next(); |
| } |
| |
| @Override |
| protected Intent getIntent() { |
| return ShowWhenLockedActivity.makeActivityIntent( |
| getApplicationContext(), getString(mInstructionRes)); |
| } |
| } |
| |
| private class NotificationWhenOccludedShowsRedactedTest extends |
| NotificationWhenOccludedBaseTest { |
| NotificationWhenOccludedShowsRedactedTest() { |
| super(R.string.np_occluding_see_redacted); |
| } |
| } |
| |
| private class NotificationWhenOccludedShowsPrivateTest extends |
| NotificationWhenOccludedBaseTest { |
| NotificationWhenOccludedShowsPrivateTest() { |
| super(R.string.np_occluding_see_private); |
| } |
| } |
| |
| private class NotificationWhenOccludedIsHiddenTest extends NotificationWhenOccludedBaseTest { |
| NotificationWhenOccludedIsHiddenTest() { |
| super(R.string.np_occluding_hidden); |
| } |
| } |
| |
| } |