Skip to content

Commit

Permalink
multi-instance: UI for moving tabs
Browse files Browse the repository at this point in the history
TargetSelectorCoordinator implements dialog UI that lets users
move tabs to other window.

Changes include:
- Reuse instance_switcher_item.xml for move target selector UI
- Merge InstanceSwitcherMediator to the coordinator, and delete it
- Factor out methods used by window switcher/tab mover UI to UiUtil
- Handle incognito tab/instance on UI properly


Bug: 1175953, 1231824, 1231825, 1231821, 1231822
Bug: 1231819, 1231820, 1231826
Change-Id: I4befe66f4667a588ad9ec5199eb1a5ec8b12a791
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3068698
Commit-Queue: Jinsuk Kim <jinsukkim@chromium.org>
Reviewed-by: Theresa  <twellington@chromium.org>
Cr-Commit-Position: refs/heads/master@{#909070}
  • Loading branch information
JinsukKim authored and Chromium LUCI CQ committed Aug 5, 2021
1 parent 13e234c commit 3994506
Show file tree
Hide file tree
Showing 32 changed files with 1,182 additions and 276 deletions.
1 change: 1 addition & 0 deletions chrome/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,7 @@ junit_binary("chrome_junit_tests") {
"//chrome/browser/ui/android/favicon:java",
"//chrome/browser/ui/android/layouts:java",
"//chrome/browser/ui/android/layouts:junit",
"//chrome/browser/ui/android/multiwindow:junit",
"//chrome/browser/ui/android/native_page:java",
"//chrome/browser/ui/android/native_page:junit",
"//chrome/browser/ui/android/night_mode:java",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,8 @@ public ChromeTabbedActivity() {
protected void onPreCreate() {
super.onPreCreate();
mMultiInstanceManager = MultiInstanceManager.create(this, getTabModelOrchestratorSupplier(),
getMultiWindowModeStateDispatcher(), getLifecycleDispatcher(), this);
getMultiWindowModeStateDispatcher(), getLifecycleDispatcher(),
getModalDialogManagerSupplier(), this);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.chromium.chrome.browser.util.AndroidTaskUtils;
import org.chromium.components.browser_ui.widget.MenuOrKeyboardActionController;
import org.chromium.ui.display.DisplayAndroidManager;
import org.chromium.ui.modaldialog.ModalDialogManager;

import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -98,6 +99,7 @@ public static void onMultiInstanceModeStarted() {
* associated activity.
* @param activityLifecycleDispatcher The {@link ActivityLifecycleDispatcher} for the
* associated activity.
* @param modalDialogManagerSupplier A supplier for the {@link ModalDialogManager}.
* @param menuOrKeyboardActionController The {@link MenuOrKeyboardActionController} for the
* associated activity.
* @return {@link MultiInstanceManager} object or {@code null} on the platform it is not needed.
Expand All @@ -106,13 +108,14 @@ public static void onMultiInstanceModeStarted() {
ObservableSupplier<TabModelOrchestrator> tabModelOrchestratorSupplier,
MultiWindowModeStateDispatcher multiWindowModeStateDispatcher,
ActivityLifecycleDispatcher activityLifecycleDispatcher,
ObservableSupplier<ModalDialogManager> modalDialogManagerSupplier,
MenuOrKeyboardActionController menuOrKeyboardActionController) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
return null;
} else if (MultiWindowUtils.instanceSwitcherEnabled()) {
return new MultiInstanceManagerApi31(activity, tabModelOrchestratorSupplier,
multiWindowModeStateDispatcher, activityLifecycleDispatcher,
menuOrKeyboardActionController);
modalDialogManagerSupplier, menuOrKeyboardActionController);
} else {
return new MultiInstanceManager(activity, tabModelOrchestratorSupplier,
multiWindowModeStateDispatcher, activityLifecycleDispatcher,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabModelObserver;
import org.chromium.chrome.browser.tabmodel.TabModelUtils;
import org.chromium.chrome.browser.tabmodel.TabWindowManager;
import org.chromium.chrome.browser.util.AndroidTaskUtils;
import org.chromium.components.browser_ui.widget.MenuOrKeyboardActionController;
import org.chromium.ui.modaldialog.ModalDialogManager;

import java.util.ArrayList;
import java.util.HashSet;
Expand All @@ -46,8 +48,11 @@ class MultiInstanceManagerApi31 extends MultiInstanceManager {
public static final int INVALID_INSTANCE_ID = MultiWindowUtils.INVALID_INSTANCE_ID;
public static final int INVALID_TASK_ID = -1; // Defined in android.app.ActivityTaskManager.

private static final String EMPTY_DATA = "";

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
protected final int mMaxInstances;
private ObservableSupplier<ModalDialogManager> mModalDialogManagerSupplier;

// Instance ID for the activity associated with this manager.
private int mInstanceId;
Expand All @@ -57,40 +62,43 @@ class MultiInstanceManagerApi31 extends MultiInstanceManager {
private TabObserver mActiveTabObserver = new EmptyTabObserver() {
@Override
public void onTitleUpdated(Tab tab) {
writeTitle(mInstanceId, tab.getTitle());
if (!tab.isIncognito()) writeTitle(mInstanceId, tab);
}

@Override
public void onUrlUpdated(Tab tab) {
writeUrl(mInstanceId, tab.getOriginalUrl().getSpec());
if (!tab.isIncognito()) writeUrl(mInstanceId, tab);
}
};

MultiInstanceManagerApi31(Activity activity,
ObservableSupplier<TabModelOrchestrator> tabModelOrchestratorSupplier,
MultiWindowModeStateDispatcher multiWindowModeStateDispatcher,
ActivityLifecycleDispatcher activityLifecycleDispatcher,
ObservableSupplier<ModalDialogManager> modalDialogManagerSupplier,
MenuOrKeyboardActionController menuOrKeyboardActionController) {
super(activity, tabModelOrchestratorSupplier, multiWindowModeStateDispatcher,
activityLifecycleDispatcher, menuOrKeyboardActionController);
mMaxInstances = MultiWindowUtils.getMaxInstances();
mModalDialogManagerSupplier = modalDialogManagerSupplier;
}

@Override
public boolean handleMenuOrKeyboardAction(int id, boolean fromMenu) {
if (id == org.chromium.chrome.R.id.manage_all_windows_menu_id) {
InstanceSwitcherCoordinator.showDialog(
mActivity, this::openInstance, this::closeInstance, getInstanceInfo());
InstanceSwitcherCoordinator.showDialog(mActivity, mModalDialogManagerSupplier.get(),
(item)
-> openInstance(item.instanceId, item.taskId),
(item) -> closeInstance(item.instanceId, item.taskId), getInstanceInfo());
return true;
}
return super.handleMenuOrKeyboardAction(id, fromMenu);
}

@Override
protected void moveTabToOtherWindow(Tab tab) {
// TODO: Invoke target selector dialog.
// TargetSelectorCoordinator.showDialog(
// mActivity, (instanceInfo) -> moveTabAction(instanceInfo, tab), getInstanceInfo());
TargetSelectorCoordinator.showDialog(mActivity, mModalDialogManagerSupplier.get(),
(instanceInfo) -> moveTabAction(instanceInfo, tab), getInstanceInfo());
}

private void moveTabAction(InstanceInfo info, Tab tab) {
Expand Down Expand Up @@ -163,8 +171,10 @@ public List<InstanceInfo> getInstanceInfo() {
}
}

// TODO: Remove unrecoverable incognito tab-only instance entries.
int taskId = getTaskFromMap(i);
result.add(new InstanceInfo(i, taskId, type, url, readTitle(i), readTabCount(i)));
result.add(new InstanceInfo(i, taskId, type, url, readTitle(i), readTabCount(i),
readIncognitoTabCount(i), readIncognitoSelected(i)));
}
return result;
}
Expand Down Expand Up @@ -222,20 +232,35 @@ public void didSelectTab(Tab tab, int type, int lastId) {
mActiveTab = tab;
if (mActiveTab != null) {
mActiveTab.addObserver(mActiveTabObserver);
// TODO: Store incognito-related info.
writeUrl(mInstanceId, mActiveTab.getOriginalUrl().getSpec());
writeTitle(mInstanceId, mActiveTab.getTitle());
writeIncognitoSelected(mInstanceId, mActiveTab);
// When an incognito tab is focused, keep the normal active tab info.
Tab urlTab = mActiveTab.isIncognito()
? TabModelUtils.getCurrentTab(selector.getModel(false))
: mActiveTab;
if (urlTab != null) {
writeUrl(mInstanceId, urlTab);
writeTitle(mInstanceId, urlTab);
} else {
writeUrl(mInstanceId, EMPTY_DATA);
writeTitle(mInstanceId, EMPTY_DATA);
}
}
}

@Override
public void didAddTab(Tab tab, int type, int creationState) {
writeTabCount(mInstanceId, selector.getTotalTabCount());
writeTabCount(mInstanceId, selector);
}

@Override
public void tabClosureCommitted(Tab tab) {
writeTabCount(mInstanceId, selector.getTotalTabCount());
writeTabCount(mInstanceId, selector);
}

@Override
public void tabRemoved(Tab tab) {
// Updates the tab count of the src activity a reparented tab gets detached from.
writeTabCount(mInstanceId, selector);
}
};
}
Expand Down Expand Up @@ -300,11 +325,22 @@ public boolean isTabModelMergingEnabled() {
return false;
}

protected static void writeIncognitoSelected(int index, Tab tab) {
SharedPreferencesManager.getInstance().writeBoolean(
incognitoSelectedKey(index), tab.isIncognito());
}

static boolean readIncognitoSelected(int index) {
return SharedPreferencesManager.getInstance().readBoolean(
incognitoSelectedKey(index), false);
}

private static String urlKey(int index) {
return ChromePreferenceKeys.MULTI_INSTANCE_URL.createKey(String.valueOf(index));
}

private static String readUrl(int index) {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
static String readUrl(int index) {
return SharedPreferencesManager.getInstance().readString(urlKey(index), null);
}

Expand All @@ -313,14 +349,30 @@ protected static void writeUrl(int index, String url) {
SharedPreferencesManager.getInstance().writeString(urlKey(index), url);
}

protected static void writeUrl(int index, Tab tab) {
assert !tab.isIncognito();
writeUrl(index, tab.getOriginalUrl().getSpec());
}

private static String incognitoSelectedKey(int index) {
return ChromePreferenceKeys.MULTI_INSTANCE_IS_INCOGNITO_SELECTED.createKey(
String.valueOf(index));
}

private static String titleKey(int index) {
return ChromePreferenceKeys.MULTI_INSTANCE_TITLE.createKey(String.valueOf(index));
}

private static String readTitle(int index) {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
static String readTitle(int index) {
return SharedPreferencesManager.getInstance().readString(titleKey(index), null);
}

private static void writeTitle(int index, Tab tab) {
assert !tab.isIncognito();
writeTitle(index, tab.getTitle());
}

private static void writeTitle(int index, String title) {
SharedPreferencesManager.getInstance().writeString(titleKey(index), title);
}
Expand All @@ -329,21 +381,38 @@ private static String tabCountKey(int index) {
return ChromePreferenceKeys.MULTI_INSTANCE_TAB_COUNT.createKey(String.valueOf(index));
}

private static int readTabCount(int index) {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
static int readTabCount(int index) {
return SharedPreferencesManager.getInstance().readInt(tabCountKey(index));
}

private static void writeTabCount(int index, int tabCount) {
SharedPreferencesManager.getInstance().writeInt(tabCountKey(index), tabCount);
private static String incognitoTabCountKey(int index) {
return ChromePreferenceKeys.MULTI_INSTANCE_INCOGNITO_TAB_COUNT.createKey(
String.valueOf(index));
}

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
static int readIncognitoTabCount(int index) {
return SharedPreferencesManager.getInstance().readInt(incognitoTabCountKey(index));
}

private static void writeTabCount(int index, TabModelSelector selector) {
SharedPreferencesManager prefs = SharedPreferencesManager.getInstance();
int tabCount = selector.getModel(false).getCount();
prefs.writeInt(tabCountKey(index), tabCount);
prefs.writeInt(incognitoTabCountKey(index), selector.getModel(true).getCount());
if (tabCount == 0) {
writeUrl(index, EMPTY_DATA);
writeTitle(index, EMPTY_DATA);
}
}

/**
* Open or launch a given instance.
* @param instanceId ID of the instance to open.
* @param taskId ID of the task the instance resides in.
* @param openAdjacently Whether the instance should be launched in the adjacent window.
*/
private void openInstance(int instanceId, int taskId, boolean openAdjacently) {
private void openInstance(int instanceId, int taskId) {
if (taskId != INVALID_TASK_ID) {
// Just bring the task foreground if it is alive. This either completes the opening
// of the instance or leads to creating a new activity.
Expand All @@ -353,6 +422,8 @@ private void openInstance(int instanceId, int taskId, boolean openAdjacently) {
return;
}
onMultiInstanceModeStarted();
// TODO: Pass this flag from UI to control the window to open.
boolean openAdjacently = true;
Intent intent =
MultiWindowUtils.createNewWindowIntent(mActivity, instanceId, openAdjacently);
if (openAdjacently) {
Expand Down Expand Up @@ -393,4 +464,9 @@ private static void removeInstanceInfo(int index) {
prefs.removeKey(titleKey(index));
prefs.removeKey(tabCountKey(index));
}

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
TabModelSelectorTabModelObserver getTabModelObserverForTesting() {
return mTabModelObserver;
}
}

0 comments on commit 3994506

Please sign in to comment.