1 /* 2 * Copyright (C) 2018 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.server.wm.lifecycle; 18 19 import static android.server.wm.StateLogger.log; 20 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP; 21 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT; 22 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_CREATE; 23 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_DESTROY; 24 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED; 25 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_NEW_INTENT; 26 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_PAUSE; 27 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_POST_CREATE; 28 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESTART; 29 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME; 30 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_START; 31 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP; 32 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED; 33 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST; 34 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE; 35 36 import static androidx.test.InstrumentationRegistry.getInstrumentation; 37 38 import android.app.Activity; 39 import android.app.PictureInPictureParams; 40 import android.content.ComponentName; 41 import android.content.Context; 42 import android.content.Intent; 43 import android.content.pm.ActivityInfo; 44 import android.content.res.Configuration; 45 import android.os.Bundle; 46 import android.os.Handler; 47 import android.server.wm.MultiDisplayTestBase; 48 import android.server.wm.lifecycle.LifecycleLog.ActivityCallback; 49 import android.util.Pair; 50 51 import androidx.test.rule.ActivityTestRule; 52 53 import org.junit.Before; 54 55 import java.util.List; 56 57 /** Base class for device-side tests that verify correct activity lifecycle transitions. */ 58 public class ActivityLifecycleClientTestBase extends MultiDisplayTestBase { 59 60 static final String EXTRA_RECREATE = "recreate"; 61 static final String EXTRA_FINISH_IN_ON_RESUME = "finish_in_on_resume"; 62 static final String EXTRA_FINISH_AFTER_RESUME = "finish_after_resume"; 63 64 static final ComponentName CALLBACK_TRACKING_ACTIVITY = 65 getComponentName(CallbackTrackingActivity.class); 66 67 static final ComponentName CONFIG_CHANGE_HANDLING_ACTIVITY = 68 getComponentName(ConfigChangeHandlingActivity.class); 69 70 final ActivityTestRule mFirstActivityTestRule = new ActivityTestRule<>(FirstActivity.class, 71 true /* initialTouchMode */, false /* launchActivity */); 72 73 final ActivityTestRule mSecondActivityTestRule = new ActivityTestRule<>(SecondActivity.class, 74 true /* initialTouchMode */, false /* launchActivity */); 75 76 final ActivityTestRule mThirdActivityTestRule = new ActivityTestRule<>(ThirdActivity.class, 77 true /* initialTouchMode */, false /* launchActivity */); 78 79 final ActivityTestRule mTranslucentActivityTestRule = new ActivityTestRule<>( 80 TranslucentActivity.class, true /* initialTouchMode */, false /* launchActivity */); 81 82 final ActivityTestRule mSecondTranslucentActivityTestRule = new ActivityTestRule<>( 83 SecondTranslucentActivity.class, true /* initialTouchMode */, 84 false /* launchActivity */); 85 86 final ActivityTestRule mLaunchForResultActivityTestRule = new ActivityTestRule<>( 87 LaunchForResultActivity.class, true /* initialTouchMode */, false /* launchActivity */); 88 89 final ActivityTestRule mCallbackTrackingActivityTestRule = new ActivityTestRule<>( 90 CallbackTrackingActivity.class, true /* initialTouchMode */, 91 false /* launchActivity */); 92 93 final ActivityTestRule mTranslucentCallbackTrackingActivityTestRule = new ActivityTestRule<>( 94 TranslucentCallbackTrackingActivity.class, true /* initialTouchMode */, 95 false /* launchActivity */); 96 97 final ActivityTestRule mShowWhenLockedCallbackTrackingActivityTestRule = new ActivityTestRule<>( 98 ShowWhenLockedCallbackTrackingActivity.class, true /* initialTouchMode */, 99 false /* launchActivity */); 100 101 final ActivityTestRule mSingleTopActivityTestRule = new ActivityTestRule<>( 102 SingleTopActivity.class, true /* initialTouchMode */, false /* launchActivity */); 103 104 final ActivityTestRule mConfigChangeHandlingActivityTestRule = new ActivityTestRule<>( 105 ConfigChangeHandlingActivity.class, true /* initialTouchMode */, 106 false /* launchActivity */); 107 108 final ActivityTestRule mPipActivityTestRule = new ActivityTestRule<>( 109 PipActivity.class, true /* initialTouchMode */, false /* launchActivity */); 110 111 final ActivityTestRule mAlwaysFocusableActivityTestRule = new ActivityTestRule<>( 112 AlwaysFocusablePipActivity.class, true /* initialTouchMode */, 113 false /* launchActivity */); 114 115 final ActivityTestRule mSlowActivityTestRule = new ActivityTestRule<>( 116 SlowActivity.class, true /* initialTouchMode */, false /* launchActivity */); 117 118 private static LifecycleLog mLifecycleLog; 119 120 protected Context mTargetContext; 121 private LifecycleTracker mLifecycleTracker; 122 123 @Before 124 @Override setUp()125 public void setUp() throws Exception { 126 super.setUp(); 127 128 mTargetContext = getInstrumentation().getTargetContext(); 129 // Log transitions for all activities that belong to this app. 130 mLifecycleLog = new LifecycleLog(); 131 mLifecycleLog.clear(); 132 133 // Track transitions and allow waiting for pending activity states. 134 mLifecycleTracker = new LifecycleTracker(mLifecycleLog); 135 } 136 137 /** Launch an activity given a class. */ launchActivity(Class<? extends Activity> activityClass)138 protected Activity launchActivity(Class<? extends Activity> activityClass) { 139 final Intent intent = new Intent(mTargetContext, activityClass); 140 return getInstrumentation().startActivitySync(intent); 141 } 142 143 /** 144 * Blocking call that will wait for activities to reach expected states with timeout. 145 */ 146 @SafeVarargs waitAndAssertActivityStates( Pair<Class<? extends Activity>, ActivityCallback>.... activityCallbacks)147 final void waitAndAssertActivityStates( 148 Pair<Class<? extends Activity>, ActivityCallback>... activityCallbacks) { 149 log("Start waitAndAssertActivityCallbacks"); 150 mLifecycleTracker.waitAndAssertActivityStates(activityCallbacks); 151 } 152 153 /** 154 * Blocking call that will wait for activities to perform the expected sequence of transitions. 155 * @see LifecycleTracker#waitForActivityTransitions(Class, List) 156 */ waitForActivityTransitions(Class<? extends Activity> activityClass, List<ActivityCallback> expectedTransitions)157 final void waitForActivityTransitions(Class<? extends Activity> activityClass, 158 List<ActivityCallback> expectedTransitions) { 159 log("Start waitForActivityTransitions"); 160 mLifecycleTracker.waitForActivityTransitions(activityClass, expectedTransitions); 161 } 162 163 /** 164 * Blocking call that will wait for activities to perform the expected sequence of transitions. 165 * After waiting it asserts that the sequence matches the expected. 166 * @see LifecycleTracker#waitForActivityTransitions(Class, List) 167 */ waitAndAssertActivityTransitions(Class<? extends Activity> activityClass, List<ActivityCallback> expectedTransitions, String message)168 final void waitAndAssertActivityTransitions(Class<? extends Activity> activityClass, 169 List<ActivityCallback> expectedTransitions, String message) { 170 log("Start waitAndAssertActivityTransition"); 171 mLifecycleTracker.waitForActivityTransitions(activityClass, expectedTransitions); 172 173 LifecycleVerifier.assertSequence(activityClass, getLifecycleLog(), expectedTransitions, 174 message); 175 } 176 getLifecycleLog()177 LifecycleLog getLifecycleLog() { 178 return mLifecycleLog; 179 } 180 state(Activity activity, ActivityCallback stage)181 static Pair<Class<? extends Activity>, ActivityCallback> state(Activity activity, 182 ActivityCallback stage) { 183 return state(activity.getClass(), stage); 184 } 185 state( Class<? extends Activity> activityClass, ActivityCallback stage)186 static Pair<Class<? extends Activity>, ActivityCallback> state( 187 Class<? extends Activity> activityClass, ActivityCallback stage) { 188 return new Pair<>(activityClass, stage); 189 } 190 191 /** 192 * Returns a pair of the activity and the state it should be in based on the configuration of 193 * occludingActivity. 194 */ occludedActivityState( Activity activity, Activity occludingActivity)195 static Pair<Class<? extends Activity>, ActivityCallback> occludedActivityState( 196 Activity activity, Activity occludingActivity) { 197 return occludedActivityState(activity, isTranslucent(occludingActivity)); 198 } 199 200 /** 201 * Returns a pair of the activity and the state it should be in based on 202 * occludingActivityIsTranslucent. 203 */ occludedActivityState( Activity activity, boolean occludingActivityIsTranslucent)204 static Pair<Class<? extends Activity>, ActivityCallback> occludedActivityState( 205 Activity activity, boolean occludingActivityIsTranslucent) { 206 // Activities behind a translucent activity should be in the paused state since they are 207 // still visible. Otherwise, they should be in the stopped state. 208 return state(activity, occludedActivityState(occludingActivityIsTranslucent)); 209 } 210 occludedActivityState(boolean occludingActivityIsTranslucent)211 static ActivityCallback occludedActivityState(boolean occludingActivityIsTranslucent) { 212 return occludingActivityIsTranslucent ? ON_PAUSE : ON_STOP; 213 } 214 215 /** Returns true if the input activity is translucent. */ isTranslucent(Activity activity)216 static boolean isTranslucent(Activity activity) { 217 return ActivityInfo.isTranslucentOrFloating(activity.getWindow().getWindowStyle()); 218 } 219 220 /** Base activity that only tracks fundamental activity lifecycle states. */ 221 public static class LifecycleTrackingActivity extends Activity { 222 LifecycleLog.LifecycleLogClient mLifecycleLogClient; 223 224 @Override onCreate(Bundle savedInstanceState)225 protected void onCreate(Bundle savedInstanceState) { 226 super.onCreate(savedInstanceState); 227 mLifecycleLogClient = LifecycleLog.LifecycleLogClient.create(this); 228 mLifecycleLogClient.onActivityCallback(PRE_ON_CREATE); 229 mLifecycleLogClient.onActivityCallback(ON_CREATE); 230 } 231 232 @Override onStart()233 protected void onStart() { 234 super.onStart(); 235 mLifecycleLogClient.onActivityCallback(ON_START); 236 } 237 238 @Override onResume()239 protected void onResume() { 240 super.onResume(); 241 mLifecycleLogClient.onActivityCallback(ON_RESUME); 242 } 243 244 @Override onPause()245 protected void onPause() { 246 super.onPause(); 247 mLifecycleLogClient.onActivityCallback(ON_PAUSE); 248 } 249 250 @Override onStop()251 protected void onStop() { 252 super.onStop(); 253 mLifecycleLogClient.onActivityCallback(ON_STOP); 254 } 255 256 @Override onDestroy()257 protected void onDestroy() { 258 super.onDestroy(); 259 mLifecycleLogClient.onActivityCallback(ON_DESTROY); 260 mLifecycleLogClient.close(); 261 } 262 263 @Override onRestart()264 protected void onRestart() { 265 super.onRestart(); 266 mLifecycleLogClient.onActivityCallback(ON_RESTART); 267 } 268 } 269 270 // Test activity 271 public static class FirstActivity extends LifecycleTrackingActivity { 272 } 273 274 // Test activity 275 public static class SecondActivity extends LifecycleTrackingActivity { 276 } 277 278 // Test activity 279 public static class ThirdActivity extends LifecycleTrackingActivity { 280 } 281 282 // Translucent test activity 283 public static class TranslucentActivity extends LifecycleTrackingActivity { 284 } 285 286 // Translucent test activity 287 public static class SecondTranslucentActivity extends LifecycleTrackingActivity { 288 } 289 290 /** 291 * Base activity that records callbacks in addition to main lifecycle transitions. 292 */ 293 public static class CallbackTrackingActivity extends LifecycleTrackingActivity { 294 295 @Override onActivityResult(int requestCode, int resultCode, Intent data)296 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 297 super.onActivityResult(requestCode, resultCode, data); 298 mLifecycleLogClient.onActivityCallback(ON_ACTIVITY_RESULT); 299 } 300 301 @Override onPostCreate(Bundle savedInstanceState)302 protected void onPostCreate(Bundle savedInstanceState) { 303 super.onPostCreate(savedInstanceState); 304 mLifecycleLogClient.onActivityCallback(ON_POST_CREATE); 305 } 306 307 @Override onNewIntent(Intent intent)308 protected void onNewIntent(Intent intent) { 309 super.onNewIntent(intent); 310 mLifecycleLogClient.onActivityCallback(ON_NEW_INTENT); 311 } 312 313 @Override onTopResumedActivityChanged(boolean isTopResumedActivity)314 public void onTopResumedActivityChanged(boolean isTopResumedActivity) { 315 mLifecycleLogClient.onActivityCallback( 316 isTopResumedActivity ? ON_TOP_POSITION_GAINED : ON_TOP_POSITION_LOST); 317 } 318 319 @Override onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig)320 public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) { 321 mLifecycleLogClient.onActivityCallback(ON_MULTI_WINDOW_MODE_CHANGED); 322 } 323 } 324 325 // Translucent callback tracking test activity 326 public static class TranslucentCallbackTrackingActivity extends CallbackTrackingActivity { 327 } 328 329 // Callback tracking activity that supports being shown on top of lock screen 330 public static class ShowWhenLockedCallbackTrackingActivity extends CallbackTrackingActivity { 331 @Override onCreate(Bundle savedInstanceState)332 protected void onCreate(Bundle savedInstanceState) { 333 super.onCreate(savedInstanceState); 334 setShowWhenLocked(true); 335 } 336 } 337 338 /** 339 * Test activity that launches {@link ResultActivity} for result. 340 */ 341 public static class LaunchForResultActivity extends CallbackTrackingActivity { 342 343 @Override onCreate(Bundle savedInstanceState)344 protected void onCreate(Bundle savedInstanceState) { 345 super.onCreate(savedInstanceState); 346 startForResult(); 347 } 348 startForResult()349 private void startForResult() { 350 final Intent intent = new Intent(this, ResultActivity.class); 351 intent.putExtras(getIntent()); 352 startActivityForResult(intent, 1 /* requestCode */); 353 } 354 } 355 356 /** Test activity that is started for result and finishes itself in ON_RESUME. */ 357 public static class ResultActivity extends CallbackTrackingActivity { 358 @Override onResume()359 protected void onResume() { 360 super.onResume(); 361 setResult(RESULT_OK); 362 final Intent intent = getIntent(); 363 if (intent.getBooleanExtra(EXTRA_FINISH_IN_ON_RESUME, false)) { 364 finish(); 365 } else if (intent.getBooleanExtra(EXTRA_FINISH_AFTER_RESUME, false)) { 366 new Handler().postDelayed(() -> finish(), 2000); 367 } 368 } 369 } 370 371 /** Test activity that can call {@link Activity#recreate()} if requested in a new intent. */ 372 public static class SingleTopActivity extends CallbackTrackingActivity { 373 374 @Override onNewIntent(Intent intent)375 protected void onNewIntent(Intent intent) { 376 super.onNewIntent(intent); 377 if (intent != null && intent.getBooleanExtra(EXTRA_RECREATE, false)) { 378 recreate(); 379 } 380 } 381 } 382 383 // Config change handling activity 384 public static class ConfigChangeHandlingActivity extends CallbackTrackingActivity { 385 } 386 387 // Callback tracking activity that runs in a separate process 388 public static class SecondProcessCallbackTrackingActivity extends CallbackTrackingActivity { 389 } 390 391 // Pip-capable activity 392 // TODO(b/123013403): Disabled onMultiWindowMode changed callbacks to make the tests pass, so 393 // that they can verify other lifecycle transitions. This should be fixed and switched to 394 // extend CallbackTrackingActivity. 395 public static class PipActivity extends LifecycleTrackingActivity { 396 @Override onCreate(Bundle savedInstanceState)397 protected void onCreate(Bundle savedInstanceState) { 398 super.onCreate(savedInstanceState); 399 400 // Enter picture in picture with the given aspect ratio if provided 401 if (getIntent().hasExtra(EXTRA_ENTER_PIP)) { 402 enterPictureInPictureMode(new PictureInPictureParams.Builder().build()); 403 } 404 } 405 } 406 407 public static class AlwaysFocusablePipActivity extends CallbackTrackingActivity { 408 } 409 410 public static class SlowActivity extends CallbackTrackingActivity { 411 412 static final String EXTRA_CONTROL_FLAGS = "extra_control_flags"; 413 static final int FLAG_SLOW_TOP_RESUME_RELEASE = 0x00000001; 414 static final int FLAG_TIMEOUT_TOP_RESUME_RELEASE = 0x00000002; 415 416 private int mFlags; 417 418 @Override onCreate(Bundle savedInstanceState)419 protected void onCreate(Bundle savedInstanceState) { 420 super.onCreate(savedInstanceState); 421 mFlags = getIntent().getIntExtra(EXTRA_CONTROL_FLAGS, 0); 422 } 423 424 @Override onNewIntent(Intent intent)425 protected void onNewIntent(Intent intent) { 426 super.onNewIntent(intent); 427 mFlags = getIntent().getIntExtra(EXTRA_CONTROL_FLAGS, 0); 428 } 429 430 @Override onTopResumedActivityChanged(boolean isTopResumedActivity)431 public void onTopResumedActivityChanged(boolean isTopResumedActivity) { 432 if (!isTopResumedActivity && (mFlags & FLAG_SLOW_TOP_RESUME_RELEASE) != 0) { 433 sleep(200); 434 } else if (!isTopResumedActivity && (mFlags & FLAG_TIMEOUT_TOP_RESUME_RELEASE) != 0) { 435 sleep(2000); 436 } 437 // Intentionally moving the logging of the state change to after sleep to facilitate 438 // race condition with other activity getting top state before this releases its. 439 super.onTopResumedActivityChanged(isTopResumedActivity); 440 } 441 sleep(long millis)442 private void sleep(long millis) { 443 try { 444 Thread.sleep(millis); 445 } catch (InterruptedException e) { 446 e.printStackTrace(); 447 } 448 } 449 } 450 getComponentName(Class<? extends Activity> activity)451 static ComponentName getComponentName(Class<? extends Activity> activity) { 452 return new ComponentName(getInstrumentation().getContext(), activity); 453 } 454 moveTaskToPrimarySplitScreenAndVerify(Activity activity)455 void moveTaskToPrimarySplitScreenAndVerify(Activity activity) { 456 getLifecycleLog().clear(); 457 458 moveTaskToPrimarySplitScreen(activity.getTaskId()); 459 460 final Class<? extends Activity> activityClass = activity.getClass(); 461 waitAndAssertActivityTransitions(activityClass, 462 LifecycleVerifier.getSplitScreenTransitionSequence(activityClass), 463 "enterSplitScreen"); 464 } 465 } 466