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