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