blob: 760bd71bc77c731aca4fcd7bfada0ef86c9cef7c [file] [log] [blame]
/*
* 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_alice)
.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);
}
}
}