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