Bug 862794 - Implement Visited page in about:home (r=bnicholson)
authorLucas Rocha <lucasr@mozilla.com>
Tue, 25 Jun 2013 17:51:57 +0100
changeset 151336 3561c794b2e4f880411672a4d3fcab0f5582f537
parent 151335 5e477b9520a28f19e18ef24576b7af0186fa4d44
child 151337 dff89eb24573650c47235051a3a93692a69a19d5
push idunknown
push userunknown
push dateunknown
reviewersbnicholson
bugs862794
milestone24.0a1
Bug 862794 - Implement Visited page in about:home (r=bnicholson)
mobile/android/base/Makefile.in
mobile/android/base/home/HomePager.java
mobile/android/base/home/VisitedPage.java
mobile/android/base/locales/en-US/android_strings.dtd
mobile/android/base/strings.xml.in
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -221,16 +221,17 @@ FENNEC_JAVA_FILES = \
   home/HomeListView.java \
   home/HomePager.java \
   home/HomePagerTabStrip.java \
   home/FadedTextView.java \
   home/FaviconsLoader.java \
   home/TopBookmarkItemView.java \
   home/TopBookmarksView.java \
   home/TwoLinePageRow.java \
+  home/VisitedPage.java \
   menu/GeckoMenu.java \
   menu/GeckoMenuInflater.java \
   menu/GeckoMenuItem.java \
   menu/GeckoSubMenu.java \
   menu/MenuItemActionBar.java \
   menu/MenuItemDefault.java \
   menu/MenuPanel.java \
   menu/MenuPopup.java \
--- a/mobile/android/base/home/HomePager.java
+++ b/mobile/android/base/home/HomePager.java
@@ -22,16 +22,17 @@ import java.util.EnumMap;
 import java.util.EnumSet;
 
 public class HomePager extends ViewPager {
     private final Context mContext;
     private volatile boolean mLoaded;
 
     // List of pages in order.
     private enum Page {
+        VISITED,
         BOOKMARKS
     }
 
     private EnumMap<Page, Fragment> mPages = new EnumMap<Page, Fragment>(Page.class);
 
     public interface OnUrlOpenListener {
         public void onUrlOpen(String url);
     }
@@ -51,16 +52,17 @@ public class HomePager extends ViewPager
      *
      * @param fm FragmentManager for the adapter
      */
     public void show(FragmentManager fm) {
         mLoaded = true;
         TabsAdapter adapter = new TabsAdapter(fm);
 
         // Add the pages to the adapter in order.
+        adapter.addTab(Page.VISITED, VisitedPage.class, null, getContext().getString(R.string.visited_title));
         adapter.addTab(Page.BOOKMARKS, BookmarksPage.class, null, getContext().getString(R.string.bookmarks_title));
 
         setAdapter(adapter);
         setVisibility(VISIBLE);
     }
 
     /**
      * Hides the pager and removes all child fragments.
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/home/VisitedPage.java
@@ -0,0 +1,223 @@
+/* -*- 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 org.mozilla.gecko.R;
+import org.mozilla.gecko.SimpleCursorLoader;
+import org.mozilla.gecko.db.BrowserDB;
+import org.mozilla.gecko.db.BrowserDB.URLColumns;
+import org.mozilla.gecko.gfx.BitmapUtils;
+import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
+import org.mozilla.gecko.home.TwoLinePageRow;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.app.LoaderManager.LoaderCallbacks;
+import android.support.v4.content.Loader;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.LayoutInflater;
+import android.widget.AdapterView;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+
+/**
+ * Fragment that displays frecency search results in a ListView.
+ */
+public class VisitedPage extends HomeFragment {
+    // Logging tag name
+    private static final String LOGTAG = "GeckoVisitedPage";
+
+    // Cursor loader ID for search query
+    private static final int FRECENCY_LOADER_ID = 0;
+
+    // Cursor loader ID for favicons query
+    private static final int FAVICONS_LOADER_ID = 1;
+
+    // Adapter for the list of search results
+    private VisitedAdapter mAdapter;
+
+    // The view shown by the fragment.
+    private ListView mList;
+
+    // Callbacks used for the search and favicon cursor loaders
+    private CursorLoaderCallbacks mCursorLoaderCallbacks;
+
+    // Inflater used by the adapter
+    private LayoutInflater mInflater;
+
+    // On URL open listener
+    private OnUrlOpenListener mUrlOpenListener;
+
+    public VisitedPage() {
+        mUrlOpenListener = null;
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+
+        try {
+            mUrlOpenListener = (OnUrlOpenListener) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString()
+                    + " must implement HomePager.OnUrlOpenListener");
+        }
+
+        mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+
+        mInflater = null;
+        mUrlOpenListener = null;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        // All list views are styled to look the same with a global activity theme.
+        // If the style of the list changes, inflate it from an XML.
+        mList = new HomeListView(container.getContext());
+        return mList;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
+        mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                final Cursor c = mAdapter.getCursor();
+                if (c == null || !c.moveToPosition(position)) {
+                    return;
+                }
+
+                final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
+                mUrlOpenListener.onUrlOpen(url);
+            }
+        });
+
+        registerForContextMenu(mList);
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mList = null;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        // Intialize the search adapter
+        mAdapter = new VisitedAdapter(getActivity());
+        mList.setAdapter(mAdapter);
+
+        // Create callbacks before the initial loader is started
+        mCursorLoaderCallbacks = new CursorLoaderCallbacks();
+
+        // Reconnect to the loader only if present
+        getLoaderManager().initLoader(FRECENCY_LOADER_ID, null, mCursorLoaderCallbacks);
+    }
+
+    private static class FrecencyCursorLoader extends SimpleCursorLoader {
+        // Max number of search results
+        private static final int SEARCH_LIMIT = 50;
+
+        public FrecencyCursorLoader(Context context) {
+            super(context);
+        }
+
+        @Override
+        public Cursor loadCursor() {
+            final ContentResolver cr = getContext().getContentResolver();
+            return BrowserDB.filter(cr, "", SEARCH_LIMIT);
+        }
+    }
+
+    private class VisitedAdapter extends SimpleCursorAdapter {
+        public VisitedAdapter(Context context) {
+            super(context, -1, null, new String[] {}, new int[] {});
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            final TwoLinePageRow row;
+            if (convertView == null) {
+                row = (TwoLinePageRow) mInflater.inflate(R.layout.home_item_row, mList, false);
+            } else {
+                row = (TwoLinePageRow) convertView;
+            }
+
+            final Cursor c = getCursor();
+            if (!c.moveToPosition(position)) {
+                throw new IllegalStateException("Couldn't move cursor to position " + position);
+            }
+
+            row.updateFromCursor(c);
+
+            return row;
+        }
+    }
+
+    private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
+        @Override
+        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+            switch(id) {
+            case FRECENCY_LOADER_ID:
+                return new FrecencyCursorLoader(getActivity());
+
+            case FAVICONS_LOADER_ID:
+                return FaviconsLoader.createInstance(getActivity(), args);
+            }
+
+            return null;
+        }
+
+        @Override
+        public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
+            final int loaderId = loader.getId();
+            switch(loaderId) {
+            case FRECENCY_LOADER_ID:
+                mAdapter.swapCursor(c);
+
+                FaviconsLoader.restartFromCursor(getLoaderManager(), FAVICONS_LOADER_ID,
+                        mCursorLoaderCallbacks, c);
+                break;
+
+            case FAVICONS_LOADER_ID:
+                // Causes the listview to recreate its children and use the
+                // now in-memory favicons.
+                mAdapter.notifyDataSetChanged();
+                break;
+            }
+        }
+
+        @Override
+        public void onLoaderReset(Loader<Cursor> loader) {
+            final int loaderId = loader.getId();
+            switch(loaderId) {
+            case FRECENCY_LOADER_ID:
+                mAdapter.swapCursor(null);
+                break;
+
+            case FAVICONS_LOADER_ID:
+                // Do nothing
+                break;
+            }
+        }
+    }
+}
\ No newline at end of file
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -8,16 +8,17 @@
 
 <!ENTITY  no_space_to_start_error "There is not enough space available for &brandShortName; to start.">
 <!ENTITY  error_loading_file "An error occurred when trying to load files required to run &brandShortName;">
 
 <!ENTITY  all_pages_title "Top Sites">
 <!ENTITY  bookmarks_title "Bookmarks">
 <!ENTITY  history_title "History">
 <!ENTITY  switch_to_tab "Switch to tab">
+<!ENTITY  visited_title "Visited">
 
 <!ENTITY  crash_reporter_title "&brandShortName; Crash Reporter">
 <!ENTITY  crash_message2 "&brandShortName; had a problem and crashed. Your tabs should be listed on the &brandShortName; Start page when you restart.">
 <!ENTITY  crash_send_report_message3 "Tell &vendorShortName; about this crash so they can fix it">
 <!ENTITY  crash_include_url2 "Include the address of the page I was on">
 <!ENTITY  crash_sorry "We\'re sorry">
 <!ENTITY  crash_comment "Add a comment (comments are publicly visible)">
 <!ENTITY  crash_allow_contact2 "Allow &vendorShortName; to contact me about this report">
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -22,16 +22,17 @@
   <string name="splash_bookmarks_history">&splash_bookmarks_history;</string>
   <string name="no_space_to_start_error">&no_space_to_start_error;</string>
   <string name="error_loading_file">&error_loading_file;</string>
 
   <string name="all_pages_title">&all_pages_title;</string>
   <string name="bookmarks_title">&bookmarks_title;</string>
   <string name="history_title">&history_title;</string>
   <string name="switch_to_tab">&switch_to_tab;</string>
+  <string name="visited_title">&visited_title;</string>
 
   <string name="crash_reporter_title">&crash_reporter_title;</string>
   <string name="crash_message2">&crash_message2;</string>
   <string name="crash_send_report_message3">&crash_send_report_message3;</string>
   <string name="crash_include_url2">&crash_include_url2;</string>
   <string name="crash_sorry">&crash_sorry;</string>
   <string name="crash_comment">&crash_comment;</string>
   <string name="crash_allow_contact2">&crash_allow_contact2;</string>