// Copyright 2015 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 android.app.Activity;
import android.util.SparseArray;
import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.ApplicationStatus.ActivityStateListener;
import org.chromium.base.ThreadUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.ui.base.WindowAndroid;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Manages multiple {@link TabModelSelector} instances, each owned by different {@link Activity}s.
*/
public class TabWindowManager implements ActivityStateListener {
/**
* An index that represents the invalid state (i.e. when the window wasn't found in the list.
*/
public static final int INVALID_WINDOW_INDEX = -1;
/** The maximum number of simultaneous TabModelSelector instances in this Application. */
public static final int MAX_SIMULTANEOUS_SELECTORS = 3;
/**
* A factory interface for building a {@link TabModelSelector} instance.
*/
public interface TabModelSelectorFactory {
/**
* Builds a {@link TabModelSelector}.
* @param activity A {@link ChromeActivity} instance.
* @param windowAndroid A {@link WindowAndroid} instance that should connect to
* {@code activity}.
* @param selectorIndex The index of the {@link TabModelSelector}.
* @return A new {@link TabModelSelector} instance.
*/
TabModelSelector buildSelector(ChromeActivity activity, WindowAndroid windowAndroid,
int selectorIndex);
}
/** The singleton reference. */
private static TabWindowManager sInstance;
private TabModelSelectorFactory mSelectorFactory = new DefaultTabModelSelectorFactory();
private List<TabModelSelector> mSelectors = new ArrayList<TabModelSelector>();
private Map<Activity, TabModelSelector> mAssignments =
new HashMap<Activity, TabModelSelector>();
/**
* @return The singleton instance of {@link TabWindowManager}.
*/
public static TabWindowManager getInstance() {
ThreadUtils.assertOnUiThread();
if (sInstance == null) sInstance = new TabWindowManager();
return sInstance;
}
/**
* Called to request a {@link TabModelSelector} based on {@code index}. Note that the
* {@link TabModelSelector} returned might not actually be the one related to {@code index}
* and {@link #getIndexForWindow(Activity)} should be called to grab the actual index if
* required.
* @param activity An instance of {@link ChromeActivity}. Must be the same {@link Activity} as
* the one referenced by {@code window}.
* @param window A {@link WindowAndroid} as an instance. The {@link TabModelSelector} that is
* created is bound to the {@link Activity} stored inside this window.
* @param index The index of the requested {@link TabModelSelector}. Not guaranteed to be
* the index of the {@link TabModelSelector} returned.
* @return A {@link TabModelSelector} index, or {@code null} if there are too many
* {@link TabModelSelector}s already built.
*/
public TabModelSelector requestSelector(ChromeActivity activity, WindowAndroid window,
int index) {
if (mAssignments.get(activity) != null) {
return mAssignments.get(activity);
}
if (index < 0 || index >= mSelectors.size()) index = 0;
if (mSelectors.get(index) != null) {
for (int i = 0; i < mSelectors.size(); i++) {
if (mSelectors.get(i) == null) {
index = i;
break;
}
}
}
// Too many activities going at once.
if (mSelectors.get(index) != null) return null;
TabModelSelector selector = mSelectorFactory.buildSelector(activity, window, index);
mSelectors.set(index, selector);
mAssignments.put(activity, selector);
return selector;
}
/**
* Finds the current index of the {@link TabModelSelector} bound to {@code window}.
* @param activity The {@link Activity} to find the index of the {@link TabModelSelector}
* for. This uses the underlying {@link Activity} stored in the
* {@link WindowAndroid}.
* @return The index of the {@link TabModelSelector} or {@link #INVALID_WINDOW_INDEX} if
* not found.
*/
public int getIndexForWindow(Activity activity) {
if (activity == null) return INVALID_WINDOW_INDEX;
TabModelSelector selector = mAssignments.get(activity);
if (selector == null) return INVALID_WINDOW_INDEX;
int index = mSelectors.indexOf(selector);
return index == -1 ? INVALID_WINDOW_INDEX : index;
}
/**
* @return The number of {@link TabModelSelector}s currently assigned to {@link Activity}s.
*/
public int getNumberOfAssignedTabModelSelectors() {
return mAssignments.size();
}
/**
* @return The total number of incognito tabs across all tab model selectors.
*/
public int getIncognitoTabCount() {
int count = 0;
for (int i = 0; i < mSelectors.size(); i++) {
if (mSelectors.get(i) != null) {
count += mSelectors.get(i).getModel(true).getCount();
}
}
// Count tabs that are moving between activities (e.g. a tab that was recently reparented
// and hasn't been attached to its new activity yet).
SparseArray<AsyncTabParams> asyncTabParams = AsyncTabParamsManager.getAsyncTabParams();
for (int i = 0; i < asyncTabParams.size(); i++) {
Tab tab = asyncTabParams.valueAt(i).getTabToReparent();
if (tab != null && tab.isIncognito()) count++;
}
return count;
}
/**
* @param tabId The ID of the tab in question.
* @return Whether the given tab exists in any currently loaded selector.
*/
public boolean tabExistsInAnySelector(int tabId) {
return getTabById(tabId) != null;
}
/**
* @param tabId The ID of the tab in question.
* @return Specified {@link Tab} or {@code null} if the {@link Tab} is not found.
*/
public Tab getTabById(int tabId) {
for (int i = 0; i < mSelectors.size(); i++) {
TabModelSelector selector = mSelectors.get(i);
if (selector != null) {
final Tab tab = selector.getTabById(tabId);
if (tab != null) return tab;
}
}
if (AsyncTabParamsManager.hasParamsForTabId(tabId)) {
return AsyncTabParamsManager.getAsyncTabParams().get(tabId).getTabToReparent();
}
return null;
}
@Override
public void onActivityStateChange(Activity activity, int newState) {
if (newState == ActivityState.DESTROYED && mAssignments.containsKey(activity)) {
int index = mSelectors.indexOf(mAssignments.remove(activity));
if (index >= 0) mSelectors.set(index, null);
// TODO(dtrainor): Move TabModelSelector#destroy() calls here.
}
}
/**
* Allows overriding the default {@link TabModelSelectorFactory} with another one. Typically
* for testing.
* @param factory A {@link TabModelSelectorFactory} instance.
*/
@VisibleForTesting
public void setTabModelSelectorFactory(TabModelSelectorFactory factory) {
mSelectorFactory = factory;
}
private TabWindowManager() {
ApplicationStatus.registerStateListenerForAllActivities(this);
for (int i = 0; i < MAX_SIMULTANEOUS_SELECTORS; i++) mSelectors.add(null);
}
private static class DefaultTabModelSelectorFactory implements TabModelSelectorFactory {
@Override
public TabModelSelector buildSelector(ChromeActivity activity, WindowAndroid windowAndroid,
int selectorIndex) {
assert activity == windowAndroid.getActivity().get();
TabPersistencePolicy persistencePolicy = new TabbedModeTabPersistencePolicy(
selectorIndex);
return new TabModelSelectorImpl(activity, persistencePolicy, windowAndroid, true);
}
}
}