Bug 1220928 - Clients and history loading. r=sebastian
MozReview-Commit-ID: 1lCKVQH54C3
--- a/mobile/android/base/java/org/mozilla/gecko/RemoteTabsExpandableListAdapter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/RemoteTabsExpandableListAdapter.java
@@ -164,17 +164,17 @@ public class RemoteTabsExpandableListAda
holder.nameView.setText(client.name);
holder.nameView.setTextColor(ContextCompat.getColor(context, textColorResId));
final long now = System.currentTimeMillis();
// It's OK to access the DB on the main thread here, as we're just
// getting a string.
final GeckoProfile profile = GeckoProfile.get(context);
- holder.lastModifiedView.setText(this.getLastSyncedString(context, now, client.lastModified));
+ holder.lastModifiedView.setText(getLastSyncedString(context, now, client.lastModified));
// These views exists only in some of our group views: they are present
// for the home panel groups and not for the tabs panel groups.
// Therefore, we must handle null.
if (holder.deviceTypeView != null) {
holder.deviceTypeView.setImageResource(deviceTypeResId);
}
@@ -231,16 +231,16 @@ public class RemoteTabsExpandableListAda
/**
* Return a relative "Last synced" time span for the given tab record.
*
* @param now local time.
* @param time to format string for.
* @return string describing time span
*/
- public String getLastSyncedString(Context context, long now, long time) {
+ public static String getLastSyncedString(Context context, long now, long time) {
if (new Date(time).before(EARLIEST_VALID_SYNCED_DATE)) {
return context.getString(R.string.remote_tabs_never_synced);
}
final CharSequence relativeTimeSpanString = DateUtils.getRelativeTimeSpanString(time, now, DateUtils.MINUTE_IN_MILLIS);
return context.getResources().getString(R.string.remote_tabs_last_synced, relativeTimeSpanString);
}
}
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryAdapter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryAdapter.java
@@ -1,16 +1,18 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.home;
+import android.support.v7.widget.RecyclerView;
+
+import android.content.Context;
import android.database.Cursor;
-import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.mozilla.gecko.R;
import org.mozilla.gecko.db.RemoteClient;
import java.util.Collections;
import java.util.List;
@@ -18,21 +20,46 @@ import java.util.List;
public class CombinedHistoryAdapter extends RecyclerView.Adapter<CombinedHistoryItem> {
// These ordinal positions are used in CombinedHistoryAdapter as viewType.
private enum ItemType {
CLIENT, HISTORY
}
private List<RemoteClient> remoteClients = Collections.emptyList();
private Cursor historyCursor;
+ private final Context context;
+
+ public CombinedHistoryAdapter(Context context) {
+ super();
+ this.context = context;
+ }
+
+ public void setClients(List<RemoteClient> clients) {
+ remoteClients = clients;
+ notifyDataSetChanged();
+ }
+
+ public void setHistory(Cursor history) {
+ historyCursor = history;
+ notifyDataSetChanged();
+ }
+
+ private int transformPosition(ItemType type, int position) {
+ if (type == ItemType.CLIENT) {
+ return position;
+ } else {
+ return position - (remoteClients == null ? 0 : remoteClients.size());
+ }
+ }
@Override
public CombinedHistoryItem onCreateViewHolder(ViewGroup viewGroup, int viewType) {
final LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
final View view;
+
if (viewType == ItemType.CLIENT.ordinal()) {
view = inflater.inflate(R.layout.home_remote_tabs_group, viewGroup, false);
return new CombinedHistoryItem.ClientItem(view);
} else {
view = inflater.inflate(R.layout.home_item_row, viewGroup, false);
return new CombinedHistoryItem.HistoryItem(view);
}
}
@@ -46,11 +73,28 @@ public class CombinedHistoryAdapter exte
@Override
public int getItemCount() {
final int remoteSize = remoteClients.size();
final int historySize = historyCursor == null ? 0 : historyCursor.getCount();
return remoteSize + historySize;
}
@Override
- public void onBindViewHolder(CombinedHistoryItem viewHolder, int position) {}
+ public void onBindViewHolder(CombinedHistoryItem viewHolder, int position) {
+ final ItemType itemType = ItemType.values()[getItemViewType(position)];
+ final int localPosition = transformPosition(itemType, position);
+ switch (itemType) {
+ case CLIENT:
+ final CombinedHistoryItem.ClientItem clientItem = (CombinedHistoryItem.ClientItem) viewHolder;
+ final RemoteClient client = remoteClients.get(localPosition);
+ clientItem.bind(client, context);
+ break;
+
+ case HISTORY:
+ if (historyCursor == null || !historyCursor.moveToPosition(localPosition)) {
+ throw new IllegalStateException("Couldn't move cursor to position " + localPosition);
+ }
+ ((CombinedHistoryItem.HistoryItem) viewHolder).bind(historyCursor);
+ break;
+ }
+ }
}
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryItem.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryItem.java
@@ -1,27 +1,56 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.home;
+import android.content.Context;
+import android.database.Cursor;
+import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView;
import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.RemoteTabsExpandableListAdapter;
+import org.mozilla.gecko.db.RemoteClient;
public abstract class CombinedHistoryItem extends RecyclerView.ViewHolder {
public CombinedHistoryItem(View view) {
super(view);
}
public static class HistoryItem extends CombinedHistoryItem {
public HistoryItem(View view) {
super(view);
}
+
+ public void bind(Cursor historyCursor) {
+ final TwoLinePageRow pageRow = (TwoLinePageRow) this.itemView;
+ pageRow.setShowIcons(true);
+ pageRow.updateFromCursor(historyCursor);
+ }
}
public static class ClientItem extends CombinedHistoryItem {
+ final TextView nameView;
+ final ImageView deviceTypeView;
+ final TextView lastModifiedView;
+
public ClientItem(View view) {
super(view);
+ nameView = (TextView) view.findViewById(R.id.client);
+ deviceTypeView = (ImageView) view.findViewById(R.id.device_type);
+ lastModifiedView = (TextView) view.findViewById(R.id.last_synced);
}
+ public void bind(RemoteClient client, Context context) {
+ this.nameView.setText(client.name);
+ this.nameView.setTextColor(ContextCompat.getColor(context, R.color.placeholder_active_grey));
+ this.deviceTypeView.setImageResource("desktop".equals(client.deviceType) ? R.drawable.sync_desktop : R.drawable.sync_mobile);
+
+ final long now = System.currentTimeMillis();
+ this.lastModifiedView.setText(RemoteTabsExpandableListAdapter.getLastSyncedString(context, now, client.lastModified));
+ }
}
}
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java
@@ -1,12 +1,134 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.home;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.support.v4.content.Loader;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import org.mozilla.gecko.GeckoProfile;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.db.BrowserDB;
+import org.mozilla.gecko.db.RemoteClient;
+
+import java.util.List;
+
public class CombinedHistoryPanel extends HomeFragment {
+ private static final String LOGTAG = "GeckoCombinedHistoryPnl";
+ private final int LOADER_ID_HISTORY = 0;
+ private final int LOADER_ID_REMOTE = 1;
+
+ private CombinedHistoryRecyclerView mRecyclerView;
+ private CombinedHistoryAdapter mAdapter;
+ private CursorLoaderCallbacks mCursorLoaderCallbacks;
+
+ // The button view for clearing browsing history.
+ private View mClearHistoryButton;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.home_combined_history_panel, container, false);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ mRecyclerView = (CombinedHistoryRecyclerView) view.findViewById(R.id.combined_recycler_view);
+ mAdapter = new CombinedHistoryAdapter(getContext());
+ mRecyclerView.setAdapter(mAdapter);
+ mClearHistoryButton = view.findViewById(R.id.clear_history_button);
+ // TODO: link up click handler for clear history button
+ // TODO: Handle date headers.
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ mCursorLoaderCallbacks = new CursorLoaderCallbacks();
+ }
@Override
- protected void load() {}
+ protected void load() {
+ getLoaderManager().initLoader(LOADER_ID_HISTORY, null, mCursorLoaderCallbacks);
+ getLoaderManager().initLoader(LOADER_ID_REMOTE, null, mCursorLoaderCallbacks);
+ }
+
+ private static class RemoteTabsCursorLoader extends SimpleCursorLoader {
+ private final GeckoProfile mProfile;
+
+ public RemoteTabsCursorLoader(Context context) {
+ super(context);
+ mProfile = GeckoProfile.get(context);
+ }
+
+ @Override
+ public Cursor loadCursor() {
+ return mProfile.getDB().getTabsAccessor().getRemoteTabsCursor(getContext());
+ }
+ }
+
+ private static class HistoryCursorLoader extends SimpleCursorLoader {
+ // Max number of history results
+ private static final int HISTORY_LIMIT = 100;
+ private final BrowserDB mDB;
+
+ public HistoryCursorLoader(Context context) {
+ super(context);
+ mDB = GeckoProfile.get(context).getDB();
+ }
+
+ @Override
+ public Cursor loadCursor() {
+ final ContentResolver cr = getContext().getContentResolver();
+ // TODO: Handle time bracketing by fetching date ranges from cursor
+ return mDB.getRecentHistory(cr, HISTORY_LIMIT);
+ }
+ }
+
+ private class CursorLoaderCallbacks extends TransitionAwareCursorLoaderCallbacks {
+ private BrowserDB mDB; // Pseudo-final: set in onCreateLoader.
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ if (mDB == null) {
+ mDB = GeckoProfile.get(getActivity()).getDB();
+ }
+
+ switch (id) {
+ case LOADER_ID_HISTORY:
+ return new HistoryCursorLoader(getContext());
+ case LOADER_ID_REMOTE:
+ return new RemoteTabsCursorLoader(getContext());
+ default:
+ Log.e(LOGTAG, "Unknown loader id!");
+ return null;
+ }
+ }
+
+ protected void onLoadFinishedAfterTransitions(Loader<Cursor> loader, Cursor c) {
+ final int loaderId = loader.getId();
+ switch (loaderId) {
+ case LOADER_ID_HISTORY:
+ mAdapter.setHistory(c);
+ break;
+
+ case LOADER_ID_REMOTE:
+ final List<RemoteClient> clients = mDB.getTabsAccessor().getClientsFromCursor(c);
+ // TODO: Handle hidden clients
+ mAdapter.setClients(clients);
+
+ break;
+ }
+
+ }
+ }
}
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryRecyclerView.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryRecyclerView.java
@@ -36,11 +36,12 @@ public class CombinedHistoryRecyclerView
setLayoutManager(layoutManager);
}
@Override
public void onItemClicked(RecyclerView recyclerView, int position, View v) {}
@Override
public boolean onItemLongClicked(RecyclerView recyclerView, int position, View v) {
+ // TODO: open context menu if not a date title
return showContextMenuForChild(this);
}
}
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeConfig.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomeConfig.java
@@ -39,16 +39,17 @@ public final class HomeConfig {
* to a default set of built-in panels. The DYNAMIC panel type is used by
* third-party services to create panels with varying types of content.
*/
@RobocopTarget
public static enum PanelType implements Parcelable {
TOP_SITES("top_sites", TopSitesPanel.class),
BOOKMARKS("bookmarks", BookmarksPanel.class),
HISTORY("history", HistoryPanel.class),
+ COMBINED_HISTORY("combined_history", CombinedHistoryPanel.class),
REMOTE_TABS("remote_tabs", RemoteTabsPanel.class),
READING_LIST("reading_list", ReadingListPanel.class),
RECENT_TABS("recent_tabs", RecentTabsPanel.class),
DYNAMIC("dynamic", DynamicPanel.class);
private final String mId;
private final Class<?> mPanelClass;
@@ -1589,16 +1590,17 @@ public final class HomeConfig {
// to open specific panels without querying the active Home Panel
// configuration. Because they don't consider the active configuration, it
// is only sensible to do this for built-in panels (and not for dynamic
// panels).
private static final String TOP_SITES_PANEL_ID = "4becc86b-41eb-429a-a042-88fe8b5a094e";
private static final String BOOKMARKS_PANEL_ID = "7f6d419a-cd6c-4e34-b26f-f68b1b551907";
private static final String READING_LIST_PANEL_ID = "20f4549a-64ad-4c32-93e4-1dcef792733b";
private static final String HISTORY_PANEL_ID = "f134bf20-11f7-4867-ab8b-e8e705d7fbe8";
+ private static final String COMBINED_HISTORY_PANEL_ID = "4d716ce2-e063-486d-9e7c-b190d7b04dc6";
private static final String RECENT_TABS_PANEL_ID = "5c2601a5-eedc-4477-b297-ce4cef52adf8";
private static final String REMOTE_TABS_PANEL_ID = "72429afd-8d8b-43d8-9189-14b779c563d0";
private final HomeConfigBackend mBackend;
public HomeConfig(HomeConfigBackend backend) {
mBackend = backend;
}
@@ -1629,16 +1631,17 @@ public final class HomeConfig {
public static int getTitleResourceIdForBuiltinPanelType(PanelType panelType) {
switch(panelType) {
case TOP_SITES:
return R.string.home_top_sites_title;
case BOOKMARKS:
return R.string.bookmarks_title;
+ case COMBINED_HISTORY:
case HISTORY:
return R.string.home_history_title;
case REMOTE_TABS:
return R.string.home_remote_tabs_title;
case READING_LIST:
return R.string.reading_list_title;
@@ -1657,16 +1660,19 @@ public final class HomeConfig {
return TOP_SITES_PANEL_ID;
case BOOKMARKS:
return BOOKMARKS_PANEL_ID;
case HISTORY:
return HISTORY_PANEL_ID;
+ case COMBINED_HISTORY:
+ return COMBINED_HISTORY_PANEL_ID;
+
case REMOTE_TABS:
return REMOTE_TABS_PANEL_ID;
case READING_LIST:
return READING_LIST_PANEL_ID;
case RECENT_TABS:
return RECENT_TABS_PANEL_ID;
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeConfigPrefsBackend.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomeConfigPrefsBackend.java
@@ -70,16 +70,17 @@ public class HomeConfigPrefsBackend impl
private State loadDefaultConfig() {
final ArrayList<PanelConfig> panelConfigs = new ArrayList<PanelConfig>();
panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.TOP_SITES,
EnumSet.of(PanelConfig.Flags.DEFAULT_PANEL)));
panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.BOOKMARKS));
panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.HISTORY));
+ panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.COMBINED_HISTORY));
// We disable Synced Tabs for guest mode / restricted profiles.
if (Restrictions.isAllowed(mContext, Restrictable.MODIFY_ACCOUNTS)) {
panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.REMOTE_TABS));
}
panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.RECENT_TABS));
panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.READING_LIST));
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/home_combined_history_panel.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <org.mozilla.gecko.home.CombinedHistoryRecyclerView
+ android:id="@+id/combined_recycler_view"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"/>
+
+ <include layout="@layout/home_history_clear_button"/>
+
+</LinearLayout>