| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.chrome.browser.tabmodel; |
| |
| import org.chromium.chrome.browser.Tab; |
| import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType; |
| |
| /** |
| * This class acts as a controller for determining where tabs should be inserted |
| * into a tab strip model. See tab_strip_model_order_controller.cc and |
| * tab_strip_model.cc |
| */ |
| public class TabModelOrderController { |
| |
| private static final int NO_TAB = -1; |
| private final TabModelSelector mTabModelSelector; |
| |
| public TabModelOrderController(TabModelSelector modelSelector) { |
| mTabModelSelector = modelSelector; |
| } |
| |
| /** |
| * Determine the insertion index of the next tab. If it's not the result of |
| * a link being pressed, the provided index will be returned. |
| * |
| * @param type The launch type of the new tab. |
| * @param position The provided position. |
| * @return Where to insert the tab. |
| */ |
| public int determineInsertionIndex(TabLaunchType type, int position, Tab newTab) { |
| if (linkClicked(type)) { |
| position = determineInsertionIndex(type, newTab); |
| } |
| |
| if (willOpenInForeground(type, newTab.isIncognito())) { |
| // Forget any existing relationships, we don't want to make things |
| // too confusing by having multiple groups active at the same time. |
| forgetAllOpeners(); |
| } |
| |
| return position; |
| } |
| |
| /** |
| * Determine the insertion index of the next tab. |
| * |
| * @param type The launch type of the new tab. |
| * @return Where to insert the tab. |
| */ |
| public int determineInsertionIndex(TabLaunchType type, Tab newTab) { |
| TabModel currentModel = mTabModelSelector.getCurrentModel(); |
| Tab currentTab = TabModelUtils.getCurrentTab(currentModel); |
| if (currentTab == null) { |
| assert (currentModel.getCount() == 0); |
| return 0; |
| } |
| int currentId = currentTab.getId(); |
| int currentIndex = TabModelUtils.getTabIndexById(currentModel, currentId); |
| |
| if (sameModelType(currentModel, newTab)) { |
| if (willOpenInForeground(type, newTab.isIncognito())) { |
| // If the tab was opened in the foreground, insert it adjacent to |
| // the tab that opened that link. |
| return currentIndex + 1; |
| } else { |
| // If the tab was opened in the background, position at the end of |
| // it's 'group'. |
| int index = getIndexOfLastTabOpenedBy(currentId, currentIndex); |
| if (index != NO_TAB) { |
| return index + 1; |
| } else { |
| return currentIndex + 1; |
| } |
| } |
| } else { |
| // If the tab is opening in the other model type, just put it at the end. |
| return mTabModelSelector.getModel(newTab.isIncognito()).getCount(); |
| } |
| } |
| |
| /** |
| * Returns the index of the last tab in the model opened by the specified |
| * opener, starting at startIndex. To clarify, the tabs are traversed in the |
| * descending order of their position in the model. This means that the tab |
| * furthest in the stack with the given opener id will be returned. |
| * |
| * @param openerId The opener of interest. |
| * @param startIndex The start point of the search. |
| * @return The last tab if found, NO_TAB otherwise. |
| */ |
| private int getIndexOfLastTabOpenedBy(int openerId, int startIndex) { |
| TabModel currentModel = mTabModelSelector.getCurrentModel(); |
| int count = currentModel.getCount(); |
| for (int i = count - 1; i >= startIndex; i--) { |
| Tab tab = currentModel.getTabAt(i); |
| if (tab.getParentId() == openerId && tab.isGroupedWithParent()) { |
| return i; |
| } |
| } |
| return NO_TAB; |
| } |
| |
| /** |
| * Clear the opener attribute on all tabs in the model. |
| */ |
| void forgetAllOpeners() { |
| TabModel currentModel = mTabModelSelector.getCurrentModel(); |
| int count = currentModel.getCount(); |
| for (int i = 0; i < count; i++) { |
| currentModel.getTabAt(i).setGroupedWithParent(false); |
| } |
| } |
| |
| /** |
| * Determine if a launch type is the result of linked being clicked. |
| */ |
| static boolean linkClicked(TabLaunchType type) { |
| return type == TabLaunchType.FROM_LINK || |
| type == TabLaunchType.FROM_LONGPRESS_FOREGROUND || |
| type == TabLaunchType.FROM_LONGPRESS_BACKGROUND; |
| } |
| |
| /** |
| * Determine if a launch type will result in the tab being opened in the |
| * foreground. |
| * @param type The type of opening event. |
| * @param isNewTabIncognito True if the new opened tab is incognito. |
| * @return True if the tab will be in the foreground |
| */ |
| public boolean willOpenInForeground(TabLaunchType type, boolean isNewTabIncognito) { |
| // Restore is handling the active index by itself. |
| return (type != TabLaunchType.FROM_LONGPRESS_BACKGROUND && |
| type != TabLaunchType.FROM_RESTORE) |
| || (!mTabModelSelector.isIncognitoSelected() && isNewTabIncognito); |
| } |
| |
| /** |
| * @return {@code true} If both tabs have the same model type, {@code false} otherwise. |
| */ |
| static boolean sameModelType(TabModel model, Tab tab) { |
| return model.isIncognito() == tab.isIncognito(); |
| } |
| |
| } |