1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.chrome.browser.multiwindow;
6 
7 import android.annotation.TargetApi;
8 import android.app.Activity;
9 import android.app.ActivityManager;
10 import android.content.Context;
11 import android.content.Intent;
12 import android.net.Uri;
13 import android.os.Build;
14 
15 import androidx.annotation.Nullable;
16 
17 import org.hamcrest.Matchers;
18 import org.junit.Assert;
19 
20 import org.chromium.base.ActivityState;
21 import org.chromium.base.ApplicationStatus;
22 import org.chromium.base.ContextUtils;
23 import org.chromium.base.test.util.Criteria;
24 import org.chromium.base.test.util.CriteriaHelper;
25 import org.chromium.base.test.util.CriteriaNotSatisfiedException;
26 import org.chromium.chrome.browser.ChromeTabbedActivity;
27 import org.chromium.chrome.browser.ChromeTabbedActivity2;
28 import org.chromium.chrome.browser.tab.Tab;
29 import org.chromium.content_public.browser.LoadUrlParams;
30 import org.chromium.content_public.browser.test.util.TestThreadUtils;
31 
32 import java.util.concurrent.atomic.AtomicReference;
33 
34 /**
35  * Utilities helper class for multi-window tests.
36  */
37 public class MultiWindowTestHelper {
38     /**
39      * Creates a new {@link ChromeTabbedActivity2} with no {@link LoadUrlParams}.
40      * @param activity A current running activity that will handle the intent to start another
41      *                 activity.
42      * @return The new {@link ChromeTabbedActivity2}.
43      */
createSecondChromeTabbedActivity(Activity activity)44     public static ChromeTabbedActivity2 createSecondChromeTabbedActivity(Activity activity) {
45         return createSecondChromeTabbedActivity(activity, null);
46     }
47 
48     /**
49      * Creates a new {@link ChromeTabbedActivity2}.
50      * @param activity A current running activity that will handle the intent to start another
51      *                 activity.
52      * @param params {@link LoadUrlParams} used to create the launch intent. May be null if no
53      *               params should be used when creating the activity.
54      * @return The new {@link ChromeTabbedActivity2}.
55      */
createSecondChromeTabbedActivity( Activity activity, @Nullable LoadUrlParams params)56     public static ChromeTabbedActivity2 createSecondChromeTabbedActivity(
57             Activity activity, @Nullable LoadUrlParams params) {
58         // TODO(twellington): after there is test support for putting an activity into multi-window
59         // mode, this should be changed to use the menu item for opening a new window.
60 
61         // Number of expected activities after the second ChromeTabbedActivity is created.
62         int numExpectedActivities = ApplicationStatus.getRunningActivities().size() + 1;
63 
64         // Get the class name to use for the second ChromeTabbedActivity. This step is important
65         // for initializing things in MultiWindowUtils.java.
66         Class<? extends Activity> secondActivityClass =
67                 MultiWindowUtils.getInstance().getOpenInOtherWindowActivity(activity);
68         Assert.assertEquals(
69                 "ChromeTabbedActivity2 should be used as the 'open in other window' activity.",
70                 ChromeTabbedActivity2.class, secondActivityClass);
71 
72         // Create an intent and start the second ChromeTabbedActivity.
73         Intent intent;
74         if (params != null) {
75             intent = new Intent(Intent.ACTION_VIEW, Uri.parse(params.getUrl()));
76         } else {
77             intent = new Intent();
78         }
79 
80         MultiWindowUtils.setOpenInOtherWindowIntentExtras(intent, activity, secondActivityClass);
81         MultiInstanceManager.onMultiInstanceModeStarted();
82         activity.startActivity(intent);
83 
84         // Wait for ChromeTabbedActivity2 to be created.
85         CriteriaHelper.pollUiThread(() -> {
86             Criteria.checkThat(ApplicationStatus.getRunningActivities().size(),
87                     Matchers.is(numExpectedActivities));
88         });
89 
90         return waitForSecondChromeTabbedActivity();
91     }
92 
93     /**
94      * Waits for an instance of ChromeTabbedActivity2, and then waits until it's resumed.
95      */
waitForSecondChromeTabbedActivity()96     public static ChromeTabbedActivity2 waitForSecondChromeTabbedActivity() {
97         AtomicReference<ChromeTabbedActivity2> returnActivity = new AtomicReference<>();
98         CriteriaHelper.pollUiThread(() -> {
99             for (Activity runningActivity : ApplicationStatus.getRunningActivities()) {
100                 if (runningActivity.getClass().equals(ChromeTabbedActivity2.class)) {
101                     returnActivity.set((ChromeTabbedActivity2) runningActivity);
102                     return;
103                 }
104             }
105             throw new CriteriaNotSatisfiedException(
106                     "Couldn't find instance of ChromeTabbedActivity2");
107         });
108         waitUntilActivityResumed(returnActivity.get());
109         return returnActivity.get();
110     }
111 
waitUntilActivityResumed(final Activity activity)112     private static void waitUntilActivityResumed(final Activity activity) {
113         CriteriaHelper.pollUiThread(() -> {
114             Criteria.checkThat(ApplicationStatus.getStateForActivity(activity),
115                     Matchers.is(ActivityState.RESUMED));
116         });
117     }
118 
119     /**
120      * Moves the given activity to the foreground so it can receive user input.
121      */
122     @TargetApi(Build.VERSION_CODES.N)
moveActivityToFront(final Activity activity)123     public static void moveActivityToFront(final Activity activity) {
124         TestThreadUtils.runOnUiThreadBlocking(() -> {
125             Context context = ContextUtils.getApplicationContext();
126             ActivityManager activityManager =
127                     (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
128             for (ActivityManager.AppTask task : activityManager.getAppTasks()) {
129                 if (activity.getTaskId() == task.getTaskInfo().id) {
130                     task.moveToFront();
131                     break;
132                 }
133             }
134         });
135         waitUntilActivityResumed(activity);
136     }
137 
138     /**
139      * Waits until 'activity' has 'expectedTotalTabCount' total tabs, and its active tab has
140      * 'expectedActiveTabId' ID. 'expectedActiveTabId' is optional and can be Tab.INVALID_TAB_ID.
141      * 'tag' is an arbitrary string that is prepended to failure reasons.
142      */
waitForTabs(final String tag, final ChromeTabbedActivity activity, final int expectedTotalTabCount, final int expectedActiveTabId)143     public static void waitForTabs(final String tag, final ChromeTabbedActivity activity,
144             final int expectedTotalTabCount, final int expectedActiveTabId) {
145         CriteriaHelper.pollUiThread(() -> {
146             int actualTotalTabCount = activity.getTabModelSelector().getTotalTabCount();
147             Criteria.checkThat(actualTotalTabCount, Matchers.is(expectedTotalTabCount));
148         });
149 
150         if (expectedActiveTabId == Tab.INVALID_TAB_ID) return;
151 
152         CriteriaHelper.pollUiThread(() -> {
153             int actualActiveTabId = activity.getActivityTab().getId();
154             Criteria.checkThat(actualActiveTabId, Matchers.is(expectedActiveTabId));
155         });
156     }
157 }
158