blob: 59c0d2a87e955eaadf1a2c68716c26eb328f4840 [file] [log] [blame]
package android.server.am.lifecycle;
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_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_STOP;
import android.annotation.Nullable;
import android.app.Activity;
import android.content.ComponentName;
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.ActivityManagerTestBase;
import android.server.am.lifecycle.LifecycleLog.ActivityCallback;
import androidx.test.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.lifecycle.ActivityLifecycleMonitor;
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
import android.util.Pair;
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 ActivityManagerTestBase {
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 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 mSingleTopActivityTestRule = new ActivityTestRule(
SingleTopActivity.class, true /* initialTouchMode */, false /* launchActivity */);
final ActivityTestRule mConfigChangeHandlingActivityTestRule = new ActivityTestRule(
ConfigChangeHandlingActivity.class, true /* initialTouchMode */,
false /* launchActivity */);
private final ActivityLifecycleMonitor mLifecycleMonitor = ActivityLifecycleMonitorRegistry
.getInstance();
private static LifecycleLog mLifecycleLog;
private LifecycleTracker mLifecycleTracker;
@Before
@Override
public void setUp() throws Exception {
super.setUp();
// Log transitions for all activities that belong to this app.
mLifecycleLog = new LifecycleLog();
mLifecycleMonitor.addLifecycleCallback(mLifecycleLog);
// Track transitions and allow waiting for pending activity states.
mLifecycleTracker = new LifecycleTracker(mLifecycleLog);
mLifecycleMonitor.addLifecycleCallback(mLifecycleTracker);
}
@After
@Override
public void tearDown() throws Exception {
mLifecycleMonitor.removeLifecycleCallback(mLifecycleLog);
mLifecycleMonitor.removeLifecycleCallback(mLifecycleTracker);
super.tearDown();
}
/** Launch an activity given a class. */
protected Activity launchActivity(Class<? extends Activity> activityClass) {
final Intent intent = new Intent(InstrumentationRegistry.getTargetContext(), activityClass);
return InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
}
/**
* Blocking call that will wait for activities to reach expected states with timeout.
*/
@SafeVarargs
final void waitAndAssertActivityStates(Pair<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 waitAndAssertActivityTransition");
mLifecycleTracker.waitForActivityTransitions(activityClass, expectedTransitions);
}
LifecycleLog getLifecycleLog() {
return mLifecycleLog;
}
static Pair<Activity, ActivityCallback> state(Activity activity, ActivityCallback stage) {
return new Pair<>(activity, stage);
}
/**
* Returns a pair of the activity and the state it should be in based on the configuration of
* occludingActivity.
*/
static Pair<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<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 new Pair<>(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());
}
// Test activity
public static class FirstActivity extends Activity {
}
// Test activity
public static class SecondActivity extends Activity {
}
// Translucent test activity
public static class TranslucentActivity extends Activity {
}
// Translucent test activity
public static class SecondTranslucentActivity extends Activity {
}
/**
* Base activity that records callbacks other then main lifecycle transitions.
*/
public static class CallbackTrackingActivity extends Activity {
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
mLifecycleLog.onActivityCallback(this, ON_ACTIVITY_RESULT);
}
@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
mLifecycleLog.onActivityCallback(this, ON_POST_CREATE);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
mLifecycleLog.onActivityCallback(this, ON_NEW_INTENT);
}
@Override
public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
mLifecycleLog.onActivityCallback(this, ON_MULTI_WINDOW_MODE_CHANGED);
}
}
/**
* 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 Activity {
@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 {
}
static ComponentName getComponentName(Class<? extends Activity> activity) {
return new ComponentName(InstrumentationRegistry.getContext(), activity);
}
}