Bug 862794 - Implement Visited page in about:home (r=bnicholson)
authorLucas Rocha <lucasr@mozilla.com>
Tue, 25 Jun 2013 17:51:57 +0100
changeset 143421 3561c794b2e4f880411672a4d3fcab0f5582f537
parent 143420 5e477b9520a28f19e18ef24576b7af0186fa4d44
child 143422 dff89eb24573650c47235051a3a93692a69a19d5
push id32723
push useremorley@mozilla.com
push dateWed, 21 Aug 2013 12:10:14 +0000
treeherdermozilla-inbound@ab6bc4d9e4c0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbnicholson
bugs862794
milestone24.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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>