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