blob: 1a2f59c0baa20bb1ccbbf5ebb9807ca8f69ea9e9 [file] [log] [blame]
/*
* Copyright (C) 2022 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 android.permission.cts;
import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
import static android.os.Process.myUserHandle;
import static android.permission.cts.TestUtils.eventually;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static java.util.concurrent.TimeUnit.SECONDS;
import android.app.NotificationManager;
import android.app.UiAutomation;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.provider.DeviceConfig;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import com.android.compatibility.common.util.DeviceConfigStateChangerRule;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import java.util.List;
/**
* Base test class used for {@code NotificationListenerCheckTest} and
* {@code NotificationListenerCheckWithSafetyCenterUnsupportedTest}
*/
public class BaseNotificationListenerCheckTest {
private static final String LOG_TAG = BaseNotificationListenerCheckTest.class.getSimpleName();
private static final boolean DEBUG = false;
protected static final String TEST_APP_PKG =
"android.permission.cts.appthathasnotificationlistener";
private static final String TEST_APP_NOTIFICATION_SERVICE =
TEST_APP_PKG + ".CtsNotificationListenerService";
protected static final String TEST_APP_NOTIFICATION_LISTENER_APK =
"/data/local/tmp/cts/permissions/CtsAppThatHasNotificationListener.apk";
private static final int NOTIFICATION_LISTENER_CHECK_JOB_ID = 4;
/**
* Device config property for whether notification listener check is enabled on the device
*/
private static final String PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED =
"notification_listener_check_enabled";
/**
* Device config property for time period in milliseconds after which current enabled
* notification
* listeners are queried
*/
protected static final String PROPERTY_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS =
"notification_listener_check_interval_millis";
protected static final Long OVERRIDE_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS =
SECONDS.toMillis(0);
private static final String ACTION_SET_UP_NOTIFICATION_LISTENER_CHECK =
"com.android.permissioncontroller.action.SET_UP_NOTIFICATION_LISTENER_CHECK";
private static final String NotificationListenerOnBootReceiver =
"com.android.permissioncontroller.privacysources.SetupPeriodicNotificationListenerCheck";
/**
* ID for notification shown by
* {@link com.android.permissioncontroller.privacysources.NotificationListenerCheck}.
*/
public static final int NOTIFICATION_LISTENER_CHECK_NOTIFICATION_ID = 3;
protected static final long UNEXPECTED_TIMEOUT_MILLIS = 10000;
protected static final long ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS = 5000;
private static final Context sContext = InstrumentationRegistry.getTargetContext();
private static final PackageManager sPackageManager = sContext.getPackageManager();
private static final UiAutomation sUiAutomation = InstrumentationRegistry.getInstrumentation()
.getUiAutomation();
private static final String PERMISSION_CONTROLLER_PKG = sContext.getPackageManager()
.getPermissionControllerPackageName();
private static List<ComponentName> sPreviouslyEnabledNotificationListeners;
// Override SafetyCenter enabled flag
@Rule
public DeviceConfigStateChangerRule sPrivacyDeviceConfigSafetyCenterEnabled =
new DeviceConfigStateChangerRule(sContext,
DeviceConfig.NAMESPACE_PRIVACY,
SafetyCenterUtils.PROPERTY_SAFETY_CENTER_ENABLED,
Boolean.toString(true));
// Override NlsCheck enabled flag
@Rule
public DeviceConfigStateChangerRule sPrivacyDeviceConfigNlsCheckEnabled =
new DeviceConfigStateChangerRule(sContext,
DeviceConfig.NAMESPACE_PRIVACY,
PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED,
Boolean.toString(true));
// Override general notification interval from once every day to once ever 1 second
@Rule
public DeviceConfigStateChangerRule sPrivacyDeviceConfigNlsCheckIntervalMillis =
new DeviceConfigStateChangerRule(sContext,
DeviceConfig.NAMESPACE_PRIVACY,
PROPERTY_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS,
Long.toString(OVERRIDE_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS));
@Rule
public CtsNotificationListenerHelperRule ctsNotificationListenerHelper =
new CtsNotificationListenerHelperRule(sContext);
@BeforeClass
public static void beforeClassSetup() throws Exception {
// Bypass battery saving restrictions
runShellCommand("cmd tare set-vip "
+ myUserHandle().getIdentifier() + " " + PERMISSION_CONTROLLER_PKG + " true");
// Disallow any OEM enabled NLS
disallowPreexistingNotificationListeners();
}
@AfterClass
public static void afterClassTearDown() throws Throwable {
// Reset battery saving restrictions
runShellCommand("cmd tare set-vip "
+ myUserHandle().getIdentifier() + " " + PERMISSION_CONTROLLER_PKG + " default");
// Reallow any previously OEM allowed NLS
reallowPreexistingNotificationListeners();
}
protected static void setDeviceConfigPrivacyProperty(String propertyName, String value) {
runWithShellPermissionIdentity(() -> {
boolean valueWasSet = DeviceConfig.setProperty(
DeviceConfig.NAMESPACE_PRIVACY,
/* name = */ propertyName,
/* value = */ value,
/* makeDefault = */ false);
if (!valueWasSet) {
throw new IllegalStateException("Could not set " + propertyName + " to " + value);
}
}, WRITE_DEVICE_CONFIG);
}
/**
* Enable or disable notification listener check
*/
protected static void setNotificationListenerCheckEnabled(boolean enabled) {
setDeviceConfigPrivacyProperty(
PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED,
/* value = */ String.valueOf(enabled));
}
/**
* Allow or disallow a {@link NotificationListenerService} component for the current user
*
* @param listenerComponent {@link NotificationListenerService} component to allow or disallow
*/
private static void setNotificationListenerServiceAllowed(ComponentName listenerComponent,
boolean allowed) {
String command = " cmd notification " + (allowed ? "allow_listener " : "disallow_listener ")
+ listenerComponent.flattenToString();
runShellCommand(command);
}
private static void disallowPreexistingNotificationListeners() {
runWithShellPermissionIdentity(() -> {
NotificationManager notificationManager =
sContext.getSystemService(NotificationManager.class);
sPreviouslyEnabledNotificationListeners =
notificationManager.getEnabledNotificationListeners();
});
if (DEBUG) {
Log.d(LOG_TAG, "Found " + sPreviouslyEnabledNotificationListeners.size()
+ " previously allowed notification listeners. Disabling before test run.");
}
for (ComponentName listener : sPreviouslyEnabledNotificationListeners) {
setNotificationListenerServiceAllowed(listener, false);
}
}
private static void reallowPreexistingNotificationListeners() {
if (DEBUG) {
Log.d(LOG_TAG, "Re-allowing " + sPreviouslyEnabledNotificationListeners.size()
+ " previously allowed notification listeners found before test run.");
}
for (ComponentName listener : sPreviouslyEnabledNotificationListeners) {
setNotificationListenerServiceAllowed(listener, true);
}
}
protected void allowTestAppNotificationListenerService() {
setNotificationListenerServiceAllowed(
new ComponentName(TEST_APP_PKG, TEST_APP_NOTIFICATION_SERVICE), true);
}
protected void disallowTestAppNotificationListenerService() {
setNotificationListenerServiceAllowed(
new ComponentName(TEST_APP_PKG, TEST_APP_NOTIFICATION_SERVICE), false);
}
/**
* Force a run of the notification listener check.
*/
protected static void runNotificationListenerCheck() throws Throwable {
TestUtils.awaitJobUntilRequestedState(
PERMISSION_CONTROLLER_PKG,
NOTIFICATION_LISTENER_CHECK_JOB_ID,
UNEXPECTED_TIMEOUT_MILLIS,
sUiAutomation,
"waiting"
);
TestUtils.runJobAndWaitUntilCompleted(
PERMISSION_CONTROLLER_PKG,
NOTIFICATION_LISTENER_CHECK_JOB_ID,
UNEXPECTED_TIMEOUT_MILLIS,
sUiAutomation
);
}
/**
* Skip tests for if Safety Center not supported
*/
protected void assumeDeviceSupportsSafetyCenter() {
assumeTrue(SafetyCenterUtils.deviceSupportsSafetyCenter(sContext));
}
/**
* Skip tests for if Safety Center IS supported
*/
protected void assumeDeviceDoesNotSupportSafetyCenter() {
assumeFalse(SafetyCenterUtils.deviceSupportsSafetyCenter(sContext));
}
protected void wakeUpAndDismissKeyguard() {
runShellCommand("input keyevent KEYCODE_WAKEUP");
runShellCommand("wm dismiss-keyguard");
}
/**
* Reset the permission controllers state before each test
*/
protected void resetPermissionControllerBeforeEachTest() throws Throwable {
resetPermissionController();
// ensure no posted notification listener notifications exits
eventually(() -> assertNull(getNotification(false)), UNEXPECTED_TIMEOUT_MILLIS);
// Reset job scheduler stats (to allow more jobs to be run)
runShellCommand(
"cmd jobscheduler reset-execution-quota -u " + myUserHandle().getIdentifier() + " "
+ PERMISSION_CONTROLLER_PKG);
runShellCommand("cmd jobscheduler reset-schedule-quota");
}
/**
* Reset the permission controllers state.
*/
private static void resetPermissionController() throws Throwable {
PermissionUtils.resetPermissionControllerJob(sUiAutomation, PERMISSION_CONTROLLER_PKG,
NOTIFICATION_LISTENER_CHECK_JOB_ID, UNEXPECTED_TIMEOUT_MILLIS,
ACTION_SET_UP_NOTIFICATION_LISTENER_CHECK, NotificationListenerOnBootReceiver);
}
/**
* Preshow/dismiss cts NotificationListener notification as it negatively affects test results
* (can result in unexpected test pass/failures)
*/
protected void triggerAndDismissCtsNotificationListenerNotification() throws Throwable {
// CtsNotificationListenerService isn't enabled at this point, but NotificationListener
// should be. Mark as notified by showing and dismissing
runNotificationListenerCheck();
// Ensure notification shows and dismiss
eventually(() -> assertNotNull(getNotification(true)),
UNEXPECTED_TIMEOUT_MILLIS);
}
/**
* Get a notification listener notification that is currently visible.
*
* @param cancelNotification if `true` the notification is canceled inside this method
* @return The notification or `null` if there is none
*/
protected StatusBarNotification getNotification(boolean cancelNotification) throws Throwable {
return CtsNotificationListenerServiceUtils.getNotificationForPackageAndId(
PERMISSION_CONTROLLER_PKG,
NOTIFICATION_LISTENER_CHECK_NOTIFICATION_ID,
cancelNotification);
}
/**
* Clear any notifications related to NotificationListenerCheck to ensure clean test setup
*/
protected void clearNotifications() throws Throwable {
// Clear notification if present
CtsNotificationListenerServiceUtils.cancelNotifications(PERMISSION_CONTROLLER_PKG);
}
}