1 /*
2  * Copyright (C) 2016 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;
18 
19 import static android.app.AppOpsManager.MODE_ALLOWED;
20 import static android.app.AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW;
21 import static android.app.Instrumentation.ActivityMonitor;
22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
23 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
24 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
25 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
26 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
27 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
28 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
29 import static android.content.Intent.ACTION_MAIN;
30 import static android.content.Intent.CATEGORY_HOME;
31 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
32 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
33 import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
34 import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
35 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
36 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
37 import static android.content.pm.PackageManager.DONT_KILL_APP;
38 import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
39 import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
40 import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
41 import static android.content.pm.PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE;
42 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
43 import static android.content.pm.PackageManager.FEATURE_INPUT_METHODS;
44 import static android.content.pm.PackageManager.FEATURE_LEANBACK;
45 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
46 import static android.content.pm.PackageManager.FEATURE_SCREEN_LANDSCAPE;
47 import static android.content.pm.PackageManager.FEATURE_SCREEN_PORTRAIT;
48 import static android.content.pm.PackageManager.FEATURE_SECURE_LOCK_SCREEN;
49 import static android.content.pm.PackageManager.FEATURE_TELEVISION;
50 import static android.content.pm.PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE;
51 import static android.content.pm.PackageManager.FEATURE_WATCH;
52 import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
53 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
54 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
55 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
56 import static android.os.UserHandle.USER_ALL;
57 import static android.provider.Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS;
58 import static android.server.wm.ActivityLauncher.KEY_ACTIVITY_TYPE;
59 import static android.server.wm.ActivityLauncher.KEY_DISPLAY_ID;
60 import static android.server.wm.ActivityLauncher.KEY_INTENT_EXTRAS;
61 import static android.server.wm.ActivityLauncher.KEY_INTENT_FLAGS;
62 import static android.server.wm.ActivityLauncher.KEY_LAUNCH_ACTIVITY;
63 import static android.server.wm.ActivityLauncher.KEY_LAUNCH_TASK_BEHIND;
64 import static android.server.wm.ActivityLauncher.KEY_LAUNCH_TO_SIDE;
65 import static android.server.wm.ActivityLauncher.KEY_MULTIPLE_INSTANCES;
66 import static android.server.wm.ActivityLauncher.KEY_MULTIPLE_TASK;
67 import static android.server.wm.ActivityLauncher.KEY_NEW_TASK;
68 import static android.server.wm.ActivityLauncher.KEY_RANDOM_DATA;
69 import static android.server.wm.ActivityLauncher.KEY_REORDER_TO_FRONT;
70 import static android.server.wm.ActivityLauncher.KEY_SUPPRESS_EXCEPTIONS;
71 import static android.server.wm.ActivityLauncher.KEY_TARGET_COMPONENT;
72 import static android.server.wm.ActivityLauncher.KEY_TASK_DISPLAY_AREA_FEATURE_ID;
73 import static android.server.wm.ActivityLauncher.KEY_USE_APPLICATION_CONTEXT;
74 import static android.server.wm.ActivityLauncher.KEY_WINDOWING_MODE;
75 import static android.server.wm.ActivityLauncher.launchActivityFromExtras;
76 import static android.server.wm.CommandSession.KEY_FORWARD;
77 import static android.server.wm.ComponentNameUtils.getActivityName;
78 import static android.server.wm.ComponentNameUtils.getLogTag;
79 import static android.server.wm.ShellCommandHelper.executeShellCommand;
80 import static android.server.wm.ShellCommandHelper.executeShellCommandAndGetStdout;
81 import static android.server.wm.StateLogger.log;
82 import static android.server.wm.StateLogger.logE;
83 import static android.server.wm.UiDeviceUtils.pressSleepButton;
84 import static android.server.wm.UiDeviceUtils.pressUnlockButton;
85 import static android.server.wm.UiDeviceUtils.pressWakeupButton;
86 import static android.server.wm.WindowManagerState.STATE_PAUSED;
87 import static android.server.wm.WindowManagerState.STATE_RESUMED;
88 import static android.server.wm.WindowManagerState.STATE_STOPPED;
89 import static android.server.wm.app.Components.BROADCAST_RECEIVER_ACTIVITY;
90 import static android.server.wm.app.Components.BroadcastReceiverActivity.ACTION_TRIGGER_BROADCAST;
91 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_BROADCAST_ORIENTATION;
92 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_CUTOUT_EXISTS;
93 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_DISMISS_KEYGUARD;
94 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_DISMISS_KEYGUARD_METHOD;
95 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_FINISH_BROADCAST;
96 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_MOVE_BROADCAST_TO_BACK;
97 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
98 import static android.server.wm.app.Components.LaunchingActivity.KEY_FINISH_BEFORE_LAUNCH;
99 import static android.server.wm.app.Components.PipActivity.ACTION_CHANGE_ASPECT_RATIO;
100 import static android.server.wm.app.Components.PipActivity.ACTION_ENTER_PIP;
101 import static android.server.wm.app.Components.PipActivity.ACTION_ENTER_PIP_AND_WAIT_FOR_UI_STATE;
102 import static android.server.wm.app.Components.PipActivity.ACTION_EXPAND_PIP;
103 import static android.server.wm.app.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION;
104 import static android.server.wm.app.Components.PipActivity.ACTION_UPDATE_PIP_STATE;
105 import static android.server.wm.app.Components.PipActivity.EXTRA_PIP_ORIENTATION;
106 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_DENOMINATOR;
107 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_NUMERATOR;
108 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR;
109 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR;
110 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_PIP_CALLBACK;
111 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_PIP_STASHED;
112 import static android.server.wm.app.Components.TEST_ACTIVITY;
113 import static android.server.wm.second.Components.SECOND_ACTIVITY;
114 import static android.server.wm.third.Components.THIRD_ACTIVITY;
115 import static android.view.Display.DEFAULT_DISPLAY;
116 import static android.view.Display.INVALID_DISPLAY;
117 import static android.view.Surface.ROTATION_0;
118 import static android.view.Surface.ROTATION_90;
119 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
120 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
121 import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
122 
123 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
124 
125 import static org.junit.Assert.assertEquals;
126 import static org.junit.Assert.assertNotEquals;
127 import static org.junit.Assert.assertNotNull;
128 import static org.junit.Assert.assertTrue;
129 import static org.junit.Assert.fail;
130 import static org.junit.Assume.assumeFalse;
131 import static org.junit.Assume.assumeTrue;
132 
133 import static java.lang.Integer.toHexString;
134 
135 import android.app.Activity;
136 import android.app.ActivityManager;
137 import android.app.ActivityOptions;
138 import android.app.ActivityTaskManager;
139 import android.app.Instrumentation;
140 import android.app.KeyguardManager;
141 import android.app.WallpaperManager;
142 import android.app.WindowConfiguration;
143 import android.content.ComponentName;
144 import android.content.Context;
145 import android.content.Intent;
146 import android.content.pm.PackageManager;
147 import android.content.pm.ResolveInfo;
148 import android.content.res.Resources;
149 import android.graphics.Bitmap;
150 import android.graphics.Canvas;
151 import android.graphics.Color;
152 import android.graphics.Rect;
153 import android.hardware.display.AmbientDisplayConfiguration;
154 import android.hardware.display.DisplayManager;
155 import android.os.Bundle;
156 import android.os.Process;
157 import android.os.RemoteCallback;
158 import android.os.SystemClock;
159 import android.os.SystemProperties;
160 import android.provider.Settings;
161 import android.server.wm.CommandSession.ActivityCallback;
162 import android.server.wm.CommandSession.ActivitySession;
163 import android.server.wm.CommandSession.ActivitySessionClient;
164 import android.server.wm.CommandSession.ConfigInfo;
165 import android.server.wm.CommandSession.LaunchInjector;
166 import android.server.wm.CommandSession.LaunchProxy;
167 import android.server.wm.CommandSession.SizeInfo;
168 import android.server.wm.TestJournalProvider.TestJournalContainer;
169 import android.server.wm.WindowManagerState.Task;
170 import android.server.wm.WindowManagerState.WindowState;
171 import android.server.wm.settings.SettingsSession;
172 import android.util.DisplayMetrics;
173 import android.util.EventLog;
174 import android.util.EventLog.Event;
175 import android.util.Log;
176 import android.util.Pair;
177 import android.util.Size;
178 import android.view.Display;
179 import android.view.View;
180 import android.view.WindowManager;
181 
182 import androidx.annotation.NonNull;
183 import androidx.annotation.Nullable;
184 import androidx.test.core.app.ApplicationProvider;
185 import androidx.test.ext.junit.rules.ActivityScenarioRule;
186 
187 import com.android.compatibility.common.util.AppOpsUtils;
188 import com.android.compatibility.common.util.FeatureUtil;
189 import com.android.compatibility.common.util.GestureNavSwitchHelper;
190 import com.android.compatibility.common.util.SystemUtil;
191 
192 import org.junit.Before;
193 import org.junit.Rule;
194 import org.junit.rules.ErrorCollector;
195 import org.junit.rules.RuleChain;
196 import org.junit.rules.TestRule;
197 import org.junit.runner.Description;
198 import org.junit.runners.model.Statement;
199 
200 import java.io.IOException;
201 import java.util.ArrayList;
202 import java.util.Arrays;
203 import java.util.Collections;
204 import java.util.Iterator;
205 import java.util.List;
206 import java.util.Objects;
207 import java.util.Optional;
208 import java.util.UUID;
209 import java.util.concurrent.CompletableFuture;
210 import java.util.concurrent.TimeUnit;
211 import java.util.concurrent.atomic.AtomicBoolean;
212 import java.util.function.BooleanSupplier;
213 import java.util.function.Consumer;
214 import java.util.function.Predicate;
215 import java.util.function.Supplier;
216 import java.util.regex.Matcher;
217 import java.util.regex.Pattern;
218 
219 public abstract class ActivityManagerTestBase {
220     private static final String TAG = ActivityManagerTestBase.class.getSimpleName();
221     private static final boolean PRETEND_DEVICE_SUPPORTS_PIP = false;
222     private static final boolean PRETEND_DEVICE_SUPPORTS_FREEFORM = false;
223     private static final String LOG_SEPARATOR = "LOG_SEPARATOR";
224     // Use one of the test tags as a separator
225     private static final int EVENT_LOG_SEPARATOR_TAG = 42;
226 
227     protected static final int[] ALL_ACTIVITY_TYPE_BUT_HOME = {
228             ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS,
229             ACTIVITY_TYPE_UNDEFINED
230     };
231 
232     private static final String TEST_PACKAGE = TEST_ACTIVITY.getPackageName();
233     private static final String SECOND_TEST_PACKAGE = SECOND_ACTIVITY.getPackageName();
234     private static final String THIRD_TEST_PACKAGE = THIRD_ACTIVITY.getPackageName();
235     private static final List<String> TEST_PACKAGES;
236 
237     static {
238         final List<String> testPackages = new ArrayList<>();
239         testPackages.add(TEST_PACKAGE);
240         testPackages.add(SECOND_TEST_PACKAGE);
241         testPackages.add(THIRD_TEST_PACKAGE);
242         testPackages.add("android.server.wm.cts");
243         testPackages.add("android.server.wm.jetpack");
244         testPackages.add("android.server.wm.jetpack.second");
245         TEST_PACKAGES = Collections.unmodifiableList(testPackages);
246     }
247 
248     protected static final String AM_START_HOME_ACTIVITY_COMMAND =
249             "am start -a android.intent.action.MAIN -c android.intent.category.HOME";
250 
251     protected static final String MSG_NO_MOCK_IME =
252             "MockIme cannot be used for devices that do not support installable IMEs";
253 
254     private static final String AM_BROADCAST_CLOSE_SYSTEM_DIALOGS =
255             "am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS --user " + USER_ALL;
256 
257     protected static final String LOCK_CREDENTIAL = "1234";
258 
259     private static final int UI_MODE_TYPE_MASK = 0x0f;
260     private static final int UI_MODE_TYPE_VR_HEADSET = 0x07;
261 
262     public static final boolean ENABLE_SHELL_TRANSITIONS =
263             SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
264 
265     private static Boolean sHasHomeScreen = null;
266     private static Boolean sSupportsSystemDecorsOnSecondaryDisplays = null;
267     private static Boolean sIsAssistantOnTop = null;
268     private static Boolean sIsTablet = null;
269     private static Boolean sDismissDreamOnActivityStart = null;
270     private static GestureNavSwitchHelper sGestureNavSwitchHelper = null;
271     private static boolean sIllegalTaskStateFound;
272 
273     protected static final int INVALID_DEVICE_ROTATION = -1;
274 
275     protected final Instrumentation mInstrumentation = getInstrumentation();
276     protected final Context mContext = getInstrumentation().getContext();
277     protected final ActivityManager mAm = mContext.getSystemService(ActivityManager.class);
278     protected final ActivityTaskManager mAtm = mContext.getSystemService(ActivityTaskManager.class);
279     protected final DisplayManager mDm = mContext.getSystemService(DisplayManager.class);
280     protected final WindowManager mWm = mContext.getSystemService(WindowManager.class);
281     protected final KeyguardManager mKm = mContext.getSystemService(KeyguardManager.class);
282 
283     /** The tracker to manage objects (especially {@link AutoCloseable}) in a test method. */
284     protected final ObjectTracker mObjectTracker = new ObjectTracker();
285 
286     /** The last rule to handle all errors. */
287     private final ErrorCollector mPostAssertionRule = new PostAssertionRule();
288 
289     /** The necessary procedures of set up and tear down. */
290     @Rule
291     public final TestRule mBaseRule = RuleChain.outerRule(mPostAssertionRule)
292             .around(new WrapperRule(null /* before */, this::tearDownBase));
293 
294     /**
295      * Whether to wait for the rotation to be stable state after testing. It can be set if the
296      * display rotation may be changed by test.
297      */
298     protected boolean mWaitForRotationOnTearDown;
299 
300     /** Indicate to wait for all non-home activities to be destroyed when test finished. */
301     protected boolean mShouldWaitForAllNonHomeActivitiesToDestroyed = false;
302 
303     /**
304      * @return the am command to start the given activity with the following extra key/value pairs.
305      * {@param extras} a list of {@link CliIntentExtra} representing a generic intent extra
306      */
307     // TODO: Make this more generic, for instance accepting flags or extras of other types.
getAmStartCmd(final ComponentName activityName, final CliIntentExtra... extras)308     protected static String getAmStartCmd(final ComponentName activityName,
309             final CliIntentExtra... extras) {
310         return getAmStartCmdInternal(getActivityName(activityName), extras);
311     }
312 
getAmStartCmdInternal(final String activityName, final CliIntentExtra... extras)313     private static String getAmStartCmdInternal(final String activityName,
314             final CliIntentExtra... extras) {
315         return appendKeyValuePairs(
316                 new StringBuilder("am start --user ").append(Process.myUserHandle().getIdentifier())
317                         .append(" -n ").append(activityName), extras);
318     }
319 
appendKeyValuePairs( final StringBuilder cmd, final CliIntentExtra... extras)320     private static String appendKeyValuePairs(
321             final StringBuilder cmd, final CliIntentExtra... extras) {
322         for (int i = 0; i < extras.length; i++) {
323             extras[i].appendTo(cmd);
324         }
325         return cmd.toString();
326     }
327 
getAmStartCmd(final ComponentName activityName, final int displayId, final CliIntentExtra... extras)328     protected static String getAmStartCmd(final ComponentName activityName, final int displayId,
329             final CliIntentExtra... extras) {
330         return getAmStartCmdInternal(getActivityName(activityName), displayId, extras);
331     }
332 
getAmStartCmdInternal(final String activityName, final int displayId, final CliIntentExtra... extras)333     private static String getAmStartCmdInternal(final String activityName, final int displayId,
334             final CliIntentExtra... extras) {
335         return appendKeyValuePairs(
336                 new StringBuilder("am start -n ")
337                         .append(activityName)
338                         .append(" -f 0x")
339                         .append(toHexString(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK))
340                         .append(" --display ")
341                         .append(displayId),
342                 extras);
343     }
344 
getAmStartCmdInNewTask(final ComponentName activityName)345     protected static String getAmStartCmdInNewTask(final ComponentName activityName) {
346         return "am start -n " + getActivityName(activityName) + " -f 0x18000000";
347     }
348 
getAmStartCmdWithData(final ComponentName activityName, String data)349     protected static String getAmStartCmdWithData(final ComponentName activityName, String data) {
350         return "am start -n " + getActivityName(activityName) + " -d " + data;
351     }
352 
getAmStartCmdWithNoAnimation(final ComponentName activityName, final CliIntentExtra... extras)353     protected static String getAmStartCmdWithNoAnimation(final ComponentName activityName,
354             final CliIntentExtra... extras) {
355         return appendKeyValuePairs(
356                 new StringBuilder("am start -n ")
357                         .append(getActivityName(activityName))
358                         .append(" -f 0x")
359                         .append(toHexString(FLAG_ACTIVITY_NO_ANIMATION)),
360                 extras);
361     }
362 
getAmStartCmdWithDismissKeyguardIfInsecure( final ComponentName activityName)363     protected static String getAmStartCmdWithDismissKeyguardIfInsecure(
364             final ComponentName activityName) {
365         return "am start --dismiss-keyguard-if-insecure -n " + getActivityName(activityName);
366     }
367 
getAmStartCmdWithNoUserAction(final ComponentName activityName, final CliIntentExtra... extras)368     protected static String getAmStartCmdWithNoUserAction(final ComponentName activityName,
369             final CliIntentExtra... extras) {
370         return appendKeyValuePairs(
371                 new StringBuilder("am start -n ")
372                         .append(getActivityName(activityName))
373                         .append(" -f 0x")
374                         .append(toHexString(FLAG_ACTIVITY_NO_USER_ACTION)),
375                 extras);
376     }
377 
getAmStartCmdWithWindowingMode( final ComponentName activityName, int windowingMode)378     protected static String getAmStartCmdWithWindowingMode(
379             final ComponentName activityName, int windowingMode) {
380         return getAmStartCmdInNewTask(activityName) + " --windowingMode " + windowingMode;
381     }
382 
383     protected WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
384     protected TouchHelper mTouchHelper = new TouchHelper(mInstrumentation, mWmState);
385     // Initialized in setUp to execute with proper permission, such as MANAGE_ACTIVITY_TASKS
386     public TestTaskOrganizer mTaskOrganizer;
387 
getWmState()388     public WindowManagerStateHelper getWmState() {
389         return mWmState;
390     }
391 
392     protected BroadcastActionTrigger mBroadcastActionTrigger = new BroadcastActionTrigger();
393 
394     /** Runs a runnable with shell permissions. These can be nested. */
runWithShellPermission(Runnable runnable)395     protected void runWithShellPermission(Runnable runnable) {
396         NestedShellPermission.run(runnable);
397     }
398 
399     /**
400      * Returns true if the activity is shown before timeout.
401      */
waitForActivityFocused(int timeoutMs, ComponentName componentName)402     protected boolean waitForActivityFocused(int timeoutMs, ComponentName componentName) {
403         waitForActivityResumed(timeoutMs, componentName);
404         return getActivityName(componentName).equals(mWmState.getFocusedActivity());
405     }
406 
waitForActivityResumed(int timeoutMs, ComponentName componentName)407     protected void waitForActivityResumed(int timeoutMs, ComponentName componentName) {
408         long endTime = System.currentTimeMillis() + timeoutMs;
409         while (endTime > System.currentTimeMillis()) {
410             mWmState.computeState();
411             if (mWmState.hasActivityState(componentName, STATE_RESUMED)) {
412                 SystemClock.sleep(200);
413                 mWmState.computeState();
414                 break;
415             }
416             SystemClock.sleep(200);
417             mWmState.computeState();
418         }
419     }
420 
421     /**
422      * Helper class to process test actions by broadcast.
423      */
424     protected class BroadcastActionTrigger {
425 
createIntentWithAction(String broadcastAction)426         private Intent createIntentWithAction(String broadcastAction) {
427             return new Intent(broadcastAction)
428                     .setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
429         }
430 
doAction(String broadcastAction)431         public void doAction(String broadcastAction) {
432             mContext.sendBroadcast(createIntentWithAction(broadcastAction));
433         }
434 
doActionWithRemoteCallback( String broadcastAction, String callbackName, RemoteCallback callback)435         public void doActionWithRemoteCallback(
436                 String broadcastAction, String callbackName, RemoteCallback callback) {
437             try {
438                 // We need also a RemoteCallback to ensure the callback passed in is properly set
439                 // in the Activity before moving forward.
440                 final CompletableFuture<Boolean> future = new CompletableFuture<>();
441                 final RemoteCallback setCallback = new RemoteCallback(
442                         (Bundle result) -> future.complete(true));
443                 mContext.sendBroadcast(createIntentWithAction(broadcastAction)
444                         .putExtra(callbackName, callback)
445                         .putExtra(EXTRA_SET_PIP_CALLBACK, setCallback));
446                 assertTrue(future.get(5000, TimeUnit.MILLISECONDS));
447             } catch (Exception e) {
448                 logE("doActionWithRemoteCallback failed", e);
449             }
450         }
451 
finishBroadcastReceiverActivity()452         public void finishBroadcastReceiverActivity() {
453             mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
454                     .putExtra(EXTRA_FINISH_BROADCAST, true));
455         }
456 
launchActivityNewTask(String launchComponent)457         public void launchActivityNewTask(String launchComponent) {
458             mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
459                     .putExtra(KEY_LAUNCH_ACTIVITY, true)
460                     .putExtra(KEY_NEW_TASK, true)
461                     .putExtra(KEY_TARGET_COMPONENT, launchComponent));
462         }
463 
moveTopTaskToBack()464         public void moveTopTaskToBack() {
465             mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
466                     .putExtra(EXTRA_MOVE_BROADCAST_TO_BACK, true));
467         }
468 
requestOrientation(int orientation)469         public void requestOrientation(int orientation) {
470             mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
471                     .putExtra(EXTRA_BROADCAST_ORIENTATION, orientation));
472         }
473 
dismissKeyguardByFlag()474         public void dismissKeyguardByFlag() {
475             mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
476                     .putExtra(EXTRA_DISMISS_KEYGUARD, true));
477         }
478 
dismissKeyguardByMethod()479         public void dismissKeyguardByMethod() {
480             mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
481                     .putExtra(EXTRA_DISMISS_KEYGUARD_METHOD, true));
482         }
483 
enterPipAndWait()484         public void enterPipAndWait() {
485             try {
486                 final CompletableFuture<Boolean> future = new CompletableFuture<>();
487                 final RemoteCallback remoteCallback = new RemoteCallback(
488                         (Bundle result) -> future.complete(true));
489                 mContext.sendBroadcast(createIntentWithAction(ACTION_ENTER_PIP)
490                         .putExtra(EXTRA_SET_PIP_CALLBACK, remoteCallback));
491                 assertTrue(future.get(5000, TimeUnit.MILLISECONDS));
492             } catch (Exception e) {
493                 logE("enterPipAndWait failed", e);
494             }
495         }
496 
expandPip()497         public void expandPip() {
498             mContext.sendBroadcast(createIntentWithAction(ACTION_EXPAND_PIP));
499         }
500 
expandPipWithAspectRatio(String extraNum, String extraDenom)501         public void expandPipWithAspectRatio(String extraNum, String extraDenom) {
502             mContext.sendBroadcast(createIntentWithAction(ACTION_EXPAND_PIP)
503                     .putExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR, extraNum)
504                     .putExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR, extraDenom));
505         }
506 
sendPipStateUpdate(RemoteCallback callback, boolean stashed)507         public void sendPipStateUpdate(RemoteCallback callback, boolean stashed) {
508             mContext.sendBroadcast(createIntentWithAction(ACTION_UPDATE_PIP_STATE)
509                     .putExtra(EXTRA_SET_PIP_CALLBACK, callback)
510                     .putExtra(EXTRA_SET_PIP_STASHED, stashed));
511         }
512 
enterPipAndWaitForPipUiStateChange(RemoteCallback callback)513         public void enterPipAndWaitForPipUiStateChange(RemoteCallback callback) {
514             mContext.sendBroadcast(createIntentWithAction(ACTION_ENTER_PIP_AND_WAIT_FOR_UI_STATE)
515                     .putExtra(EXTRA_SET_PIP_CALLBACK, callback));
516         }
517 
requestOrientationForPip(int orientation)518         public void requestOrientationForPip(int orientation) {
519             mContext.sendBroadcast(createIntentWithAction(ACTION_SET_REQUESTED_ORIENTATION)
520                     .putExtra(EXTRA_PIP_ORIENTATION, String.valueOf(orientation)));
521         }
522 
changeAspectRatio(int numerator, int denominator)523         public void changeAspectRatio(int numerator, int denominator) {
524             mContext.sendBroadcast(createIntentWithAction(ACTION_CHANGE_ASPECT_RATIO)
525                     .putExtra(EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(numerator))
526                     .putExtra(EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(denominator)));
527         }
528     }
529 
530     /**
531      * Helper class to launch / close test activity by instrumentation way.
532      */
533     protected class TestActivitySession<T extends Activity> implements AutoCloseable {
534         private T mTestActivity;
535         boolean mFinishAfterClose;
536         private static final int ACTIVITY_LAUNCH_TIMEOUT = 10000;
537         private static final int WAIT_SLICE = 50;
538 
setFinishAfterClose(boolean value)539         public void setFinishAfterClose(boolean value) {
540             mFinishAfterClose = value;
541         }
542 
543         /**
544          * Launches an {@link Activity} on a target display synchronously.
545          * @param activityClass The {@link Activity} class to be launched
546          * @param displayId ID of the target display
547          */
launchTestActivityOnDisplaySync(Class<T> activityClass, int displayId)548         public void launchTestActivityOnDisplaySync(Class<T> activityClass, int displayId) {
549             launchTestActivityOnDisplaySync(activityClass, displayId, WINDOWING_MODE_UNDEFINED);
550         }
551 
552         /**
553          * Launches an {@link Activity} on a target display synchronously.
554          *
555          * @param activityClass The {@link Activity} class to be launched
556          * @param displayId ID of the target display
557          * @param windowingMode Windowing mode at launch
558          */
launchTestActivityOnDisplaySync( Class<T> activityClass, int displayId, int windowingMode)559         public void launchTestActivityOnDisplaySync(
560                 Class<T> activityClass, int displayId, int windowingMode) {
561             final Intent intent = new Intent(mContext, activityClass)
562                     .addFlags(FLAG_ACTIVITY_NEW_TASK);
563             final String className = intent.getComponent().getClassName();
564             launchTestActivityOnDisplaySync(className, intent, displayId, windowingMode);
565         }
566 
567         /**
568          * Launches an {@link Activity} synchronously on a target display. The class name needs to
569          * be provided either implicitly through the {@link Intent} or explicitly as a parameter
570          *
571          * @param className Optional class name of expected activity
572          * @param intent Intent to launch an activity
573          * @param displayId ID for the target display
574          */
launchTestActivityOnDisplaySync( @ullable String className, Intent intent, int displayId)575         public void launchTestActivityOnDisplaySync(
576                 @Nullable String className, Intent intent, int displayId) {
577             launchTestActivityOnDisplaySync(className, intent, displayId, WINDOWING_MODE_UNDEFINED);
578         }
579 
580         /**
581          * Launches an {@link Activity} synchronously on a target display. The class name needs to
582          * be provided either implicitly through the {@link Intent} or explicitly as a parameter
583          *
584          * @param className Optional class name of expected activity
585          * @param intent Intent to launch an activity
586          * @param displayId ID for the target display
587          * @param windowingMode Windowing mode at launch
588          */
launchTestActivityOnDisplaySync( @ullable String className, Intent intent, int displayId, int windowingMode)589         public void launchTestActivityOnDisplaySync(
590                 @Nullable String className, Intent intent, int displayId, int windowingMode) {
591             runWithShellPermission(
592                     () -> {
593                         mTestActivity =
594                                 launchActivityOnDisplay(
595                                         className, intent, displayId, windowingMode);
596                         // Check activity is launched and resumed.
597                         final ComponentName testActivityName = mTestActivity.getComponentName();
598                         waitAndAssertTopResumedActivity(
599                                 testActivityName, displayId, "Activity must be resumed");
600                     });
601         }
602 
603         /**
604          * Launches an {@link Activity} on a target display asynchronously.
605          * @param activityClass The {@link Activity} class to be launched
606          * @param displayId ID of the target display
607          */
launchTestActivityOnDisplay(Class<T> activityClass, int displayId)608         public void launchTestActivityOnDisplay(Class<T> activityClass, int displayId) {
609             final Intent intent = new Intent(mContext, activityClass)
610                     .addFlags(FLAG_ACTIVITY_NEW_TASK);
611             final String className = intent.getComponent().getClassName();
612             runWithShellPermission(
613                     () -> {
614                         mTestActivity =
615                                 launchActivityOnDisplay(
616                                         className, intent, displayId, WINDOWING_MODE_UNDEFINED);
617                         assertNotNull(mTestActivity);
618                     });
619         }
620 
621         /**
622          * Launches an {@link Activity} on a target display. In order to return the correct activity
623          * the class name or an explicit {@link Intent} must be provided.
624          *
625          * @param className Optional class name of expected activity
626          * @param intent {@link Intent} to launch an activity
627          * @param displayId ID for the target display
628          * @param windowingMode Windowing mode at launch
629          * @return The {@link Activity} that was launched
630          */
launchActivityOnDisplay( @ullable String className, Intent intent, int displayId, int windowingMode)631         private T launchActivityOnDisplay(
632                 @Nullable String className, Intent intent, int displayId, int windowingMode) {
633             final String localClassName = className != null ? className :
634               (intent.getComponent() != null ? intent.getComponent().getClassName() : null);
635             if (localClassName == null || localClassName.isEmpty()) {
636                 fail("Must provide either a class name or an intent with a component");
637             }
638             final ActivityOptions launchOptions = ActivityOptions.makeBasic();
639             launchOptions.setLaunchDisplayId(displayId);
640             launchOptions.setLaunchWindowingMode(windowingMode);
641             final Bundle bundle = launchOptions.toBundle();
642             final ActivityMonitor monitor = mInstrumentation.addMonitor(localClassName, null,
643                     false);
644             mContext.startActivity(intent.addFlags(FLAG_ACTIVITY_NEW_TASK), bundle);
645             // Wait for activity launch with timeout.
646             mTestActivity = (T) mInstrumentation.waitForMonitorWithTimeout(monitor,
647                     ACTIVITY_LAUNCH_TIMEOUT);
648             assertNotNull(mTestActivity);
649             return mTestActivity;
650         }
651 
finishCurrentActivityNoWait()652         public void finishCurrentActivityNoWait() {
653             if (mTestActivity != null) {
654                 mTestActivity.finishAndRemoveTask();
655                 mTestActivity = null;
656             }
657         }
658 
runOnMainSyncAndWait(Runnable runnable)659         public void runOnMainSyncAndWait(Runnable runnable) {
660             mInstrumentation.runOnMainSync(runnable);
661             mInstrumentation.waitForIdleSync();
662         }
663 
runOnMainAndAssertWithTimeout( @onNull BooleanSupplier condition, long timeoutMs, String message)664         public void runOnMainAndAssertWithTimeout(
665                 @NonNull BooleanSupplier condition, long timeoutMs, String message) {
666             final AtomicBoolean result = new AtomicBoolean();
667             final long expiredTime = System.currentTimeMillis() + timeoutMs;
668             while (!result.get()) {
669                 if (System.currentTimeMillis() >= expiredTime) {
670                     fail(message);
671                 }
672                 runOnMainSyncAndWait(() -> {
673                     if (condition.getAsBoolean()) {
674                         result.set(true);
675                     }
676                 });
677                 SystemClock.sleep(WAIT_SLICE);
678             }
679         }
680 
getActivity()681         public T getActivity() {
682             return mTestActivity;
683         }
684 
685         @Override
close()686         public void close() {
687             if (mTestActivity != null && mFinishAfterClose) {
688                 mTestActivity.finishAndRemoveTask();
689             }
690         }
691     }
692 
693     @Before
setUp()694     public void setUp() throws Exception {
695         UiDeviceUtils.wakeUpAndUnlock(mContext);
696         if (isKeyguardLocked()) {
697             unlockUnexpectedLockedKeyguard();
698         }
699 
700         launchHomeActivityNoWait();
701         // TODO(b/242933292): Consider removing all the tasks belonging to android.server.wm
702         // instead of removing all and then waiting for allActivitiesResumed.
703         removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
704 
705         runWithShellPermission(() -> {
706             // TaskOrganizer ctor requires MANAGE_ACTIVITY_TASKS permission
707             mTaskOrganizer = new TestTaskOrganizer();
708             // Clear launch params for all test packages to make sure each test is run in a clean
709             // state.
710             mAtm.clearLaunchParamsForPackages(TEST_PACKAGES);
711         });
712 
713         // removeRootTaskWithActivityTypes() removes all the tasks apart from home. In a few cases,
714         // the systemUI might have a few tasks that need to be displayed all the time.
715         // For such tasks, systemUI might have a restart-logic that restarts those tasks. Those
716         // restarts can interfere with the test state. To avoid that, its better to wait for all
717         // the activities to come in the resumed state.
718         mWmState.waitForWithAmState(WindowManagerState::allActivitiesResumed, "Root Tasks should "
719                 + "be either empty or resumed");
720     }
721 
722     /** It always executes after {@link org.junit.After}. */
tearDownBase()723     private void tearDownBase() {
724         mObjectTracker.tearDown(mPostAssertionRule::addError);
725 
726         if (mTaskOrganizer != null) {
727             mTaskOrganizer.unregisterOrganizerIfNeeded();
728         }
729         // Synchronous execution of removeRootTasksWithActivityTypes() ensures that all
730         // activities but home are cleaned up from the root task at the end of each test. Am force
731         // stop shell commands might be asynchronous and could interrupt the task cleanup
732         // process if executed first.
733         UiDeviceUtils.wakeUpAndUnlock(mContext);
734         launchHomeActivityNoWait();
735         removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
736         stopTestPackage(TEST_PACKAGE);
737         stopTestPackage(SECOND_TEST_PACKAGE);
738         stopTestPackage(THIRD_TEST_PACKAGE);
739         if (mShouldWaitForAllNonHomeActivitiesToDestroyed) {
740             mWmState.waitForAllNonHomeActivitiesToDestroyed();
741         }
742 
743         if (mWaitForRotationOnTearDown) {
744             mWmState.waitForDisplayUnfrozen();
745         }
746 
747         if (ENABLE_SHELL_TRANSITIONS
748                 && !mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY)) {
749             mPostAssertionRule.addError(
750                     new IllegalStateException("Shell transition left unfinished!"));
751         }
752     }
753 
754     /** This should only be called if keyguard is still locked unexpectedly. */
unlockUnexpectedLockedKeyguard()755     private void unlockUnexpectedLockedKeyguard() {
756         logE("Try to recover unexpected locked keyguard");
757         // To clear the credential immediately, the screen need to be turned on.
758         pressWakeupButton();
759         if (supportsSecureLock()) {
760             removeLockCredential();
761         }
762         // Off/on to refresh the keyguard state.
763         pressSleepButton();
764         pressWakeupButton();
765         pressUnlockButton();
766     }
767 
768     /**
769      * After home key is pressed ({@link #pressHomeButton} is called), the later launch may be
770      * deferred if the calling uid doesn't have android.permission.STOP_APP_SWITCHES. This method
771      * will resume the temporary stopped state, so the launch won't be affected.
772      */
resumeAppSwitches()773     protected void resumeAppSwitches() {
774         SystemUtil.runWithShellPermissionIdentity(ActivityManager::resumeAppSwitches);
775     }
776 
startActivityOnDisplay(int displayId, ComponentName component)777     protected void startActivityOnDisplay(int displayId, ComponentName component) {
778         final ActivityOptions options = ActivityOptions.makeBasic();
779         options.setLaunchDisplayId(displayId);
780 
781         mContext.startActivity(new Intent().addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
782                 .setComponent(component), options.toBundle());
783     }
784 
noHomeScreen()785     protected boolean noHomeScreen() {
786         try {
787             return mContext.getResources().getBoolean(
788                     Resources.getSystem().getIdentifier("config_noHomeScreen", "bool",
789                             "android"));
790         } catch (Resources.NotFoundException e) {
791             // Assume there's a home screen.
792             return false;
793         }
794     }
795 
getSupportsSystemDecorsOnSecondaryDisplays()796     private boolean getSupportsSystemDecorsOnSecondaryDisplays() {
797         try {
798             return mContext.getResources().getBoolean(
799                     Resources.getSystem().getIdentifier(
800                             "config_supportsSystemDecorsOnSecondaryDisplays", "bool", "android"));
801         } catch (Resources.NotFoundException e) {
802             // Assume this device support system decorations.
803             return true;
804         }
805     }
806 
createHomeIntent(String category)807     protected Intent createHomeIntent(String category) {
808         int resId = Resources.getSystem().getIdentifier(
809                 "config_secondaryHomePackage", "string", "android");
810         final Intent intent = new Intent(Intent.ACTION_MAIN);
811         intent.addCategory(category);
812         intent.setPackage(mContext.getResources().getString(resId));
813         return intent;
814     }
815 
getDefaultSecondaryHomeComponent()816     protected ComponentName getDefaultSecondaryHomeComponent() {
817         assumeTrue(supportsMultiDisplay());
818         final Intent intent = createHomeIntent(Intent.CATEGORY_SECONDARY_HOME);
819         final ResolveInfo resolveInfo =
820                 mContext.getPackageManager().resolveActivity(intent, MATCH_DEFAULT_ONLY);
821         assertNotNull("Should have default secondary home activity", resolveInfo);
822 
823         return new ComponentName(resolveInfo.activityInfo.packageName,
824                 resolveInfo.activityInfo.name);
825     }
826 
827     /**
828      * Insert an input event (ACTION_DOWN -> ACTION_CANCEL) to ensures the display to be focused
829      * without triggering potential clicked to impact the test environment.
830      * (e.g: Keyguard credential activated unexpectedly.)
831      *
832      * @param displayId the display ID to gain focused by inject swipe action
833      */
touchAndCancelOnDisplayCenterSync(int displayId)834     protected void touchAndCancelOnDisplayCenterSync(int displayId) {
835         mTouchHelper.touchAndCancelOnDisplayCenterSync(displayId);
836     }
837 
tapOnDisplaySync(int x, int y, int displayId)838     protected void tapOnDisplaySync(int x, int y, int displayId) {
839         mTouchHelper.tapOnDisplaySync(x, y, displayId);
840     }
841 
tapOnDisplay(int x, int y, int displayId, boolean sync)842     private void tapOnDisplay(int x, int y, int displayId, boolean sync) {
843         mTouchHelper.tapOnDisplay(x, y, displayId, sync);
844     }
845 
tapOnCenter(Rect bounds, int displayId)846     protected void tapOnCenter(Rect bounds, int displayId) {
847         mTouchHelper.tapOnCenter(bounds, displayId);
848     }
849 
tapOnViewCenter(View view)850     protected void tapOnViewCenter(View view) {
851         mTouchHelper.tapOnViewCenter(view);
852     }
853 
tapOnTaskCenter(Task task)854     protected void tapOnTaskCenter(Task task) {
855         mTouchHelper.tapOnTaskCenter(task);
856     }
857 
tapOnDisplayCenter(int displayId)858     protected void tapOnDisplayCenter(int displayId) {
859         mTouchHelper.tapOnDisplayCenter(displayId);
860     }
861 
injectKey(int keyCode, boolean longPress, boolean sync)862     public static void injectKey(int keyCode, boolean longPress, boolean sync) {
863         TouchHelper.injectKey(keyCode, longPress, sync);
864     }
865 
removeRootTasksWithActivityTypes(int... activityTypes)866     protected void removeRootTasksWithActivityTypes(int... activityTypes) {
867         runWithShellPermission(() -> mAtm.removeRootTasksWithActivityTypes(activityTypes));
868         waitForIdle();
869     }
870 
removeRootTasksInWindowingModes(int... windowingModes)871     protected void removeRootTasksInWindowingModes(int... windowingModes) {
872         runWithShellPermission(() -> mAtm.removeRootTasksInWindowingModes(windowingModes));
873         waitForIdle();
874     }
875 
removeRootTask(int taskId)876     protected void removeRootTask(int taskId) {
877         runWithShellPermission(() -> mAtm.removeTask(taskId));
878         waitForIdle();
879     }
880 
takeScreenshot()881     protected Bitmap takeScreenshot() {
882         return mInstrumentation.getUiAutomation().takeScreenshot();
883     }
884 
885     /**
886      * Do a back gesture and trigger a back event from it.
887      * Attempt to simulate human behavior, so don't wait for animations.
888      */
triggerBackEventByGesture(int displayId)889     protected void triggerBackEventByGesture(int displayId) {
890         mTouchHelper.triggerBackEventByGesture(
891                 displayId, true /* sync */, false /* waitForAnimations */);
892     }
893 
launchActivity(final ComponentName activityName, final CliIntentExtra... extras)894     protected void launchActivity(final ComponentName activityName,
895             final CliIntentExtra... extras) {
896         launchActivityNoWait(activityName, extras);
897         mWmState.waitForValidState(activityName);
898     }
899 
launchActivityNoWait(final ComponentName activityName, final CliIntentExtra... extras)900     protected void launchActivityNoWait(final ComponentName activityName,
901             final CliIntentExtra... extras) {
902         executeShellCommand(getAmStartCmd(activityName, extras));
903     }
904 
launchActivityInNewTask(final ComponentName activityName)905     protected void launchActivityInNewTask(final ComponentName activityName) {
906         executeShellCommand(getAmStartCmdInNewTask(activityName));
907         mWmState.waitForValidState(activityName);
908     }
909 
launchActivityWithData(final ComponentName activityName, String data)910     protected void launchActivityWithData(final ComponentName activityName, String data) {
911         executeShellCommand(getAmStartCmdWithData(activityName, data));
912         mWmState.waitForValidState(activityName);
913     }
914 
launchActivityWithNoAnimation(final ComponentName activityName, final CliIntentExtra... extras)915     protected void launchActivityWithNoAnimation(final ComponentName activityName,
916             final CliIntentExtra... extras) {
917         executeShellCommand(getAmStartCmdWithNoAnimation(activityName, extras));
918         mWmState.waitForValidState(activityName);
919     }
920 
launchActivityWithDismissKeyguardIfInsecure( final ComponentName activityName)921     protected void launchActivityWithDismissKeyguardIfInsecure(
922             final ComponentName activityName) {
923         executeShellCommand(getAmStartCmdWithDismissKeyguardIfInsecure(activityName));
924         mWmState.waitForValidState(activityName);
925     }
926 
launchActivityWithNoUserAction(final ComponentName activityName, final CliIntentExtra... extras)927     protected void launchActivityWithNoUserAction(final ComponentName activityName,
928             final CliIntentExtra... extras) {
929         executeShellCommand(getAmStartCmdWithNoUserAction(activityName, extras));
930         mWmState.waitForValidState(activityName);
931     }
932 
launchActivityInFullscreen(final ComponentName activityName)933     protected void launchActivityInFullscreen(final ComponentName activityName) {
934         executeShellCommand(
935                 getAmStartCmdWithWindowingMode(activityName, WINDOWING_MODE_FULLSCREEN));
936         mWmState.waitForValidState(activityName);
937     }
938 
waitForIdle()939     protected static void waitForIdle() {
940         getInstrumentation().waitForIdleSync();
941     }
942 
waitForOrFail(String message, BooleanSupplier condition)943     public static void waitForOrFail(String message, BooleanSupplier condition) {
944         Condition.waitFor(new Condition<>(message, condition)
945                 .setRetryIntervalMs(500)
946                 .setRetryLimit(20)
947                 .setOnFailure(unusedResult -> fail("FAILED because unsatisfied: " + message)));
948     }
949 
950     /** Returns the root task that contains the provided leaf task id. */
getRootTaskForLeafTaskId(int taskId)951     protected Task getRootTaskForLeafTaskId(int taskId) {
952         mWmState.computeState();
953         final List<Task> rootTasks = mWmState.getRootTasks();
954         for (Task rootTask : rootTasks) {
955             if (rootTask.getTask(taskId) != null) {
956                 return rootTask;
957             }
958         }
959         return null;
960     }
961 
getRootTask(int taskId)962     protected Task getRootTask(int taskId) {
963         mWmState.computeState();
964         final List<Task> rootTasks = mWmState.getRootTasks();
965         for (Task rootTask : rootTasks) {
966             if (rootTask.getRootTaskId() == taskId) {
967                 return rootTask;
968             }
969         }
970         return null;
971     }
972 
getDefaultWindowingModeByActivity(ComponentName activity)973     protected int getDefaultWindowingModeByActivity(ComponentName activity) {
974         return mWmState.getTaskDisplayArea(activity).getWindowingMode();
975     }
976 
closeSystemDialogs()977     public static void closeSystemDialogs() {
978         executeShellCommand(AM_BROADCAST_CLOSE_SYSTEM_DIALOGS);
979     }
980 
981     /**
982      * Launches the home activity directly. If there is no specific reason to simulate a home key
983      * (which will trigger stop-app-switches), it is the recommended method to go home.
984      */
launchHomeActivityNoWait()985     public static void launchHomeActivityNoWait() {
986         // dismiss all system dialogs before launch home.
987         closeSystemDialogs();
988         executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND);
989     }
990 
launchHomeActivityNoWaitExpectFailure()991     protected static void launchHomeActivityNoWaitExpectFailure() {
992         closeSystemDialogs();
993         try {
994             executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND);
995         } catch (AssertionError e) {
996             if (e.getMessage().contains("Error: Activity not started")) {
997                 // expected
998                 return;
999             }
1000             throw new AssertionError("Expected activity start to fail, but got", e);
1001         }
1002         fail("Expected home activity launch to fail but didn't.");
1003     }
1004 
1005     /** Launches the home activity directly with waiting for it to be visible. */
launchHomeActivity()1006     protected void launchHomeActivity() {
1007         launchHomeActivityNoWait();
1008         mWmState.waitForHomeActivityVisible();
1009     }
1010 
launchActivityNoWait(ComponentName activityName, int windowingMode, final CliIntentExtra... extras)1011     protected void launchActivityNoWait(ComponentName activityName, int windowingMode,
1012             final CliIntentExtra... extras) {
1013         executeShellCommand(getAmStartCmd(activityName, extras)
1014                 + " --windowingMode " + windowingMode);
1015     }
1016 
launchActivity(ComponentName activityName, int windowingMode, final CliIntentExtra... keyValuePairs)1017     protected void launchActivity(ComponentName activityName, int windowingMode,
1018             final CliIntentExtra... keyValuePairs) {
1019         launchActivityNoWait(activityName, windowingMode, keyValuePairs);
1020         mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
1021                 .setWindowingMode(windowingMode)
1022                 .build());
1023     }
1024 
launchActivityOnDisplay(ComponentName activityName, int windowingMode, int displayId, final CliIntentExtra... extras)1025     protected void launchActivityOnDisplay(ComponentName activityName, int windowingMode,
1026             int displayId, final CliIntentExtra... extras) {
1027         executeShellCommand(getAmStartCmd(activityName, displayId, extras)
1028                 + " --windowingMode " + windowingMode);
1029         mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
1030                 .setWindowingMode(windowingMode)
1031                 .build());
1032     }
1033 
launchActivityOnTaskDisplayArea(ComponentName activityName, int windowingMode, int launchTaskDisplayAreaFeatureId, final CliIntentExtra... extras)1034     protected void launchActivityOnTaskDisplayArea(ComponentName activityName, int windowingMode,
1035             int launchTaskDisplayAreaFeatureId, final CliIntentExtra... extras) {
1036         executeShellCommand(getAmStartCmd(activityName, extras)
1037                 + " --task-display-area-feature-id " + launchTaskDisplayAreaFeatureId
1038                 + " --windowingMode " + windowingMode);
1039         mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
1040                 .setWindowingMode(windowingMode)
1041                 .build());
1042     }
1043 
launchActivityOnTaskDisplayArea(ComponentName activityName, int windowingMode, int launchTaskDisplayAreaFeatureId, int displayId, final CliIntentExtra... extras)1044     protected void launchActivityOnTaskDisplayArea(ComponentName activityName, int windowingMode,
1045             int launchTaskDisplayAreaFeatureId, int displayId, final CliIntentExtra... extras) {
1046         executeShellCommand(getAmStartCmd(activityName, displayId, extras)
1047                 + " --task-display-area-feature-id " + launchTaskDisplayAreaFeatureId
1048                 + " --windowingMode " + windowingMode);
1049         mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
1050                 .setWindowingMode(windowingMode)
1051                 .build());
1052     }
1053 
launchActivityOnDisplay(ComponentName activityName, int displayId, CliIntentExtra... extras)1054     protected void launchActivityOnDisplay(ComponentName activityName, int displayId,
1055             CliIntentExtra... extras) {
1056         launchActivityOnDisplayNoWait(activityName, displayId, extras);
1057         mWmState.waitForValidState(activityName);
1058     }
1059 
launchActivityOnDisplayNoWait(ComponentName activityName, int displayId, CliIntentExtra... extras)1060     protected void launchActivityOnDisplayNoWait(ComponentName activityName, int displayId,
1061             CliIntentExtra... extras) {
1062         executeShellCommand(getAmStartCmd(activityName, displayId, extras));
1063     }
1064 
launchActivityInPrimarySplit(ComponentName activityName)1065     protected void launchActivityInPrimarySplit(ComponentName activityName) {
1066         runWithShellPermission(() -> {
1067             launchActivity(activityName);
1068             final int taskId = mWmState.getTaskByActivity(activityName).mTaskId;
1069             mTaskOrganizer.putTaskInSplitPrimary(taskId);
1070             mWmState.waitForValidState(activityName);
1071         });
1072     }
1073 
launchActivityInSecondarySplit(ComponentName activityName)1074     protected void launchActivityInSecondarySplit(ComponentName activityName) {
1075         runWithShellPermission(() -> {
1076             launchActivity(activityName);
1077             final int taskId = mWmState.getTaskByActivity(activityName).mTaskId;
1078             mTaskOrganizer.putTaskInSplitSecondary(taskId);
1079             mWmState.waitForValidState(activityName);
1080         });
1081     }
1082 
putActivityInPrimarySplit(ComponentName activityName)1083     protected void putActivityInPrimarySplit(ComponentName activityName) {
1084         final int taskId = mWmState.getTaskByActivity(activityName).mTaskId;
1085         mTaskOrganizer.putTaskInSplitPrimary(taskId);
1086         mWmState.waitForValidState(activityName);
1087     }
1088 
putActivityInSecondarySplit(ComponentName activityName)1089     protected void putActivityInSecondarySplit(ComponentName activityName) {
1090         final int taskId = mWmState.getTaskByActivity(activityName).mTaskId;
1091         mTaskOrganizer.putTaskInSplitSecondary(taskId);
1092         mWmState.waitForValidState(activityName);
1093     }
1094 
1095     /**
1096      * Launches {@param primaryActivity} into split-screen primary windowing mode
1097      * and {@param secondaryActivity} to the side in split-screen secondary windowing mode.
1098      */
launchActivitiesInSplitScreen(LaunchActivityBuilder primaryActivity, LaunchActivityBuilder secondaryActivity)1099     protected void launchActivitiesInSplitScreen(LaunchActivityBuilder primaryActivity,
1100             LaunchActivityBuilder secondaryActivity) {
1101         // Launch split-screen primary.
1102         primaryActivity
1103                 .setUseInstrumentation()
1104                 .setWaitForLaunched(true)
1105                 .execute();
1106 
1107         final int primaryTaskId = mWmState.getTaskByActivity(
1108                 primaryActivity.mTargetActivity).mTaskId;
1109         mTaskOrganizer.putTaskInSplitPrimary(primaryTaskId);
1110 
1111         // Launch split-screen secondary
1112         secondaryActivity
1113                 .setUseInstrumentation()
1114                 .setWaitForLaunched(true)
1115                 .setNewTask(true)
1116                 .setMultipleTask(true)
1117                 .execute();
1118 
1119         final int secondaryTaskId = mWmState.getTaskByActivity(
1120                 secondaryActivity.mTargetActivity).mTaskId;
1121         mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId);
1122         mWmState.computeState(primaryActivity.getTargetActivity(),
1123                 secondaryActivity.getTargetActivity());
1124         log("launchActivitiesInSplitScreen(), primaryTaskId=" + primaryTaskId +
1125                 ", secondaryTaskId=" + secondaryTaskId);
1126     }
1127 
1128     /**
1129      * Move the task of {@param primaryActivity} into split-screen primary and the task of
1130      * {@param secondaryActivity} to the side in split-screen secondary.
1131      */
moveActivitiesToSplitScreen(ComponentName primaryActivity, ComponentName secondaryActivity)1132     protected void moveActivitiesToSplitScreen(ComponentName primaryActivity,
1133             ComponentName secondaryActivity) {
1134         final int primaryTaskId = mWmState.getTaskByActivity(primaryActivity).mTaskId;
1135         mTaskOrganizer.putTaskInSplitPrimary(primaryTaskId);
1136 
1137         final int secondaryTaskId = mWmState.getTaskByActivity(secondaryActivity).mTaskId;
1138         mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId);
1139 
1140         mWmState.computeState(primaryActivity, secondaryActivity);
1141         log("moveActivitiesToSplitScreen(), primaryTaskId=" + primaryTaskId +
1142                 ", secondaryTaskId=" + secondaryTaskId);
1143     }
1144 
dismissSplitScreen(boolean primaryOnTop)1145     protected void dismissSplitScreen(boolean primaryOnTop) {
1146         if (mTaskOrganizer != null) {
1147             mTaskOrganizer.dismissSplitScreen(primaryOnTop);
1148         }
1149     }
1150 
1151     /**
1152      * Move activity to root task or on top of the given root task when the root task is also a leaf
1153      * task.
1154      */
moveActivityToRootTaskOrOnTop(ComponentName activityName, int rootTaskId)1155     protected void moveActivityToRootTaskOrOnTop(ComponentName activityName, int rootTaskId) {
1156         moveActivityToRootTaskOrOnTop(activityName, rootTaskId, FEATURE_UNDEFINED);
1157     }
1158 
moveActivityToRootTaskOrOnTop(ComponentName activityName, int rootTaskId, int taskDisplayAreaFeatureId)1159     protected void moveActivityToRootTaskOrOnTop(ComponentName activityName, int rootTaskId,
1160                                                  int taskDisplayAreaFeatureId) {
1161         mWmState.computeState(activityName);
1162         Task rootTask = getRootTask(rootTaskId);
1163         if (rootTask.getActivities().size() != 0) {
1164             // If the root task is a 1-level task, start the activity on top of given task.
1165             getLaunchActivityBuilder()
1166                     .setDisplayId(rootTask.mDisplayId)
1167                     .setWindowingMode(rootTask.getWindowingMode())
1168                     .setActivityType(rootTask.getActivityType())
1169                     .setLaunchTaskDisplayAreaFeatureId(taskDisplayAreaFeatureId)
1170                     .setTargetActivity(activityName)
1171                     .allowMultipleInstances(false)
1172                     .setUseInstrumentation()
1173                     .execute();
1174         } else {
1175             final int taskId = mWmState.getTaskByActivity(activityName).mTaskId;
1176             runWithShellPermission(() -> mAtm.moveTaskToRootTask(taskId, rootTaskId, true));
1177         }
1178         mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
1179                 .setRootTaskId(rootTaskId)
1180                 .build());
1181     }
1182 
resizeActivityTask( ComponentName activityName, int left, int top, int right, int bottom)1183     protected void resizeActivityTask(
1184             ComponentName activityName, int left, int top, int right, int bottom) {
1185         mWmState.computeState(activityName);
1186         final int taskId = mWmState.getTaskByActivity(activityName).mTaskId;
1187         runWithShellPermission(() -> mAtm.resizeTask(taskId, new Rect(left, top, right, bottom)));
1188     }
1189 
supportsVrMode()1190     protected boolean supportsVrMode() {
1191         return hasDeviceFeature(FEATURE_VR_MODE_HIGH_PERFORMANCE);
1192     }
1193 
supportsPip()1194     protected boolean supportsPip() {
1195         return hasDeviceFeature(FEATURE_PICTURE_IN_PICTURE)
1196                 || PRETEND_DEVICE_SUPPORTS_PIP;
1197     }
1198 
supportsExpandedPip()1199     protected boolean supportsExpandedPip() {
1200         return hasDeviceFeature(FEATURE_EXPANDED_PICTURE_IN_PICTURE);
1201     }
1202 
supportsFreeform()1203     protected boolean supportsFreeform() {
1204         return hasDeviceFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)
1205                 || PRETEND_DEVICE_SUPPORTS_FREEFORM;
1206     }
1207 
1208     /** Whether or not the device supports lock screen. */
supportsLockScreen()1209     protected boolean supportsLockScreen() {
1210         return supportsInsecureLock() || supportsSecureLock();
1211     }
1212 
1213     /** Whether or not the device supports pin/pattern/password lock. */
supportsSecureLock()1214     protected boolean supportsSecureLock() {
1215         return FeatureUtil.hasSystemFeature(FEATURE_SECURE_LOCK_SCREEN);
1216     }
1217 
1218     /** Whether or not the device supports "swipe" lock. */
supportsInsecureLock()1219     protected boolean supportsInsecureLock() {
1220         return !FeatureUtil.hasAnySystemFeature(
1221                 FEATURE_LEANBACK, FEATURE_WATCH, FEATURE_EMBEDDED, FEATURE_AUTOMOTIVE)
1222                 && getSupportsInsecureLockScreen();
1223     }
1224 
1225     /** Try to enable gesture navigation mode */
enableAndAssumeGestureNavigationMode()1226     protected void enableAndAssumeGestureNavigationMode() {
1227         if (sGestureNavSwitchHelper == null) {
1228             sGestureNavSwitchHelper = new GestureNavSwitchHelper();
1229         }
1230         assumeTrue(sGestureNavSwitchHelper.enableGestureNavigationMode());
1231     }
1232 
supportsBlur()1233     protected boolean supportsBlur() {
1234         return SystemProperties.get("ro.surface_flinger.supports_background_blur", "default")
1235                 .equals("1");
1236     }
1237 
isWatch()1238     protected boolean isWatch() {
1239         return hasDeviceFeature(FEATURE_WATCH);
1240     }
1241 
isCar()1242     protected boolean isCar() {
1243         return hasDeviceFeature(FEATURE_AUTOMOTIVE);
1244     }
1245 
isLeanBack()1246     protected boolean isLeanBack() {
1247         return hasDeviceFeature(FEATURE_TELEVISION);
1248     }
1249 
isTablet()1250     public static boolean isTablet() {
1251         if (sIsTablet == null) {
1252             // Use WindowContext with type application overlay to prevent the metrics overridden by
1253             // activity bounds. Note that process configuration may still be overridden by
1254             // foreground Activity.
1255             final Context appContext = ApplicationProvider.getApplicationContext();
1256             final Display defaultDisplay = appContext.getSystemService(DisplayManager.class)
1257                     .getDisplay(DEFAULT_DISPLAY);
1258             final Context windowContext = appContext.createWindowContext(defaultDisplay,
1259                     TYPE_APPLICATION_OVERLAY, null /* options */);
1260             sIsTablet = windowContext.getResources().getConfiguration().smallestScreenWidthDp
1261                     >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
1262         }
1263         return sIsTablet;
1264     }
1265 
waitAndAssertActivityState(ComponentName activityName, String state, String message)1266     protected void waitAndAssertActivityState(ComponentName activityName,
1267             String state, String message) {
1268         mWmState.waitForActivityState(activityName, state);
1269 
1270         assertTrue(message, mWmState.hasActivityState(activityName, state));
1271     }
1272 
isKeyguardLocked()1273     protected boolean isKeyguardLocked() {
1274         return mKm != null && mKm.isKeyguardLocked();
1275     }
1276 
waitAndAssertActivityStateOnDisplay(ComponentName activityName, String state, int displayId, String message)1277     protected void waitAndAssertActivityStateOnDisplay(ComponentName activityName, String state,
1278             int displayId, String message) {
1279         waitAndAssertActivityState(activityName, state, message);
1280         assertEquals(message,
1281                 /* expected = */ displayId,
1282                 /* actual = */ mWmState.getDisplayByActivity(activityName));
1283     }
1284 
waitAndAssertTopResumedActivity(ComponentName activityName, int displayId, String message)1285     public void waitAndAssertTopResumedActivity(ComponentName activityName, int displayId,
1286             String message) {
1287         final String activityClassName = getActivityName(activityName);
1288         mWmState.waitForWithAmState(state -> activityClassName.equals(state.getFocusedActivity()),
1289                 "activity to be on top");
1290         waitAndAssertResumedActivity(activityName, "Activity must be resumed");
1291         mWmState.assertFocusedActivity(message, activityName);
1292 
1293         final int frontRootTaskId = mWmState.getFrontRootTaskId(displayId);
1294         Task frontRootTaskOnDisplay = mWmState.getRootTask(frontRootTaskId);
1295         assertEquals(
1296                 "Resumed activity of front root task of the target display must match. " + message,
1297                 activityClassName,
1298                 frontRootTaskOnDisplay.isLeafTask() ? frontRootTaskOnDisplay.mResumedActivity
1299                         : frontRootTaskOnDisplay.getTopTask().mResumedActivity);
1300         mWmState.assertFocusedRootTask("Top activity's rootTask must also be on top",
1301                 frontRootTaskId);
1302     }
1303 
1304     /**
1305      * Waits and asserts that the activity represented by the given activity name is resumed and
1306      * visible, but is not necessarily the top activity.
1307      *
1308      * @param activityName the activity name
1309      */
waitAndAssertResumedActivity(ComponentName activityName)1310     public void waitAndAssertResumedActivity(ComponentName activityName) {
1311         waitAndAssertResumedActivity(
1312                 activityName, activityName.toShortString() + " must be resumed");
1313     }
1314 
1315     /**
1316      * Waits and asserts that the activity represented by the given activity name is resumed and
1317      * visible, but is not necessarily the top activity.
1318      *
1319      * @param activityName the activity name
1320      * @param message the error message
1321      */
waitAndAssertResumedActivity(ComponentName activityName, String message)1322     public void waitAndAssertResumedActivity(ComponentName activityName, String message) {
1323         mWmState.waitForActivityState(activityName, STATE_RESUMED);
1324         mWmState.waitForValidState(activityName);
1325         mWmState.assertValidity();
1326         assertTrue(message, mWmState.hasActivityState(activityName, STATE_RESUMED));
1327         mWmState.assertVisibility(activityName, true /* visible */);
1328     }
1329 
1330     /**
1331      * Waits and asserts that the activity represented by the given activity name is stopped and
1332      * invisible.
1333      *
1334      * @param activityName the activity name
1335      */
waitAndAssertStoppedActivity(ComponentName activityName)1336     public void waitAndAssertStoppedActivity(ComponentName activityName) {
1337         waitAndAssertStoppedActivity(
1338                 activityName, activityName.toShortString() + " must be stopped");
1339     }
1340 
1341     /**
1342      * Waits and asserts that the activity represented by the given activity name is stopped and
1343      * invisible.
1344      *
1345      * @param activityName the activity name
1346      * @param message the error message
1347      */
waitAndAssertStoppedActivity(ComponentName activityName, String message)1348     public void waitAndAssertStoppedActivity(ComponentName activityName, String message) {
1349         mWmState.waitForValidState(activityName);
1350         mWmState.waitForActivityState(activityName, STATE_STOPPED);
1351         mWmState.assertValidity();
1352         assertTrue(message, mWmState.hasActivityState(activityName, STATE_STOPPED));
1353         mWmState.assertVisibility(activityName, false /* visible */);
1354     }
1355 
1356     // TODO: Switch to using a feature flag, when available.
isUiModeLockedToVrHeadset()1357     protected static boolean isUiModeLockedToVrHeadset() {
1358         final String output = runCommandAndPrintOutput("dumpsys uimode");
1359 
1360         Integer curUiMode = null;
1361         Boolean uiModeLocked = null;
1362         for (String line : output.split("\\n")) {
1363             line = line.trim();
1364             Matcher matcher = sCurrentUiModePattern.matcher(line);
1365             if (matcher.find()) {
1366                 curUiMode = Integer.parseInt(matcher.group(1), 16);
1367             }
1368             matcher = sUiModeLockedPattern.matcher(line);
1369             if (matcher.find()) {
1370                 uiModeLocked = matcher.group(1).equals("true");
1371             }
1372         }
1373 
1374         boolean uiModeLockedToVrHeadset = (curUiMode != null) && (uiModeLocked != null)
1375                 && ((curUiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET) && uiModeLocked;
1376 
1377         if (uiModeLockedToVrHeadset) {
1378             log("UI mode is locked to VR headset");
1379         }
1380 
1381         return uiModeLockedToVrHeadset;
1382     }
1383 
supportsMultiWindow()1384     protected boolean supportsMultiWindow() {
1385         Display defaultDisplay = mDm.getDisplay(DEFAULT_DISPLAY);
1386         return supportsMultiWindow(mContext.createDisplayContext(defaultDisplay));
1387     }
1388 
1389     /**
1390      * Returns true if the Context supports multi-window-mode
1391      */
supportsMultiWindow(Context context)1392     protected final boolean supportsMultiWindow(Context context) {
1393         return ActivityTaskManager.supportsSplitScreenMultiWindow(context);
1394     }
1395 
1396     /** Returns true if the default display supports split screen multi-window. */
supportsSplitScreenMultiWindow()1397     protected boolean supportsSplitScreenMultiWindow() {
1398         Display defaultDisplay = mDm.getDisplay(DEFAULT_DISPLAY);
1399         return supportsSplitScreenMultiWindow(mContext.createDisplayContext(defaultDisplay));
1400     }
1401 
1402     /**
1403      * Returns true if the display associated with the supplied {@code context} supports split
1404      * screen multi-window.
1405      */
supportsSplitScreenMultiWindow(Context context)1406     protected boolean supportsSplitScreenMultiWindow(Context context) {
1407         return ActivityTaskManager.supportsSplitScreenMultiWindow(context);
1408     }
1409 
hasHomeScreen()1410     protected boolean hasHomeScreen() {
1411         if (sHasHomeScreen == null) {
1412             sHasHomeScreen = !noHomeScreen();
1413         }
1414         return sHasHomeScreen;
1415     }
1416 
supportsSystemDecorsOnSecondaryDisplays()1417     protected boolean supportsSystemDecorsOnSecondaryDisplays() {
1418         if (sSupportsSystemDecorsOnSecondaryDisplays == null) {
1419             sSupportsSystemDecorsOnSecondaryDisplays = getSupportsSystemDecorsOnSecondaryDisplays();
1420         }
1421         return sSupportsSystemDecorsOnSecondaryDisplays;
1422     }
1423 
getSupportsInsecureLockScreen()1424     protected boolean getSupportsInsecureLockScreen() {
1425         boolean insecure;
1426         try {
1427             insecure = mContext.getResources().getBoolean(
1428                     Resources.getSystem().getIdentifier(
1429                             "config_supportsInsecureLockScreen", "bool", "android"));
1430         } catch (Resources.NotFoundException e) {
1431             insecure = true;
1432         }
1433         return insecure;
1434     }
1435 
isAssistantOnTopOfDream()1436     protected boolean isAssistantOnTopOfDream() {
1437         if (sIsAssistantOnTop == null) {
1438             sIsAssistantOnTop = mContext.getResources().getBoolean(
1439                     android.R.bool.config_assistantOnTopOfDream);
1440         }
1441         return sIsAssistantOnTop;
1442     }
1443 
dismissDreamOnActivityStart()1444     protected boolean dismissDreamOnActivityStart() {
1445         if (sDismissDreamOnActivityStart == null) {
1446             try {
1447                 sDismissDreamOnActivityStart = mContext.getResources().getBoolean(
1448                         Resources.getSystem().getIdentifier(
1449                                 "config_dismissDreamOnActivityStart", "bool", "android"));
1450             } catch (Resources.NotFoundException e) {
1451                 sDismissDreamOnActivityStart = true;
1452             }
1453         }
1454         return sDismissDreamOnActivityStart;
1455     }
1456 
1457     /**
1458      * Rotation support is indicated by explicitly having both landscape and portrait
1459      * features or not listing either at all.
1460      */
supportsRotation()1461     protected boolean supportsRotation() {
1462         final boolean supportsLandscape = hasDeviceFeature(FEATURE_SCREEN_LANDSCAPE);
1463         final boolean supportsPortrait = hasDeviceFeature(FEATURE_SCREEN_PORTRAIT);
1464         return (supportsLandscape && supportsPortrait)
1465                 || (!supportsLandscape && !supportsPortrait);
1466     }
1467 
1468     /**
1469      * The device should support orientation request from apps if it supports rotation and the
1470      * display is not close to square.
1471      */
supportsOrientationRequest()1472     protected boolean supportsOrientationRequest() {
1473         return supportsRotation() && !isCloseToSquareDisplay();
1474     }
1475 
1476     /** Checks whether the display dimension is close to square. */
isCloseToSquareDisplay()1477     protected boolean isCloseToSquareDisplay() {
1478         return isCloseToSquareDisplay(mContext);
1479     }
1480 
1481     /** Checks whether the display dimension is close to square. */
isCloseToSquareDisplay(Context context)1482     public static boolean isCloseToSquareDisplay(Context context) {
1483         final Resources resources = context.getResources();
1484         final float closeToSquareMaxAspectRatio;
1485         try {
1486             closeToSquareMaxAspectRatio = resources.getFloat(resources.getIdentifier(
1487                     "config_closeToSquareDisplayMaxAspectRatio", "dimen", "android"));
1488         } catch (Resources.NotFoundException e) {
1489             // Assume device is not close to square.
1490             return false;
1491         }
1492         final DisplayMetrics displayMetrics = new DisplayMetrics();
1493         context.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY)
1494                 .getRealMetrics(displayMetrics);
1495         final int w = displayMetrics.widthPixels;
1496         final int h = displayMetrics.heightPixels;
1497         final float aspectRatio = Math.max(w, h) / (float) Math.min(w, h);
1498         return aspectRatio <= closeToSquareMaxAspectRatio;
1499     }
1500 
hasDeviceFeature(final String requiredFeature)1501     protected boolean hasDeviceFeature(final String requiredFeature) {
1502         return mContext.getPackageManager()
1503                 .hasSystemFeature(requiredFeature);
1504     }
1505 
isDisplayPortrait()1506     protected static boolean isDisplayPortrait() {
1507         final DisplayManager displayManager = getInstrumentation()
1508                 .getContext().getSystemService(DisplayManager.class);
1509         final Display display = displayManager.getDisplay(DEFAULT_DISPLAY);
1510         final DisplayMetrics displayMetrics = new DisplayMetrics();
1511         display.getRealMetrics(displayMetrics);
1512         return displayMetrics.widthPixels < displayMetrics.heightPixels;
1513     }
1514 
isDisplayOn(int displayId)1515     protected static boolean isDisplayOn(int displayId) {
1516         final DisplayManager displayManager = getInstrumentation()
1517                 .getContext().getSystemService(DisplayManager.class);
1518         final Display display = displayManager.getDisplay(displayId);
1519         return display != null && display.getState() == Display.STATE_ON;
1520     }
1521 
perDisplayFocusEnabled()1522     protected static boolean perDisplayFocusEnabled() {
1523         return getInstrumentation().getTargetContext().getResources()
1524                 .getBoolean(android.R.bool.config_perDisplayFocusEnabled);
1525     }
1526 
removeLockCredential()1527     protected static void removeLockCredential() {
1528         runCommandAndPrintOutput("locksettings clear --old " + LOCK_CREDENTIAL);
1529     }
1530 
remoteInsetsControllerControlsSystemBars()1531     protected static boolean remoteInsetsControllerControlsSystemBars() {
1532         return getInstrumentation().getTargetContext().getResources()
1533                 .getBoolean(android.R.bool.config_remoteInsetsControllerControlsSystemBars);
1534     }
1535 
1536     /** @see ObjectTracker#manage(AutoCloseable) */
createManagedHomeActivitySession(ComponentName homeActivity)1537     protected HomeActivitySession createManagedHomeActivitySession(ComponentName homeActivity) {
1538         return mObjectTracker.manage(new HomeActivitySession(homeActivity));
1539     }
1540 
1541     /** @see ObjectTracker#manage(AutoCloseable) */
createManagedActivityClientSession()1542     protected ActivitySessionClient createManagedActivityClientSession() {
1543         return mObjectTracker.manage(new ActivitySessionClient(mContext));
1544     }
1545 
1546     /** @see ObjectTracker#manage(AutoCloseable) */
createManagedLockScreenSession()1547     protected LockScreenSession createManagedLockScreenSession() {
1548         return mObjectTracker.manage(new LockScreenSession(mInstrumentation, mWmState));
1549     }
1550 
1551     /** @see ObjectTracker#manage(AutoCloseable) */
createManagedLockScreenSessionAndClearActivitiesOnClose()1552     protected LockScreenSession createManagedLockScreenSessionAndClearActivitiesOnClose() {
1553         return mObjectTracker.manage(new LockScreenSession(mInstrumentation, mWmState) {
1554             @Override
1555             public void close() {
1556                 removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
1557                 super.close();
1558             }
1559         });
1560     }
1561 
1562     /** @see ObjectTracker#manage(AutoCloseable) */
1563     protected RotationSession createManagedRotationSession() {
1564         mWaitForRotationOnTearDown = true;
1565         return mObjectTracker.manage(new RotationSession(mWmState));
1566     }
1567 
1568     /** @see ObjectTracker#manage(AutoCloseable) */
1569     protected AodSession createManagedAodSession() {
1570         return mObjectTracker.manage(new AodSession());
1571     }
1572 
1573     /** @see ObjectTracker#manage(AutoCloseable) */
1574     protected DevEnableNonResizableMultiWindowSession
1575     createManagedDevEnableNonResizableMultiWindowSession() {
1576         return mObjectTracker.manage(new DevEnableNonResizableMultiWindowSession());
1577     }
1578 
1579     /** @see ObjectTracker#manage(AutoCloseable) */
1580     protected <T extends Activity> TestActivitySession<T> createManagedTestActivitySession() {
1581         return new TestActivitySession<T>();
1582     }
1583 
1584     /** @see ObjectTracker#manage(AutoCloseable) */
1585     protected SystemAlertWindowAppOpSession createAllowSystemAlertWindowAppOpSession() {
1586         return mObjectTracker.manage(
1587                 new SystemAlertWindowAppOpSession(mContext.getOpPackageName(), MODE_ALLOWED));
1588     }
1589 
1590     /** @see ObjectTracker#manage(AutoCloseable) */
1591     protected FontScaleSession createManagedFontScaleSession() {
1592         return mObjectTracker.manage(new FontScaleSession());
1593     }
1594 
1595     /** Allows requesting orientation in case ignore_orientation_request is set to true. */
1596     protected void disableIgnoreOrientationRequest() {
1597         mObjectTracker.manage(new IgnoreOrientationRequestSession(false /* enable */));
1598     }
1599 
1600     /**
1601      * Test @Rule class that disables Immersive mode confirmation dialog.
1602      */
1603     public static class DisableImmersiveModeConfirmationRule implements TestRule {
1604         @Override
1605         public Statement apply(Statement base, Description description) {
1606             return new Statement() {
1607                 @Override
1608                 public void evaluate() throws Throwable {
1609                     try (SettingsSession<String> immersiveModeConfirmationSetting =
1610                                  new SettingsSession<>(
1611                             Settings.Secure.getUriFor(IMMERSIVE_MODE_CONFIRMATIONS),
1612                             Settings.Secure::getString, Settings.Secure::putString)) {
1613                         immersiveModeConfirmationSetting.set("confirmed");
1614                         base.evaluate();
1615                     }
1616                 }
1617             };
1618         }
1619     }
1620 
1621     /**
1622      * Test @Rule class that disables screen doze settings before each test method running and
1623      * restoring to initial values after test method finished.
1624      */
1625     protected class DisableScreenDozeRule implements TestRule {
1626         AmbientDisplayConfiguration mConfig;
1627 
1628         public DisableScreenDozeRule() {
1629             mConfig = new AmbientDisplayConfiguration(mContext);
1630         }
1631 
1632         @Override
1633         public Statement apply(final Statement base, final Description description) {
1634             return new Statement() {
1635                 @Override
1636                 public void evaluate() throws Throwable {
1637                     try {
1638                         SystemUtil.runWithShellPermissionIdentity(() -> {
1639                             // disable current doze settings
1640                             mConfig.disableDozeSettings(true /* shouldDisableNonUserConfigurable */,
1641                                     android.os.Process.myUserHandle().getIdentifier());
1642                         });
1643                         base.evaluate();
1644                     } finally {
1645                         SystemUtil.runWithShellPermissionIdentity(() -> {
1646                             // restore doze settings
1647                             mConfig.restoreDozeSettings(
1648                                     android.os.Process.myUserHandle().getIdentifier());
1649                         });
1650                     }
1651                 }
1652             };
1653         }
1654     }
1655 
1656     public ComponentName getDefaultHomeComponent() {
1657         final Intent intent = new Intent(ACTION_MAIN);
1658         intent.addCategory(CATEGORY_HOME);
1659         intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
1660         final ResolveInfo resolveInfo =
1661                 mContext.getPackageManager().resolveActivity(intent, MATCH_DEFAULT_ONLY);
1662         if (resolveInfo == null) {
1663             throw new AssertionError("Home activity not found");
1664         }
1665         return new ComponentName(resolveInfo.activityInfo.packageName,
1666                 resolveInfo.activityInfo.name);
1667     }
1668 
1669     /**
1670      * HomeActivitySession is used to replace the default home component, so that you can use
1671      * your preferred home for testing within the session. The original default home will be
1672      * restored automatically afterward.
1673      */
1674     protected class HomeActivitySession implements AutoCloseable {
1675         private PackageManager mPackageManager;
1676         private ComponentName mOrigHome;
1677         private ComponentName mSessionHome;
1678 
1679         HomeActivitySession(ComponentName sessionHome) {
1680             mSessionHome = sessionHome;
1681             mPackageManager = mContext.getPackageManager();
1682             mOrigHome = getDefaultHomeComponent();
1683 
1684             runWithShellPermission(
1685                     () -> mPackageManager.setComponentEnabledSetting(mSessionHome,
1686                             COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP));
1687             setDefaultHome(mSessionHome);
1688         }
1689 
1690         @Override
1691         public void close() {
1692             runWithShellPermission(
1693                     () -> mPackageManager.setComponentEnabledSetting(mSessionHome,
1694                             COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP));
1695             if (mOrigHome != null) {
1696                 setDefaultHome(mOrigHome);
1697             }
1698         }
1699 
1700         private void setDefaultHome(ComponentName componentName) {
1701             executeShellCommand("cmd package set-home-activity --user "
1702                     + android.os.Process.myUserHandle().getIdentifier() + " "
1703                     + componentName.flattenToString());
1704         }
1705     }
1706 
1707     /** Helper class to set and restore appop mode "android:system_alert_window". */
1708     protected static class SystemAlertWindowAppOpSession implements AutoCloseable {
1709         private final String mPackageName;
1710         private final int mPreviousOpMode;
1711 
1712         SystemAlertWindowAppOpSession(String packageName, int mode) {
1713             mPackageName = packageName;
1714             try {
1715                 mPreviousOpMode = AppOpsUtils.getOpMode(mPackageName, OPSTR_SYSTEM_ALERT_WINDOW);
1716             } catch (IOException e) {
1717                 throw new RuntimeException(e);
1718             }
1719             setOpMode(mode);
1720         }
1721 
1722         @Override
1723         public void close() {
1724             setOpMode(mPreviousOpMode);
1725         }
1726 
1727         void setOpMode(int mode) {
1728             try {
1729                 AppOpsUtils.setOpMode(mPackageName, OPSTR_SYSTEM_ALERT_WINDOW, mode);
1730             } catch (IOException e) {
1731                 throw new RuntimeException(e);
1732             }
1733         }
1734     }
1735 
1736     protected class AodSession extends SettingsSession<Integer> {
1737         private AmbientDisplayConfiguration mConfig;
1738 
1739         AodSession() {
1740             super(Settings.Secure.getUriFor(Settings.Secure.DOZE_ALWAYS_ON),
1741                     Settings.Secure::getInt,
1742                     Settings.Secure::putInt);
1743             mConfig = new AmbientDisplayConfiguration(mContext);
1744         }
1745 
1746         public boolean isAodAvailable() {
1747             return mConfig.alwaysOnAvailable();
1748         }
1749 
1750         public void setAodEnabled(boolean enabled) {
1751             set(enabled ? 1 : 0);
1752         }
1753     }
1754 
1755     protected class DevEnableNonResizableMultiWindowSession extends SettingsSession<Integer> {
1756         DevEnableNonResizableMultiWindowSession() {
1757             super(Settings.Global.getUriFor(
1758                     Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW),
1759                     (cr, name) -> Settings.Global.getInt(cr, name, 0 /* def */),
1760                     Settings.Global::putInt);
1761         }
1762     }
1763 
1764     /** Helper class to save, set, and restore font_scale preferences. */
1765     protected static class FontScaleSession extends SettingsSession<Float> {
1766         FontScaleSession() {
1767             super(Settings.System.getUriFor(Settings.System.FONT_SCALE),
1768                     Settings.System::getFloat,
1769                     Settings.System::putFloat);
1770         }
1771 
1772         @Override
1773         public Float get() {
1774             Float value = super.get();
1775             return value == null ? 1f : value;
1776         }
1777     }
1778 
1779     protected ChangeWallpaperSession createManagedChangeWallpaperSession() {
1780         return mObjectTracker.manage(new ChangeWallpaperSession());
1781     }
1782 
1783     protected class ChangeWallpaperSession implements AutoCloseable {
1784         private final WallpaperManager mWallpaperManager;
1785         private Bitmap mTestBitmap;
1786 
1787         public ChangeWallpaperSession() {
1788             mWallpaperManager = WallpaperManager.getInstance(mContext);
1789         }
1790 
1791         public Bitmap getTestBitmap() {
1792             if (mTestBitmap == null) {
1793                 mTestBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
1794                 final Canvas canvas = new Canvas(mTestBitmap);
1795                 canvas.drawColor(Color.BLUE);
1796             }
1797             return mTestBitmap;
1798         }
1799 
1800         public void setImageWallpaper(Bitmap bitmap) {
1801             SystemUtil.runWithShellPermissionIdentity(() ->
1802                     mWallpaperManager.setBitmap(bitmap));
1803         }
1804 
1805         public void setWallpaperComponent(ComponentName componentName) {
1806             SystemUtil.runWithShellPermissionIdentity(() ->
1807                     mWallpaperManager.setWallpaperComponent(componentName));
1808         }
1809 
1810         @Override
1811         public void close() {
1812             SystemUtil.runWithShellPermissionIdentity(() -> mWallpaperManager.clearWallpaper());
1813             if (mTestBitmap != null) {
1814                 mTestBitmap.recycle();
1815             }
1816             // Turning screen off/on to flush deferred color events due to wallpaper changed.
1817             pressSleepButton();
1818             pressWakeupButton();
1819             pressUnlockButton();
1820         }
1821     }
1822     /**
1823      * Returns whether the test device respects settings of locked user rotation mode.
1824      *
1825      * The method sets the locked user rotation settings to the rotation that rotates the display by
1826      * 180 degrees and checks if the actual display rotation changes after that.
1827      *
1828      * This is a necessary assumption check before leveraging user rotation mode to force display
1829      * rotation, because there is no requirement that an Android device that supports both
1830      * orientations needs to support user rotation mode.
1831      *
1832      * @param session   the rotation session used to set user rotation
1833      * @param displayId the display ID to check rotation against
1834      * @return {@code true} if test device respects settings of locked user rotation mode;
1835      * {@code false} if not.
1836      */
1837     protected boolean supportsLockedUserRotation(RotationSession session, int displayId) {
1838         final int origRotation = getDeviceRotation(displayId);
1839         // Use the same orientation as target rotation to avoid affect of app-requested orientation.
1840         final int targetRotation = (origRotation + 2) % 4;
1841         session.set(targetRotation);
1842         final boolean result = (getDeviceRotation(displayId) == targetRotation);
1843         session.set(origRotation);
1844         return result;
1845     }
1846 
1847     protected int getDeviceRotation(int displayId) {
1848         final String displays = runCommandAndPrintOutput("dumpsys display displays").trim();
1849         Pattern pattern = Pattern.compile(
1850                 "(mDisplayId=" + displayId + ")([\\s\\S]*?)(mOverrideDisplayInfo)(.*)"
1851                         + "(rotation)(\\s+)(\\d+)");
1852         Matcher matcher = pattern.matcher(displays);
1853         if (matcher.find()) {
1854             final String match = matcher.group(7);
1855             return Integer.parseInt(match);
1856         }
1857 
1858         return INVALID_DEVICE_ROTATION;
1859     }
1860 
1861     /**
1862      * Creates a {#link ActivitySessionClient} instance with instrumentation context. It is used
1863      * when the caller doen't need try-with-resource.
1864      */
1865     public static ActivitySessionClient createActivitySessionClient() {
1866         return new ActivitySessionClient(getInstrumentation().getContext());
1867     }
1868 
1869     /** Empties the test journal so the following events won't be mixed-up with previous records. */
1870     protected void separateTestJournal() {
1871         TestJournalContainer.start();
1872     }
1873 
1874     protected static String runCommandAndPrintOutput(String command) {
1875         final String output = executeShellCommandAndGetStdout(command);
1876         log(output);
1877         return output;
1878     }
1879 
1880     protected static class LogSeparator {
1881         private final String mUniqueString;
1882 
1883         private LogSeparator() {
1884             mUniqueString = UUID.randomUUID().toString();
1885         }
1886 
1887         @Override
1888         public String toString() {
1889             return mUniqueString;
1890         }
1891     }
1892 
1893     /**
1894      * Inserts a log separator so we can always find the starting point from where to evaluate
1895      * following logs.
1896      *
1897      * @return Unique log separator.
1898      */
1899     protected LogSeparator separateLogs() {
1900         final LogSeparator logSeparator = new LogSeparator();
1901         executeShellCommand("log -t " + LOG_SEPARATOR + " " + logSeparator);
1902         EventLog.writeEvent(EVENT_LOG_SEPARATOR_TAG, logSeparator.mUniqueString);
1903         return logSeparator;
1904     }
1905 
1906     protected static String[] getDeviceLogsForComponents(
1907             LogSeparator logSeparator, String... logTags) {
1908         String filters = LOG_SEPARATOR + ":I ";
1909         for (String component : logTags) {
1910             filters += component + ":I ";
1911         }
1912         final String[] result = executeShellCommandAndGetStdout(
1913                 "logcat -v brief -d " + filters + " *:S").split("\\n");
1914         if (logSeparator == null) {
1915             return result;
1916         }
1917 
1918         // Make sure that we only check logs after the separator.
1919         int i = 0;
1920         boolean lookingForSeparator = true;
1921         while (i < result.length && lookingForSeparator) {
1922             if (result[i].contains(logSeparator.toString())) {
1923                 lookingForSeparator = false;
1924             }
1925             i++;
1926         }
1927         final String[] filteredResult = new String[result.length - i];
1928         for (int curPos = 0; i < result.length; curPos++, i++) {
1929             filteredResult[curPos] = result[i];
1930         }
1931         return filteredResult;
1932     }
1933 
1934     protected static List<Event> getEventLogsForComponents(LogSeparator logSeparator, int... tags) {
1935         List<Event> events = new ArrayList<>();
1936 
1937         int[] searchTags = Arrays.copyOf(tags, tags.length + 1);
1938         searchTags[searchTags.length - 1] = EVENT_LOG_SEPARATOR_TAG;
1939 
1940         try {
1941             EventLog.readEvents(searchTags, events);
1942         } catch (IOException e) {
1943             fail("Could not read from event log." + e);
1944         }
1945 
1946         for (Iterator<Event> itr = events.iterator(); itr.hasNext(); ) {
1947             Event event = itr.next();
1948             itr.remove();
1949             if (event.getTag() == EVENT_LOG_SEPARATOR_TAG &&
1950                     logSeparator.mUniqueString.equals(event.getData())) {
1951                 break;
1952             }
1953         }
1954         return events;
1955     }
1956 
1957     protected boolean supportsMultiDisplay() {
1958         return mContext.getPackageManager().hasSystemFeature(
1959                 FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
1960     }
1961 
1962     protected boolean supportsInstallableIme() {
1963         return mContext.getPackageManager().hasSystemFeature(FEATURE_INPUT_METHODS);
1964     }
1965 
1966     /**
1967      * Waits until the given activity has entered picture-in-picture mode (allowing for the
1968      * subsequent animation to start).
1969      */
1970     protected void waitForEnterPip(@NonNull ComponentName activityName) {
1971         mWmState.waitForWithAmState(wmState -> {
1972             Task task = wmState.getTaskByActivity(activityName);
1973             return task != null
1974                     && task.getActivity(activityName).getWindowingMode() == WINDOWING_MODE_PINNED
1975                     && task.isVisible();
1976         }, "checking task windowing mode");
1977     }
1978 
1979     /**
1980      * Waits until the picture-in-picture animation has finished.
1981      */
1982     protected void waitForEnterPipAnimationComplete(@NonNull ComponentName activityName) {
1983         waitForEnterPip(activityName);
1984         mWmState.waitForWithAmState(wmState -> {
1985             Task task = wmState.getTaskByActivity(activityName);
1986             if (task == null) {
1987                 return false;
1988             }
1989             WindowManagerState.Activity activity = task.getActivity(activityName);
1990             return activity.getWindowingMode() == WINDOWING_MODE_PINNED
1991                     && activity.getState().equals(STATE_PAUSED);
1992         }, "checking activity windowing mode");
1993         if (ENABLE_SHELL_TRANSITIONS) {
1994             mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
1995         }
1996     }
1997 
1998     public static class CountSpec<T> {
1999         static final int DONT_CARE = Integer.MIN_VALUE;
2000         public static final int EQUALS = 1;
2001         public static final int GREATER_THAN = 2;
2002         static final int LESS_THAN = 3;
2003         public static final int GREATER_THAN_OR_EQUALS = 4;
2004 
2005         final T mEvent;
2006         final int mRule;
2007         final int mCount;
2008         final String mMessage;
2009 
2010         CountSpec(T event, int rule, int count, String message) {
2011             mEvent = event;
2012             mRule = count == DONT_CARE ? DONT_CARE : rule;
2013             mCount = count;
2014             if (message != null) {
2015                 mMessage = message;
2016             } else {
2017                 switch (rule) {
2018                     case EQUALS:
2019                         mMessage = event + " must equal to " + count;
2020                         break;
2021                     case GREATER_THAN:
2022                         mMessage = event + " must be greater than " + count;
2023                         break;
2024                     case LESS_THAN:
2025                         mMessage = event + " must be less than " + count;
2026                         break;
2027                     case GREATER_THAN_OR_EQUALS:
2028                         mMessage = event + " must be greater than (or equals to) " + count;
2029                         break;
2030                     default:
2031                         mMessage = "Don't care";
2032                 }
2033             }
2034         }
2035 
2036         /** @return {@code true} if the given value is satisfied the condition. */
2037         boolean validate(int value) {
2038             switch (mRule) {
2039                 case DONT_CARE:
2040                     return true;
2041                 case EQUALS:
2042                     return value == mCount;
2043                 case GREATER_THAN:
2044                     return value > mCount;
2045                 case LESS_THAN:
2046                     return value < mCount;
2047                 case GREATER_THAN_OR_EQUALS:
2048                     return value >= mCount;
2049                 default:
2050             }
2051             throw new RuntimeException("Unknown CountSpec rule");
2052         }
2053     }
2054 
2055     static <T> CountSpec<T> countSpec(T event, int rule, int count, String message) {
2056         return new CountSpec<>(event, rule, count, message);
2057     }
2058 
2059     public static <T> CountSpec<T> countSpec(T event, int rule, int count) {
2060         return new CountSpec<>(event, rule, count, null /* message */);
2061     }
2062 
2063     static void assertLifecycleCounts(ComponentName activityName, String message,
2064             int createCount, int startCount, int resumeCount, int pauseCount, int stopCount,
2065             int destroyCount, int configChangeCount) {
2066         new ActivityLifecycleCounts(activityName).assertCountWithRetry(
2067                 message,
2068                 countSpec(ActivityCallback.ON_CREATE, CountSpec.EQUALS, createCount),
2069                 countSpec(ActivityCallback.ON_START, CountSpec.EQUALS, startCount),
2070                 countSpec(ActivityCallback.ON_RESUME, CountSpec.EQUALS, resumeCount),
2071                 countSpec(ActivityCallback.ON_PAUSE, CountSpec.EQUALS, pauseCount),
2072                 countSpec(ActivityCallback.ON_STOP, CountSpec.EQUALS, stopCount),
2073                 countSpec(ActivityCallback.ON_DESTROY, CountSpec.EQUALS, destroyCount),
2074                 countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS,
2075                         configChangeCount));
2076     }
2077 
2078     public static void assertLifecycleCounts(
2079             ComponentName activityName,
2080             int createCount,
2081             int startCount,
2082             int resumeCount,
2083             int pauseCount,
2084             int stopCount,
2085             int destroyCount,
2086             int configChangeCount) {
2087         assertLifecycleCounts(activityName, "Assert lifecycle of " + getLogTag(activityName),
2088                 createCount, startCount, resumeCount, pauseCount, stopCount,
2089                 destroyCount, configChangeCount);
2090     }
2091 
2092     public static void assertSingleLaunch(ComponentName activityName) {
2093         assertLifecycleCounts(activityName,
2094                 "activity create, start, and resume",
2095                 1 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
2096                 0 /* pauseCount */, 0 /* stopCount */, 0 /* destroyCount */,
2097                 CountSpec.DONT_CARE /* configChangeCount */);
2098     }
2099 
2100     public static void assertSingleLaunchAndStop(ComponentName activityName) {
2101         assertLifecycleCounts(activityName,
2102                 "activity create, start, resume, pause, and stop",
2103                 1 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
2104                 1 /* pauseCount */, 1 /* stopCount */, 0 /* destroyCount */,
2105                 CountSpec.DONT_CARE /* configChangeCount */);
2106     }
2107 
2108     public static void assertSingleStartAndStop(ComponentName activityName) {
2109         assertLifecycleCounts(activityName,
2110                 "activity start, resume, pause, and stop",
2111                 0 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
2112                 1 /* pauseCount */, 1 /* stopCount */, 0 /* destroyCount */,
2113                 CountSpec.DONT_CARE /* configChangeCount */);
2114     }
2115 
2116     protected static void assertSingleStart(ComponentName activityName) {
2117         assertLifecycleCounts(activityName,
2118                 "activity start and resume",
2119                 0 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
2120                 0 /* pauseCount */, 0 /* stopCount */, 0 /* destroyCount */,
2121                 CountSpec.DONT_CARE /* configChangeCount */);
2122     }
2123 
2124     /** Assert the activity is either relaunched or received configuration changed. */
2125     protected static void assertActivityLifecycle(ComponentName activityName, boolean relaunched) {
2126         Condition.<String>waitForResult(
2127                 activityName + (relaunched ? " relaunched" : " config changed"),
2128                 condition -> condition
2129                 .setResultSupplier(() -> checkActivityIsRelaunchedOrConfigurationChanged(
2130                         getActivityName(activityName),
2131                         TestJournalContainer.get(activityName).callbacks, relaunched))
2132                 .setResultValidator(failedReasons -> failedReasons == null)
2133                 .setOnFailure(failedReasons -> fail(failedReasons)));
2134     }
2135 
2136     /** Assert the activity is either relaunched or received configuration changed. */
2137     public static List<ActivityCallback> assertActivityLifecycle(
2138             ActivitySession activitySession, boolean relaunched) {
2139         final String name = activitySession.getName().flattenToShortString();
2140         final List<ActivityCallback> callbackHistory = activitySession.takeCallbackHistory();
2141         String failedReason = checkActivityIsRelaunchedOrConfigurationChanged(
2142                 name, callbackHistory, relaunched);
2143         if (failedReason != null) {
2144             fail(failedReason);
2145         }
2146         return callbackHistory;
2147     }
2148 
2149     private static String checkActivityIsRelaunchedOrConfigurationChanged(String name,
2150             List<ActivityCallback> callbackHistory, boolean relaunched) {
2151         final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(callbackHistory);
2152         if (relaunched) {
2153             return lifecycles.validateCount(
2154                     countSpec(ActivityCallback.ON_DESTROY, CountSpec.GREATER_THAN, 0,
2155                             name + " must have been destroyed."),
2156                     countSpec(ActivityCallback.ON_CREATE, CountSpec.GREATER_THAN, 0,
2157                             name + " must have been (re)created."));
2158         }
2159         return lifecycles.validateCount(
2160                 countSpec(ActivityCallback.ON_DESTROY, CountSpec.LESS_THAN, 1,
2161                         name + " must *NOT* have been destroyed."),
2162                 countSpec(ActivityCallback.ON_CREATE, CountSpec.LESS_THAN, 1,
2163                         name + " must *NOT* have been (re)created."),
2164                 countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.GREATER_THAN, 0,
2165                                 name + " must have received configuration changed."));
2166     }
2167 
2168     public static void assertRelaunchOrConfigChanged(
2169             ComponentName activityName, int numRelaunch, int numConfigChange) {
2170         new ActivityLifecycleCounts(activityName).assertCountWithRetry("relaunch or config changed",
2171                 countSpec(ActivityCallback.ON_DESTROY, CountSpec.EQUALS, numRelaunch),
2172                 countSpec(ActivityCallback.ON_CREATE, CountSpec.EQUALS, numRelaunch),
2173                 countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS,
2174                         numConfigChange));
2175     }
2176 
2177     public static void assertActivityDestroyed(ComponentName activityName) {
2178         new ActivityLifecycleCounts(activityName).assertCountWithRetry("activity destroyed",
2179                 countSpec(ActivityCallback.ON_DESTROY, CountSpec.EQUALS, 1),
2180                 countSpec(ActivityCallback.ON_CREATE, CountSpec.EQUALS, 0),
2181                 countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS, 0));
2182     }
2183 
2184     public static void assertSecurityExceptionFromActivityLauncher() {
2185         waitForOrFail("SecurityException from " + ActivityLauncher.TAG,
2186                 ActivityLauncher::hasCaughtSecurityException);
2187     }
2188 
2189     private static final Pattern sCurrentUiModePattern = Pattern.compile("mCurUiMode=0x(\\d+)");
2190     private static final Pattern sUiModeLockedPattern =
2191             Pattern.compile("mUiModeLocked=(true|false)");
2192 
2193     @NonNull
2194     public SizeInfo getLastReportedSizesForActivity(ComponentName activityName) {
2195         return Condition.waitForResult("sizes of " + activityName + " to be reported",
2196                 condition -> condition.setResultSupplier(() -> {
2197                     final ConfigInfo info = TestJournalContainer.get(activityName).lastConfigInfo;
2198                     return info != null ? info.sizeInfo : null;
2199                 }).setResultValidator(Objects::nonNull).setOnFailure(unusedResult ->
2200                         fail("No config reported from " + activityName)));
2201     }
2202 
2203     /** Check if a device has display cutout. */
2204     public boolean hasDisplayCutout() {
2205         // Launch an activity to report cutout state
2206         separateTestJournal();
2207         launchActivity(BROADCAST_RECEIVER_ACTIVITY);
2208 
2209         // Read the logs to check if cutout is present
2210         final Boolean displayCutoutPresent = getCutoutStateForActivity(BROADCAST_RECEIVER_ACTIVITY);
2211         assertNotNull("The activity should report cutout state", displayCutoutPresent);
2212 
2213         // Finish activity
2214         mBroadcastActionTrigger.finishBroadcastReceiverActivity();
2215         mWmState.waitForWithAmState(
2216                 (state) -> !state.containsActivity(BROADCAST_RECEIVER_ACTIVITY),
2217                 "activity to be removed");
2218 
2219         return displayCutoutPresent;
2220     }
2221 
2222     /**
2223      * Wait for activity to report cutout state in logs and return it. Will return {@code null}
2224      * after timeout.
2225      */
2226     @Nullable
2227     private Boolean getCutoutStateForActivity(ComponentName activityName) {
2228         return Condition.waitForResult("cutout state to be reported", condition -> condition
2229                 .setResultSupplier(() -> {
2230                     final Bundle extras = TestJournalContainer.get(activityName).extras;
2231                     return extras.containsKey(EXTRA_CUTOUT_EXISTS)
2232                             ? extras.getBoolean(EXTRA_CUTOUT_EXISTS)
2233                             : null;
2234                 }).setResultValidator(cutoutExists -> cutoutExists != null));
2235     }
2236 
2237     /** Waits for at least one onMultiWindowModeChanged event. */
2238     public ActivityLifecycleCounts waitForOnMultiWindowModeChanged(ComponentName activityName) {
2239         final ActivityLifecycleCounts counts = new ActivityLifecycleCounts(activityName);
2240         Condition.waitFor(counts.countWithRetry("waitForOnMultiWindowModeChanged", countSpec(
2241                 ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED, CountSpec.GREATER_THAN, 0)));
2242         return counts;
2243     }
2244 
2245     protected WindowState getPackageWindowState(String packageName) {
2246         final WindowManagerState.WindowState window =
2247                 mWmState.getWindowByPackageName(packageName, TYPE_BASE_APPLICATION);
2248         assertNotNull(window);
2249         return window;
2250     }
2251 
2252     public static class ActivityLifecycleCounts {
2253         private final int[] mCounts = new int[ActivityCallback.SIZE];
2254         private final int[] mFirstIndexes = new int[ActivityCallback.SIZE];
2255         private final int[] mLastIndexes = new int[ActivityCallback.SIZE];
2256         private ComponentName mActivityName;
2257 
2258         public ActivityLifecycleCounts(ComponentName componentName) {
2259             mActivityName = componentName;
2260             updateCount(TestJournalContainer.get(componentName).callbacks);
2261         }
2262 
2263         public ActivityLifecycleCounts(List<ActivityCallback> callbacks) {
2264             updateCount(callbacks);
2265         }
2266 
2267         private void updateCount(List<ActivityCallback> callbacks) {
2268             // The callback list could be from the reference of TestJournal. If we are counting for
2269             // retrying, there may be new data added to the list from other threads.
2270             TestJournalContainer.withThreadSafeAccess(() -> {
2271                 Arrays.fill(mFirstIndexes, -1);
2272                 for (int i = 0; i < callbacks.size(); i++) {
2273                     final ActivityCallback callback = callbacks.get(i);
2274                     final int ordinal = callback.ordinal();
2275                     mCounts[ordinal]++;
2276                     mLastIndexes[ordinal] = i;
2277                     if (mFirstIndexes[ordinal] == -1) {
2278                         mFirstIndexes[ordinal] = i;
2279                     }
2280                 }
2281             });
2282         }
2283 
2284         public int getCount(ActivityCallback callback) {
2285             return mCounts[callback.ordinal()];
2286         }
2287 
2288         public int getFirstIndex(ActivityCallback callback) {
2289             return mFirstIndexes[callback.ordinal()];
2290         }
2291 
2292         public int getLastIndex(ActivityCallback callback) {
2293             return mLastIndexes[callback.ordinal()];
2294         }
2295 
2296         @SafeVarargs
2297         public final Condition<String> countWithRetry(
2298                 String message, CountSpec<ActivityCallback>... countSpecs) {
2299             if (mActivityName == null) {
2300                 throw new IllegalStateException(
2301                         "It is meaningless to retry without specified activity");
2302             }
2303             return new Condition<String>(message)
2304                     .setOnRetry(() -> {
2305                         Arrays.fill(mCounts, 0);
2306                         Arrays.fill(mLastIndexes, 0);
2307                         updateCount(TestJournalContainer.get(mActivityName).callbacks);
2308                     })
2309                     .setResultSupplier(() -> validateCount(countSpecs))
2310                     .setResultValidator(failedReasons -> failedReasons == null);
2311         }
2312 
2313         @SafeVarargs
2314         public final void assertCountWithRetry(
2315                 String message, CountSpec<ActivityCallback>... countSpecs) {
2316             if (mActivityName == null) {
2317                 throw new IllegalStateException(
2318                         "It is meaningless to retry without specified activity");
2319             }
2320             Condition.<String>waitForResult(countWithRetry(message, countSpecs)
2321                     .setOnFailure(failedReasons -> fail(message + ": " + failedReasons)));
2322         }
2323 
2324         @SafeVarargs
2325         final String validateCount(CountSpec<ActivityCallback>... countSpecs) {
2326             ArrayList<String> failedReasons = null;
2327             for (CountSpec<ActivityCallback> spec : countSpecs) {
2328                 final int realCount = mCounts[spec.mEvent.ordinal()];
2329                 if (!spec.validate(realCount)) {
2330                     if (failedReasons == null) {
2331                         failedReasons = new ArrayList<>();
2332                     }
2333                     failedReasons.add(spec.mMessage + " (got " + realCount + ")");
2334                 }
2335             }
2336             return failedReasons == null ? null : String.join("\n", failedReasons);
2337         }
2338     }
2339 
2340     protected void stopTestPackage(final String packageName) {
2341         runWithShellPermission(() -> mAm.forceStopPackage(packageName));
2342     }
2343 
2344     protected LaunchActivityBuilder getLaunchActivityBuilder() {
2345         return new LaunchActivityBuilder(mWmState);
2346     }
2347 
2348     public static <T extends Activity>
2349     ActivityScenarioRule<T> createFullscreenActivityScenarioRule(Class<T> clazz) {
2350         final ActivityOptions options = ActivityOptions.makeBasic();
2351         options.setLaunchWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
2352         return new ActivityScenarioRule<>(clazz, options.toBundle());
2353     }
2354 
2355     protected static class LaunchActivityBuilder implements LaunchProxy {
2356         private final WindowManagerStateHelper mAmWmState;
2357 
2358         // The activity to be launched
2359         private ComponentName mTargetActivity = TEST_ACTIVITY;
2360         private boolean mUseApplicationContext;
2361         private boolean mToSide;
2362         private boolean mRandomData;
2363         private boolean mNewTask;
2364         private boolean mMultipleTask;
2365         private boolean mAllowMultipleInstances = true;
2366         private boolean mLaunchTaskBehind;
2367         private boolean mFinishBeforeLaunch;
2368         private int mDisplayId = INVALID_DISPLAY;
2369         private int mWindowingMode = -1;
2370         private int mActivityType = ACTIVITY_TYPE_UNDEFINED;
2371         // A proxy activity that launches other activities including mTargetActivityName
2372         private ComponentName mLaunchingActivity = LAUNCHING_ACTIVITY;
2373         private boolean mReorderToFront;
2374         private boolean mWaitForLaunched;
2375         private boolean mSuppressExceptions;
2376         private boolean mWithShellPermission;
2377         // Use of the following variables indicates that a broadcast receiver should be used instead
2378         // of a launching activity;
2379         private ComponentName mBroadcastReceiver;
2380         private String mBroadcastReceiverAction;
2381         private int mIntentFlags;
2382         private Bundle mExtras;
2383         private LaunchInjector mLaunchInjector;
2384         private ActivitySessionClient mActivitySessionClient;
2385         private int mLaunchTaskDisplayAreaFeatureId = FEATURE_UNDEFINED;
2386 
2387         private enum LauncherType {
2388             INSTRUMENTATION, LAUNCHING_ACTIVITY, BROADCAST_RECEIVER
2389         }
2390 
2391         private LauncherType mLauncherType = LauncherType.LAUNCHING_ACTIVITY;
2392 
2393         public LaunchActivityBuilder(WindowManagerStateHelper amWmState) {
2394             mAmWmState = amWmState;
2395             mWaitForLaunched = true;
2396             mWithShellPermission = true;
2397         }
2398 
2399         public LaunchActivityBuilder setToSide(boolean toSide) {
2400             mToSide = toSide;
2401             return this;
2402         }
2403 
2404         public LaunchActivityBuilder setRandomData(boolean randomData) {
2405             mRandomData = randomData;
2406             return this;
2407         }
2408 
2409         public LaunchActivityBuilder setNewTask(boolean newTask) {
2410             mNewTask = newTask;
2411             return this;
2412         }
2413 
2414         public LaunchActivityBuilder setMultipleTask(boolean multipleTask) {
2415             mMultipleTask = multipleTask;
2416             return this;
2417         }
2418 
2419         public LaunchActivityBuilder allowMultipleInstances(boolean allowMultipleInstances) {
2420             mAllowMultipleInstances = allowMultipleInstances;
2421             return this;
2422         }
2423 
2424         public LaunchActivityBuilder setLaunchTaskBehind(boolean launchTaskBehind) {
2425             mLaunchTaskBehind = launchTaskBehind;
2426             return this;
2427         }
2428 
2429         public LaunchActivityBuilder setReorderToFront(boolean reorderToFront) {
2430             mReorderToFront = reorderToFront;
2431             return this;
2432         }
2433 
2434         public LaunchActivityBuilder setUseApplicationContext(boolean useApplicationContext) {
2435             mUseApplicationContext = useApplicationContext;
2436             return this;
2437         }
2438 
2439         public LaunchActivityBuilder setFinishBeforeLaunch(boolean finishBeforeLaunch) {
2440             mFinishBeforeLaunch = finishBeforeLaunch;
2441             return this;
2442         }
2443 
2444         public ComponentName getTargetActivity() {
2445             return mTargetActivity;
2446         }
2447 
2448         public boolean isTargetActivityTranslucent() {
2449             return mAmWmState.isActivityTranslucent(mTargetActivity);
2450         }
2451 
2452         public LaunchActivityBuilder setTargetActivity(ComponentName targetActivity) {
2453             mTargetActivity = targetActivity;
2454             return this;
2455         }
2456 
2457         public LaunchActivityBuilder setDisplayId(int id) {
2458             mDisplayId = id;
2459             return this;
2460         }
2461 
2462         public LaunchActivityBuilder setWindowingMode(int windowingMode) {
2463             mWindowingMode = windowingMode;
2464             return this;
2465         }
2466 
2467         public LaunchActivityBuilder setActivityType(int type) {
2468             mActivityType = type;
2469             return this;
2470         }
2471 
2472         public LaunchActivityBuilder setLaunchingActivity(ComponentName launchingActivity) {
2473             mLaunchingActivity = launchingActivity;
2474             mLauncherType = LauncherType.LAUNCHING_ACTIVITY;
2475             return this;
2476         }
2477 
2478         public LaunchActivityBuilder setWaitForLaunched(boolean shouldWait) {
2479             mWaitForLaunched = shouldWait;
2480             return this;
2481         }
2482 
2483         public LaunchActivityBuilder setLaunchTaskDisplayAreaFeatureId(
2484                 int launchTaskDisplayAreaFeatureId) {
2485             mLaunchTaskDisplayAreaFeatureId = launchTaskDisplayAreaFeatureId;
2486             return this;
2487         }
2488 
2489         /** Use broadcast receiver as a launchpad for activities. */
2490         public LaunchActivityBuilder setUseBroadcastReceiver(final ComponentName broadcastReceiver,
2491                 final String broadcastAction) {
2492             mBroadcastReceiver = broadcastReceiver;
2493             mBroadcastReceiverAction = broadcastAction;
2494             mLauncherType = LauncherType.BROADCAST_RECEIVER;
2495             return this;
2496         }
2497 
2498         /** Use {@link android.app.Instrumentation} as a launchpad for activities. */
2499         public LaunchActivityBuilder setUseInstrumentation() {
2500             mLauncherType = LauncherType.INSTRUMENTATION;
2501             // Calling startActivity() from outside of an Activity context requires the
2502             // FLAG_ACTIVITY_NEW_TASK flag.
2503             setNewTask(true);
2504             return this;
2505         }
2506 
2507         public LaunchActivityBuilder setSuppressExceptions(boolean suppress) {
2508             mSuppressExceptions = suppress;
2509             return this;
2510         }
2511 
2512         public LaunchActivityBuilder setWithShellPermission(boolean withShellPermission) {
2513             mWithShellPermission = withShellPermission;
2514             return this;
2515         }
2516 
2517         public LaunchActivityBuilder setActivitySessionClient(ActivitySessionClient sessionClient) {
2518             mActivitySessionClient = sessionClient;
2519             return this;
2520         }
2521 
2522         @Override
2523         public boolean shouldWaitForLaunched() {
2524             return mWaitForLaunched;
2525         }
2526 
2527         public LaunchActivityBuilder setIntentFlags(int flags) {
2528             mIntentFlags = flags;
2529             return this;
2530         }
2531 
2532         public LaunchActivityBuilder setIntentExtra(Consumer<Bundle> extrasConsumer) {
2533             if (extrasConsumer != null) {
2534                 mExtras = new Bundle();
2535                 extrasConsumer.accept(mExtras);
2536             }
2537             return this;
2538         }
2539 
2540         @Override
2541         public Bundle getExtras() {
2542             return mExtras;
2543         }
2544 
2545         @Override
2546         public void setLaunchInjector(LaunchInjector injector) {
2547             mLaunchInjector = injector;
2548         }
2549 
2550         @Override
2551         public void execute() {
2552             if (mActivitySessionClient != null) {
2553                 final ActivitySessionClient client = mActivitySessionClient;
2554                 // Clear the session client so its startActivity can call the real execute().
2555                 mActivitySessionClient = null;
2556                 client.startActivity(this);
2557                 return;
2558             }
2559             switch (mLauncherType) {
2560                 case INSTRUMENTATION:
2561                     if (mWithShellPermission) {
2562                         NestedShellPermission.run(this::launchUsingInstrumentation);
2563                     } else {
2564                         launchUsingInstrumentation();
2565                     }
2566                     break;
2567                 case LAUNCHING_ACTIVITY:
2568                 case BROADCAST_RECEIVER:
2569                     launchUsingShellCommand();
2570             }
2571 
2572             if (mWaitForLaunched) {
2573                 mAmWmState.waitForValidState(mTargetActivity);
2574             }
2575         }
2576 
2577         /** Launch an activity using instrumentation. */
2578         private void launchUsingInstrumentation() {
2579             final Bundle b = new Bundle();
2580             b.putBoolean(KEY_LAUNCH_ACTIVITY, true);
2581             b.putBoolean(KEY_LAUNCH_TO_SIDE, mToSide);
2582             b.putBoolean(KEY_RANDOM_DATA, mRandomData);
2583             b.putBoolean(KEY_NEW_TASK, mNewTask);
2584             b.putBoolean(KEY_MULTIPLE_TASK, mMultipleTask);
2585             b.putBoolean(KEY_MULTIPLE_INSTANCES, mAllowMultipleInstances);
2586             b.putBoolean(KEY_LAUNCH_TASK_BEHIND, mLaunchTaskBehind);
2587             b.putBoolean(KEY_REORDER_TO_FRONT, mReorderToFront);
2588             b.putInt(KEY_DISPLAY_ID, mDisplayId);
2589             b.putInt(KEY_WINDOWING_MODE, mWindowingMode);
2590             b.putInt(KEY_ACTIVITY_TYPE, mActivityType);
2591             b.putBoolean(KEY_USE_APPLICATION_CONTEXT, mUseApplicationContext);
2592             b.putString(KEY_TARGET_COMPONENT, getActivityName(mTargetActivity));
2593             b.putBoolean(KEY_SUPPRESS_EXCEPTIONS, mSuppressExceptions);
2594             b.putInt(KEY_INTENT_FLAGS, mIntentFlags);
2595             b.putBundle(KEY_INTENT_EXTRAS, getExtras());
2596             b.putInt(KEY_TASK_DISPLAY_AREA_FEATURE_ID, mLaunchTaskDisplayAreaFeatureId);
2597             final Context context = getInstrumentation().getContext();
2598             launchActivityFromExtras(context, b, mLaunchInjector);
2599         }
2600 
2601         /** Build and execute a shell command to launch an activity. */
2602         private void launchUsingShellCommand() {
2603             StringBuilder commandBuilder = new StringBuilder();
2604             if (mBroadcastReceiver != null && mBroadcastReceiverAction != null) {
2605                 // Use broadcast receiver to launch the target.
2606                 commandBuilder.append("am broadcast -a ").append(mBroadcastReceiverAction)
2607                         .append(" -p ").append(mBroadcastReceiver.getPackageName())
2608                         // Include stopped packages
2609                         .append(" -f 0x00000020");
2610             } else {
2611                 // If new task flag isn't set the windowing mode of launcher activity will be the
2612                 // windowing mode of the target activity, so we need to launch launcher activity in
2613                 // it.
2614                 String amStartCmd =
2615                         (mWindowingMode == -1 || mNewTask)
2616                                 ? getAmStartCmd(mLaunchingActivity)
2617                                 : getAmStartCmd(mLaunchingActivity, mDisplayId)
2618                                         + " --windowingMode " + mWindowingMode;
2619                 // Use launching activity to launch the target.
2620                 commandBuilder.append(amStartCmd)
2621                         .append(" -f 0x20000020");
2622             }
2623 
2624             // Add a flag to ensure we actually mean to launch an activity.
2625             commandBuilder.append(" --ez " + KEY_LAUNCH_ACTIVITY + " true");
2626 
2627             if (mToSide) {
2628                 commandBuilder.append(" --ez " + KEY_LAUNCH_TO_SIDE + " true");
2629             }
2630             if (mRandomData) {
2631                 commandBuilder.append(" --ez " + KEY_RANDOM_DATA + " true");
2632             }
2633             if (mNewTask) {
2634                 commandBuilder.append(" --ez " + KEY_NEW_TASK + " true");
2635             }
2636             if (mMultipleTask) {
2637                 commandBuilder.append(" --ez " + KEY_MULTIPLE_TASK + " true");
2638             }
2639             if (mAllowMultipleInstances) {
2640                 commandBuilder.append(" --ez " + KEY_MULTIPLE_INSTANCES + " true");
2641             }
2642             if (mReorderToFront) {
2643                 commandBuilder.append(" --ez " + KEY_REORDER_TO_FRONT + " true");
2644             }
2645             if (mFinishBeforeLaunch) {
2646                 commandBuilder.append(" --ez " + KEY_FINISH_BEFORE_LAUNCH + " true");
2647             }
2648             if (mDisplayId != INVALID_DISPLAY) {
2649                 commandBuilder.append(" --ei " + KEY_DISPLAY_ID + " ").append(mDisplayId);
2650             }
2651             if (mWindowingMode != -1) {
2652                 commandBuilder.append(" --ei " + KEY_WINDOWING_MODE + " ").append(mWindowingMode);
2653             }
2654             if (mActivityType != ACTIVITY_TYPE_UNDEFINED) {
2655                 commandBuilder.append(" --ei " + KEY_ACTIVITY_TYPE + " ").append(mActivityType);
2656             }
2657 
2658             if (mUseApplicationContext) {
2659                 commandBuilder.append(" --ez " + KEY_USE_APPLICATION_CONTEXT + " true");
2660             }
2661 
2662             if (mTargetActivity != null) {
2663                 // {@link ActivityLauncher} parses this extra string by
2664                 // {@link ComponentName#unflattenFromString(String)}.
2665                 commandBuilder.append(" --es " + KEY_TARGET_COMPONENT + " ")
2666                         .append(getActivityName(mTargetActivity));
2667             }
2668 
2669             if (mSuppressExceptions) {
2670                 commandBuilder.append(" --ez " + KEY_SUPPRESS_EXCEPTIONS + " true");
2671             }
2672 
2673             if (mIntentFlags != 0) {
2674                 commandBuilder.append(" --ei " + KEY_INTENT_FLAGS + " ").append(mIntentFlags);
2675             }
2676 
2677             if (mLaunchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) {
2678                 commandBuilder.append(" --task-display-area-feature-id ")
2679                         .append(mLaunchTaskDisplayAreaFeatureId);
2680                 commandBuilder.append(" --ei " + KEY_TASK_DISPLAY_AREA_FEATURE_ID + " ")
2681                         .append(mLaunchTaskDisplayAreaFeatureId);
2682             }
2683 
2684             if (mLaunchInjector != null) {
2685                 commandBuilder.append(" --ez " + KEY_FORWARD + " true");
2686                 mLaunchInjector.setupShellCommand(commandBuilder);
2687             }
2688             executeShellCommand(commandBuilder.toString());
2689         }
2690     }
2691 
2692     /**
2693      * The actions which wraps a test method. It is used to set necessary rules that cannot be
2694      * overridden by subclasses. It executes in the outer scope of {@link Before} and {@link After}.
2695      */
2696     protected class WrapperRule implements TestRule {
2697         private final Runnable mBefore;
2698         private final Runnable mAfter;
2699 
2700         protected WrapperRule(Runnable before, Runnable after) {
2701             mBefore = before;
2702             mAfter = after;
2703         }
2704 
2705         @Override
2706         public Statement apply(final Statement base, final Description description) {
2707             return new Statement() {
2708                 @Override
2709                 public void evaluate()  {
2710                     if (mBefore != null) {
2711                         mBefore.run();
2712                     }
2713                     try {
2714                         base.evaluate();
2715                     } catch (Throwable e) {
2716                         mPostAssertionRule.addError(e);
2717                     } finally {
2718                         if (mAfter != null) {
2719                             mAfter.run();
2720                         }
2721                     }
2722                 }
2723             };
2724         }
2725     }
2726 
2727     /**
2728      * The post assertion to ensure all test methods don't violate the generic rule. It is also used
2729      * to collect multiple errors.
2730      */
2731     private class PostAssertionRule extends ErrorCollector {
2732         private Throwable mLastError;
2733 
2734         @Override
2735         protected void verify() throws Throwable {
2736             if (mLastError != null) {
2737                 // Try to recover the bad state of device to avoid subsequent test failures.
2738                 if (isKeyguardLocked()) {
2739                     mLastError.addSuppressed(new IllegalStateException("Keyguard is locked"));
2740                     unlockUnexpectedLockedKeyguard();
2741                 }
2742                 final String overlayDisplaySettings = Settings.Global.getString(
2743                         mContext.getContentResolver(), Settings.Global.OVERLAY_DISPLAY_DEVICES);
2744                 if (overlayDisplaySettings != null && overlayDisplaySettings.length() > 0) {
2745                     mLastError.addSuppressed(new IllegalStateException(
2746                             "Overlay display is found: " + overlayDisplaySettings));
2747                     // Remove the overlay display because it may obscure the screen and causes the
2748                     // next tests to fail.
2749                     SettingsSession.delete(Settings.Global.getUriFor(
2750                             Settings.Global.OVERLAY_DISPLAY_DEVICES));
2751                 }
2752             }
2753             if (!sIllegalTaskStateFound) {
2754                 // Skip if a illegal task state was already found in previous test, or all tests
2755                 // afterward could also fail and fire unnecessary false alarms.
2756                 try {
2757                     mWmState.assertIllegalTaskState();
2758                 } catch (Throwable t) {
2759                     sIllegalTaskStateFound = true;
2760                     addError(t);
2761                 }
2762             }
2763             super.verify();
2764         }
2765 
2766         @Override
2767         public void addError(Throwable error) {
2768             super.addError(error);
2769             logE("addError: " + error);
2770             mLastError = error;
2771         }
2772     }
2773 
2774     /** Activity that can handle all config changes. */
2775     public static class ConfigChangeHandlingActivity extends CommandSession.BasicTestActivity {
2776     }
2777 
2778     public static class ReportedDisplayMetrics {
2779         private static final String WM_SIZE = "wm size";
2780         private static final String WM_DENSITY = "wm density";
2781         private static final Pattern PHYSICAL_SIZE =
2782                 Pattern.compile("Physical size: (\\d+)x(\\d+)");
2783         private static final Pattern OVERRIDE_SIZE =
2784                 Pattern.compile("Override size: (\\d+)x(\\d+)");
2785         private static final Pattern PHYSICAL_DENSITY =
2786                 Pattern.compile("Physical density: (\\d+)");
2787         private static final Pattern OVERRIDE_DENSITY =
2788                 Pattern.compile("Override density: (\\d+)");
2789 
2790         /** The size of the physical display. */
2791         @NonNull
2792         final Size physicalSize;
2793         /** The density of the physical display. */
2794         final int physicalDensity;
2795 
2796         /** The pre-existing size override applied to a logical display. */
2797         @Nullable
2798         final Size overrideSize;
2799         /** The pre-existing density override applied to a logical display. */
2800         @Nullable
2801         final Integer overrideDensity;
2802 
2803         final int mDisplayId;
2804 
2805         @NonNull
2806         public Size getPhysicalSize() {
2807             return physicalSize;
2808         }
2809 
2810         public int getPhysicalDensity() {
2811             return physicalDensity;
2812         }
2813 
2814         @Nullable
2815         public Size getOverrideSize() {
2816             return overrideSize;
2817         }
2818 
2819         public Integer getOverrideDensity() {
2820             return overrideDensity;
2821         }
2822 
2823         /** Get physical and override display metrics from WM for specified display. */
2824         public static ReportedDisplayMetrics getDisplayMetrics(int displayId) {
2825             return new ReportedDisplayMetrics(
2826                     executeShellCommandAndGetStdout(WM_SIZE + " -d " + displayId)
2827                     + executeShellCommandAndGetStdout(WM_DENSITY + " -d " + displayId), displayId);
2828         }
2829 
2830         public void setDisplayMetrics(final Size size, final int density) {
2831             setSize(size);
2832             setDensity(density);
2833         }
2834 
2835         public void restoreDisplayMetrics() {
2836             if (overrideSize != null) {
2837                 setSize(overrideSize);
2838             } else {
2839                 executeShellCommand(WM_SIZE + " reset -d " + mDisplayId);
2840             }
2841             if (overrideDensity != null) {
2842                 setDensity(overrideDensity);
2843             } else {
2844                 executeShellCommand(WM_DENSITY + " reset -d " + mDisplayId);
2845             }
2846         }
2847 
2848         public void setSize(final Size size) {
2849             executeShellCommand(
2850                     WM_SIZE + " " + size.getWidth() + "x" + size.getHeight() + " -d " + mDisplayId);
2851         }
2852 
2853         public void setDensity(final int density) {
2854             executeShellCommand(WM_DENSITY + " " + density + " -d " + mDisplayId);
2855         }
2856 
2857         /** Get display size that WM operates with. */
2858         public Size getSize() {
2859             return overrideSize != null ? overrideSize : physicalSize;
2860         }
2861 
2862         /** Get density that WM operates with. */
2863         public int getDensity() {
2864             return overrideDensity != null ? overrideDensity : physicalDensity;
2865         }
2866 
2867         private ReportedDisplayMetrics(final String lines, int displayId) {
2868             mDisplayId = displayId;
2869             Matcher matcher = PHYSICAL_SIZE.matcher(lines);
2870             assertTrue("Physical display size must be reported", matcher.find());
2871             log(matcher.group());
2872             physicalSize = new Size(
2873                     Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)));
2874 
2875             matcher = PHYSICAL_DENSITY.matcher(lines);
2876             assertTrue("Physical display density must be reported", matcher.find());
2877             log(matcher.group());
2878             physicalDensity = Integer.parseInt(matcher.group(1));
2879 
2880             matcher = OVERRIDE_SIZE.matcher(lines);
2881             if (matcher.find()) {
2882                 log(matcher.group());
2883                 overrideSize = new Size(
2884                         Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)));
2885             } else {
2886                 overrideSize = null;
2887             }
2888 
2889             matcher = OVERRIDE_DENSITY.matcher(lines);
2890             if (matcher.find()) {
2891                 log(matcher.group());
2892                 overrideDensity = Integer.parseInt(matcher.group(1));
2893             } else {
2894                 overrideDensity = null;
2895             }
2896         }
2897     }
2898 
2899     /**
2900      * Either launches activity via {@link CommandSession.ActivitySessionClient} in case it is
2901      * a subclass of {@link CommandSession.BasicTestActivity} (then activity can be destroyed
2902      * by means of sending the finish command). Otherwise, launches activity via ADB commands
2903      * ({@link #launchActivityOnDisplay}), in this case the activity can be destroyed only as part
2904      * of the app package with ADB command `am stop-app`. In this case the activity can be destroyed
2905      * only if it is defined in another apk, so the test suit is not destroyed, this is detected
2906      * when catching {@link ClassNotFoundException} exception.
2907      */
2908     public class ActivitySessionCloseable implements AutoCloseable {
2909         private final ComponentName mActivityName;
2910         @Nullable
2911         protected CommandSession.ActivitySession mActivity;
2912         @Nullable
2913         private CommandSession.ActivitySessionClient mSession;
2914 
2915         public ActivitySessionCloseable(final ComponentName activityName) {
2916             this(activityName, WINDOWING_MODE_FULLSCREEN);
2917         }
2918 
2919         public ActivitySessionCloseable(final ComponentName activityName, final int windowingMode) {
2920             this(activityName, windowingMode, /* forceCommandActivity */ false);
2921         }
2922 
2923         /**
2924          * @param activityName can be created with
2925          *              {@link android.server.wm.component.ComponentsBase#component}.
2926          * @param windowingMode {@link WindowConfiguration.WindowingMode}
2927          * @param forceCommandActivity sometimes Activity implements
2928          *              {@link CommandSession.BasicTestActivity} but is defined in a different apk,
2929          *              so can not be verified if it is a subclass of
2930          *              {@link CommandSession.BasicTestActivity}. In this case forceCommandActivity
2931          *              argument can be used to ensure that this activity is managed as
2932          *              {@link CommandSession.BasicTestActivity}.
2933          */
2934         public ActivitySessionCloseable(
2935                 final ComponentName activityName,
2936                 final int windowingMode,
2937                 final boolean forceCommandActivity) {
2938             mActivityName = activityName;
2939 
2940             if (forceCommandActivity || isCommandActivity()) {
2941                 mSession = new CommandSession.ActivitySessionClient(mContext);
2942                 mActivity = mSession.startActivity(getLaunchActivityBuilder()
2943                                 .setUseInstrumentation()
2944                                 .setWaitForLaunched(true)
2945                                 .setNewTask(true)
2946                                 .setMultipleTask(true)
2947                                 .setWindowingMode(windowingMode)
2948                                 .setTargetActivity(activityName));
2949             } else {
2950                 launchActivityOnDisplay(activityName, windowingMode, DEFAULT_DISPLAY);
2951                 mWmState.computeState(new WaitForValidActivityState(activityName));
2952             }
2953         }
2954 
2955         private boolean isAnotherApp() {
2956             try {
2957                 Class.forName(mActivityName.getClassName());
2958                 return false;
2959             } catch (ClassNotFoundException e) {
2960                 return true;
2961             }
2962         }
2963 
2964         private boolean isCommandActivity() {
2965             try {
2966                 var c = Class.forName(mActivityName.getClassName());
2967                 return CommandSession.BasicTestActivity.class.isAssignableFrom(c);
2968             } catch (ClassNotFoundException e) {
2969                 Log.w(TAG, "Class " + mActivityName.getClassName() + " is not found", e);
2970                 return false;
2971             }
2972         }
2973 
2974         @Override
2975         public void close() {
2976             if (mSession != null && mActivity != null) {
2977                 mSession.close();
2978                 mWmState.waitForActivityRemoved(mActivityName);
2979             } else if (isAnotherApp()) {
2980                 executeShellCommand("am stop-app " + mActivityName.getPackageName());
2981                 mWmState.waitForActivityRemoved(mActivityName);
2982             } else {
2983                 Log.w(TAG, "No explicit cleanup possible for " + mActivityName);
2984             }
2985         }
2986 
2987         public WindowManagerState.Activity getActivityState() {
2988             return getActivityWaitState(mActivityName);
2989         }
2990 
2991         /**
2992          * Not null only for {@link CommandSession.BasicTestActivity} activities.
2993          */
2994         @Nullable
2995         public CommandSession.ActivitySession getActivitySession() {
2996             return mActivity;
2997         }
2998     }
2999 
3000     /**
3001      * Same as ActivitySessionCloseable, but with forceCommandActivity = true
3002      */
3003     public class BaseActivitySessionCloseable extends ActivitySessionCloseable {
3004         public BaseActivitySessionCloseable(ComponentName activityName) {
3005             this(activityName, WINDOWING_MODE_FULLSCREEN);
3006         }
3007 
3008         public BaseActivitySessionCloseable(
3009                 final ComponentName activityName, final int windowingMode) {
3010             super(activityName, windowingMode, /* forceCommandActivity */ true);
3011         }
3012 
3013         @Override
3014         @NonNull
3015         public CommandSession.ActivitySession getActivitySession() {
3016             assertNotNull(mActivity);
3017             return mActivity;
3018         }
3019     }
3020 
3021     /**
3022      * Launches primary and secondary activities in split-screen.
3023      */
3024     public class SplitScreenActivitiesCloseable implements AutoCloseable {
3025         private final ActivitySessionCloseable mPrimarySession;
3026         private final ActivitySessionCloseable mSecondarySession;
3027 
3028         public SplitScreenActivitiesCloseable(
3029                 final ComponentName primaryActivityName,
3030                 final ComponentName secondaryActivityName) {
3031             this(primaryActivityName, WINDOWING_MODE_FULLSCREEN,
3032                     /* forcePrimaryCommandActivity */ false,
3033                     secondaryActivityName, WINDOWING_MODE_FULLSCREEN,
3034                     /* forceSecondaryCommandActivity */ false);
3035         }
3036 
3037         public SplitScreenActivitiesCloseable(
3038                 final ComponentName primaryActivityName,
3039                 final int primaryWindowingMode,
3040                 final boolean forcePrimaryCommandActivity,
3041                 final ComponentName secondaryActivityName,
3042                 final int secondaryWindowingMode,
3043                 final boolean forceSecondaryCommandActivity) {
3044             mPrimarySession = new ActivitySessionCloseable(primaryActivityName,
3045                     primaryWindowingMode, forcePrimaryCommandActivity);
3046             mTaskOrganizer.putTaskInSplitPrimary(
3047                     mWmState.getTaskByActivity(primaryActivityName).mTaskId);
3048             mSecondarySession = new ActivitySessionCloseable(secondaryActivityName,
3049                     secondaryWindowingMode, forceSecondaryCommandActivity);
3050             mTaskOrganizer.putTaskInSplitSecondary(
3051                     mWmState.getTaskByActivity(secondaryActivityName).mTaskId);
3052             mWmState.computeState(new WaitForValidActivityState(primaryActivityName),
3053                     new WaitForValidActivityState(secondaryActivityName));
3054         }
3055 
3056         @Override
3057         public void close() {
3058             mPrimarySession.close();
3059             mSecondarySession.close();
3060         }
3061 
3062         public ActivitySessionCloseable getPrimaryActivity() {
3063             return mPrimarySession;
3064         }
3065 
3066         public ActivitySessionCloseable getSecondaryActivity() {
3067             return mSecondarySession;
3068         }
3069     }
3070 
3071     /**
3072      * Ensures the device is rotated to portrait orientation.
3073      */
3074     public class DeviceOrientationCloseable implements AutoCloseable {
3075         @Nullable
3076         private final RotationSession mRotationSession;
3077 
3078         /** Needed to restore the previous orientation in {@link #close} */
3079         private final Integer mPreviousRotation;
3080 
3081         /**
3082          * @param requestedOrientation values are Configuration#Orientation
3083          *          either {@link ORIENTATION_PORTRAIT} or {@link ORIENTATION_LANDSCAPE}
3084          */
3085         public DeviceOrientationCloseable(int requestedOrientation) {
3086             // Need to use window to get the size of the screen taking orientation into account.
3087             // mWmState.getDisplay(DEFAULT_DISPLAY).getFullConfiguration().orientation
3088             // can not be used because returned orientation can be {@link ORIENTATION_UNDEFINED}
3089             final Size windowSize = asSize(mWm.getMaximumWindowMetrics().getBounds());
3090 
3091             boolean isRotationRequired = false;
3092             if (ORIENTATION_PORTRAIT == requestedOrientation) {
3093                 isRotationRequired = windowSize.getHeight() < windowSize.getWidth();
3094             } else if (ORIENTATION_LANDSCAPE == requestedOrientation) {
3095                 isRotationRequired = windowSize.getHeight() > windowSize.getWidth();
3096             }
3097 
3098             if (isRotationRequired) {
3099                 mPreviousRotation = mWmState.getRotation();
3100                 mRotationSession = new RotationSession(mWmState);
3101                 mRotationSession.set(ROTATION_90);
3102                 assertTrue("display rotation must be ROTATION_90 now",
3103                         mWmState.waitForRotation(ROTATION_90));
3104             } else {
3105                 mRotationSession = null;
3106                 mPreviousRotation = ROTATION_0;
3107             }
3108         }
3109 
3110         @Override
3111         public void close() {
3112             if (mRotationSession != null) {
3113                 mRotationSession.close();
3114                 mWmState.waitForRotation(mPreviousRotation);
3115             }
3116         }
3117 
3118         public boolean isRotationApplied() {
3119             return mRotationSession != null;
3120         }
3121     }
3122 
3123     /**
3124      * Makes sure {@link DisplayMetricsSession} is closed with waitFor original display content
3125      * is restored.
3126      */
3127     public class DisplayMetricsWaitCloseable extends DisplayMetricsSession {
3128         private final int mDisplayId;
3129         private final WindowManagerState.DisplayContent mOriginalDC;
3130 
3131         public DisplayMetricsWaitCloseable() {
3132             this(DEFAULT_DISPLAY);
3133         }
3134 
3135         public DisplayMetricsWaitCloseable(int displayId) {
3136             super(displayId);
3137             mDisplayId = displayId;
3138             mOriginalDC = mWmState.getDisplay(displayId);
3139         }
3140 
3141         @Override
3142         public void restoreDisplayMetrics() {
3143             mWmState.waitForWithAmState(wmState -> {
3144                 super.restoreDisplayMetrics();
3145                 return mWmState.getDisplay(mDisplayId).equals(mOriginalDC);
3146             }, "waiting for display to be restored");
3147         }
3148     }
3149 
3150     /**
3151      * AutoClosable class used for try-with-resources compat change tests, which require a separate
3152      * application task to be started.
3153      */
3154     public static class CompatChangeCloseable implements AutoCloseable {
3155         private final String mChangeName;
3156         private final String mPackageName;
3157 
3158         public CompatChangeCloseable(final Long changeId, String packageName) {
3159             this(changeId.toString(), packageName);
3160         }
3161 
3162         public CompatChangeCloseable(final String changeName, String packageName) {
3163             this.mChangeName = changeName;
3164             this.mPackageName = packageName;
3165 
3166             // Enable change
3167             executeShellCommand("am compat enable " + changeName + " " + packageName);
3168         }
3169 
3170         @Override
3171         public void close() {
3172             executeShellCommand("am compat disable " + mChangeName + " " + mPackageName);
3173         }
3174     }
3175 
3176     /**
3177      * Scales the display size
3178      */
3179     public class DisplaySizeScaleCloseable extends DisplaySizeCloseable {
3180         /**
3181          * @param sizeScaleFactor display size scaling factor.
3182          * @param activity can be null, the activity which is currently on the screen.
3183          */
3184         public DisplaySizeScaleCloseable(double sizeScaleFactor, @Nullable ComponentName activity) {
3185             super(sizeScaleFactor, /* densityScaleFactor */ 1, ORIENTATION_UNDEFINED,
3186                     /* aspectRatio */ -1, asList(activity));
3187         }
3188     }
3189 
3190     /**
3191      * Changes aspectRatio of the display.
3192      */
3193     public class DisplayAspectRatioCloseable extends DisplaySizeCloseable {
3194         /**
3195          * @param requestedOrientation orientation.
3196          * @param aspectRatio aspect ratio of the screen.
3197          */
3198         public DisplayAspectRatioCloseable(int requestedOrientation, double aspectRatio) {
3199             super(/* sizeScaleFactor */ 1, /* densityScaleFactor */ 1, requestedOrientation,
3200                     aspectRatio, /* activities */ List.of());
3201         }
3202 
3203         /**
3204          * @param requestedOrientation orientation.
3205          * @param aspectRatio aspect ratio of the screen.
3206          * @param activity the current activity.
3207          */
3208         public DisplayAspectRatioCloseable(int requestedOrientation, double aspectRatio,
3209                 @Nullable ComponentName activity) {
3210             super(/* sizeScaleFactor */ 1, /* densityScaleFactor */ 1, requestedOrientation,
3211                     aspectRatio, asList(activity));
3212         }
3213     }
3214 
3215     public class DisplaySizeCloseable extends DisplayMetricsWaitCloseable {
3216 
3217         private List<Pair<ComponentName, Rect>> mNewBounds = List.of();
3218 
3219         private static boolean isLandscape(Size s) {
3220             return s.getWidth() > s.getHeight();
3221         }
3222 
3223         protected static <T> List<T> asList(@Nullable T v) {
3224             return (v != null) ? List.of(v) : List.of();
3225         }
3226 
3227         /**
3228          * @param sizeScaleFactor display size scaling factor.
3229          * @param densityScaleFactor density scaling factor.
3230          * @param activities can be empty, the activities which are currently on the screen.
3231          */
3232         public DisplaySizeCloseable(double sizeScaleFactor, double densityScaleFactor,
3233                 final int requestedOrientation, final double aspectRatio,
3234                 @NonNull List<ComponentName> activities) {
3235             if (sizeScaleFactor != 1 || densityScaleFactor != 1) {
3236                 var originalBounds = activities.stream()
3237                         .map(a -> new Pair<>(a, getActivityWaitState(a).getBounds()))
3238                         .toList();
3239 
3240                 final var origDisplaySize = getDisplayMetrics().getSize();
3241 
3242                 changeDisplayMetrics(sizeScaleFactor, densityScaleFactor);
3243                 waitForDisplaySizeChanged(origDisplaySize, sizeScaleFactor);
3244 
3245                 originalBounds.forEach(activityAndBounds -> {
3246                     waitForActivityBoundsChanged(activityAndBounds.first, activityAndBounds.second);
3247                     mWmState.computeState(new WaitForValidActivityState(activityAndBounds.first));
3248                 });
3249 
3250                 mNewBounds = activities.stream()
3251                         .map(a -> new Pair<>(a, getActivityWaitState(a).getBounds()))
3252                         .toList();
3253             }
3254 
3255             if (ORIENTATION_UNDEFINED != requestedOrientation && aspectRatio > 0) {
3256                 final Size maxWindowSize = asSize(mWm.getMaximumWindowMetrics().getBounds());
3257                 final var origDisplaySize = getDisplayMetrics().getSize();
3258 
3259                 var isMatchingOrientation =
3260                         isLandscape(origDisplaySize) == isLandscape(maxWindowSize);
3261                 if (ORIENTATION_LANDSCAPE == requestedOrientation) {
3262                     changeAspectRatio(aspectRatio,
3263                             isMatchingOrientation ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT);
3264                     waitForDisplaySizeChanged(origDisplaySize, aspectRatio);
3265                 } else if (ORIENTATION_PORTRAIT == requestedOrientation) {
3266                     changeAspectRatio(aspectRatio,
3267                             isMatchingOrientation ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE);
3268                     waitForDisplaySizeChanged(origDisplaySize, aspectRatio);
3269                 }
3270             }
3271         }
3272 
3273         @Override
3274         public void close() {
3275             super.close();
3276             mNewBounds.forEach(activityAndBounds -> {
3277                 if (mWmState.isActivityVisible(activityAndBounds.first)) {
3278                     waitForActivityBoundsChanged(activityAndBounds.first, activityAndBounds.second);
3279                     mWmState.computeState(new WaitForValidActivityState(activityAndBounds.first));
3280                 }
3281             });
3282         }
3283 
3284 
3285         /**
3286          * Waits until the given activity has updated task bounds.
3287          */
3288         private void waitForActivityBoundsChanged(ComponentName activityName,
3289                 Rect priorActivityBounds) {
3290             mWmState.waitForWithAmState(wmState -> {
3291                 mWmState.computeState(new WaitForValidActivityState(activityName));
3292                 WindowManagerState.Activity activity = wmState.getActivity(activityName);
3293                 return activity != null && !activity.getBounds().equals(priorActivityBounds);
3294             }, "checking activity bounds updated");
3295         }
3296 
3297         /**
3298          * Waits until the display bounds changed.
3299          */
3300         private void waitForDisplaySizeChanged(final Size originalDisplaySize, final double ratio) {
3301             if (!mWmState.waitForWithAmState(wmState ->
3302                     !originalDisplaySize.equals(getDisplayMetrics().getSize()),
3303                     "waiting for display changing aspect ratio")) {
3304 
3305                 final Size currentDisplaySize = getDisplayMetrics().getSize();
3306                 // Sometimes display size can be capped, making it impossible to scale the size up
3307                 // b/192406238.
3308                 if (ratio >= 1f) {
3309                     assumeFalse("If a display size is capped, resizing may be a no-op",
3310                             originalDisplaySize.equals(currentDisplaySize));
3311                 } else {
3312                     assertNotEquals("Display size must change if sizeRatio < 1f",
3313                             originalDisplaySize, currentDisplaySize);
3314                 }
3315             }
3316         }
3317 
3318         public float getInitialDisplayAspectRatio() {
3319             Size size = getInitialDisplayMetrics().getSize();
3320             return Math.max(size.getHeight(), size.getWidth())
3321                     / (float) (Math.min(size.getHeight(), size.getWidth()));
3322         }
3323     }
3324 
3325     public static Size asSize(Rect r) {
3326         return new Size(r.width(), r.height());
3327     }
3328 
3329     public <T> void waitAssertEquals(final String message, final T expected, Supplier<T> actual) {
3330         assertTrue(message, mWmState.waitFor(state -> expected.equals(actual.get()),
3331                 "wait for correct result"));
3332     }
3333 
3334     public WindowManagerState.Activity getActivityWaitState(ComponentName activityName) {
3335         mWmState.computeState(new WaitForValidActivityState(activityName));
3336         return mWmState.getActivity(activityName);
3337     }
3338 
3339     /**
3340      * Inset given frame if the insets source exist.
3341      *
3342      * @param windowState The window which have the insets source.
3343      * @param predicate Inset source predicate.
3344      * @param inOutBounds In/out the given frame from the inset source.
3345      */
3346     public static void insetGivenFrame(WindowManagerState.WindowState windowState,
3347             Predicate<WindowManagerState.InsetsSource> predicate, Rect inOutBounds) {
3348         Optional<WindowManagerState.InsetsSource> insetsOptional =
3349                 windowState.getMergedLocalInsetsSources().stream().filter(
3350                         predicate).findFirst();
3351         insetsOptional.ifPresent(insets -> insets.insetGivenFrame(inOutBounds));
3352     }
3353 
3354     /**
3355      * Checks whether the device has automotive split-screen multitasking feature enabled
3356      */
3357     protected boolean hasAutomotiveSplitscreenMultitaskingFeature() {
3358         return mContext.getPackageManager()
3359                 .hasSystemFeature(/* PackageManager.FEATURE_CAR_SPLITSCREEN_MULTITASKING */
3360                         "android.software.car.splitscreen_multitasking") && isCar();
3361     }
3362 }
3363