1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.cts.verifier.notifications; 18 19 import static android.app.Notification.VISIBILITY_PRIVATE; 20 import static android.app.Notification.VISIBILITY_PUBLIC; 21 import static android.app.Notification.VISIBILITY_SECRET; 22 import static android.app.NotificationManager.IMPORTANCE_DEFAULT; 23 import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE; 24 import static android.provider.Settings.EXTRA_APP_PACKAGE; 25 import static android.provider.Settings.EXTRA_CHANNEL_ID; 26 27 import android.annotation.SuppressLint; 28 import android.app.KeyguardManager; 29 import android.app.Notification; 30 import android.app.NotificationChannel; 31 import android.content.Intent; 32 import android.os.Bundle; 33 import android.provider.Settings; 34 import android.util.Log; 35 import android.view.View; 36 import android.view.ViewGroup; 37 38 import androidx.annotation.StringRes; 39 40 import com.android.cts.verifier.R; 41 import com.android.cts.verifier.features.FeatureUtil; 42 43 import java.util.ArrayList; 44 import java.util.List; 45 import java.util.UUID; 46 47 /** 48 * A verifier test which validates the lockscreen behaviors of notifications under various settings 49 */ 50 public class NotificationPrivacyVerifierActivity extends InteractiveVerifierActivity 51 implements Runnable { 52 static final String TAG = "NotifPrivacyVerifier"; 53 private static final String NOTIFICATION_CHANNEL_ID = TAG; 54 55 @Override onCreate(Bundle savedState)56 protected void onCreate(Bundle savedState) { 57 super.onCreate(savedState); 58 } 59 60 @Override getTitleResource()61 protected int getTitleResource() { 62 return R.string.notif_privacy_test; 63 } 64 65 @Override getInstructionsResource()66 protected int getInstructionsResource() { 67 return R.string.notif_privacy_info; 68 } 69 getChannelVisibility()70 private int getChannelVisibility() { 71 NotificationChannel channel = mNm.getNotificationChannel(NOTIFICATION_CHANNEL_ID); 72 int visibility = channel.getLockscreenVisibility(); 73 if (visibility == VISIBILITY_NO_OVERRIDE) { 74 visibility = getGlobalVisibility(); 75 } 76 if (visibility != VISIBILITY_SECRET 77 && visibility != VISIBILITY_PRIVATE 78 && visibility != VISIBILITY_PUBLIC) { 79 throw new RuntimeException("Unexpected visibility: " + visibility); 80 } 81 return visibility; 82 } 83 getGlobalVisibility()84 private int getGlobalVisibility() { 85 if (!getLockscreenNotificationsEnabled()) { 86 return VISIBILITY_SECRET; 87 } else if (!getLockscreenAllowPrivateNotifications()) { 88 return VISIBILITY_PRIVATE; 89 } 90 return VISIBILITY_PUBLIC; 91 } 92 getLockscreenNotificationsEnabled()93 private boolean getLockscreenNotificationsEnabled() { 94 return Settings.Secure.getInt(mContext.getContentResolver(), 95 Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0) != 0; 96 } 97 getLockscreenAllowPrivateNotifications()98 private boolean getLockscreenAllowPrivateNotifications() { 99 return Settings.Secure.getInt(mContext.getContentResolver(), 100 Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0) != 0; 101 } 102 103 104 // Test Setup 105 106 @Override createTestItems()107 protected List<InteractiveTestCase> createTestItems() { 108 List<InteractiveTestCase> tests = new ArrayList<>(); 109 110 // FIRST: enable lock screen 111 tests.add(new SetScreenLockEnabledStep()); 112 113 // for watches, no notifications should appear on the secure lock screen 114 if (!FeatureUtil.isWatch(this)) { 115 // THEN: set redaction settings 116 tests.add(new SetGlobalVisibilityPublicStep()); 117 tests.add(new SetChannelLockscreenVisibilityPrivateStep()); 118 // NOW TESTING: redacted by channel 119 tests.add(new NotificationWhenLockedShowsRedactedTest()); 120 tests.add(new NotificationWhenOccludedShowsRedactedTest()); 121 122 tests.add(new SetChannelLockscreenVisibilityPublicStep()); 123 // NOW TESTING: not redacted at all 124 tests.add(new SecureActionOnLockScreenTest()); 125 tests.add(new NotificationWhenLockedShowsPrivateTest()); 126 tests.add(new NotificationWhenOccludedShowsPrivateTest()); 127 128 tests.add(new SetGlobalVisibilityPrivateStep()); 129 // NOW TESTING: redacted globally 130 tests.add(new NotificationWhenLockedShowsRedactedTest()); 131 tests.add(new NotificationWhenOccludedShowsRedactedTest()); 132 133 tests.add(new SetGlobalVisibilitySecretStep()); 134 } 135 136 // NOW TESTING: notifications do not appear 137 tests.add(new NotificationWhenLockedIsHiddenTest()); 138 if (!FeatureUtil.isWatch(this)) { 139 tests.add(new NotificationWhenOccludedIsHiddenTest()); 140 } 141 142 // FINALLY: restore device state 143 tests.add(new SetScreenLockDisabledStep()); 144 return tests; 145 } 146 createChannels()147 private void createChannels() { 148 NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, 149 NOTIFICATION_CHANNEL_ID, IMPORTANCE_DEFAULT); 150 mNm.createNotificationChannel(channel); 151 } 152 deleteChannels()153 private void deleteChannels() { 154 mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID); 155 } 156 157 @SuppressLint("NewApi") sendNotification()158 private void sendNotification() { 159 String tag = UUID.randomUUID().toString(); 160 long when = System.currentTimeMillis(); 161 Log.d(TAG, "Sending: tag=" + tag + " when=" + when); 162 163 mPackageString = "com.android.cts.verifier"; 164 165 Notification publicVersion = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) 166 .setContentTitle(getString(R.string.np_public_version_text)) 167 .setSmallIcon(R.drawable.ic_stat_alice) 168 .setWhen(when) 169 .build(); 170 Notification privateVersion = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) 171 .setContentTitle(getString(R.string.np_private_version_text)) 172 .setSmallIcon(R.drawable.ic_stat_alice) 173 .setWhen(when) 174 .setPublicVersion(publicVersion) 175 .build(); 176 mNm.notify(tag, NOTIFICATION_ID, privateVersion); 177 } 178 179 /** 180 * Asks the user to set the lockscreen visibility of the channel to the given value 181 */ 182 private abstract class SetChannelLockscreenVisibilityBaseStep extends InteractiveTestCase { 183 @StringRes 184 private final int mInstructionRes; 185 private final int mExpectVisibility; 186 private View mView; 187 SetChannelLockscreenVisibilityBaseStep(@tringRes int instructionRes, int expectVisibility)188 SetChannelLockscreenVisibilityBaseStep(@StringRes int instructionRes, 189 int expectVisibility) { 190 mInstructionRes = instructionRes; 191 mExpectVisibility = expectVisibility; 192 } 193 194 @Override inflate(ViewGroup parent)195 protected View inflate(ViewGroup parent) { 196 mView = createUserItem(parent, R.string.np_start_channel_settings, mInstructionRes); 197 setButtonsEnabled(mView, false); 198 return mView; 199 } 200 201 @Override setUp()202 protected void setUp() { 203 createChannels(); 204 status = READY; 205 setButtonsEnabled(mView, true); 206 next(); 207 } 208 209 @Override autoStart()210 boolean autoStart() { 211 return true; 212 } 213 214 @Override test()215 protected void test() { 216 if (getChannelVisibility() == mExpectVisibility) { 217 status = PASS; 218 } else { 219 // user hasn't jumped to settings yet 220 status = WAIT_FOR_USER; 221 } 222 223 next(); 224 } 225 tearDown()226 protected void tearDown() { 227 deleteChannels(); 228 next(); 229 } 230 231 @Override getIntent()232 protected Intent getIntent() { 233 return new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) 234 .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName()) 235 .putExtra(EXTRA_CHANNEL_ID, NOTIFICATION_CHANNEL_ID); 236 } 237 } 238 239 private class SetChannelLockscreenVisibilityPrivateStep extends 240 SetChannelLockscreenVisibilityBaseStep { SetChannelLockscreenVisibilityPrivateStep()241 SetChannelLockscreenVisibilityPrivateStep() { 242 super(R.string.nls_visibility, VISIBILITY_PRIVATE); 243 } 244 } 245 246 private class SetChannelLockscreenVisibilityPublicStep extends 247 SetChannelLockscreenVisibilityBaseStep { SetChannelLockscreenVisibilityPublicStep()248 SetChannelLockscreenVisibilityPublicStep() { 249 super(R.string.nls_restore_visibility, VISIBILITY_PUBLIC); 250 } 251 } 252 253 private abstract class SetScreenLockBaseStep extends InteractiveTestCase { 254 @StringRes 255 private final int mInstructionRes; 256 private final boolean mExpectSecure; 257 private View mView; 258 SetScreenLockBaseStep(int instructionRes, boolean expectSecure)259 private SetScreenLockBaseStep(int instructionRes, boolean expectSecure) { 260 mInstructionRes = instructionRes; 261 mExpectSecure = expectSecure; 262 } 263 264 @Override inflate(ViewGroup parent)265 protected View inflate(ViewGroup parent) { 266 mView = createUserItem(parent, R.string.np_start_security_settings, mInstructionRes); 267 setButtonsEnabled(mView, false); 268 return mView; 269 } 270 271 @Override setUp()272 protected void setUp() { 273 status = READY; 274 setButtonsEnabled(mView, true); 275 next(); 276 } 277 278 @Override autoStart()279 boolean autoStart() { 280 return true; 281 } 282 283 @Override test()284 protected void test() { 285 KeyguardManager km = getSystemService(KeyguardManager.class); 286 if (km.isDeviceSecure() == mExpectSecure) { 287 status = PASS; 288 } else { 289 status = WAIT_FOR_USER; 290 } 291 292 next(); 293 } 294 295 @Override getIntent()296 protected Intent getIntent() { 297 return new Intent(Settings.ACTION_SECURITY_SETTINGS) 298 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); 299 } 300 } 301 302 private class SetScreenLockEnabledStep extends SetScreenLockBaseStep { SetScreenLockEnabledStep()303 private SetScreenLockEnabledStep() { 304 super(R.string.add_screen_lock, true /* secure */); 305 } 306 } 307 308 private class SetScreenLockDisabledStep extends SetScreenLockBaseStep { SetScreenLockDisabledStep()309 private SetScreenLockDisabledStep() { 310 super(R.string.remove_screen_lock, false /* secure */); 311 } 312 } 313 314 private abstract class SetGlobalVisibilityBaseStep extends InteractiveTestCase { 315 @StringRes 316 private final int mInstructionRes; 317 private final int mExpectVisibility; 318 private View mView; 319 SetGlobalVisibilityBaseStep(int instructionRes, int expectVisibility)320 private SetGlobalVisibilityBaseStep(int instructionRes, int expectVisibility) { 321 mInstructionRes = instructionRes; 322 mExpectVisibility = expectVisibility; 323 } 324 325 @Override inflate(ViewGroup parent)326 protected View inflate(ViewGroup parent) { 327 mView = createUserItem(parent, R.string.np_start_notif_settings, mInstructionRes); 328 setButtonsEnabled(mView, false); 329 return mView; 330 } 331 332 @Override setUp()333 protected void setUp() { 334 status = READY; 335 setButtonsEnabled(mView, true); 336 next(); 337 } 338 339 @Override autoStart()340 boolean autoStart() { 341 return true; 342 } 343 344 @Override test()345 protected void test() { 346 KeyguardManager km = getSystemService(KeyguardManager.class); 347 if (!km.isDeviceSecure()) { 348 // if lockscreen itself not set, this setting won't be available. 349 status = FAIL; 350 } else if (getGlobalVisibility() == mExpectVisibility) { 351 status = PASS; 352 } else { 353 status = WAIT_FOR_USER; 354 } 355 356 next(); 357 } 358 359 @Override getIntent()360 protected Intent getIntent() { 361 return new Intent(Settings.ACTION_NOTIFICATION_SETTINGS) 362 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); 363 } 364 } 365 366 private class SetGlobalVisibilityPublicStep extends SetGlobalVisibilityBaseStep { SetGlobalVisibilityPublicStep()367 private SetGlobalVisibilityPublicStep() { 368 super(R.string.set_global_visibility_public, VISIBILITY_PUBLIC); 369 } 370 } 371 372 private class SetGlobalVisibilityPrivateStep extends SetGlobalVisibilityBaseStep { SetGlobalVisibilityPrivateStep()373 private SetGlobalVisibilityPrivateStep() { 374 super(R.string.set_global_visibility_private, VISIBILITY_PRIVATE); 375 } 376 } 377 378 private class SetGlobalVisibilitySecretStep extends SetGlobalVisibilityBaseStep { SetGlobalVisibilitySecretStep()379 private SetGlobalVisibilitySecretStep() { 380 super(R.string.set_global_visibility_secret, VISIBILITY_SECRET); 381 } 382 } 383 384 private class SecureActionOnLockScreenTest extends InteractiveTestCase { 385 private View mView; 386 387 @Override setUp()388 protected void setUp() { 389 createChannels(); 390 ActionTriggeredReceiver.sendNotification(mContext, true); 391 setButtonsEnabled(mView, true); 392 status = READY; 393 next(); 394 } 395 396 @Override tearDown()397 protected void tearDown() { 398 mNm.cancelAll(); 399 deleteChannels(); 400 delay(); 401 } 402 403 @Override inflate(ViewGroup parent)404 protected View inflate(ViewGroup parent) { 405 mView = createPassFailItem(parent, R.string.secure_action_lockscreen); 406 setButtonsEnabled(mView, false); 407 return mView; 408 } 409 410 @Override autoStart()411 boolean autoStart() { 412 return true; 413 } 414 415 @Override test()416 protected void test() { 417 status = WAIT_FOR_USER; 418 next(); 419 } 420 } 421 422 private abstract class NotificationPrivacyBaseTest extends InteractiveTestCase { 423 private View mView; 424 @StringRes 425 private final int mInstructionRes; 426 NotificationPrivacyBaseTest(@tringRes int instructionRes)427 NotificationPrivacyBaseTest(@StringRes int instructionRes) { 428 mInstructionRes = instructionRes; 429 } 430 431 @Override setUp()432 protected void setUp() { 433 createChannels(); 434 sendNotification(); 435 setButtonsEnabled(mView, true); 436 status = READY; 437 next(); 438 } 439 440 @Override tearDown()441 protected void tearDown() { 442 mNm.cancelAll(); 443 deleteChannels(); 444 delay(); 445 } 446 447 @Override inflate(ViewGroup parent)448 protected View inflate(ViewGroup parent) { 449 mView = createPassFailItem(parent, mInstructionRes); 450 setButtonsEnabled(mView, false); 451 return mView; 452 } 453 454 @Override autoStart()455 boolean autoStart() { 456 return true; 457 } 458 459 @Override test()460 protected void test() { 461 status = WAIT_FOR_USER; 462 next(); 463 } 464 } 465 466 private class NotificationWhenLockedShowsRedactedTest extends NotificationPrivacyBaseTest { NotificationWhenLockedShowsRedactedTest()467 NotificationWhenLockedShowsRedactedTest() { 468 super(R.string.np_when_locked_see_redacted); 469 } 470 } 471 472 private class NotificationWhenLockedShowsPrivateTest extends NotificationPrivacyBaseTest { NotificationWhenLockedShowsPrivateTest()473 NotificationWhenLockedShowsPrivateTest() { 474 super(R.string.np_when_locked_see_private); 475 } 476 } 477 478 private class NotificationWhenLockedIsHiddenTest extends NotificationPrivacyBaseTest { NotificationWhenLockedIsHiddenTest()479 NotificationWhenLockedIsHiddenTest() { 480 super(R.string.np_when_locked_hidden); 481 } 482 } 483 484 private abstract class NotificationWhenOccludedBaseTest extends InteractiveTestCase { 485 private View mView; 486 @StringRes 487 private final int mInstructionRes; 488 NotificationWhenOccludedBaseTest(@tringRes int instructionRes)489 NotificationWhenOccludedBaseTest(@StringRes int instructionRes) { 490 mInstructionRes = instructionRes; 491 } 492 493 @Override setUp()494 protected void setUp() { 495 createChannels(); 496 sendNotification(); 497 setButtonsEnabled(mView, true); 498 status = READY; 499 next(); 500 } 501 502 @Override tearDown()503 protected void tearDown() { 504 mNm.cancelAll(); 505 deleteChannels(); 506 delay(); 507 } 508 509 @Override inflate(ViewGroup parent)510 protected View inflate(ViewGroup parent) { 511 mView = createUserAndPassFailItem( 512 parent, R.string.np_start_occluding, R.string.np_occluding_instructions); 513 setButtonsEnabled(mView, false); 514 return mView; 515 } 516 517 @Override autoStart()518 boolean autoStart() { 519 return true; 520 } 521 522 @Override test()523 protected void test() { 524 status = WAIT_FOR_USER; 525 next(); 526 } 527 528 @Override getIntent()529 protected Intent getIntent() { 530 return ShowWhenLockedActivity.makeActivityIntent( 531 getApplicationContext(), getString(mInstructionRes), 532 /* clearActivity = */ true); 533 } 534 } 535 536 private class NotificationWhenOccludedShowsRedactedTest extends 537 NotificationWhenOccludedBaseTest { NotificationWhenOccludedShowsRedactedTest()538 NotificationWhenOccludedShowsRedactedTest() { 539 super(R.string.np_occluding_see_redacted); 540 } 541 } 542 543 private class NotificationWhenOccludedShowsPrivateTest extends 544 NotificationWhenOccludedBaseTest { NotificationWhenOccludedShowsPrivateTest()545 NotificationWhenOccludedShowsPrivateTest() { 546 super(R.string.np_occluding_see_private); 547 } 548 } 549 550 private class NotificationWhenOccludedIsHiddenTest extends NotificationWhenOccludedBaseTest { NotificationWhenOccludedIsHiddenTest()551 NotificationWhenOccludedIsHiddenTest() { 552 super(R.string.np_occluding_hidden); 553 } 554 } 555 } 556