Bug 1251362 - Part 5 - Actually show recently closed tabs when opening the smart folder. r=liuche

This fills the new smart folder we've previously added with life and displays the recently closed tabs as we receive them from the session store. If we can find a sessionstore.bak file (previously the "Tabs from last time"), we also add those tabs to the bottom of the list.

Most of the code for communicating with the session store and reading sessionstore.bak is adapted from the original Recent Tabs panel, however unlike the previous implementation, I've opted for a cursor-less approach of storing and retrieving the recent tabs data, since the recent tabs data isn't actually powered by a database anyway. Instead, the RecentTabsAdapter maintains two arrays for storing "Recently closed tabs" (as received through messages from the Gecko session store) and "Tabs from last time" (as read from sessionstore.bak during panel initialisation).

Also, as per the other Combined History panel adapters and because we're now using a RecyclerView instead of a ListView, list item types are now determined on demand through getItemTypeForPosition() instead of precalculating them during a data update and directly storing together with the tab data items in a cursor.

MozReview-Commit-ID: IpoUr9f0JBP

--HG--
extra : transplant_source : %C4%D5%AF%CA%A4_-%85%1AL%1D%9CF%2B%20Lh%7B%02%21
This commit is contained in:
Jan Henning 2016-06-02 18:08:23 +02:00
parent 2b737b8819
commit ebe769ff81
3 changed files with 250 additions and 4 deletions

View File

@ -15,6 +15,7 @@ import android.widget.TextView;
import org.mozilla.gecko.R;
import org.mozilla.gecko.db.RemoteClient;
import org.mozilla.gecko.db.RemoteTab;
import org.mozilla.gecko.home.RecentTabsAdapter.ClosedTab;
public abstract class CombinedHistoryItem extends RecyclerView.ViewHolder {
private static final String LOGTAG = "CombinedHistoryItem";
@ -84,6 +85,12 @@ public abstract class CombinedHistoryItem extends RecyclerView.ViewHolder {
childPageRow.setShowIcons(true);
childPageRow.update(remoteTab.title, remoteTab.url);
}
public void bind(ClosedTab closedTab) {
final TwoLinePageRow childPageRow = (TwoLinePageRow) this.itemView;
childPageRow.setShowIcons(false);
childPageRow.update(closedTab.title, closedTab.url);
}
}
public static class ClientItem extends CombinedHistoryItem {

View File

@ -110,7 +110,7 @@ public class CombinedHistoryPanel extends HomeFragment implements RemoteClientsD
mHistoryAdapter = new CombinedHistoryAdapter(getResources());
mClientsAdapter = new ClientsAdapter(getContext());
mRecentTabsAdapter = new RecentTabsAdapter();
mRecentTabsAdapter = new RecentTabsAdapter(getContext());
mSyncStatusListener = new RemoteTabsSyncListener();
FirefoxAccounts.addSyncStatusListener(mSyncStatusListener);
@ -138,6 +138,8 @@ public class CombinedHistoryPanel extends HomeFragment implements RemoteClientsD
mPanelFooterButton = (Button) view.findViewById(R.id.clear_history_button);
mPanelFooterButton.setOnClickListener(new OnFooterButtonClickListener());
mRecentTabsAdapter.startListeningForClosedTabs();
}
private void setUpRecyclerView() {
@ -578,6 +580,13 @@ public class CombinedHistoryPanel extends HomeFragment implements RemoteClientsD
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
mRecentTabsAdapter.stopListeningForClosedTabs();
}
@Override
public void onDestroy() {
super.onDestroy();

View File

@ -5,18 +5,190 @@
package org.mozilla.gecko.home;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.mozilla.gecko.AboutPages;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.R;
import org.mozilla.gecko.SessionParser;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.util.ThreadUtils;
import java.util.ArrayList;
import java.util.List;
import static org.mozilla.gecko.home.CombinedHistoryItem.ItemType;
public class RecentTabsAdapter extends RecyclerView.Adapter<CombinedHistoryItem> implements CombinedHistoryRecyclerView.AdapterContextMenuBuilder {
public class RecentTabsAdapter extends RecyclerView.Adapter<CombinedHistoryItem>
implements CombinedHistoryRecyclerView.AdapterContextMenuBuilder, NativeEventListener {
private static final String LOGTAG = "GeckoRecentTabsAdapter";
private static final int NAVIGATION_BACK_BUTTON_INDEX = 0;
// Recently closed tabs from Gecko.
private ClosedTab[] recentlyClosedTabs;
// "Tabs from last time".
private ClosedTab[] lastSessionTabs;
public static final class ClosedTab {
public final String url;
public final String title;
public final String data;
public ClosedTab(String url, String title, String data) {
this.url = url;
this.title = title;
this.data = data;
}
}
private final Context context;
public RecentTabsAdapter(Context context) {
this.context = context;
recentlyClosedTabs = new ClosedTab[0];
lastSessionTabs = new ClosedTab[0];
readPreviousSessionData();
}
public void startListeningForClosedTabs() {
EventDispatcher.getInstance().registerGeckoThreadListener(this, "ClosedTabs:Data");
GeckoAppShell.notifyObservers("ClosedTabs:StartNotifications", null);
}
public void stopListeningForClosedTabs() {
GeckoAppShell.notifyObservers("ClosedTabs:StopNotifications", null);
EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "ClosedTabs:Data");
}
@Override
public void handleMessage(String event, NativeJSObject message, EventCallback callback) {
final NativeJSObject[] tabs = message.getObjectArray("tabs");
final int length = tabs.length;
final ClosedTab[] closedTabs = new ClosedTab[length];
for (int i = 0; i < length; i++) {
final NativeJSObject tab = tabs[i];
closedTabs[i] = new ClosedTab(tab.getString("url"), tab.getString("title"), tab.getObject("data").toString());
}
// Only modify recentlyClosedTabs on the UI thread.
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
// Save some data about the old panel state, so we can be
// smarter about notifying the recycler view which bits changed.
int prevClosedTabsCount = recentlyClosedTabs.length;
boolean prevSectionHeaderVisibility = isSectionHeaderVisible();
int prevSectionHeaderIndex = getSectionHeaderIndex();
recentlyClosedTabs = closedTabs;
// Handle the section header hiding/unhiding.
updateHeaderVisibility(prevSectionHeaderVisibility, prevSectionHeaderIndex);
// Update the "Recently closed" part of the tab list.
updateTabsList(prevClosedTabsCount, recentlyClosedTabs.length, getFirstRecentTabIndex(), getLastRecentTabIndex());
}
});
}
private void readPreviousSessionData() {
// Make sure that the start up code has had a chance to update sessionstore.bak as necessary.
GeckoProfile.get(context).waitForOldSessionDataProcessing();
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
final String jsonString = GeckoProfile.get(context).readSessionFile(true);
if (jsonString == null) {
// No previous session data.
return;
}
final List<ClosedTab> parsedTabs = new ArrayList<>();
new SessionParser() {
@Override
public void onTabRead(SessionTab tab) {
final String url = tab.getUrl();
// Don't show last tabs for about:home
if (AboutPages.isAboutHome(url)) {
return;
}
parsedTabs.add(new ClosedTab(url, tab.getTitle(), tab.getTabObject().toString()));
}
}.parse(jsonString);
final ClosedTab[] closedTabs = parsedTabs.toArray(new ClosedTab[parsedTabs.size()]);
// Only modify lastSessionTabs on the UI thread.
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
// Save some data about the old panel state, so we can be
// smarter about notifying the recycler view which bits changed.
int prevClosedTabsCount = lastSessionTabs.length;
boolean prevSectionHeaderVisibility = isSectionHeaderVisible();
int prevSectionHeaderIndex = getSectionHeaderIndex();
lastSessionTabs = closedTabs;
// Handle the section header hiding/unhiding.
updateHeaderVisibility(prevSectionHeaderVisibility, prevSectionHeaderIndex);
// Update the "Tabs from last time" part of the tab list.
updateTabsList(prevClosedTabsCount, lastSessionTabs.length, getFirstLastSessionTabIndex(), getLastLastSessionTabIndex());
}
});
}
});
}
private void updateHeaderVisibility(boolean prevSectionHeaderVisibility, int prevSectionHeaderIndex) {
if (prevSectionHeaderVisibility && !isSectionHeaderVisible()) {
notifyItemRemoved(prevSectionHeaderIndex);
} else if (!prevSectionHeaderVisibility && isSectionHeaderVisible()) {
notifyItemInserted(getSectionHeaderIndex());
}
}
/**
* Updates the tab list as necessary to account for any changes in tab count in a particular data source.
*
* Since the session store only sends out full updates, we don't know for sure what has changed compared
* to the last data set, so we can only animate if the tab count actually changes.
*
* @param prevClosedTabsCount The previous number of closed tabs from that data source.
* @param closedTabsCount The current number of closed tabs contained in that data source.
* @param firstTabListIndex The current position of that data source's first item in the RecyclerView.
* @param lastTabListIndex The current position of that data source's last item in the RecyclerView.
*/
private void updateTabsList(int prevClosedTabsCount, int closedTabsCount, int firstTabListIndex, int lastTabListIndex) {
final int closedTabsCountChange = closedTabsCount - prevClosedTabsCount;
if (closedTabsCountChange <= 0) {
notifyItemRangeRemoved(lastTabListIndex + 1, -closedTabsCountChange); // Remove tabs from the bottom of the list.
notifyItemRangeChanged(firstTabListIndex, closedTabsCount); // Update the contents of the remaining items.
} else { // closedTabsCountChange > 0
notifyItemRangeInserted(firstTabListIndex, closedTabsCountChange); // Add additional tabs at the top of the list.
notifyItemRangeChanged(firstTabListIndex + closedTabsCountChange, prevClosedTabsCount); // Update any previous list items.
}
}
@Override
public CombinedHistoryItem onCreateViewHolder(ViewGroup parent, int viewType) {
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
@ -43,18 +215,46 @@ public class RecentTabsAdapter extends RecyclerView.Adapter<CombinedHistoryItem>
@Override
public void onBindViewHolder(CombinedHistoryItem holder, final int position) {
final CombinedHistoryItem.ItemType itemType = getItemTypeForPosition(position);
switch (itemType) {
case SECTION_HEADER:
((TextView) holder.itemView).setText(context.getString(R.string.home_closed_tabs_title2));
break;
case CLOSED_TAB:
final ClosedTab closedTab;
if (position <= getLastRecentTabIndex()) {
closedTab = recentlyClosedTabs[position - getFirstRecentTabIndex()];
} else {
closedTab = lastSessionTabs[position - getFirstLastSessionTabIndex()];
}
((CombinedHistoryItem.HistoryItem) holder).bind(closedTab);
break;
}
}
@Override
public int getItemCount() {
return 1;
int itemCount = 1; // NAVIGATION_BACK button is always visible.
if (isSectionHeaderVisible()) {
itemCount += 1;
}
itemCount += getClosedTabsCount();
return itemCount;
}
private CombinedHistoryItem.ItemType getItemTypeForPosition(int position) {
if (position == 0) {
if (position == NAVIGATION_BACK_BUTTON_INDEX) {
return ItemType.NAVIGATION_BACK;
}
if (position == getSectionHeaderIndex() && isSectionHeaderVisible()) {
return ItemType.SECTION_HEADER;
}
return ItemType.CLOSED_TAB;
}
@ -63,6 +263,36 @@ public class RecentTabsAdapter extends RecyclerView.Adapter<CombinedHistoryItem>
return CombinedHistoryItem.ItemType.itemTypeToViewType(getItemTypeForPosition(position));
}
public int getClosedTabsCount() {
return recentlyClosedTabs.length + lastSessionTabs.length;
}
private boolean isSectionHeaderVisible() {
return recentlyClosedTabs.length > 0 || lastSessionTabs.length > 0;
}
private int getSectionHeaderIndex() {
return isSectionHeaderVisible() ?
NAVIGATION_BACK_BUTTON_INDEX + 1 :
NAVIGATION_BACK_BUTTON_INDEX;
}
private int getFirstRecentTabIndex() {
return getSectionHeaderIndex() + 1;
}
private int getLastRecentTabIndex() {
return getSectionHeaderIndex() + recentlyClosedTabs.length;
}
private int getFirstLastSessionTabIndex() {
return getLastRecentTabIndex() + 1;
}
private int getLastLastSessionTabIndex() {
return getLastRecentTabIndex() + lastSessionTabs.length;
}
@Override
public HomeContextMenuInfo makeContextMenuInfoFromPosition(View view, int position) {
final CombinedHistoryItem.ItemType itemType = getItemTypeForPosition(position);