Commit fe2b5e8c authored by ckitagawa's avatar ckitagawa Committed by Commit Bot

Reland "[Multi-Instance] Add method to detect Multi-Instance"

This is a reland of 621c283e

Looks like activities were being preemptively killed on test-n-tablet.
This caused a failure with the way CallbackHelper was used.

Reverted due to failing on test-n-tablet.
> Reason for revert:
> testAreMultipleChromeInstancesRunningFirstInstanceKilledFirst
> failing on test-n-tablet
> https://ci.chromium.org/p/chrome/builders/ci/test-n-tablet

Original change's description:
> [Multi-Instance] Add method to detect Multi-Instance
>
> This change provides a simple method to detect if Chrome is in
> multi-instance mode.
>
> Bug: 1071925
> Change-Id: I3b82e5fe7a97e923f67f0ca49735c724f9ed4f17
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2155017
> Commit-Queue: Calder Kitagawa <ckitagawa@chromium.org>
> Reviewed-by: Theresa  <twellington@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#762344}

Bug: 1071925
Change-Id: Ifcfc192011b0ddf4fdb7b9cc5ecae90b63ae4fb5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2167992Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Commit-Queue: Calder Kitagawa <ckitagawa@chromium.org>
Cr-Commit-Position: refs/heads/master@{#763387}
parent 539b849c
...@@ -247,14 +247,8 @@ public class MultiInstanceManager ...@@ -247,14 +247,8 @@ public class MultiInstanceManager
List<ActivityManager.AppTask> appTasks = activityManager.getAppTasks(); List<ActivityManager.AppTask> appTasks = activityManager.getAppTasks();
ActivityManager.AppTask otherActivityTask = null; ActivityManager.AppTask otherActivityTask = null;
for (ActivityManager.AppTask task : appTasks) { for (ActivityManager.AppTask task : appTasks) {
if (task.getTaskInfo() == null || task.getTaskInfo().baseActivity == null) continue; String baseActivity = MultiWindowUtils.getActivityNameFromTask(task);
String baseActivity = task.getTaskInfo().baseActivity.getClassName();
// Contrary to the documentation task.getTaskInfo().baseActivity for the .LauncherMain
// activity alias is the alias itself, and not the implementation. Filed b/66729258;
// for now translate the alias manually.
if (baseActivity.equals(ChromeTabbedActivity.MAIN_LAUNCHER_ACTIVITY_NAME)) {
baseActivity = ChromeTabbedActivity.class.getName();
}
if (baseActivity.equals(otherWindowActivityClass.getName())) { if (baseActivity.equals(otherWindowActivityClass.getName())) {
otherActivityTask = task; otherActivityTask = task;
} }
......
...@@ -191,6 +191,61 @@ public class MultiWindowUtils implements ActivityStateListener { ...@@ -191,6 +191,61 @@ public class MultiWindowUtils implements ActivityStateListener {
} }
} }
/**
* Determines the name of an activity from its {@link AppTask}.
* @param task The AppTask to get the name of.
*/
public static String getActivityNameFromTask(AppTask task) {
if (task.getTaskInfo() == null || task.getTaskInfo().baseActivity == null) return "";
String baseActivity = task.getTaskInfo().baseActivity.getClassName();
// Contrary to the documentation task.getTaskInfo().baseActivity for the .LauncherMain
// activity alias is the alias itself, and not the implementation. Filed b/66729258;
// for now translate the alias manually.
if (TextUtils.equals(baseActivity, ChromeTabbedActivity.MAIN_LAUNCHER_ACTIVITY_NAME)) {
baseActivity = ChromeTabbedActivity.class.getName();
}
return baseActivity;
}
/**
* Determines if multiple instances of Chrome are running.
* @param context The current Context, used to retrieve the ActivityManager system service.
* @return True if multiple instances of Chrome are running.
*/
public boolean areMultipleChromeInstancesRunning(Context context) {
// Exit early if multi-window isn't supported.
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) return false;
// Check if both tasks are running.
boolean tabbedTaskRunning = false;
boolean tabbed2TaskRunning = false;
for (Activity activity : ApplicationStatus.getRunningActivities()) {
if (activity.getClass().equals(ChromeTabbedActivity.class)) {
tabbedTaskRunning = true;
} else if (activity.getClass().equals(ChromeTabbedActivity2.class)) {
tabbed2TaskRunning = true;
}
}
if (tabbedTaskRunning && tabbed2TaskRunning) return true;
// If a task isn't running check if it is in recents since another instance could be
// recovered from there.
ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<AppTask> appTasks = activityManager.getAppTasks();
for (AppTask task : appTasks) {
String baseActivity = getActivityNameFromTask(task);
if (TextUtils.equals(baseActivity, ChromeTabbedActivity.class.getName())) {
tabbedTaskRunning = true;
} else if (TextUtils.equals(baseActivity, ChromeTabbedActivity2.class.getName())) {
tabbed2TaskRunning = true;
}
}
return tabbedTaskRunning && tabbed2TaskRunning;
}
/** /**
* Determines the correct ChromeTabbedActivity class to use for an incoming intent. * Determines the correct ChromeTabbedActivity class to use for an incoming intent.
* @param intent The incoming intent that is starting ChromeTabbedActivity. * @param intent The incoming intent that is starting ChromeTabbedActivity.
...@@ -284,12 +339,7 @@ public class MultiWindowUtils implements ActivityStateListener { ...@@ -284,12 +339,7 @@ public class MultiWindowUtils implements ActivityStateListener {
context.getSystemService(Context.ACTIVITY_SERVICE); context.getSystemService(Context.ACTIVITY_SERVICE);
List<AppTask> appTasks = activityManager.getAppTasks(); List<AppTask> appTasks = activityManager.getAppTasks();
for (AppTask task : appTasks) { for (AppTask task : appTasks) {
if (task.getTaskInfo() == null || task.getTaskInfo().baseActivity == null) continue; String baseActivity = getActivityNameFromTask(task);
String baseActivity = task.getTaskInfo().baseActivity.getClassName();
if (TextUtils.equals(baseActivity, ChromeTabbedActivity.MAIN_LAUNCHER_ACTIVITY_NAME)) {
baseActivity = ChromeTabbedActivity.class.getName();
}
if (TextUtils.equals(baseActivity, className)) return true; if (TextUtils.equals(baseActivity, className)) return true;
} }
......
...@@ -7,10 +7,12 @@ package org.chromium.chrome.browser.multiwindow; ...@@ -7,10 +7,12 @@ package org.chromium.chrome.browser.multiwindow;
import static org.chromium.chrome.browser.multiwindow.MultiWindowTestHelper.createSecondChromeTabbedActivity; import static org.chromium.chrome.browser.multiwindow.MultiWindowTestHelper.createSecondChromeTabbedActivity;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest;
import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
...@@ -19,6 +21,7 @@ import org.junit.runner.RunWith; ...@@ -19,6 +21,7 @@ import org.junit.runner.RunWith;
import org.chromium.base.ActivityState; import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationStatus; import org.chromium.base.ApplicationStatus;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags; import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Feature; import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.MinAndroidSdkLevel; import org.chromium.base.test.util.MinAndroidSdkLevel;
...@@ -33,6 +36,7 @@ import org.chromium.content_public.browser.test.util.Criteria; ...@@ -33,6 +36,7 @@ import org.chromium.content_public.browser.test.util.Criteria;
import org.chromium.content_public.browser.test.util.CriteriaHelper; import org.chromium.content_public.browser.test.util.CriteriaHelper;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.TimeoutException;
/** /**
* Class for testing MultiWindowUtils. * Class for testing MultiWindowUtils.
...@@ -49,6 +53,11 @@ public class MultiWindowUtilsTest { ...@@ -49,6 +53,11 @@ public class MultiWindowUtilsTest {
mActivityTestRule.startMainActivityOnBlankPage(); mActivityTestRule.startMainActivityOnBlankPage();
} }
@After
public void teardown() {
MultiWindowUtils.getInstance().setIsInMultiWindowModeForTesting(false);
}
/** /**
* Tests that ChromeTabbedActivity2 is used for intents when EXTRA_WINDOW_ID is set to 2. * Tests that ChromeTabbedActivity2 is used for intents when EXTRA_WINDOW_ID is set to 2.
*/ */
...@@ -177,4 +186,145 @@ public class MultiWindowUtilsTest { ...@@ -177,4 +186,145 @@ public class MultiWindowUtilsTest {
mActivityTestRule.getActivity().getIntent(), mActivityTestRule.getActivity()); mActivityTestRule.getActivity().getIntent(), mActivityTestRule.getActivity());
Assert.assertFalse(MultiWindowUtils.getInstance().getTabbedActivity2TaskRunning()); Assert.assertFalse(MultiWindowUtils.getInstance().getTabbedActivity2TaskRunning());
} }
/**
* Tests that {@link MultiWindowUtils#areMultipleChromeInstancesRunning} behaves correctly in
* the case the second instance is killed first.
*/
@Test
@SmallTest
@Feature("MultiWindow")
public void testAreMultipleChromeInstancesRunningSecondInstanceKilledFirst()
throws TimeoutException {
ChromeTabbedActivity activity1 = mActivityTestRule.getActivity();
MultiWindowUtils.getInstance().setIsInMultiWindowModeForTesting(true);
Assert.assertFalse("Only a single instance should be running at the start.",
MultiWindowUtils.getInstance().areMultipleChromeInstancesRunning(activity1));
CallbackHelper activity1StoppedCallback = new CallbackHelper();
CallbackHelper activity1DestroyedCallback = new CallbackHelper();
CallbackHelper activity1ResumedCallback = new CallbackHelper();
ApplicationStatus.ActivityStateListener activity1StateListener =
new ApplicationStatus.ActivityStateListener() {
@Override
public void onActivityStateChange(Activity activity, int newState) {
switch (newState) {
case ActivityState.STOPPED:
activity1StoppedCallback.notifyCalled();
break;
case ActivityState.DESTROYED:
activity1DestroyedCallback.notifyCalled();
break;
case ActivityState.RESUMED:
activity1ResumedCallback.notifyCalled();
break;
}
}
};
ApplicationStatus.registerStateListenerForActivity(activity1StateListener, activity1);
// Starting activity2 will stop activity1 as this is not truly multi-window mode.
int activity1CallCount = activity1StoppedCallback.getCallCount();
ChromeTabbedActivity activity2 = createSecondChromeTabbedActivity(activity1);
activity1StoppedCallback.waitForCallback(activity1CallCount);
Assert.assertTrue("Both instances should be running now that the second has started.",
MultiWindowUtils.getInstance().areMultipleChromeInstancesRunning(activity1));
CallbackHelper activity2DestroyedCallback = new CallbackHelper();
ApplicationStatus.ActivityStateListener activity2StateListener =
new ApplicationStatus.ActivityStateListener() {
@Override
public void onActivityStateChange(Activity activity, int newState) {
switch (newState) {
case ActivityState.DESTROYED:
activity2DestroyedCallback.notifyCalled();
break;
}
}
};
ApplicationStatus.registerStateListenerForActivity(activity2StateListener, activity2);
// activity1 may have been destroyed in the background. After destroying activity2 it is
// necessary to make sure activity1 gets resumed.
activity1CallCount = activity1ResumedCallback.getCallCount();
activity2.finishAndRemoveTask();
activity2DestroyedCallback.waitForFirst();
activity1ResumedCallback.waitForCallback(activity1CallCount);
Assert.assertFalse("Only a single instance should be running after the second is killed.",
MultiWindowUtils.getInstance().areMultipleChromeInstancesRunning(activity1));
// activity1 may have been destroyed in the background and now it is in the foreground.
// Wait on the next destroyed call rather than the first.
activity1CallCount = activity1DestroyedCallback.getCallCount();
activity1.finishAndRemoveTask();
activity1DestroyedCallback.waitForCallback(activity1CallCount);
Assert.assertFalse("No instances should be running as all instances are killed.",
MultiWindowUtils.getInstance().areMultipleChromeInstancesRunning(activity1));
}
/**
* Tests that {@link MultiWindowUtils#areMultipleChromeInstancesRunning} behaves correctly in
* the case the first instance is killed first.
*/
@Test
@SmallTest
@Feature("MultiWindow")
public void testAreMultipleChromeInstancesRunningFirstInstanceKilledFirst()
throws TimeoutException {
ChromeTabbedActivity activity1 = mActivityTestRule.getActivity();
MultiWindowUtils.getInstance().setIsInMultiWindowModeForTesting(true);
Assert.assertFalse("Only a single instance should be running at the start.",
MultiWindowUtils.getInstance().areMultipleChromeInstancesRunning(activity1));
CallbackHelper activity1StoppedCallback = new CallbackHelper();
CallbackHelper activity1DestroyedCallback = new CallbackHelper();
ApplicationStatus.ActivityStateListener activity1StateListener =
new ApplicationStatus.ActivityStateListener() {
@Override
public void onActivityStateChange(Activity activity, int newState) {
switch (newState) {
case ActivityState.STOPPED:
activity1StoppedCallback.notifyCalled();
break;
case ActivityState.DESTROYED:
activity1DestroyedCallback.notifyCalled();
break;
}
}
};
ApplicationStatus.registerStateListenerForActivity(activity1StateListener, activity1);
// Starting activity2 will stop activity1 as this is not truly multi-window mode.
// activity1 may be killed in the background, but since it is never foregrounded again
// there should be only one call for both stopped and destroyed in this test.
ChromeTabbedActivity activity2 = createSecondChromeTabbedActivity(activity1);
activity1StoppedCallback.waitForFirst();
Assert.assertTrue("Both instances should be running now that the second has started.",
MultiWindowUtils.getInstance().areMultipleChromeInstancesRunning(activity1));
CallbackHelper activity2DestroyedCallback = new CallbackHelper();
ApplicationStatus.ActivityStateListener activity2StateListener =
new ApplicationStatus.ActivityStateListener() {
@Override
public void onActivityStateChange(Activity activity, int newState) {
switch (newState) {
case ActivityState.DESTROYED:
activity2DestroyedCallback.notifyCalled();
break;
}
}
};
ApplicationStatus.registerStateListenerForActivity(activity2StateListener, activity2);
activity1.finishAndRemoveTask();
activity1DestroyedCallback.waitForFirst();
Assert.assertFalse("Only a single instance should be running after the first is killed.",
MultiWindowUtils.getInstance().areMultipleChromeInstancesRunning(activity1));
// activity2 is always in the foreground so this should be the first time it is destroyed.
activity2.finishAndRemoveTask();
activity2DestroyedCallback.waitForFirst();
Assert.assertFalse("No instances should be running as all instances are killed.",
MultiWindowUtils.getInstance().areMultipleChromeInstancesRunning(activity1));
}
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment