Clients and history loading. draft
authorChenxia Liu <liuche@mozilla.com>
Wed, 24 Feb 2016 14:27:55 -0800
changeset 338838 bf30b513b19995e6b745482d14f557bf31bcbb05
parent 338837 0575d9cfce6f0cb8087b24943ead4cd51d49242c
child 338839 baed884b4b66572dd307f76eb7bddafb1b8d77ec
push id12587
push usercliu@mozilla.com
push dateThu, 10 Mar 2016 01:56:14 +0000
milestone48.0a1
Clients and history loading. MozReview-Commit-ID: 1lCKVQH54C3
mobile/android/base/java/org/mozilla/gecko/RemoteTabsExpandableListAdapter.java
mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryAdapter.java
mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryItem.java
mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java
mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryRecyclerView.java
mobile/android/base/java/org/mozilla/gecko/home/HomeConfig.java
mobile/android/base/java/org/mozilla/gecko/home/HomeConfigPrefsBackend.java
mobile/android/base/resources/layout/home_combined_history_panel.xml
--- 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,61 +1,100 @@
 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.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.RemoteTabsExpandableListAdapter;
+import org.mozilla.gecko.db.RemoteClient;
 
 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
     }
 
-    List<CombinedHistoryItem> adapter;
-    List<CombinedHistoryItem> remoteClients;
-    List<CombinedHistoryItem> historyItems;
+    private List<RemoteClient> remoteClients;
+    private Cursor historyCursor;
+    private Context context;
+
+    public void replaceClients(List<RemoteClient> clients) {
+        remoteClients = clients;
+        notifyDataSetChanged();
+    }
 
-    public CombinedHistoryAdapter(List<CombinedHistoryItem> model) {
-        // Optional
-        adapter = model;
+    public void replaceHistory(Cursor history) {
+        historyCursor = history;
+        notifyDataSetChanged();
+    }
+
+    private int transformPosition(ItemType type, int position) {
+        if (type == ItemType.CLIENT) {
+            return position;
+        } else {
+            return position - remoteClients.size();
+        }
     }
 
     @Override
     public CombinedHistoryItem onCreateViewHolder(ViewGroup viewGroup, int viewType) {
         final LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
         final View view;
-        switch (viewType) {
-            case 0: // ItemType.CLIENT
-                view = inflater.inflate(R.layout.home_item_row, viewGroup, false);
-                return new CombinedHistoryItem.ClientItem(view);
-                // TODO: custom remote layout
-            case 1: // ItemType.HISTORY
-                view = inflater.inflate(R.layout.home_item_row, viewGroup, false);
-                return new CombinedHistoryItem.HistoryItem(view);
-                // TODO: custom History layout
+
+        context = viewGroup.getContext();
+        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(view);
         }
-        return null;
     }
 
     @Override
     public int getItemViewType(int position) {
         final int numClients = remoteClients == null ? 0 : remoteClients.size();
         return (position < numClients) ? ItemType.CLIENT.ordinal() : ItemType.HISTORY.ordinal();
     }
 
     @Override
     public int getItemCount() {
         final int remoteSize = remoteClients == null ? 0 : remoteClients.size();
-        final int historySize = historyItems == null ? 0 : historyItems.size();
+        final int historySize = historyCursor == null ? 0 : historyCursor.getCount();
         return remoteSize + historySize;
     }
 
     @Override
     public void onBindViewHolder(CombinedHistoryItem viewHolder, int position) {
-        // set item views based on position from adapter
+        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.nameView.setText(client.name);
+                clientItem.nameView.setTextColor(ContextCompat.getColor(context, R.color.placeholder_active_grey));
+                clientItem.deviceTypeView.setImageResource("desktop".equals(client.deviceType) ? R.drawable.sync_desktop : R.drawable.sync_mobile);
+
+                final long now = System.currentTimeMillis();
+                // It's OK to access the DB on the main thread here, as we're just
+                // getting a string.
+                clientItem.lastModifiedView.setText(RemoteTabsExpandableListAdapter.getLastSyncedString(context, now, client.lastModified));
+                break;
+
+            case HISTORY:
+                final TwoLinePageRow pageRow = (TwoLinePageRow) viewHolder.itemView;
+                pageRow.setShowIcons(true);
+                if (historyCursor == null || !historyCursor.moveToPosition(localPosition)) {
+                    throw new IllegalStateException("Couldn't move cursor to position " + localPosition);
+                }
+                pageRow.updateFromCursor(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,24 +1,27 @@
 package org.mozilla.gecko.home;
 
 import android.support.v7.widget.RecyclerView;
 import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+import org.mozilla.gecko.R;
 
-public abstract class CombinedHistoryItem extends RecyclerView.ViewHolder {
+public class CombinedHistoryItem extends RecyclerView.ViewHolder {
     public CombinedHistoryItem(View view) {
         super(view);
     }
 
-    public static class HistoryItem extends CombinedHistoryItem {
-        public HistoryItem(View view) {
-            super(view);
-            // TODO: set ids from the views
-        }
-    }
+    public static class ClientItem extends CombinedHistoryItem {
+        final TextView nameView;
+        final ImageView deviceTypeView;
+        final TextView lastModifiedView;
 
-    public static class ClientItem extends CombinedHistoryItem {
         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);
         }
 
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java
@@ -1,14 +1,154 @@
 /* -*- 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.Iterator;
+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();
+        mRecyclerView.setAdapter(mAdapter);
+//            mRangeList = (HomeListView) view.findViewById(R.id.range_list);
+//            mList.setTag(HomePager.LIST_TAG_HISTORY);
+        mClearHistoryButton = view.findViewById(R.id.clear_history_button);
+        // TODO: link up click handler for clear history button
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        mCursorLoaderCallbacks = new CursorLoaderCallbacks();
+    }
 
     @Override
     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
+//            updateRecentSectionOffset(getContext());
+//            MostRecentSectionRange mostRecentSectionRange = recentSectionTimeOffsetList.get(selected.ordinal());
+//            return mDB.getRecentHistoryBetweenTime(cr, HISTORY_LIMIT, mostRecentSectionRange.start, mostRecentSectionRange.end);
+            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.replaceHistory(c);
+                    break;
+
+                case LOADER_ID_REMOTE:
+                    final List<RemoteClient> clients = mDB.getTabsAccessor().getClientsFromCursor(c);
+
+                    // Filter the hidden clients out of the clients list. The clients
+                    // list is updated in place; the hidden clients list is built
+                    // incrementally.
+                    // mHiddenClients.clear();
+                    final Iterator<RemoteClient> it = clients.iterator();
+                    while (it.hasNext()) {
+                        final RemoteClient client = it.next();
+                        // TODO: Handle hidden clients
+                        /*
+                        if (sState.isClientHidden(client.guid)) {
+                            it.remove();
+                            mHiddenClients.add(client);
+                        }
+                        */
+                    }
+                    mAdapter.replaceClients(clients);
+
+                    break;
+            }
+
+        }
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryRecyclerView.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryRecyclerView.java
@@ -35,16 +35,17 @@ public class CombinedHistoryRecyclerView
         layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
         // you can set the first visible item like this:
         layoutManager.scrollToPosition(0);
         setLayoutManager(layoutManager);
     }
 
     @Override
     public void onItemClicked(RecyclerView recyclerView, int position, View v) {
-
+        // TODO: handle clicks and open links, add telemetry
     }
 
     @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
@@ -36,16 +36,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;
 
@@ -1583,16 +1584,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;
     }
@@ -1623,16 +1625,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;
@@ -1651,16 +1654,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>