Commit f67a9ef4 authored by Mei Liang's avatar Mei Liang Committed by Commit Bot

[TabGroup] TabGroupModelFilter Implemenation

This CL implements TabGroupModelFilter, but it's not used in anywhere
yet.

Bug: 934557
Change-Id: I3f25fc913aa9f76aa8b5f393c6c59b6e082681a3
Reviewed-on: https://chromium-review.googlesource.com/c/1480721Reviewed-by: default avatarWei-Yin Chen (陳威尹) <wychen@chromium.org>
Reviewed-by: default avatarYusuf Ozuysal <yusufo@chromium.org>
Commit-Queue: Yusuf Ozuysal <yusufo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#636629}
parent 025bc7dc
...@@ -25,6 +25,9 @@ public class EmptyTabModelFilter extends TabModelFilter { ...@@ -25,6 +25,9 @@ public class EmptyTabModelFilter extends TabModelFilter {
@Override @Override
protected void selectTab(Tab tab) {} protected void selectTab(Tab tab) {}
@Override
protected void reorder() {}
// TabList implementation. // TabList implementation.
@Override @Override
public boolean isIncognito() { public boolean isIncognito() {
......
...@@ -47,4 +47,7 @@ public class EmptyTabModelObserver implements TabModelObserver { ...@@ -47,4 +47,7 @@ public class EmptyTabModelObserver implements TabModelObserver {
@Override @Override
public void tabRemoved(Tab tab) {} public void tabRemoved(Tab tab) {}
@Override
public void restoreCompleted() {}
} }
...@@ -75,7 +75,7 @@ public abstract class TabModelFilter extends EmptyTabModelObserver implements Ta ...@@ -75,7 +75,7 @@ public abstract class TabModelFilter extends EmptyTabModelObserver implements Ta
* @return An unmodifiable list of {@link Tab} that relate with the given tab id. * @return An unmodifiable list of {@link Tab} that relate with the given tab id.
*/ */
@NonNull @NonNull
public List<Tab> getUnmodifiableRelatedTabList(int tabId) { public List<Tab> getRelatedTabList(int tabId) {
return sEmptyRelatedTabList; return sEmptyRelatedTabList;
} }
...@@ -100,6 +100,11 @@ public abstract class TabModelFilter extends EmptyTabModelObserver implements Ta ...@@ -100,6 +100,11 @@ public abstract class TabModelFilter extends EmptyTabModelObserver implements Ta
*/ */
protected abstract void selectTab(Tab tab); protected abstract void selectTab(Tab tab);
/**
* Concrete class requires to define the ordering of each Tab within the filter.
*/
protected abstract void reorder();
// TabModelObserver implementation. // TabModelObserver implementation.
@Override @Override
public void didSelectTab(Tab tab, int type, int lastId) { public void didSelectTab(Tab tab, int type, int lastId) {
...@@ -188,4 +193,13 @@ public abstract class TabModelFilter extends EmptyTabModelObserver implements Ta ...@@ -188,4 +193,13 @@ public abstract class TabModelFilter extends EmptyTabModelObserver implements Ta
observer.tabRemoved(tab); observer.tabRemoved(tab);
} }
} }
@Override
public void restoreCompleted() {
if (getCount() != 0) reorder();
for (TabModelObserver observer : mFilteredObservers) {
observer.restoreCompleted();
}
}
} }
...@@ -108,6 +108,12 @@ public class TabModelImpl extends TabModelJniBridge { ...@@ -108,6 +108,12 @@ public class TabModelImpl extends TabModelJniBridge {
super.destroy(); super.destroy();
} }
@Override
public void broadcastSessionRestoreComplete() {
super.broadcastSessionRestoreComplete();
for (TabModelObserver observer : mObservers) observer.restoreCompleted();
}
@Override @Override
public void addObserver(TabModelObserver observer) { public void addObserver(TabModelObserver observer) {
mObservers.addObserver(observer); mObservers.addObserver(observer);
......
...@@ -104,4 +104,10 @@ public interface TabModelObserver { ...@@ -104,4 +104,10 @@ public interface TabModelObserver {
* @param tab The tab that has been removed. * @param tab The tab that has been removed.
*/ */
void tabRemoved(Tab tab); void tabRemoved(Tab tab);
/**
* Called after all {@link org.chromium.chrome.browser.tab.TabState}s within {@link TabModel}
* are loaded from storage.
*/
void restoreCompleted();
} }
...@@ -108,6 +108,9 @@ class TabModelObserverJniBridge implements TabModelObserver { ...@@ -108,6 +108,9 @@ class TabModelObserverJniBridge implements TabModelObserver {
nativeTabRemoved(mNativeTabModelObserverJniBridge, tab); nativeTabRemoved(mNativeTabModelObserverJniBridge, tab);
} }
@Override
public void restoreCompleted() {}
/** /**
* Creates an observer bridge for the given tab model. The native counterpart to this object * Creates an observer bridge for the given tab model. The native counterpart to this object
* will hold a global reference to the Java endpoint and manage its lifetime. This is private as * will hold a global reference to the Java endpoint and manage its lifetime. This is private as
......
// Copyright 2019 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.tasks.tabgroup;
import android.support.annotation.NonNull;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabList;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelFilter;
import org.chromium.chrome.browser.tabmodel.TabModelUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* An implementation of {@link TabModelFilter} that puts {@link Tab}s into a group
* structure.
*
* A group is a collection of {@link Tab}s that share a common ancestor {@link Tab}. This filter is
* also a {@link TabList} that contains the last shown {@link Tab} from every group.
*/
public class TabGroupModelFilter extends TabModelFilter {
/**
* This class is a representation of a group of tabs. It knows the last selected tab within the
* group.
*/
private class TabGroup {
private final Set<Integer> mTabIds;
private int mLastShownTabId;
TabGroup() {
mTabIds = new LinkedHashSet<>();
mLastShownTabId = Tab.INVALID_TAB_ID;
}
void addTab(int tabId) {
mTabIds.add(tabId);
if (mLastShownTabId == Tab.INVALID_TAB_ID) setLastShownTabId(tabId);
}
void removeTab(int tabId) {
assert mTabIds.contains(tabId);
if (mLastShownTabId == tabId) {
int nextIdToShow = nextTabIdToShow(tabId);
if (nextIdToShow != Tab.INVALID_TAB_ID) setLastShownTabId(nextIdToShow);
}
mTabIds.remove(tabId);
}
void moveToEndInGroup(int tabId) {
if (mTabIds.contains(tabId)) mTabIds.remove(tabId);
mTabIds.add(tabId);
}
boolean contains(int tabId) {
return mTabIds.contains(tabId);
}
int size() {
return mTabIds.size();
}
List<Integer> getTabIdList() {
return Collections.unmodifiableList(new ArrayList<>(mTabIds));
}
int getLastShownTabId() {
return mLastShownTabId;
}
void setLastShownTabId(int tabId) {
assert mTabIds.contains(tabId);
mLastShownTabId = tabId;
}
int nextTabIdToShow(int tabId) {
if (mTabIds.size() == 1 || !mTabIds.contains(tabId)) return Tab.INVALID_TAB_ID;
List<Integer> ids = getTabIdList();
int position = ids.indexOf(tabId);
if (position == 0) return ids.get(position + 1);
return ids.get(position - 1);
}
}
private Map<Integer, Integer> mGroupIdToGroupIndexMap = new HashMap<>();
private Map<Integer, TabGroup> mGroupIdToGroupMap = new HashMap<>();
private int mCurrentGroupIndex = TabList.INVALID_TAB_INDEX;
public TabGroupModelFilter(TabModel tabModel) {
super(tabModel);
}
// TabModelFilter implementation.
@NonNull
@Override
public List<Tab> getRelatedTabList(int id) {
// TODO(meiliang): In worst case, this method runs in O(n^2). This method needs to perform
// better, especially when we try to call it in a loop for all tabs.
Tab tab = TabModelUtils.getTabById(getTabModel(), id);
if (tab == null) return super.getRelatedTabList(id);
int groupId = tab.getRootId();
TabGroup group = mGroupIdToGroupMap.get(groupId);
return getRelatedTabList(group.getTabIdList());
}
private List<Tab> getRelatedTabList(List<Integer> ids) {
List<Tab> tabs = new ArrayList<>();
for (Integer id : ids) {
tabs.add(TabModelUtils.getTabById(getTabModel(), id));
}
return Collections.unmodifiableList(tabs);
}
@Override
protected void addTab(Tab tab) {
if (tab.isIncognito() != isIncognito()) {
throw new IllegalStateException("Attempting to open tab in the wrong model");
}
int groupId = tab.getRootId();
if (mGroupIdToGroupMap.containsKey(groupId)) {
mGroupIdToGroupMap.get(groupId).addTab(tab.getId());
} else {
TabGroup tabGroup = new TabGroup();
tabGroup.addTab(tab.getId());
mGroupIdToGroupMap.put(groupId, tabGroup);
mGroupIdToGroupIndexMap.put(groupId, mGroupIdToGroupIndexMap.size());
}
}
@Override
protected void closeTab(Tab tab) {
int groupId = tab.getRootId();
if (tab.isIncognito() != isIncognito() || mGroupIdToGroupMap.get(groupId) == null
|| !mGroupIdToGroupMap.get(groupId).contains(tab.getId())) {
throw new IllegalStateException("Attempting to close tab in the wrong model");
}
TabGroup group = mGroupIdToGroupMap.get(groupId);
group.removeTab(tab.getId());
if (group.size() == 0) {
updateGroupIdToGroupIndexMapAfterGroupClosed(groupId);
mGroupIdToGroupIndexMap.remove(groupId);
mGroupIdToGroupMap.remove(groupId);
}
}
private void updateGroupIdToGroupIndexMapAfterGroupClosed(int groupId) {
int indexToRemove = mGroupIdToGroupIndexMap.get(groupId);
Set<Integer> groupIdSet = mGroupIdToGroupIndexMap.keySet();
for (Integer groupIdKey : groupIdSet) {
int groupIndex = mGroupIdToGroupIndexMap.get(groupIdKey);
if (groupIndex > indexToRemove) {
mGroupIdToGroupIndexMap.put(groupIdKey, groupIndex - 1);
}
}
}
@Override
protected void selectTab(Tab tab) {
int groupId = tab.getRootId();
mGroupIdToGroupMap.get(groupId).setLastShownTabId(tab.getId());
mCurrentGroupIndex = mGroupIdToGroupIndexMap.get(groupId);
}
@Override
protected void reorder() {
mGroupIdToGroupIndexMap.clear();
TabModel tabModel = getTabModel();
for (int i = 0; i < tabModel.getCount(); i++) {
Tab tab = tabModel.getTabAt(i);
int groupId = tab.getRootId();
mGroupIdToGroupMap.get(groupId).moveToEndInGroup(tab.getId());
if (!mGroupIdToGroupIndexMap.containsKey(groupId)) {
mGroupIdToGroupIndexMap.put(groupId, mGroupIdToGroupIndexMap.size());
}
}
selectTab(tabModel.getTabAt(tabModel.index()));
assert mGroupIdToGroupIndexMap.size() == mGroupIdToGroupMap.size();
}
// TabList implementation.
@Override
public boolean isIncognito() {
return getTabModel().isIncognito();
}
@Override
public int index() {
return mCurrentGroupIndex;
}
@Override
public int getCount() {
return mGroupIdToGroupMap.size();
}
@Override
public Tab getTabAt(int index) {
if (index < 0 || index >= getCount()) return null;
int groupId = Tab.INVALID_TAB_ID;
Set<Integer> groupIdSet = mGroupIdToGroupIndexMap.keySet();
for (Integer groupIdKey : groupIdSet) {
if (mGroupIdToGroupIndexMap.get(groupIdKey) == index) {
groupId = groupIdKey;
break;
}
}
if (groupId == Tab.INVALID_TAB_ID) return null;
return TabModelUtils.getTabById(
getTabModel(), mGroupIdToGroupMap.get(groupId).getLastShownTabId());
}
@Override
public int indexOf(Tab tab) {
if (tab == null) return TabList.INVALID_TAB_INDEX;
return mGroupIdToGroupIndexMap.get(tab.getId());
}
@Override
public boolean isClosurePending(int tabId) {
return getTabModel().isClosurePending(tabId);
}
}
...@@ -127,6 +127,11 @@ public class TabCountProvider { ...@@ -127,6 +127,11 @@ public class TabCountProvider {
public void tabRemoved(Tab tab) { public void tabRemoved(Tab tab) {
updateTabCount(); updateTabCount();
} }
@Override
public void restoreCompleted() {
updateTabCount();
}
}; };
mTabModelSelector.getTabModelFilterProvider().addTabModelFilterObserver( mTabModelSelector.getTabModelFilterProvider().addTabModelFilterObserver(
......
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