blob: a5f95079f0eef4dcb7ce77c5e7760e7af4ecbbd3 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package android.server.am.lifecycle;
import static android.server.am.Components.PipActivity.EXTRA_ENTER_PIP;
import static android.server.am.StateLogger.log;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_CREATE;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_DESTROY;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_NEW_INTENT;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_PAUSE;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_POST_CREATE;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_RESTART;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_START;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import android.app.Activity;
import android.app.PictureInPictureParams;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
import android.server.am.ActivityManagerDisplayTestBase;
import android.server.am.lifecycle.LifecycleLog.ActivityCallback;
import android.util.Pair;
import androidx.test.rule.ActivityTestRule;
import org.junit.After;
import org.junit.Before;
import java.util.List;
/** Base class for device-side tests that verify correct activity lifecycle transitions. */
public class ActivityLifecycleClientTestBase extends ActivityManagerDisplayTestBase {
static final String EXTRA_RECREATE = "recreate";
static final String EXTRA_FINISH_IN_ON_RESUME = "finish_in_on_resume";
static final String EXTRA_FINISH_AFTER_RESUME = "finish_after_resume";
static final ComponentName CALLBACK_TRACKING_ACTIVITY =
getComponentName(CallbackTrackingActivity.class);
static final ComponentName CONFIG_CHANGE_HANDLING_ACTIVITY =
getComponentName(ConfigChangeHandlingActivity.class);
final ActivityTestRule mFirstActivityTestRule = new ActivityTestRule(FirstActivity.class,
true /* initialTouchMode */, false /* launchActivity */);
final ActivityTestRule mSecondActivityTestRule = new ActivityTestRule(SecondActivity.class,
true /* initialTouchMode */, false /* launchActivity */);
final ActivityTestRule mThirdActivityTestRule = new ActivityTestRule(ThirdActivity.class,
true /* initialTouchMode */, false /* launchActivity */);
final ActivityTestRule mTranslucentActivityTestRule = new ActivityTestRule(
TranslucentActivity.class, true /* initialTouchMode */, false /* launchActivity */);
final ActivityTestRule mSecondTranslucentActivityTestRule = new ActivityTestRule(
SecondTranslucentActivity.class, true /* initialTouchMode */,
false /* launchActivity */);
final ActivityTestRule mLaunchForResultActivityTestRule = new ActivityTestRule(
LaunchForResultActivity.class, true /* initialTouchMode */, false /* launchActivity */);
final ActivityTestRule mCallbackTrackingActivityTestRule = new ActivityTestRule(
CallbackTrackingActivity.class, true /* initialTouchMode */,
false /* launchActivity */);
final ActivityTestRule mTranslucentCallbackTrackingActivityTestRule = new ActivityTestRule(
TranslucentCallbackTrackingActivity.class, true /* initialTouchMode */,
false /* launchActivity */);
final ActivityTestRule mShowWhenLockedCallbackTrackingActivityTestRule = new ActivityTestRule(
ShowWhenLockedCallbackTrackingActivity.class, true /* initialTouchMode */,
false /* launchActivity */);
final ActivityTestRule mSingleTopActivityTestRule = new ActivityTestRule(
SingleTopActivity.class, true /* initialTouchMode */, false /* launchActivity */);
final ActivityTestRule mConfigChangeHandlingActivityTestRule = new ActivityTestRule(
ConfigChangeHandlingActivity.class, true /* initialTouchMode */,
false /* launchActivity */);
final ActivityTestRule mPipActivityTestRule = new ActivityTestRule(
PipActivity.class, true /* initialTouchMode */, false /* launchActivity */);
final ActivityTestRule mAlwaysFocusableActivityTestRule = new ActivityTestRule(
AlwaysFocusablePipActivity.class, true /* initialTouchMode */,
false /* launchActivity */);
final ActivityTestRule mSlowActivityTestRule = new ActivityTestRule(
SlowActivity.class, true /* initialTouchMode */, false /* launchActivity */);
private static LifecycleLog mLifecycleLog;
protected Context mTargetContext;
private LifecycleTracker mLifecycleTracker;
@Before
@Override
public void setUp() throws Exception {
super.setUp();
mTargetContext = getInstrumentation().getTargetContext();
// Log transitions for all activities that belong to this app.
mLifecycleLog = new LifecycleLog();
mLifecycleLog.clear();
// Track transitions and allow waiting for pending activity states.
mLifecycleTracker = new LifecycleTracker(mLifecycleLog);
}
/** Launch an activity given a class. */
protected Activity launchActivity(Class<? extends Activity> activityClass) {
final Intent intent = new Intent(mTargetContext, activityClass);
return getInstrumentation().startActivitySync(intent);
}
/**
* Blocking call that will wait for activities to reach expected states with timeout.
*/
@SafeVarargs
final void waitAndAssertActivityStates(
Pair<Class<? extends Activity>, ActivityCallback>... activityCallbacks) {
log("Start waitAndAssertActivityCallbacks");
mLifecycleTracker.waitAndAssertActivityStates(activityCallbacks);
}
/**
* Blocking call that will wait for activities to perform the expected sequence of transitions.
* @see LifecycleTracker#waitForActivityTransitions(Class, List)
*/
final void waitForActivityTransitions(Class<? extends Activity> activityClass,
List<ActivityCallback> expectedTransitions) {
log("Start waitForActivityTransitions");
mLifecycleTracker.waitForActivityTransitions(activityClass, expectedTransitions);
}
/**
* Blocking call that will wait for activities to perform the expected sequence of transitions.
* After waiting it asserts that the sequence matches the expected.
* @see LifecycleTracker#waitForActivityTransitions(Class, List)
*/
final void waitAndAssertActivityTransitions(Class<? extends Activity> activityClass,
List<ActivityCallback> expectedTransitions, String message) {
log("Start waitAndAssertActivityTransition");
mLifecycleTracker.waitForActivityTransitions(activityClass, expectedTransitions);
LifecycleVerifier.assertSequence(activityClass, getLifecycleLog(), expectedTransitions,
message);
}
LifecycleLog getLifecycleLog() {
return mLifecycleLog;
}
static Pair<Class<? extends Activity>, ActivityCallback> state(Activity activity,
ActivityCallback stage) {
return state(activity.getClass(), stage);
}
static Pair<Class<? extends Activity>, ActivityCallback> state(
Class<? extends Activity> activityClass, ActivityCallback stage) {
return new Pair<>(activityClass, stage);
}
/**
* Returns a pair of the activity and the state it should be in based on the configuration of
* occludingActivity.
*/
static Pair<Class<? extends Activity>, ActivityCallback> occludedActivityState(
Activity activity, Activity occludingActivity) {
return occludedActivityState(activity, isTranslucent(occludingActivity));
}
/**
* Returns a pair of the activity and the state it should be in based on
* occludingActivityIsTranslucent.
*/
static Pair<Class<? extends Activity>, ActivityCallback> occludedActivityState(
Activity activity, boolean occludingActivityIsTranslucent) {
// Activities behind a translucent activity should be in the paused state since they are
// still visible. Otherwise, they should be in the stopped state.
return state(activity, occludedActivityState(occludingActivityIsTranslucent));
}
static ActivityCallback occludedActivityState(boolean occludingActivityIsTranslucent) {
return occludingActivityIsTranslucent ? ON_PAUSE : ON_STOP;
}
/** Returns true if the input activity is translucent. */
static boolean isTranslucent(Activity activity) {
return ActivityInfo.isTranslucentOrFloating(activity.getWindow().getWindowStyle());
}
/** Base activity that only tracks fundamental activity lifecycle states. */
public static class LifecycleTrackingActivity extends Activity {
LifecycleLog.LifecycleLogClient mLifecycleLogClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLifecycleLogClient = LifecycleLog.LifecycleLogClient.create(this);
mLifecycleLogClient.onActivityCallback(PRE_ON_CREATE);
mLifecycleLogClient.onActivityCallback(ON_CREATE);
}
@Override
protected void onStart() {
super.onStart();
mLifecycleLogClient.onActivityCallback(ON_START);
}
@Override
protected void onResume() {
super.onResume();
mLifecycleLogClient.onActivityCallback(ON_RESUME);
}
@Override
protected void onPause() {
super.onPause();
mLifecycleLogClient.onActivityCallback(ON_PAUSE);
}
@Override
protected void onStop() {
super.onStop();
mLifecycleLogClient.onActivityCallback(ON_STOP);
}
@Override
protected void onDestroy() {
super.onDestroy();
mLifecycleLogClient.onActivityCallback(ON_DESTROY);
mLifecycleLogClient.close();
}
@Override
protected void onRestart() {
super.onRestart();
mLifecycleLogClient.onActivityCallback(ON_RESTART);
}
}
// Test activity
public static class FirstActivity extends LifecycleTrackingActivity {
}
// Test activity
public static class SecondActivity extends LifecycleTrackingActivity {
}
// Test activity
public static class ThirdActivity extends LifecycleTrackingActivity {
}
// Translucent test activity
public static class TranslucentActivity extends LifecycleTrackingActivity {
}
// Translucent test activity
public static class SecondTranslucentActivity extends LifecycleTrackingActivity {
}
/**
* Base activity that records callbacks in addition to main lifecycle transitions.
*/
public static class CallbackTrackingActivity extends LifecycleTrackingActivity {
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
mLifecycleLogClient.onActivityCallback(ON_ACTIVITY_RESULT);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
mLifecycleLogClient.onActivityCallback(ON_POST_CREATE);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
mLifecycleLogClient.onActivityCallback(ON_NEW_INTENT);
}
@Override
public void onTopResumedActivityChanged(boolean isTopResumedActivity) {
mLifecycleLogClient.onActivityCallback(
isTopResumedActivity ? ON_TOP_POSITION_GAINED : ON_TOP_POSITION_LOST);
}
@Override
public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
mLifecycleLogClient.onActivityCallback(ON_MULTI_WINDOW_MODE_CHANGED);
}
}
// Translucent callback tracking test activity
public static class TranslucentCallbackTrackingActivity extends CallbackTrackingActivity {
}
// Callback tracking activity that supports being shown on top of lock screen
public static class ShowWhenLockedCallbackTrackingActivity extends CallbackTrackingActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setShowWhenLocked(true);
}
}
/**
* Test activity that launches {@link ResultActivity} for result.
*/
public static class LaunchForResultActivity extends CallbackTrackingActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startForResult();
}
private void startForResult() {
final Intent intent = new Intent(this, ResultActivity.class);
intent.putExtras(getIntent());
startActivityForResult(intent, 1 /* requestCode */);
}
}
/** Test activity that is started for result and finishes itself in ON_RESUME. */
public static class ResultActivity extends CallbackTrackingActivity {
@Override
protected void onResume() {
super.onResume();
setResult(RESULT_OK);
final Intent intent = getIntent();
if (intent.getBooleanExtra(EXTRA_FINISH_IN_ON_RESUME, false)) {
finish();
} else if (intent.getBooleanExtra(EXTRA_FINISH_AFTER_RESUME, false)) {
new Handler().postDelayed(() -> finish(), 2000);
}
}
}
/** Test activity that can call {@link Activity#recreate()} if requested in a new intent. */
public static class SingleTopActivity extends CallbackTrackingActivity {
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent != null && intent.getBooleanExtra(EXTRA_RECREATE, false)) {
recreate();
}
}
}
// Config change handling activity
public static class ConfigChangeHandlingActivity extends CallbackTrackingActivity {
}
// Callback tracking activity that runs in a separate process
public static class SecondProcessCallbackTrackingActivity extends CallbackTrackingActivity {
}
// Pip-capable activity
// TODO(b/123013403): Disabled onMultiWindowMode changed callbacks to make the tests pass, so
// that they can verify other lifecycle transitions. This should be fixed and switched to
// extend CallbackTrackingActivity.
public static class PipActivity extends LifecycleTrackingActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Enter picture in picture with the given aspect ratio if provided
if (getIntent().hasExtra(EXTRA_ENTER_PIP)) {
enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
}
}
}
public static class AlwaysFocusablePipActivity extends CallbackTrackingActivity {
}
public static class SlowActivity extends CallbackTrackingActivity {
static final String EXTRA_CONTROL_FLAGS = "extra_control_flags";
static final int FLAG_SLOW_TOP_RESUME_RELEASE = 0x00000001;
static final int FLAG_TIMEOUT_TOP_RESUME_RELEASE = 0x00000002;
private int mFlags;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mFlags = getIntent().getIntExtra(EXTRA_CONTROL_FLAGS, 0);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
mFlags = getIntent().getIntExtra(EXTRA_CONTROL_FLAGS, 0);
}
@Override
public void onTopResumedActivityChanged(boolean isTopResumedActivity) {
if (!isTopResumedActivity && (mFlags & FLAG_SLOW_TOP_RESUME_RELEASE) != 0) {
sleep(200);
} else if (!isTopResumedActivity && (mFlags & FLAG_TIMEOUT_TOP_RESUME_RELEASE) != 0) {
sleep(2000);
}
// Intentionally moving the logging of the state change to after sleep to facilitate
// race condition with other activity getting top state before this releases its.
super.onTopResumedActivityChanged(isTopResumedActivity);
}
private void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static ComponentName getComponentName(Class<? extends Activity> activity) {
return new ComponentName(getInstrumentation().getContext(), activity);
}
void moveTaskToPrimarySplitScreenAndVerify(Activity activity) {
getLifecycleLog().clear();
moveTaskToPrimarySplitScreen(activity.getTaskId());
final Class<? extends Activity> activityClass = activity.getClass();
waitAndAssertActivityTransitions(activityClass,
LifecycleVerifier.getSplitScreenTransitionSequence(activityClass),
"enterSplitScreen");
}
}