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 com.android.car.pm; 18 19 import static androidx.car.app.activity.CarAppActivity.ACTION_SHOW_DIALOG; 20 import static androidx.car.app.activity.CarAppActivity.ACTION_START_SECOND_INSTANCE; 21 import static androidx.car.app.activity.CarAppActivity.SECOND_INSTANCE_TITLE; 22 23 import static com.google.common.truth.Truth.assertThat; 24 25 import static org.junit.Assert.assertNotNull; 26 import static org.junit.Assume.assumeTrue; 27 28 import android.app.Activity; 29 import android.app.ActivityOptions; 30 import android.app.AlertDialog; 31 import android.app.UiAutomation; 32 import android.car.Car; 33 import android.car.content.pm.CarPackageManager; 34 import android.car.drivingstate.CarDrivingStateEvent; 35 import android.car.drivingstate.CarDrivingStateManager; 36 import android.content.ComponentName; 37 import android.content.Context; 38 import android.content.Intent; 39 import android.os.Bundle; 40 import android.support.test.uiautomator.By; 41 import android.support.test.uiautomator.Configurator; 42 import android.support.test.uiautomator.UiDevice; 43 import android.support.test.uiautomator.UiObject2; 44 import android.support.test.uiautomator.Until; 45 import android.view.Display; 46 47 import androidx.car.app.activity.CarAppActivity; 48 import androidx.test.InstrumentationRegistry; 49 import androidx.test.ext.junit.runners.AndroidJUnit4; 50 import androidx.test.filters.MediumTest; 51 52 import org.junit.After; 53 import org.junit.Before; 54 import org.junit.Test; 55 import org.junit.runner.RunWith; 56 57 import java.util.concurrent.CopyOnWriteArrayList; 58 import java.util.concurrent.CountDownLatch; 59 import java.util.concurrent.TimeUnit; 60 61 @RunWith(AndroidJUnit4.class) 62 @MediumTest 63 public class CarPackageManagerServiceTest { 64 private static final String ACTIVITY_BLOCKING_ACTIVITY_TEXTVIEW_ID = 65 "com.android.systemui:id/blocking_text"; 66 private static final String ACTIVITY_BLOCKING_ACTIVITY_EXIT_BUTTON_ID = 67 "com.android.systemui:id/exit_button"; 68 69 // cf_x86_auto is very slow, so uses very long timeout. 70 private static final int UI_TIMEOUT_MS = 20_000; 71 private static final int NOT_FOUND_UI_TIMEOUT_MS = 10_000; 72 private static final long ACTIVITY_TIMEOUT_MS = 5000; 73 private static final int HOME_DISPLAYED_TIMEOUT_MS = 5_000; 74 75 private CarDrivingStateManager mCarDrivingStateManager; 76 private CarPackageManager mCarPackageManager; 77 78 private UiDevice mDevice; 79 80 private static final CopyOnWriteArrayList<TempActivity> sTestingActivities = 81 new CopyOnWriteArrayList<>(); 82 83 @Before setUp()84 public void setUp() throws Exception { 85 Car car = Car.createCar(getContext()); 86 mCarDrivingStateManager = (CarDrivingStateManager) 87 car.getCarManager(Car.CAR_DRIVING_STATE_SERVICE); 88 mCarPackageManager = (CarPackageManager) 89 car.getCarManager(Car.PACKAGE_SERVICE); 90 assertNotNull(mCarDrivingStateManager); 91 92 Configurator.getInstance() 93 .setUiAutomationFlags(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES); 94 mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 95 ensureHomeIsDisplayed(); 96 setDrivingStateMoving(); 97 } 98 99 @After tearDown()100 public void tearDown() throws Exception { 101 setDrivingStateParked(); 102 103 for (TempActivity testingActivity : sTestingActivities) { 104 testingActivity.finishCompletely(); 105 } 106 } 107 108 @Test testBlockingActivity_doActivity_isNotBlocked()109 public void testBlockingActivity_doActivity_isNotBlocked() throws Exception { 110 startDoActivity(/* extra= */ null); 111 112 assertThat(mDevice.wait(Until.findObject(By.text( 113 DoActivity.class.getSimpleName())), 114 UI_TIMEOUT_MS)).isNotNull(); 115 assertBlockingActivityNotFound(); 116 } 117 118 @Test testBlockingActivity_doActivity_showingDialog_isNotBlocked()119 public void testBlockingActivity_doActivity_showingDialog_isNotBlocked() throws Exception { 120 startDoActivity(DoActivity.INTENT_EXTRA_SHOW_DIALOG); 121 122 assertThat(mDevice.wait(Until.findObject(By.text( 123 DoActivity.DIALOG_TITLE)), 124 UI_TIMEOUT_MS)).isNotNull(); 125 assertBlockingActivityNotFound(); 126 } 127 128 @Test testBlockingActivity_doTemplateActivity_isNotBlocked()129 public void testBlockingActivity_doTemplateActivity_isNotBlocked() throws Exception { 130 startActivity(toComponentName(getTestContext(), CarAppActivity.class)); 131 132 assertThat(mDevice.wait(Until.findObject(By.text( 133 CarAppActivity.class.getSimpleName())), 134 UI_TIMEOUT_MS)).isNotNull(); 135 assertBlockingActivityNotFound(); 136 } 137 138 @Test testBlockingActivity_multipleDoTemplateActivity_notBlocked()139 public void testBlockingActivity_multipleDoTemplateActivity_notBlocked() throws Exception { 140 startActivity(toComponentName(getTestContext(), CarAppActivity.class)); 141 assertThat(mDevice.wait(Until.findObject(By.text( 142 CarAppActivity.class.getSimpleName())), 143 UI_TIMEOUT_MS)).isNotNull(); 144 getContext().sendBroadcast(new Intent().setAction(ACTION_START_SECOND_INSTANCE) 145 .setPackage(getTestContext().getPackageName())); 146 assertThat(mDevice.wait(Until.findObject(By.text( 147 SECOND_INSTANCE_TITLE)), 148 UI_TIMEOUT_MS)).isNotNull(); 149 assertBlockingActivityNotFound(); 150 } 151 152 @Test testBlockingActivity_doTemplateActivity_showingDialog_isBlocked()153 public void testBlockingActivity_doTemplateActivity_showingDialog_isBlocked() throws Exception { 154 startActivity(toComponentName(getTestContext(), CarAppActivity.class)); 155 assertThat(mDevice.wait(Until.findObject(By.text( 156 CarAppActivity.class.getSimpleName())), 157 UI_TIMEOUT_MS)).isNotNull(); 158 assertBlockingActivityNotFound(); 159 assertThat(mCarPackageManager.isActivityDistractionOptimized( 160 getTestContext().getPackageName(), 161 CarAppActivity.class.getName() 162 )).isTrue(); 163 164 getContext().sendBroadcast(new Intent().setAction(ACTION_SHOW_DIALOG) 165 .setPackage(getTestContext().getPackageName())); 166 167 assertThat(mDevice.wait(Until.findObject(By.res(ACTIVITY_BLOCKING_ACTIVITY_TEXTVIEW_ID)), 168 UI_TIMEOUT_MS)).isNotNull(); 169 assertThat(mCarPackageManager.isActivityDistractionOptimized( 170 getTestContext().getPackageName(), 171 CarAppActivity.class.getName() 172 )).isFalse(); 173 } 174 175 @Test testBlockingActivity_nonDoActivity_isBlocked()176 public void testBlockingActivity_nonDoActivity_isBlocked() throws Exception { 177 startNonDoActivity(NonDoActivity.EXTRA_DO_NOTHING); 178 179 // The label should be 'Close app' since NonDoActivity is the root task. 180 assertBlockingActivityFoundAndExit("Close app"); 181 182 // To exit ABA will close nonDoActivity. 183 assertBlockingActivityNotFound(); 184 assertThat(mDevice.wait(Until.findObject(By.text(NonDoActivity.class.getSimpleName())), 185 UI_TIMEOUT_MS)).isNull(); 186 } 187 188 @Test testBlockingActivity_DoLaunchesNonDoOnCreate_isBlocked()189 public void testBlockingActivity_DoLaunchesNonDoOnCreate_isBlocked() throws Exception { 190 startDoActivity(DoActivity.INTENT_EXTRA_LAUNCH_NONDO); 191 192 // The label should be 'Back' since NonDo's root task is DO. 193 assertBlockingActivityFoundAndExit("Back"); 194 195 // To exit ABA will show the root task, DoActivity. 196 assertBlockingActivityNotFound(); 197 assertThat(mDevice.wait(Until.findObject(By.text(DoActivity.class.getSimpleName())), 198 UI_TIMEOUT_MS)).isNotNull(); 199 } 200 201 @Test testBlockingActivity_DoLaunchesNonDo_nonDoIsKilled_noBlockingActivity()202 public void testBlockingActivity_DoLaunchesNonDo_nonDoIsKilled_noBlockingActivity() 203 throws Exception { 204 startDoActivity(DoActivity.INTENT_EXTRA_LAUNCH_NONDO_NEW_TASK); 205 assertBlockingActivityFound(); 206 207 for (TempActivity activity : sTestingActivities) { 208 if (activity instanceof NonDoActivity) { 209 activity.finishCompletely(); 210 sTestingActivities.remove(activity); 211 } 212 } 213 214 assertBlockingActivityNotFound(); 215 // After NonDo & ABA finishes, DoActivity will come to the top. 216 assertActivityLaunched(DoActivity.class.getSimpleName()); 217 } 218 219 @Test testBlockingActivity_nonDoFinishesOnCreate_noBlockingActivity()220 public void testBlockingActivity_nonDoFinishesOnCreate_noBlockingActivity() 221 throws Exception { 222 startNonDoActivity(NonDoActivity.EXTRA_ONCREATE_FINISH_IMMEDIATELY); 223 224 assertBlockingActivityNotFound(); 225 } 226 227 @Test testBlockingActivity_nonDoLaunchesDoOnCreate_noBlockingActivity()228 public void testBlockingActivity_nonDoLaunchesDoOnCreate_noBlockingActivity() 229 throws Exception { 230 startNonDoActivity(NonDoActivity.EXTRA_ONCREATE_LAUNCH_DO_IMMEDIATELY); 231 232 assertBlockingActivityNotFound(); 233 } 234 235 @Test testBlockingActivity_nonDoFinishesOnResume_noBlockingActivity()236 public void testBlockingActivity_nonDoFinishesOnResume_noBlockingActivity() 237 throws Exception { 238 startNonDoActivity(NonDoActivity.EXTRA_ONRESUME_FINISH_IMMEDIATELY); 239 240 assertBlockingActivityNotFound(); 241 } 242 243 @Test testBlockingActivity_nonDoLaunchesDoOnResume_noBlockingActivity()244 public void testBlockingActivity_nonDoLaunchesDoOnResume_noBlockingActivity() 245 throws Exception { 246 startNonDoActivity(NonDoActivity.EXTRA_ONRESUME_LAUNCH_DO_IMMEDIATELY); 247 248 assertBlockingActivityNotFound(); 249 } 250 251 @Test testBlockingActivity_nonDoNoHistory_isBlocked()252 public void testBlockingActivity_nonDoNoHistory_isBlocked() throws Exception { 253 startActivity(toComponentName(getTestContext(), NonDoNoHistoryActivity.class)); 254 255 assertThat(mDevice.wait(Until.findObject(By.res(ACTIVITY_BLOCKING_ACTIVITY_TEXTVIEW_ID)), 256 UI_TIMEOUT_MS)).isNotNull(); 257 } 258 259 @Test testIsActivityBackedBySafeActivity_notMoving_nonDoActivity_returnsTrue()260 public void testIsActivityBackedBySafeActivity_notMoving_nonDoActivity_returnsTrue() 261 throws Exception { 262 setDrivingStateParked(); 263 264 startNonDoActivity(NonDoActivity.EXTRA_DO_NOTHING); 265 assertActivityLaunched(NonDoActivity.class.getSimpleName()); 266 267 ComponentName nonDoActivity = toComponentName(getTestContext(), NonDoActivity.class); 268 assertThat(mCarPackageManager.isActivityBackedBySafeActivity(nonDoActivity)).isTrue(); 269 } 270 271 @Test testIsActivityBackedBySafeActivity_moving_rootNonDoActivity_returnsFalse()272 public void testIsActivityBackedBySafeActivity_moving_rootNonDoActivity_returnsFalse() 273 throws Exception { 274 startNonDoActivity(NonDoActivity.EXTRA_DO_NOTHING); 275 assertBlockingActivityFound(); 276 277 ComponentName nonDoActivity = toComponentName(getTestContext(), NonDoActivity.class); 278 assertThat(mCarPackageManager.isActivityBackedBySafeActivity(nonDoActivity)).isFalse(); 279 } 280 281 @Test testIsActivityBackedBySafeActivity_moving_nonDoActivityBackedByDo_returnsTrue()282 public void testIsActivityBackedBySafeActivity_moving_nonDoActivityBackedByDo_returnsTrue() 283 throws Exception { 284 startDoActivity(DoActivity.INTENT_EXTRA_LAUNCH_NONDO); 285 // DoActivity will launch NonDoActivity consecutively. 286 assertBlockingActivityFound(); 287 288 ComponentName nonDoActivity = toComponentName(getTestContext(), NonDoActivity.class); 289 assertThat(mCarPackageManager.isActivityBackedBySafeActivity(nonDoActivity)).isTrue(); 290 } 291 292 @Test testIsActivityBackedBySafeActivity_moving_doActivity_returnsFalse()293 public void testIsActivityBackedBySafeActivity_moving_doActivity_returnsFalse() 294 throws Exception { 295 startDoActivity(/* extra= */ null); 296 assertActivityLaunched(DoActivity.class.getSimpleName()); 297 298 ComponentName doActivity = toComponentName(getTestContext(), DoActivity.class); 299 assertThat(mCarPackageManager.isActivityBackedBySafeActivity(doActivity)).isFalse(); 300 } 301 assertBlockingActivityNotFound()302 private void assertBlockingActivityNotFound() { 303 assertThat(mDevice.wait(Until.gone(By.res(ACTIVITY_BLOCKING_ACTIVITY_TEXTVIEW_ID)), 304 NOT_FOUND_UI_TIMEOUT_MS)).isNotNull(); 305 } 306 assertBlockingActivityFound()307 private void assertBlockingActivityFound() { 308 assertThat(mDevice.wait(Until.findObject(By.res(ACTIVITY_BLOCKING_ACTIVITY_TEXTVIEW_ID)), 309 UI_TIMEOUT_MS)).isNotNull(); 310 } 311 assertBlockingActivityFoundAndExit(String exitLabel)312 private void assertBlockingActivityFoundAndExit(String exitLabel) { 313 assertBlockingActivityFound(); 314 UiObject2 button = mDevice.findObject(By.res(ACTIVITY_BLOCKING_ACTIVITY_EXIT_BUTTON_ID)); 315 // TODO(b/200948830): Make the test not to compare the text directly. 316 assertThat(button.getText()).isEqualTo(exitLabel); 317 318 button.click(); 319 } 320 assertActivityLaunched(String title)321 private void assertActivityLaunched(String title) { 322 assertThat(mDevice.wait(Until.findObject(By.text(title)), UI_TIMEOUT_MS)).isNotNull(); 323 } 324 startActivity(ComponentName name)325 private void startActivity(ComponentName name) { 326 Intent intent = new Intent(); 327 intent.setComponent(name); 328 startActivity(intent); 329 } 330 startActivity(Intent intent)331 private void startActivity(Intent intent) { 332 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 333 ActivityOptions options = ActivityOptions.makeBasic(); 334 options.setLaunchDisplayId(Display.DEFAULT_DISPLAY); 335 getContext().startActivity(intent, options.toBundle()); 336 } 337 startNonDoActivity(int firstActionFlag)338 private void startNonDoActivity(int firstActionFlag) { 339 ComponentName activity = toComponentName(getTestContext(), NonDoActivity.class); 340 Intent intent = new Intent(); 341 intent.setComponent(activity); 342 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 343 intent.putExtra(NonDoActivity.EXTRA_FIRST_ACTION, firstActionFlag); 344 345 ActivityOptions options = ActivityOptions.makeBasic(); 346 options.setLaunchDisplayId(Display.DEFAULT_DISPLAY); 347 348 getContext().startActivity(intent, options.toBundle()); 349 } 350 startDoActivity(String extra)351 private void startDoActivity(String extra) { 352 Intent intent = new Intent() 353 .setComponent(toComponentName(getTestContext(), DoActivity.class)) 354 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 355 if (extra != null) { 356 intent.putExtra(extra, true); 357 } 358 startActivity(intent); 359 } 360 ensureHomeIsDisplayed()361 private void ensureHomeIsDisplayed() { 362 mDevice.pressHome(); 363 final String launcherPackage = mDevice.getLauncherPackageName(); 364 assertNotNull(launcherPackage); 365 366 assumeTrue("Home is not displayed even after " + HOME_DISPLAYED_TIMEOUT_MS + "ms.", 367 mDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), 368 HOME_DISPLAYED_TIMEOUT_MS)); 369 } 370 setDrivingStateMoving()371 private void setDrivingStateMoving() { 372 mCarDrivingStateManager.injectDrivingState(CarDrivingStateEvent.DRIVING_STATE_MOVING); 373 } 374 setDrivingStateParked()375 private void setDrivingStateParked() { 376 mCarDrivingStateManager.injectDrivingState(CarDrivingStateEvent.DRIVING_STATE_PARKED); 377 } 378 toComponentName(Context ctx, Class<?> cls)379 private static ComponentName toComponentName(Context ctx, Class<?> cls) { 380 return ComponentName.createRelative(ctx, cls.getName()); 381 } 382 383 public static class NonDoActivity extends TempActivity { 384 385 static final String EXTRA_FIRST_ACTION = "first_action"; 386 387 static final int EXTRA_DO_NOTHING = 0; 388 static final int EXTRA_ONCREATE_FINISH_IMMEDIATELY = 1; 389 static final int EXTRA_ONCREATE_LAUNCH_DO_IMMEDIATELY = 2; 390 static final int EXTRA_ONRESUME_FINISH_IMMEDIATELY = 3; 391 static final int EXTRA_ONRESUME_LAUNCH_DO_IMMEDIATELY = 4; 392 393 @Override onCreate(Bundle savedInstanceState)394 protected void onCreate(Bundle savedInstanceState) { 395 super.onCreate(savedInstanceState); 396 Bundle extras = getIntent().getExtras(); 397 if (extras != null) { 398 switch (extras.getInt(EXTRA_FIRST_ACTION, EXTRA_DO_NOTHING)) { 399 case EXTRA_ONCREATE_LAUNCH_DO_IMMEDIATELY: 400 startActivity(new Intent(this, DoActivity.class)); 401 finish(); 402 break; 403 case EXTRA_ONCREATE_FINISH_IMMEDIATELY: 404 finish(); 405 break; 406 default: 407 // do nothing 408 } 409 } 410 } 411 412 @Override onResume()413 protected void onResume() { 414 super.onResume(); 415 Bundle extras = getIntent().getExtras(); 416 if (extras != null) { 417 switch (extras.getInt(EXTRA_FIRST_ACTION, EXTRA_DO_NOTHING)) { 418 case EXTRA_ONRESUME_LAUNCH_DO_IMMEDIATELY: 419 startActivity(new Intent(this, DoActivity.class)); 420 finish(); 421 break; 422 case EXTRA_ONRESUME_FINISH_IMMEDIATELY: 423 finish(); 424 break; 425 default: 426 // do nothing 427 } 428 } 429 } 430 } 431 432 public static class NonDoNoHistoryActivity extends TempActivity { 433 } 434 435 public static class DoActivity extends TempActivity { 436 public static final String INTENT_EXTRA_SHOW_DIALOG = "SHOW_DIALOG"; 437 public static final String DIALOG_TITLE = "Title"; 438 439 public static final String INTENT_EXTRA_LAUNCH_NONDO = "LAUNCH_NONDO"; 440 public static final String INTENT_EXTRA_LAUNCH_NONDO_NEW_TASK = "LAUNCH_NONDO_NEW_TASK"; 441 442 @Override onCreate(Bundle savedInstanceState)443 protected void onCreate(Bundle savedInstanceState) { 444 super.onCreate(savedInstanceState); 445 if (getIntent().getBooleanExtra(INTENT_EXTRA_LAUNCH_NONDO, false)) { 446 startActivity(new Intent(this, NonDoActivity.class)); 447 } 448 if (getIntent().getBooleanExtra(INTENT_EXTRA_LAUNCH_NONDO_NEW_TASK, false)) { 449 startActivity(new Intent(this, NonDoActivity.class) 450 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 451 } 452 if (getIntent().getBooleanExtra(INTENT_EXTRA_SHOW_DIALOG, false)) { 453 AlertDialog dialog = new AlertDialog.Builder(DoActivity.this) 454 .setTitle(DIALOG_TITLE) 455 .setMessage("Message") 456 .create(); 457 dialog.show(); 458 } 459 } 460 } 461 462 /** Activity that closes itself after some timeout to clean up the screen. */ 463 public static class TempActivity extends Activity { 464 private final CountDownLatch mDestroyed = new CountDownLatch(1); 465 466 @Override onCreate(Bundle savedInstanceState)467 protected void onCreate(Bundle savedInstanceState) { 468 super.onCreate(savedInstanceState); 469 setTitle(this.getClass().getSimpleName()); 470 sTestingActivities.add(this); 471 } 472 473 @Override onDestroy()474 protected void onDestroy() { 475 sTestingActivities.remove(this); 476 super.onDestroy(); 477 mDestroyed.countDown(); 478 } 479 finishCompletely()480 void finishCompletely() throws InterruptedException { 481 finish(); 482 waitForDestroy(); 483 } 484 waitForDestroy()485 boolean waitForDestroy() throws InterruptedException { 486 return mDestroyed.await(ACTIVITY_TIMEOUT_MS, TimeUnit.MILLISECONDS); 487 } 488 } 489 getContext()490 private Context getContext() { 491 return InstrumentationRegistry.getInstrumentation().getTargetContext(); 492 } 493 getTestContext()494 private Context getTestContext() { 495 return InstrumentationRegistry.getInstrumentation().getContext(); 496 } 497 } 498