1 /*
2  * Copyright (C) 2022 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 android.permission.cts;
18 
19 import static android.os.Process.myUserHandle;
20 import static android.permission.cts.TestUtils.eventually;
21 
22 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
23 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
24 
25 import static org.junit.Assert.assertNotNull;
26 import static org.junit.Assert.assertNull;
27 import static org.junit.Assume.assumeFalse;
28 import static org.junit.Assume.assumeTrue;
29 
30 import static java.util.concurrent.TimeUnit.SECONDS;
31 
32 import android.app.NotificationManager;
33 import android.app.UiAutomation;
34 import android.content.ComponentName;
35 import android.content.Context;
36 import android.content.pm.PackageManager;
37 import android.provider.DeviceConfig;
38 import android.service.notification.NotificationListenerService;
39 import android.service.notification.StatusBarNotification;
40 import android.util.Log;
41 
42 import androidx.test.InstrumentationRegistry;
43 
44 import com.android.compatibility.common.util.DeviceConfigStateChangerRule;
45 
46 import org.junit.AfterClass;
47 import org.junit.BeforeClass;
48 import org.junit.Rule;
49 
50 import java.util.List;
51 
52 /**
53  * Base test class used for {@code NotificationListenerCheckTest} and
54  * {@code NotificationListenerCheckWithSafetyCenterUnsupportedTest}
55  */
56 public class BaseNotificationListenerCheckTest {
57     private static final String LOG_TAG = BaseNotificationListenerCheckTest.class.getSimpleName();
58     private static final boolean DEBUG = false;
59 
60     protected static final String TEST_APP_PKG =
61             "android.permission.cts.appthathasnotificationlistener";
62     private static final String TEST_APP_NOTIFICATION_SERVICE =
63             TEST_APP_PKG + ".CtsNotificationListenerService";
64     protected static final String TEST_APP_NOTIFICATION_LISTENER_APK =
65             "/data/local/tmp/cts-permission/CtsAppThatHasNotificationListener.apk";
66 
67     private static final int NOTIFICATION_LISTENER_CHECK_JOB_ID = 4;
68 
69     /**
70      * Device config property for whether notification listener check is enabled on the device
71      */
72     private static final String PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED =
73             "notification_listener_check_enabled";
74 
75     /**
76      * Device config property for time period in milliseconds after which current enabled
77      * notification
78      * listeners are queried
79      */
80     protected static final String PROPERTY_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS =
81             "notification_listener_check_interval_millis";
82 
83     protected static final Long OVERRIDE_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS =
84             SECONDS.toMillis(0);
85 
86     private static final String ACTION_SET_UP_NOTIFICATION_LISTENER_CHECK =
87             "com.android.permissioncontroller.action.SET_UP_NOTIFICATION_LISTENER_CHECK";
88     private static final String NotificationListenerOnBootReceiver =
89             "com.android.permissioncontroller.privacysources.SetupPeriodicNotificationListenerCheck";
90 
91     /**
92      * ID for notification shown by
93      * {@link com.android.permissioncontroller.privacysources.NotificationListenerCheck}.
94      */
95     public static final int NOTIFICATION_LISTENER_CHECK_NOTIFICATION_ID = 3;
96 
97     protected static final long UNEXPECTED_TIMEOUT_MILLIS = 10000;
98     protected static final long ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS = 5000;
99 
100     private static final Context sContext = InstrumentationRegistry.getTargetContext();
101     private static final PackageManager sPackageManager = sContext.getPackageManager();
102     private static final UiAutomation sUiAutomation = InstrumentationRegistry.getInstrumentation()
103             .getUiAutomation();
104 
105     private static final String PERMISSION_CONTROLLER_PKG = sContext.getPackageManager()
106             .getPermissionControllerPackageName();
107 
108     private static List<ComponentName> sPreviouslyEnabledNotificationListeners;
109 
110     // Override SafetyCenter enabled flag
111     @Rule
112     public DeviceConfigStateChangerRule sPrivacyDeviceConfigSafetyCenterEnabled =
113             new DeviceConfigStateChangerRule(sContext,
114                     DeviceConfig.NAMESPACE_PRIVACY,
115                     SafetyCenterUtils.PROPERTY_SAFETY_CENTER_ENABLED,
116                     Boolean.toString(true));
117 
118     // Override NlsCheck enabled flag
119     @Rule
120     public DeviceConfigStateChangerRule sPrivacyDeviceConfigNlsCheckEnabled =
121             new DeviceConfigStateChangerRule(sContext,
122                     DeviceConfig.NAMESPACE_PRIVACY,
123                     PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED,
124                     Boolean.toString(true));
125 
126     // Override general notification interval from once every day to once ever 1 second
127     @Rule
128     public DeviceConfigStateChangerRule sPrivacyDeviceConfigNlsCheckIntervalMillis =
129             new DeviceConfigStateChangerRule(sContext,
130                     DeviceConfig.NAMESPACE_PRIVACY,
131                     PROPERTY_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS,
132                     Long.toString(OVERRIDE_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS));
133 
134     @Rule
135     public CtsNotificationListenerHelperRule ctsNotificationListenerHelper =
136             new CtsNotificationListenerHelperRule(sContext);
137 
138     @BeforeClass
beforeClassSetup()139     public static void beforeClassSetup() throws Exception {
140         // Disallow any OEM enabled NLS
141         disallowPreexistingNotificationListeners();
142     }
143 
144     @AfterClass
afterClassTearDown()145     public static void afterClassTearDown() throws Throwable {
146         // Reallow any previously OEM allowed NLS
147         reallowPreexistingNotificationListeners();
148     }
149 
setDeviceConfigPrivacyProperty(String propertyName, String value)150     protected static void setDeviceConfigPrivacyProperty(String propertyName, String value) {
151         runWithShellPermissionIdentity(() -> {
152             boolean valueWasSet =  DeviceConfig.setProperty(
153                     DeviceConfig.NAMESPACE_PRIVACY,
154                     /* name = */ propertyName,
155                     /* value = */ value,
156                     /* makeDefault = */ false);
157             if (!valueWasSet) {
158                 throw new  IllegalStateException("Could not set " + propertyName + " to " + value);
159             }
160         });
161     }
162 
163     /**
164      * Enable or disable notification listener check
165      */
setNotificationListenerCheckEnabled(boolean enabled)166     protected static void setNotificationListenerCheckEnabled(boolean enabled) {
167         setDeviceConfigPrivacyProperty(
168                 PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED,
169                 /* value = */ String.valueOf(enabled));
170     }
171 
172     /**
173      * Allow or disallow a {@link NotificationListenerService} component for the current user
174      *
175      * @param listenerComponent {@link NotificationListenerService} component to allow or disallow
176      */
setNotificationListenerServiceAllowed(ComponentName listenerComponent, boolean allowed)177     private static void setNotificationListenerServiceAllowed(ComponentName listenerComponent,
178             boolean allowed) {
179         String command = " cmd notification " + (allowed ? "allow_listener " : "disallow_listener ")
180                 + listenerComponent.flattenToString();
181         runShellCommand(command);
182     }
183 
disallowPreexistingNotificationListeners()184     private static void disallowPreexistingNotificationListeners() {
185         runWithShellPermissionIdentity(() -> {
186             NotificationManager notificationManager =
187                     sContext.getSystemService(NotificationManager.class);
188             sPreviouslyEnabledNotificationListeners =
189                     notificationManager.getEnabledNotificationListeners();
190         });
191         if (DEBUG) {
192             Log.d(LOG_TAG, "Found " + sPreviouslyEnabledNotificationListeners.size()
193                     + " previously allowed notification listeners. Disabling before test run.");
194         }
195         for (ComponentName listener : sPreviouslyEnabledNotificationListeners) {
196             setNotificationListenerServiceAllowed(listener, false);
197         }
198     }
199 
reallowPreexistingNotificationListeners()200     private static void reallowPreexistingNotificationListeners() {
201         if (DEBUG) {
202             Log.d(LOG_TAG, "Re-allowing " + sPreviouslyEnabledNotificationListeners.size()
203                     + " previously allowed notification listeners found before test run.");
204         }
205         for (ComponentName listener : sPreviouslyEnabledNotificationListeners) {
206             setNotificationListenerServiceAllowed(listener, true);
207         }
208     }
209 
allowTestAppNotificationListenerService()210     protected void allowTestAppNotificationListenerService() {
211         setNotificationListenerServiceAllowed(
212                 new ComponentName(TEST_APP_PKG, TEST_APP_NOTIFICATION_SERVICE), true);
213     }
214 
disallowTestAppNotificationListenerService()215     protected void disallowTestAppNotificationListenerService() {
216         setNotificationListenerServiceAllowed(
217                 new ComponentName(TEST_APP_PKG, TEST_APP_NOTIFICATION_SERVICE), false);
218     }
219 
220     /**
221      * Force a run of the notification listener check.
222      */
runNotificationListenerCheck()223     protected static void runNotificationListenerCheck() throws Throwable {
224         TestUtils.awaitJobUntilRequestedState(
225                 PERMISSION_CONTROLLER_PKG,
226                 NOTIFICATION_LISTENER_CHECK_JOB_ID,
227                 UNEXPECTED_TIMEOUT_MILLIS,
228                 sUiAutomation,
229                 "waiting"
230         );
231 
232         TestUtils.runJobAndWaitUntilCompleted(
233                 PERMISSION_CONTROLLER_PKG,
234                 NOTIFICATION_LISTENER_CHECK_JOB_ID,
235                 UNEXPECTED_TIMEOUT_MILLIS,
236                 sUiAutomation
237         );
238     }
239 
240     /**
241      * Skip tests for if Safety Center not supported
242      */
assumeDeviceSupportsSafetyCenter()243     protected void assumeDeviceSupportsSafetyCenter() {
244         assumeTrue(SafetyCenterUtils.deviceSupportsSafetyCenter(sContext));
245     }
246 
247     /**
248      * Skip tests for if Safety Center IS supported
249      */
assumeDeviceDoesNotSupportSafetyCenter()250     protected void assumeDeviceDoesNotSupportSafetyCenter() {
251         assumeFalse(SafetyCenterUtils.deviceSupportsSafetyCenter(sContext));
252     }
253 
wakeUpAndDismissKeyguard()254     protected void wakeUpAndDismissKeyguard() {
255         runShellCommand("input keyevent KEYCODE_WAKEUP");
256         runShellCommand("wm dismiss-keyguard");
257     }
258 
259     /**
260      * Reset the permission controllers state before each test
261      */
resetPermissionControllerBeforeEachTest()262     protected void resetPermissionControllerBeforeEachTest() throws Throwable {
263         resetPermissionController();
264 
265         // ensure no posted notification listener notifications exits
266         eventually(() -> assertNull(getNotification(false)), UNEXPECTED_TIMEOUT_MILLIS);
267 
268         // Reset job scheduler stats (to allow more jobs to be run)
269         runShellCommand(
270                 "cmd jobscheduler reset-execution-quota -u " + myUserHandle().getIdentifier() + " "
271                         + PERMISSION_CONTROLLER_PKG);
272         runShellCommand("cmd jobscheduler reset-schedule-quota");
273     }
274 
275     /**
276      * Reset the permission controllers state.
277      */
resetPermissionController()278     private static void resetPermissionController() throws Throwable {
279         PermissionUtils.resetPermissionControllerJob(sUiAutomation, PERMISSION_CONTROLLER_PKG,
280                 NOTIFICATION_LISTENER_CHECK_JOB_ID, 45000,
281                 ACTION_SET_UP_NOTIFICATION_LISTENER_CHECK, NotificationListenerOnBootReceiver);
282     }
283 
284     /**
285      * Preshow/dismiss cts NotificationListener notification as it negatively affects test results
286      * (can result in unexpected test pass/failures)
287      */
triggerAndDismissCtsNotificationListenerNotification()288     protected void triggerAndDismissCtsNotificationListenerNotification() throws Throwable {
289         // CtsNotificationListenerService isn't enabled at this point, but NotificationListener
290         // should be. Mark as notified by showing and dismissing
291         runNotificationListenerCheck();
292 
293         // Ensure notification shows and dismiss
294         eventually(() -> assertNotNull(getNotification(true)),
295                 UNEXPECTED_TIMEOUT_MILLIS);
296     }
297 
298     /**
299      * Get a notification listener notification that is currently visible.
300      *
301      * @param cancelNotification if `true` the notification is canceled inside this method
302      * @return The notification or `null` if there is none
303      */
getNotification(boolean cancelNotification)304     protected StatusBarNotification getNotification(boolean cancelNotification) throws Throwable {
305         return CtsNotificationListenerServiceUtils.getNotificationForPackageAndId(
306                 PERMISSION_CONTROLLER_PKG,
307                 NOTIFICATION_LISTENER_CHECK_NOTIFICATION_ID,
308                 cancelNotification);
309     }
310 
311     /**
312      * Clear any notifications related to NotificationListenerCheck to ensure clean test setup
313      */
clearNotifications()314     protected void clearNotifications() throws Throwable {
315         // Clear notification if present
316         CtsNotificationListenerServiceUtils.cancelNotifications(PERMISSION_CONTROLLER_PKG);
317     }
318 }
319