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.content.pm.PackageManager;
33 import android.os.Bundle;
34 import android.provider.Settings;
35 import android.util.Log;
36 import android.view.View;
37 import android.view.ViewGroup;
38 
39 import androidx.annotation.StringRes;
40 
41 import com.android.cts.verifier.R;
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         boolean isAutomotive = getPackageManager().hasSystemFeature(
109                 PackageManager.FEATURE_AUTOMOTIVE);
110         List<InteractiveTestCase> tests = new ArrayList<>();
111         if (!isAutomotive) {
112             // FIRST: set redaction settings
113             tests.add(new SetScreenLockEnabledStep());
114             tests.add(new SetGlobalVisibilityPublicStep());
115             tests.add(new SetChannelLockscreenVisibilityPrivateStep());
116             // NOW TESTING: redacted by channel
117             tests.add(new NotificationWhenLockedShowsRedactedTest());
118             // TODO: Test: notification CAN be dismissed on lockscreen
119             tests.add(new NotificationWhenOccludedShowsRedactedTest());
120 
121             tests.add(new SetChannelLockscreenVisibilityPublicStep());
122             // NOW TESTING: not redacted at all
123             tests.add(new SecureActionOnLockScreenTest());
124             tests.add(new NotificationWhenLockedShowsPrivateTest());
125             // TODO: Test: notification can NOT be dismissed on lockscreen
126             tests.add(new NotificationWhenOccludedShowsPrivateTest());
127 
128             tests.add(new SetGlobalVisibilityPrivateStep());
129             // NOW TESTING: redacted globally
130             tests.add(new NotificationWhenLockedShowsRedactedTest());
131             // TODO: Test: notification CAN be dismissed on lockscreen
132             tests.add(new NotificationWhenOccludedShowsRedactedTest());
133 
134             tests.add(new SetGlobalVisibilitySecretStep());
135             // NOW TESTING: notifications do not appear
136             tests.add(new NotificationWhenLockedIsHiddenTest());
137             tests.add(new NotificationWhenOccludedIsHiddenTest());
138 
139             // FINALLY: restore device state
140             tests.add(new SetScreenLockDisabledStep());
141         }
142         return tests;
143     }
144 
createChannels()145     private void createChannels() {
146         NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
147                 NOTIFICATION_CHANNEL_ID, IMPORTANCE_DEFAULT);
148         mNm.createNotificationChannel(channel);
149     }
150 
deleteChannels()151     private void deleteChannels() {
152         mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
153     }
154 
155     @SuppressLint("NewApi")
sendNotification()156     private void sendNotification() {
157         String tag = UUID.randomUUID().toString();
158         long when = System.currentTimeMillis();
159         Log.d(TAG, "Sending: tag=" + tag + " when=" + when);
160 
161         mPackageString = "com.android.cts.verifier";
162 
163         Notification publicVersion = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
164                 .setContentTitle(getString(R.string.np_public_version_text))
165                 .setSmallIcon(R.drawable.ic_stat_alice)
166                 .setWhen(when)
167                 .build();
168         Notification privateVersion = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
169                 .setContentTitle(getString(R.string.np_private_version_text))
170                 .setSmallIcon(R.drawable.ic_stat_alice)
171                 .setWhen(when)
172                 .setPublicVersion(publicVersion)
173                 .build();
174         mNm.notify(tag, NOTIFICATION_ID, privateVersion);
175     }
176 
177     /**
178      * Asks the user to set the lockscreen visibility of the channel to the given value
179      */
180     private abstract class SetChannelLockscreenVisibilityBaseStep extends InteractiveTestCase {
181         @StringRes
182         private final int mInstructionRes;
183         private final int mExpectVisibility;
184         private View mView;
185 
SetChannelLockscreenVisibilityBaseStep(@tringRes int instructionRes, int expectVisibility)186         SetChannelLockscreenVisibilityBaseStep(@StringRes int instructionRes,
187                 int expectVisibility) {
188             mInstructionRes = instructionRes;
189             mExpectVisibility = expectVisibility;
190         }
191 
192         @Override
inflate(ViewGroup parent)193         protected View inflate(ViewGroup parent) {
194             mView = createUserItem(parent, R.string.np_start_channel_settings, mInstructionRes);
195             setButtonsEnabled(mView, false);
196             return mView;
197         }
198 
199         @Override
setUp()200         protected void setUp() {
201             createChannels();
202             status = READY;
203             setButtonsEnabled(mView, true);
204             next();
205         }
206 
207         @Override
autoStart()208         boolean autoStart() {
209             return true;
210         }
211 
212         @Override
test()213         protected void test() {
214             if (getChannelVisibility() == mExpectVisibility) {
215                 status = PASS;
216             } else {
217                 // user hasn't jumped to settings yet
218                 status = WAIT_FOR_USER;
219             }
220 
221             next();
222         }
223 
tearDown()224         protected void tearDown() {
225             deleteChannels();
226             next();
227         }
228 
229         @Override
getIntent()230         protected Intent getIntent() {
231             return new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
232                     .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName())
233                     .putExtra(EXTRA_CHANNEL_ID, NOTIFICATION_CHANNEL_ID);
234         }
235     }
236 
237     private class SetChannelLockscreenVisibilityPrivateStep extends
238             SetChannelLockscreenVisibilityBaseStep {
SetChannelLockscreenVisibilityPrivateStep()239         SetChannelLockscreenVisibilityPrivateStep() {
240             super(R.string.nls_visibility, VISIBILITY_PRIVATE);
241         }
242     }
243 
244     private class SetChannelLockscreenVisibilityPublicStep extends
245             SetChannelLockscreenVisibilityBaseStep {
SetChannelLockscreenVisibilityPublicStep()246         SetChannelLockscreenVisibilityPublicStep() {
247             super(R.string.nls_restore_visibility, VISIBILITY_PUBLIC);
248         }
249     }
250 
251     private abstract class SetScreenLockBaseStep extends InteractiveTestCase {
252         @StringRes
253         private final int mInstructionRes;
254         private final boolean mExpectSecure;
255         private View mView;
256 
SetScreenLockBaseStep(int instructionRes, boolean expectSecure)257         private SetScreenLockBaseStep(int instructionRes, boolean expectSecure) {
258             mInstructionRes = instructionRes;
259             mExpectSecure = expectSecure;
260         }
261 
262         @Override
inflate(ViewGroup parent)263         protected View inflate(ViewGroup parent) {
264             mView = createUserItem(parent, R.string.np_start_security_settings, mInstructionRes);
265             setButtonsEnabled(mView, false);
266             return mView;
267         }
268 
269         @Override
setUp()270         protected void setUp() {
271             status = READY;
272             setButtonsEnabled(mView, true);
273             next();
274         }
275 
276         @Override
autoStart()277         boolean autoStart() {
278             return true;
279         }
280 
281         @Override
test()282         protected void test() {
283             KeyguardManager km = getSystemService(KeyguardManager.class);
284             if (km.isDeviceSecure() == mExpectSecure) {
285                 status = PASS;
286             } else {
287                 status = WAIT_FOR_USER;
288             }
289 
290             next();
291         }
292 
293         @Override
getIntent()294         protected Intent getIntent() {
295             return new Intent(Settings.ACTION_SECURITY_SETTINGS)
296                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
297         }
298     }
299 
300     private class SetScreenLockEnabledStep extends SetScreenLockBaseStep {
SetScreenLockEnabledStep()301         private SetScreenLockEnabledStep() {
302             super(R.string.add_screen_lock, true /* secure */);
303         }
304     }
305 
306     private class SetScreenLockDisabledStep extends SetScreenLockBaseStep {
SetScreenLockDisabledStep()307         private SetScreenLockDisabledStep() {
308             super(R.string.remove_screen_lock, false /* secure */);
309         }
310     }
311 
312     private abstract class SetGlobalVisibilityBaseStep extends InteractiveTestCase {
313         @StringRes
314         private final int mInstructionRes;
315         private final int mExpectVisibility;
316         private View mView;
317 
SetGlobalVisibilityBaseStep(int instructionRes, int expectVisibility)318         private SetGlobalVisibilityBaseStep(int instructionRes, int expectVisibility) {
319             mInstructionRes = instructionRes;
320             mExpectVisibility = expectVisibility;
321         }
322 
323         @Override
inflate(ViewGroup parent)324         protected View inflate(ViewGroup parent) {
325             mView = createUserItem(parent, R.string.np_start_notif_settings, mInstructionRes);
326             setButtonsEnabled(mView, false);
327             return mView;
328         }
329 
330         @Override
setUp()331         protected void setUp() {
332             status = READY;
333             setButtonsEnabled(mView, true);
334             next();
335         }
336 
337         @Override
autoStart()338         boolean autoStart() {
339             return true;
340         }
341 
342         @Override
test()343         protected void test() {
344             KeyguardManager km = getSystemService(KeyguardManager.class);
345             if (!km.isDeviceSecure()) {
346                 // if lockscreen itself not set, this setting won't be available.
347                 status = FAIL;
348             } else if (getGlobalVisibility() == mExpectVisibility) {
349                 status = PASS;
350             } else {
351                 status = WAIT_FOR_USER;
352             }
353 
354             next();
355         }
356 
357         @Override
getIntent()358         protected Intent getIntent() {
359             return new Intent(Settings.ACTION_NOTIFICATION_SETTINGS)
360                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
361         }
362     }
363 
364     private class SetGlobalVisibilityPublicStep extends SetGlobalVisibilityBaseStep {
SetGlobalVisibilityPublicStep()365         private SetGlobalVisibilityPublicStep() {
366             super(R.string.set_global_visibility_public, VISIBILITY_PUBLIC);
367         }
368     }
369 
370     private class SetGlobalVisibilityPrivateStep extends SetGlobalVisibilityBaseStep {
SetGlobalVisibilityPrivateStep()371         private SetGlobalVisibilityPrivateStep() {
372             super(R.string.set_global_visibility_private, VISIBILITY_PRIVATE);
373         }
374     }
375 
376     private class SetGlobalVisibilitySecretStep extends SetGlobalVisibilityBaseStep {
SetGlobalVisibilitySecretStep()377         private SetGlobalVisibilitySecretStep() {
378             super(R.string.set_global_visibility_secret, VISIBILITY_SECRET);
379         }
380     }
381 
382     private class SecureActionOnLockScreenTest extends InteractiveTestCase {
383         private View mView;
384 
385         @Override
setUp()386         protected void setUp() {
387             createChannels();
388             ActionTriggeredReceiver.sendNotification(mContext, true);
389             setButtonsEnabled(mView, true);
390             status = READY;
391             next();
392         }
393 
394         @Override
tearDown()395         protected void tearDown() {
396             mNm.cancelAll();
397             deleteChannels();
398             delay();
399         }
400 
401         @Override
inflate(ViewGroup parent)402         protected View inflate(ViewGroup parent) {
403             mView = createPassFailItem(parent, R.string.secure_action_lockscreen);
404             setButtonsEnabled(mView, false);
405             return mView;
406         }
407 
408         @Override
autoStart()409         boolean autoStart() {
410             return true;
411         }
412 
413         @Override
test()414         protected void test() {
415             status = WAIT_FOR_USER;
416             next();
417         }
418     }
419 
420 
421     private abstract class NotificationPrivacyBaseTest extends InteractiveTestCase {
422         private View mView;
423         @StringRes
424         private final int mInstructionRes;
425 
NotificationPrivacyBaseTest(@tringRes int instructionRes)426         NotificationPrivacyBaseTest(@StringRes int instructionRes) {
427             mInstructionRes = instructionRes;
428         }
429 
430         @Override
setUp()431         protected void setUp() {
432             createChannels();
433             sendNotification();
434             setButtonsEnabled(mView, true);
435             status = READY;
436             next();
437         }
438 
439         @Override
tearDown()440         protected void tearDown() {
441             mNm.cancelAll();
442             deleteChannels();
443             delay();
444         }
445 
446         @Override
inflate(ViewGroup parent)447         protected View inflate(ViewGroup parent) {
448             mView = createPassFailItem(parent, mInstructionRes);
449             setButtonsEnabled(mView, false);
450             return mView;
451         }
452 
453         @Override
autoStart()454         boolean autoStart() {
455             return true;
456         }
457 
458         @Override
test()459         protected void test() {
460             status = WAIT_FOR_USER;
461             next();
462         }
463     }
464 
465     private class NotificationWhenLockedShowsRedactedTest extends NotificationPrivacyBaseTest {
NotificationWhenLockedShowsRedactedTest()466         NotificationWhenLockedShowsRedactedTest() {
467             super(R.string.np_when_locked_see_redacted);
468         }
469     }
470 
471     private class NotificationWhenLockedShowsPrivateTest extends NotificationPrivacyBaseTest {
NotificationWhenLockedShowsPrivateTest()472         NotificationWhenLockedShowsPrivateTest() {
473             super(R.string.np_when_locked_see_private);
474         }
475     }
476 
477     private class NotificationWhenLockedIsHiddenTest extends NotificationPrivacyBaseTest {
NotificationWhenLockedIsHiddenTest()478         NotificationWhenLockedIsHiddenTest() {
479             super(R.string.np_when_locked_hidden);
480         }
481     }
482 
483     private abstract class NotificationWhenOccludedBaseTest extends InteractiveTestCase {
484         private View mView;
485         @StringRes
486         private final int mInstructionRes;
487 
NotificationWhenOccludedBaseTest(@tringRes int instructionRes)488         NotificationWhenOccludedBaseTest(@StringRes int instructionRes) {
489             mInstructionRes = instructionRes;
490         }
491 
492         @Override
setUp()493         protected void setUp() {
494             createChannels();
495             sendNotification();
496             setButtonsEnabled(mView, true);
497             status = READY;
498             next();
499         }
500 
501         @Override
tearDown()502         protected void tearDown() {
503             mNm.cancelAll();
504             deleteChannels();
505             delay();
506         }
507 
508         @Override
inflate(ViewGroup parent)509         protected View inflate(ViewGroup parent) {
510             mView = createUserAndPassFailItem(
511                     parent, R.string.np_start_occluding, R.string.np_occluding_instructions);
512             setButtonsEnabled(mView, false);
513             return mView;
514         }
515 
516         @Override
autoStart()517         boolean autoStart() {
518             return true;
519         }
520 
521         @Override
test()522         protected void test() {
523             status = WAIT_FOR_USER;
524             next();
525         }
526 
527         @Override
getIntent()528         protected Intent getIntent() {
529             return ShowWhenLockedActivity.makeActivityIntent(
530                     getApplicationContext(), getString(mInstructionRes));
531         }
532     }
533 
534     private class NotificationWhenOccludedShowsRedactedTest extends
535             NotificationWhenOccludedBaseTest {
NotificationWhenOccludedShowsRedactedTest()536         NotificationWhenOccludedShowsRedactedTest() {
537             super(R.string.np_occluding_see_redacted);
538         }
539     }
540 
541     private class NotificationWhenOccludedShowsPrivateTest extends
542             NotificationWhenOccludedBaseTest {
NotificationWhenOccludedShowsPrivateTest()543         NotificationWhenOccludedShowsPrivateTest() {
544             super(R.string.np_occluding_see_private);
545         }
546     }
547 
548     private class NotificationWhenOccludedIsHiddenTest extends NotificationWhenOccludedBaseTest {
NotificationWhenOccludedIsHiddenTest()549         NotificationWhenOccludedIsHiddenTest() {
550             super(R.string.np_occluding_hidden);
551         }
552     }
553 
554 }
555