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