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.car.settings.notifications;
18 
19 import static com.android.internal.notification.NotificationAccessConfirmationActivityContract.EXTRA_COMPONENT_NAME;
20 import static com.android.internal.notification.NotificationAccessConfirmationActivityContract.EXTRA_PACKAGE_TITLE;
21 
22 import static com.google.common.truth.Truth.assertThat;
23 
24 import static org.mockito.Mockito.doReturn;
25 import static org.mockito.Mockito.eq;
26 import static org.mockito.Mockito.spy;
27 import static org.mockito.Mockito.verify;
28 
29 import android.Manifest;
30 import android.app.AlertDialog;
31 import android.app.NotificationManager;
32 import android.car.test.mocks.AndroidMockitoHelper;
33 import android.content.ComponentName;
34 import android.content.Context;
35 import android.content.DialogInterface;
36 import android.content.Intent;
37 import android.content.pm.PackageManager;
38 import android.content.pm.ServiceInfo;
39 
40 import androidx.annotation.Nullable;
41 import androidx.lifecycle.Lifecycle;
42 import androidx.test.core.app.ActivityScenario;
43 import androidx.test.core.app.ApplicationProvider;
44 import androidx.test.ext.junit.runners.AndroidJUnit4;
45 import androidx.test.platform.app.InstrumentationRegistry;
46 
47 import com.android.car.settings.common.ConfirmationDialogFragment;
48 
49 import org.junit.Before;
50 import org.junit.Test;
51 import org.junit.runner.RunWith;
52 import org.mockito.MockitoAnnotations;
53 
54 import java.util.Locale;
55 
56 /**
57  * Unit tests for {@link NotificationAccessConfirmationActivity}
58  */
59 @RunWith(AndroidJUnit4.class)
60 public final class NotificationAccessConfirmationActivityTest {
61     private static final ComponentName TEMP_COMPONENT_NAME = ComponentName.unflattenFromString(
62             "com.temp/com.temp.k");
63 
64     private final Context mContext = ApplicationProvider.getApplicationContext();
65     private ActivityScenario<TestActivity> mActivityScenario;
66     private TestActivity mActivity;
67     private PackageManager mPackageManager;
68     private NotificationManager mNotificationManager;
69 
createServiceInfoForTempComponent(String permission)70     private static ServiceInfo createServiceInfoForTempComponent(String permission) {
71         ServiceInfo info = new ServiceInfo();
72         info.name = TEMP_COMPONENT_NAME.getClassName();
73         info.packageName = TEMP_COMPONENT_NAME.getPackageName();
74         info.permission = permission;
75         return info;
76     }
77 
78     @Before
setup()79     public void setup() {
80         MockitoAnnotations.initMocks(this);
81     }
82 
83     @Test
packageTitleEmpty_finishes()84     public void packageTitleEmpty_finishes() {
85         Intent intent = new Intent(mContext, TestActivity.class)
86                 .putExtra(EXTRA_COMPONENT_NAME, TEMP_COMPONENT_NAME);
87         mActivityScenario = ActivityScenario.launch(intent);
88 
89         assertThat(mActivityScenario.getState()).isEqualTo(Lifecycle.State.DESTROYED);
90     }
91 
92     @Test
packageTitleNonEmpty_showsConfirmationDialog()93     public void packageTitleNonEmpty_showsConfirmationDialog() {
94         launchActivityWithValidIntent();
95 
96         assertThat(mActivityScenario.getState()).isEqualTo(Lifecycle.State.RESUMED);
97     }
98 
99     @Test
onAllow_permissionMissing_finishes()100     public void onAllow_permissionMissing_finishes() throws Exception {
101         launchActivityWithValidIntent();
102         assertThat(mActivityScenario.getState()).isEqualTo(Lifecycle.State.RESUMED);
103         ServiceInfo info = createServiceInfoForTempComponent(/* permission = */ "");
104         doReturn(info).when(mPackageManager).getServiceInfo(info.getComponentName(), 0);
105 
106         AndroidMockitoHelper.syncRunOnUiThread(mActivity, () -> {
107             getConfirmationDialog().getButton(DialogInterface.BUTTON_POSITIVE).performClick();
108         });
109         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
110 
111         // Not asserting DESTROYED state as mActivityScenario.getState() returns STARTED state for
112         // some unknown reason.
113         assertThat(mActivity.mFinishTriggered).isTrue();
114     }
115 
116     @Test
onAllow_permissionAvailable_callsNotificationManager()117     public void onAllow_permissionAvailable_callsNotificationManager() throws Exception {
118         launchActivityWithValidIntent();
119         assertThat(mActivityScenario.getState()).isEqualTo(Lifecycle.State.RESUMED);
120         ServiceInfo info = createServiceInfoForTempComponent(
121                 Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE);
122         doReturn(info).when(mPackageManager).getServiceInfo(info.getComponentName(), 0);
123 
124         AndroidMockitoHelper.syncRunOnUiThread(mActivity, () -> {
125             getConfirmationDialog().getButton(DialogInterface.BUTTON_POSITIVE).performClick();
126         });
127         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
128 
129         verify(mNotificationManager)
130                 .setNotificationListenerAccessGranted(eq(info.getComponentName()), eq(true));
131     }
132 
133     @Test
onDeny_finishes()134     public void onDeny_finishes() throws Exception {
135         launchActivityWithValidIntent();
136         assertThat(mActivityScenario.getState()).isEqualTo(Lifecycle.State.RESUMED);
137 
138         AndroidMockitoHelper.syncRunOnUiThread(mActivity, () -> {
139             getConfirmationDialog().getButton(DialogInterface.BUTTON_NEGATIVE).performClick();
140         });
141         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
142 
143         // Not asserting DESTROYED state as mActivityScenario.getState() returns STARTED state for
144         // some unknown reason.
145         assertThat(mActivity.mFinishTriggered).isTrue();
146     }
147 
getConfirmationDialog()148     private AlertDialog getConfirmationDialog() {
149         return (AlertDialog) ((ConfirmationDialogFragment) mActivity.getSupportFragmentManager()
150                 .findFragmentByTag(ConfirmationDialogFragment.TAG))
151                 .getDialog();
152     }
153 
launchActivityWithValidIntent()154     private void launchActivityWithValidIntent() {
155         Intent intent = new Intent(mContext, TestActivity.class)
156                 .putExtra(EXTRA_COMPONENT_NAME, TEMP_COMPONENT_NAME)
157                 .putExtra(EXTRA_PACKAGE_TITLE, "my_package_title");
158 
159         mActivityScenario = ActivityScenario.launch(intent);
160         mActivityScenario.onActivity(
161                 activity -> {
162                     mActivity = activity;
163                     mPackageManager = spy(mActivity.getPackageManager());
164                     mNotificationManager = spy(
165                             mActivity.getSystemService(NotificationManager.class));
166                     mActivity.setPackageManagerSpy(mPackageManager);
167                     mActivity.setNotificationManagerSpy(mNotificationManager);
168                 });
169     }
170 
171     public static final class TestActivity extends NotificationAccessConfirmationActivity {
172         @Nullable private PackageManager mPackageManagerSpy;
173         @Nullable private NotificationManager mNotificationManagerSpy;
174         boolean mFinishTriggered;
175 
setPackageManagerSpy(PackageManager packageManagerSpy)176         void setPackageManagerSpy(PackageManager packageManagerSpy) {
177             mPackageManagerSpy = packageManagerSpy;
178         }
179 
setNotificationManagerSpy(NotificationManager notificationManagerSpy)180         void setNotificationManagerSpy(NotificationManager notificationManagerSpy) {
181             mNotificationManagerSpy = notificationManagerSpy;
182         }
183 
184         @Override
getPackageManager()185         public PackageManager getPackageManager() {
186             return mPackageManagerSpy != null ? mPackageManagerSpy : super.getPackageManager();
187         }
188 
189         @Override
getSystemService(String name)190         public Object getSystemService(String name) {
191             return mNotificationManagerSpy != null
192                     && name.toLowerCase(Locale.ROOT).contains("notification")
193                     ? mNotificationManagerSpy
194                     : super.getSystemService(name);
195         }
196 
197         @Override
finish()198         public void finish() {
199             mFinishTriggered = true;
200             super.finish();
201         }
202     }
203 }
204