1 /* 2 * Copyright (C) 2020 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.app.cts; 18 19 import static android.app.UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES; 20 import static android.app.cts.NotificationManagerTest.toggleListenerAccess; 21 import static android.view.Display.DEFAULT_DISPLAY; 22 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; 23 24 import static com.google.common.truth.Truth.assertThat; 25 26 import static junit.framework.Assert.assertTrue; 27 28 import static org.junit.Assume.assumeTrue; 29 import static org.testng.Assert.assertThrows; 30 31 import android.app.ActivityManager; 32 import android.app.Instrumentation; 33 import android.app.UiAutomation; 34 import android.app.cts.android.app.cts.tools.FutureServiceConnection; 35 import android.app.cts.android.app.cts.tools.NotificationHelper; 36 import android.app.stubs.TestNotificationListener; 37 import android.app.stubs.shared.FakeView; 38 import android.app.stubs.shared.ICloseSystemDialogsTestsService; 39 import android.content.BroadcastReceiver; 40 import android.content.ComponentName; 41 import android.content.ContentResolver; 42 import android.content.Context; 43 import android.content.Intent; 44 import android.content.IntentFilter; 45 import android.hardware.display.DisplayManager; 46 import android.os.Bundle; 47 import android.os.ConditionVariable; 48 import android.os.Handler; 49 import android.os.Looper; 50 import android.os.ResultReceiver; 51 import android.provider.Settings; 52 import android.server.wm.WindowManagerStateHelper; 53 import android.view.Display; 54 import android.view.WindowManager; 55 import android.view.WindowManager.LayoutParams; 56 57 import androidx.test.platform.app.InstrumentationRegistry; 58 import androidx.test.runner.AndroidJUnit4; 59 60 import com.android.compatibility.common.util.SystemUtil; 61 62 import org.junit.After; 63 import org.junit.Before; 64 import org.junit.Test; 65 import org.junit.runner.RunWith; 66 67 import java.util.concurrent.CompletableFuture; 68 import java.util.concurrent.TimeUnit; 69 70 @RunWith(AndroidJUnit4.class) 71 public class CloseSystemDialogsTest { 72 private static final String TEST_SERVICE = 73 "android.app.stubs.shared.CloseSystemDialogsTestService"; 74 private static final String APP_COMPAT_ENABLE = "enable"; 75 private static final String APP_COMPAT_DISABLE = "disable"; 76 private static final String APP_COMPAT_RESET = "reset"; 77 private static final String ACTION_SENTINEL = "sentinel"; 78 private static final String REASON = "test"; 79 private static final long TIMEOUT_MS = 3000; 80 private static final String ACCESSIBILITY_SERVICE = 81 "android.app.stubs.shared.AppAccessibilityService"; 82 83 /** 84 * This test is not self-instrumenting, so we need to bind to the service in the instrumentation 85 * target package (instead of our package). 86 */ 87 private static final String APP_SELF = "android.app.stubs"; 88 89 /** 90 * Use com.android.app1 instead of android.app.stubs because the latter is the target of 91 * instrumentation, hence it also has shell powers for {@link 92 * Intent#ACTION_CLOSE_SYSTEM_DIALOGS} and we don't want those powers under simulation. 93 */ 94 private static final String APP_HELPER = "com.android.app4"; 95 96 private Instrumentation mInstrumentation; 97 private FutureServiceConnection mConnection; 98 private Context mContext; 99 private ContentResolver mResolver; 100 private ICloseSystemDialogsTestsService mService; 101 private volatile WindowManager mSawWindowManager; 102 private volatile Context mSawContext; 103 private volatile CompletableFuture<Void> mCloseSystemDialogsReceived; 104 private volatile ConditionVariable mSentinelReceived; 105 private volatile FakeView mFakeView; 106 private WindowManagerStateHelper mWindowState; 107 private IntentReceiver mIntentReceiver; 108 private Handler mMainHandler; 109 private TestNotificationListener mNotificationListener; 110 private NotificationHelper mNotificationHelper; 111 private String mPreviousHiddenApiPolicy; 112 private String mPreviousAccessibilityServices; 113 private String mPreviousAccessibilityEnabled; 114 private boolean mResetAccessibility; 115 116 117 @Before setUp()118 public void setUp() throws Exception { 119 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 120 mContext = mInstrumentation.getTargetContext(); 121 mResolver = mContext.getContentResolver(); 122 mMainHandler = new Handler(Looper.getMainLooper()); 123 toggleListenerAccess(mContext, true); 124 mNotificationListener = TestNotificationListener.getInstance(); 125 mNotificationHelper = new NotificationHelper(mContext, () -> mNotificationListener); 126 mWindowState = new WindowManagerStateHelper(); 127 enableUserFinal(); 128 129 // We need to test that a few hidden APIs are properly protected in the helper app. The 130 // helper app we're using doesn't have the checks disabled because it's not the target of 131 // instrumentation, see comment on APP_HELPER for details. 132 mPreviousHiddenApiPolicy = setHiddenApiPolicy("1"); 133 134 // Add a receiver that will verify if the intent was sent or not 135 mIntentReceiver = new IntentReceiver(); 136 mCloseSystemDialogsReceived = new CompletableFuture<>(); 137 IntentFilter filter = new IntentFilter(); 138 filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 139 filter.addAction(ACTION_SENTINEL); 140 mContext.registerReceiver(mIntentReceiver, filter); 141 142 // Add a view to verify if the view got the callback or not 143 mSawContext = getContextForSaw(mContext); 144 mSawWindowManager = mSawContext.getSystemService(WindowManager.class); 145 mMainHandler.post(() -> { 146 mFakeView = new FakeView(mSawContext); 147 mSawWindowManager.addView(mFakeView, new LayoutParams(TYPE_APPLICATION_OVERLAY)); 148 }); 149 } 150 151 @After tearDown()152 public void tearDown() throws Exception { 153 if (mConnection != null) { 154 mContext.unbindService(mConnection); 155 } 156 if (mResetAccessibility) { 157 setAccessibilityState(mPreviousAccessibilityEnabled, mPreviousAccessibilityServices); 158 } 159 mMainHandler.post(() -> mSawWindowManager.removeViewImmediate(mFakeView)); 160 mContext.unregisterReceiver(mIntentReceiver); 161 resetUserFinal(); 162 setHiddenApiPolicy(mPreviousHiddenApiPolicy); 163 compat(APP_COMPAT_RESET, ActivityManager.LOCK_DOWN_CLOSE_SYSTEM_DIALOGS, APP_HELPER); 164 compat(APP_COMPAT_RESET, "NOTIFICATION_TRAMPOLINE_BLOCK", APP_HELPER); 165 mNotificationListener.resetData(); 166 } 167 168 /** Intent.ACTION_CLOSE_SYSTEM_DIALOGS */ 169 170 @Test testCloseSystemDialogs_whenTargetSdkCurrent_isBlockedAndThrows()171 public void testCloseSystemDialogs_whenTargetSdkCurrent_isBlockedAndThrows() throws Exception { 172 setTargetCurrent(); 173 mService = getService(APP_HELPER); 174 175 assertThrows(SecurityException.class, () -> mService.sendCloseSystemDialogsBroadcast()); 176 177 assertCloseSystemDialogsNotReceived(); 178 } 179 180 @Test testCloseSystemDialogs_whenTargetSdk30_isBlockedButDoesNotThrow()181 public void testCloseSystemDialogs_whenTargetSdk30_isBlockedButDoesNotThrow() throws Exception { 182 mService = getService(APP_HELPER); 183 184 mService.sendCloseSystemDialogsBroadcast(); 185 186 assertCloseSystemDialogsNotReceived(); 187 } 188 189 @Test testCloseSystemDialogs_whenTestInstrumentedViaShell_isSent()190 public void testCloseSystemDialogs_whenTestInstrumentedViaShell_isSent() throws Exception { 191 mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); 192 193 assertCloseSystemDialogsReceived(); 194 } 195 196 @Test testCloseSystemDialogs_whenRunningAsShell_isSent()197 public void testCloseSystemDialogs_whenRunningAsShell_isSent() throws Exception { 198 SystemUtil.runWithShellPermissionIdentity( 199 () -> mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))); 200 201 assertCloseSystemDialogsReceived(); 202 } 203 204 @Test testCloseSystemDialogs_inTrampolineWhenTargetSdkCurrent_isBlockedAndThrows()205 public void testCloseSystemDialogs_inTrampolineWhenTargetSdkCurrent_isBlockedAndThrows() 206 throws Exception { 207 setTargetCurrent(); 208 int notificationId = 42; 209 CompletableFuture<Integer> result = new CompletableFuture<>(); 210 mService = getService(APP_HELPER); 211 212 mService.postNotification(notificationId, new FutureReceiver(result), 213 /* usePendingIntent */ false); 214 215 mNotificationHelper.clickNotification(notificationId, /* searchAll */ true); 216 assertThat(result.get()).isEqualTo( 217 ICloseSystemDialogsTestsService.RESULT_SECURITY_EXCEPTION); 218 assertCloseSystemDialogsNotReceived(); 219 } 220 221 @Test testCloseSystemDialogs_inTrampolineWhenTargetSdk30_isSent()222 public void testCloseSystemDialogs_inTrampolineWhenTargetSdk30_isSent() throws Exception { 223 int notificationId = 43; 224 CompletableFuture<Integer> result = new CompletableFuture<>(); 225 mService = getService(APP_HELPER); 226 227 mService.postNotification(notificationId, new FutureReceiver(result), 228 /* usePendingIntent */ false); 229 230 mNotificationHelper.clickNotification(notificationId, /* searchAll */ true); 231 assertThat(result.get()).isEqualTo(ICloseSystemDialogsTestsService.RESULT_OK); 232 assertCloseSystemDialogsReceived(); 233 } 234 235 /** System doesn't throw on the PI's sender call stack. */ 236 @Test testCloseSystemDialogs_inTrampolineViaPendingIntentWhenTargetSdkCurrent_isBlocked()237 public void testCloseSystemDialogs_inTrampolineViaPendingIntentWhenTargetSdkCurrent_isBlocked() 238 throws Exception { 239 setTargetCurrent(); 240 int notificationId = 44; 241 CompletableFuture<Integer> result = new CompletableFuture<>(); 242 mService = getService(APP_HELPER); 243 244 mService.postNotification(notificationId, new FutureReceiver(result), 245 /* usePendingIntent */ true); 246 247 mNotificationHelper.clickNotification(notificationId, /* searchAll */ true); 248 assertThat(result.get()).isEqualTo(ICloseSystemDialogsTestsService.RESULT_OK); 249 assertCloseSystemDialogsNotReceived(); 250 } 251 252 @Test testCloseSystemDialogs_inTrampolineViaPendingIntentWhenTargetSdk30_isSent()253 public void testCloseSystemDialogs_inTrampolineViaPendingIntentWhenTargetSdk30_isSent() 254 throws Exception { 255 int notificationId = 45; 256 CompletableFuture<Integer> result = new CompletableFuture<>(); 257 mService = getService(APP_HELPER); 258 259 mService.postNotification(notificationId, new FutureReceiver(result), 260 /* usePendingIntent */ true); 261 262 mNotificationHelper.clickNotification(notificationId, /* searchAll */ true); 263 assertThat(result.get()).isEqualTo(ICloseSystemDialogsTestsService.RESULT_OK); 264 assertCloseSystemDialogsReceived(); 265 } 266 267 @Test testCloseSystemDialogs_withWindowAboveShadeAndTargetSdk30_isSent()268 public void testCloseSystemDialogs_withWindowAboveShadeAndTargetSdk30_isSent() 269 throws Exception { 270 // Test is only applicable to devices that have a notification shade. 271 assumeTrue(mWindowState.hasNotificationShade()); 272 mService = getService(APP_HELPER); 273 setAccessibilityService(APP_HELPER, ACCESSIBILITY_SERVICE); 274 assertTrue(mService.waitForAccessibilityServiceWindow(TIMEOUT_MS)); 275 276 mService.sendCloseSystemDialogsBroadcast(); 277 278 assertCloseSystemDialogsReceived(); 279 } 280 281 /** IWindowManager.closeSystemDialogs() */ 282 283 @Test testCloseSystemDialogsViaWindowManager_whenTestInstrumentedViaShell_isSent()284 public void testCloseSystemDialogsViaWindowManager_whenTestInstrumentedViaShell_isSent() 285 throws Exception { 286 mService = getService(APP_SELF); 287 288 mService.closeSystemDialogsViaWindowManager(REASON); 289 290 assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(REASON); 291 } 292 293 @Test testCloseSystemDialogsViaWindowManager_whenRunningAsShell_isSent()294 public void testCloseSystemDialogsViaWindowManager_whenRunningAsShell_isSent() 295 throws Exception { 296 mService = getService(APP_SELF); 297 298 SystemUtil.runWithShellPermissionIdentity( 299 () -> mService.closeSystemDialogsViaWindowManager(REASON)); 300 301 assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(REASON); 302 } 303 304 @Test testCloseSystemDialogsViaWindowManager_whenTargetSdkCurrent_isBlockedAndThrows()305 public void testCloseSystemDialogsViaWindowManager_whenTargetSdkCurrent_isBlockedAndThrows() 306 throws Exception { 307 setTargetCurrent(); 308 mService = getService(APP_HELPER); 309 310 assertThrows(SecurityException.class, 311 () -> mService.closeSystemDialogsViaWindowManager(REASON)); 312 313 assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(null); 314 } 315 316 317 @Test testCloseSystemDialogsViaWindowManager_whenTargetSdk30_isBlockedButDoesNotThrow()318 public void testCloseSystemDialogsViaWindowManager_whenTargetSdk30_isBlockedButDoesNotThrow() 319 throws Exception { 320 mService = getService(APP_HELPER); 321 322 mService.closeSystemDialogsViaWindowManager(REASON); 323 324 assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(null); 325 } 326 327 /** IActivityManager.closeSystemDialogs() */ 328 329 @Test testCloseSystemDialogsViaActivityManager_whenTestInstrumentedViaShell_isSent()330 public void testCloseSystemDialogsViaActivityManager_whenTestInstrumentedViaShell_isSent() 331 throws Exception { 332 mService = getService(APP_SELF); 333 334 mService.closeSystemDialogsViaActivityManager(REASON); 335 336 assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(REASON); 337 assertCloseSystemDialogsReceived(); 338 } 339 340 @Test testCloseSystemDialogsViaActivityManager_whenRunningAsShell_isSent()341 public void testCloseSystemDialogsViaActivityManager_whenRunningAsShell_isSent() 342 throws Exception { 343 mService = getService(APP_SELF); 344 345 SystemUtil.runWithShellPermissionIdentity( 346 () -> mService.closeSystemDialogsViaActivityManager(REASON)); 347 348 assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(REASON); 349 assertCloseSystemDialogsReceived(); 350 } 351 352 @Test testCloseSystemDialogsViaActivityManager_whenTargetSdkCurrent_isBlockedAndThrows()353 public void testCloseSystemDialogsViaActivityManager_whenTargetSdkCurrent_isBlockedAndThrows() 354 throws Exception { 355 setTargetCurrent(); 356 mService = getService(APP_HELPER); 357 358 assertThrows(SecurityException.class, 359 () -> mService.closeSystemDialogsViaActivityManager(REASON)); 360 361 assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(null); 362 assertCloseSystemDialogsNotReceived(); 363 } 364 365 @Test testCloseSystemDialogsViaActivityManager_whenTargetSdk30_isBlockedButDoesNotThrow()366 public void testCloseSystemDialogsViaActivityManager_whenTargetSdk30_isBlockedButDoesNotThrow() 367 throws Exception { 368 mService = getService(APP_HELPER); 369 370 mService.closeSystemDialogsViaActivityManager(REASON); 371 372 assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(null); 373 assertCloseSystemDialogsNotReceived(); 374 } 375 setTargetCurrent()376 private void setTargetCurrent() { 377 // The helper app has targetSdk=30, opting-in to changes emulates targeting latest sdk. 378 compat(APP_COMPAT_ENABLE, ActivityManager.LOCK_DOWN_CLOSE_SYSTEM_DIALOGS, APP_HELPER); 379 compat(APP_COMPAT_ENABLE, "NOTIFICATION_TRAMPOLINE_BLOCK", APP_HELPER); 380 } 381 assertCloseSystemDialogsNotReceived()382 private void assertCloseSystemDialogsNotReceived() { 383 // If both broadcasts are sent, they will be received in order here since they are both 384 // registered receivers in the "bg" queue in system_server and belong to the same app. 385 // This is guaranteed by a series of handlers that are the same in both cases and due to the 386 // fact that the binder that system_server uses to call into the app is the same (since the 387 // app is the same) and one-way calls on the same binder object are ordered. 388 mSentinelReceived = new ConditionVariable(false); 389 Intent intent = new Intent(ACTION_SENTINEL); 390 intent.setPackage(mContext.getPackageName()); 391 mContext.sendBroadcast(intent); 392 mSentinelReceived.block(); 393 assertThat(mCloseSystemDialogsReceived.isDone()).isFalse(); 394 } 395 assertCloseSystemDialogsReceived()396 private void assertCloseSystemDialogsReceived() throws Exception { 397 mCloseSystemDialogsReceived.get(TIMEOUT_MS, TimeUnit.MILLISECONDS); 398 // No TimeoutException thrown 399 } 400 getService(String packageName)401 private ICloseSystemDialogsTestsService getService(String packageName) throws Exception { 402 ICloseSystemDialogsTestsService service = 403 ICloseSystemDialogsTestsService.Stub.asInterface( 404 connect(packageName).get(TIMEOUT_MS)); 405 assertTrue("Can't call @hide methods", service.waitUntilReady(TIMEOUT_MS)); 406 return service; 407 } 408 connect(String packageName)409 private FutureServiceConnection connect(String packageName) { 410 if (mConnection != null) { 411 return mConnection; 412 } 413 mConnection = new FutureServiceConnection(); 414 Intent intent = new Intent(); 415 intent.setComponent(ComponentName.createRelative(packageName, TEST_SERVICE)); 416 assertTrue(mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)); 417 return mConnection; 418 } 419 setHiddenApiPolicy(String policy)420 private String setHiddenApiPolicy(String policy) throws Exception { 421 return SystemUtil.callWithShellPermissionIdentity(() -> { 422 String previous = Settings.Global.getString(mResolver, 423 Settings.Global.HIDDEN_API_POLICY); 424 Settings.Global.putString(mResolver, Settings.Global.HIDDEN_API_POLICY, policy); 425 return previous; 426 }); 427 } 428 setAccessibilityService(String packageName, String service)429 private void setAccessibilityService(String packageName, String service) throws Exception { 430 setAccessibilityState("1", packageName + "/" + service); 431 } 432 setAccessibilityState(String enabled, String services)433 private void setAccessibilityState(String enabled, String services) { 434 mResetAccessibility = true; 435 UiAutomation uiAutomation = mInstrumentation.getUiAutomation( 436 FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES); 437 SystemUtil.runWithShellPermissionIdentity(uiAutomation, () -> { 438 mPreviousAccessibilityServices = Settings.Secure.getString(mResolver, 439 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); 440 mPreviousAccessibilityEnabled = Settings.Secure.getString(mResolver, 441 Settings.Secure.ACCESSIBILITY_ENABLED); 442 Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, 443 services); 444 Settings.Secure.putString(mResolver, Settings.Secure.ACCESSIBILITY_ENABLED, enabled); 445 }); 446 } 447 enableUserFinal()448 private static void enableUserFinal() { 449 SystemUtil.runShellCommand( 450 "settings put global force_non_debuggable_final_build_for_compat 1"); 451 } 452 resetUserFinal()453 private static void resetUserFinal() { 454 SystemUtil.runShellCommand( 455 "settings put global force_non_debuggable_final_build_for_compat 0"); 456 } 457 compat(String command, String changeId, String packageName)458 private static void compat(String command, String changeId, String packageName) { 459 SystemUtil.runShellCommand( 460 String.format("am compat %s %s %s", command, changeId, packageName)); 461 } 462 compat(String command, long changeId, String packageName)463 private static void compat(String command, long changeId, String packageName) { 464 compat(command, Long.toString(changeId), packageName); 465 } 466 getContextForSaw(Context context)467 private static Context getContextForSaw(Context context) { 468 DisplayManager displayManager = context.getSystemService(DisplayManager.class); 469 Display display = displayManager.getDisplay(DEFAULT_DISPLAY); 470 Context displayContext = context.createDisplayContext(display); 471 return displayContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null); 472 } 473 474 private class IntentReceiver extends BroadcastReceiver { 475 @Override onReceive(Context context, Intent intent)476 public void onReceive(Context context, Intent intent) { 477 switch (intent.getAction()) { 478 case Intent.ACTION_CLOSE_SYSTEM_DIALOGS: 479 mCloseSystemDialogsReceived.complete(null); 480 break; 481 case ACTION_SENTINEL: 482 mSentinelReceived.open(); 483 break; 484 } 485 } 486 } 487 488 private class FutureReceiver extends ResultReceiver { 489 private final CompletableFuture<Integer> mFuture; 490 FutureReceiver(CompletableFuture<Integer> future)491 FutureReceiver(CompletableFuture<Integer> future) { 492 super(mMainHandler); 493 mFuture = future; 494 } 495 496 @Override onReceiveResult(int resultCode, Bundle resultData)497 protected void onReceiveResult(int resultCode, Bundle resultData) { 498 mFuture.complete(resultCode); 499 } 500 } 501 } 502