mobile/android/base/home/MostRecentPanel.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
     2  * This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 package org.mozilla.gecko.home;
     8 import java.util.Date;
     9 import java.util.EnumSet;
    11 import org.mozilla.gecko.R;
    12 import org.mozilla.gecko.Telemetry;
    13 import org.mozilla.gecko.TelemetryContract;
    14 import org.mozilla.gecko.db.BrowserContract.Combined;
    15 import org.mozilla.gecko.db.BrowserDB;
    16 import org.mozilla.gecko.db.BrowserDB.URLColumns;
    17 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
    19 import android.app.Activity;
    20 import android.content.ContentResolver;
    21 import android.content.Context;
    22 import android.database.Cursor;
    23 import android.os.Bundle;
    24 import android.support.v4.app.LoaderManager.LoaderCallbacks;
    25 import android.support.v4.content.Loader;
    26 import android.util.SparseArray;
    27 import android.view.LayoutInflater;
    28 import android.view.View;
    29 import android.view.ViewGroup;
    30 import android.view.ViewStub;
    31 import android.widget.AdapterView;
    32 import android.widget.ImageView;
    33 import android.widget.TextView;
    35 /**
    36  * Fragment that displays recent history in a ListView.
    37  */
    38 public class MostRecentPanel extends HomeFragment {
    39     // Logging tag name
    40     private static final String LOGTAG = "GeckoMostRecentPanel";
    42     // Cursor loader ID for history query
    43     private static final int LOADER_ID_HISTORY = 0;
    45     // Adapter for the list of search results
    46     private MostRecentAdapter mAdapter;
    48     // The view shown by the fragment.
    49     private HomeListView mList;
    51     // Reference to the View to display when there are no results.
    52     private View mEmptyView;
    54     // Callbacks used for the search and favicon cursor loaders
    55     private CursorLoaderCallbacks mCursorLoaderCallbacks;
    57     // On URL open listener
    58     private OnUrlOpenListener mUrlOpenListener;
    60     public static MostRecentPanel newInstance() {
    61         return new MostRecentPanel();
    62     }
    64     public MostRecentPanel() {
    65         mUrlOpenListener = null;
    66     }
    68     @Override
    69     public void onAttach(Activity activity) {
    70         super.onAttach(activity);
    72         try {
    73             mUrlOpenListener = (OnUrlOpenListener) activity;
    74         } catch (ClassCastException e) {
    75             throw new ClassCastException(activity.toString()
    76                     + " must implement HomePager.OnUrlOpenListener");
    77         }
    78     }
    80     @Override
    81     public void onDetach() {
    82         super.onDetach();
    83         mUrlOpenListener = null;
    84     }
    86     @Override
    87     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    88         return inflater.inflate(R.layout.home_most_recent_panel, container, false);
    89     }
    91     @Override
    92     public void onViewCreated(View view, Bundle savedInstanceState) {
    93         mList = (HomeListView) view.findViewById(R.id.list);
    94         mList.setTag(HomePager.LIST_TAG_MOST_RECENT);
    96         mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    97             @Override
    98             public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    99                 position -= mAdapter.getMostRecentSectionsCountBefore(position);
   100                 final Cursor c = mAdapter.getCursor(position);
   101                 final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
   103                 Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL);
   105                 // This item is a TwoLinePageRow, so we allow switch-to-tab.
   106                 mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
   107             }
   108         });
   110         mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
   111             @Override
   112             public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
   113                 final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
   114                 info.url = cursor.getString(cursor.getColumnIndexOrThrow(Combined.URL));
   115                 info.title = cursor.getString(cursor.getColumnIndexOrThrow(Combined.TITLE));
   116                 info.display = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.DISPLAY));
   117                 info.historyId = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.HISTORY_ID));
   118                 final int bookmarkIdCol = cursor.getColumnIndexOrThrow(Combined.BOOKMARK_ID);
   119                 if (cursor.isNull(bookmarkIdCol)) {
   120                     // If this is a combined cursor, we may get a history item without a
   121                     // bookmark, in which case the bookmarks ID column value will be null.
   122                     info.bookmarkId =  -1;
   123                 } else {
   124                     info.bookmarkId = cursor.getInt(bookmarkIdCol);
   125                 }
   126                 return info;
   127             }
   128         });
   129         registerForContextMenu(mList);
   130     }
   132     @Override
   133     public void onDestroyView() {
   134         super.onDestroyView();
   135         mList = null;
   136         mEmptyView = null;
   137     }
   139     @Override
   140     public void onActivityCreated(Bundle savedInstanceState) {
   141         super.onActivityCreated(savedInstanceState);
   143         // Intialize adapter
   144         mAdapter = new MostRecentAdapter(getActivity());
   145         mList.setAdapter(mAdapter);
   147         // Create callbacks before the initial loader is started
   148         mCursorLoaderCallbacks = new CursorLoaderCallbacks();
   149         loadIfVisible();
   150     }
   152     @Override
   153     protected void load() {
   154         getLoaderManager().initLoader(LOADER_ID_HISTORY, null, mCursorLoaderCallbacks);
   155     }
   157     private static class MostRecentCursorLoader extends SimpleCursorLoader {
   158         // Max number of history results
   159         private static final int HISTORY_LIMIT = 100;
   161         public MostRecentCursorLoader(Context context) {
   162             super(context);
   163         }
   165         @Override
   166         public Cursor loadCursor() {
   167             final ContentResolver cr = getContext().getContentResolver();
   168             return BrowserDB.getRecentHistory(cr, HISTORY_LIMIT);
   169         }
   170     }
   172     private void updateUiFromCursor(Cursor c) {
   173         if (c != null && c.getCount() > 0) {
   174             return;
   175         }
   177         // Cursor is empty, so set the empty view if it hasn't been set already.
   178         if (mEmptyView == null) {
   179             // Set empty panel view. We delay this so that the empty view won't flash.
   180             final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
   181             mEmptyView = emptyViewStub.inflate();
   183             final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
   184             emptyIcon.setImageResource(R.drawable.icon_most_recent_empty);
   186             final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text);
   187             emptyText.setText(R.string.home_most_recent_empty);
   189             mList.setEmptyView(mEmptyView);
   190         }
   191     }
   193     private static class MostRecentAdapter extends MultiTypeCursorAdapter {
   194         private static final int ROW_HEADER = 0;
   195         private static final int ROW_STANDARD = 1;
   197         private static final int[] VIEW_TYPES = new int[] { ROW_STANDARD, ROW_HEADER };
   198         private static final int[] LAYOUT_TYPES = new int[] { R.layout.home_item_row, R.layout.home_header_row };
   200         // For the time sections in history
   201         private static final long MS_PER_DAY = 86400000;
   202         private static final long MS_PER_WEEK = MS_PER_DAY * 7;
   204         // The time ranges for each section
   205         private static enum MostRecentSection {
   206             TODAY,
   207             YESTERDAY,
   208             WEEK,
   209             OLDER
   210         };
   212         private final Context mContext;
   214         // Maps headers in the list with their respective sections
   215         private final SparseArray<MostRecentSection> mMostRecentSections;
   217         public MostRecentAdapter(Context context) {
   218             super(context, null, VIEW_TYPES, LAYOUT_TYPES);
   220             mContext = context;
   222             // Initialize map of history sections
   223             mMostRecentSections = new SparseArray<MostRecentSection>();
   224         }
   226         @Override
   227         public Object getItem(int position) {
   228             final int type = getItemViewType(position);
   230             // Header items are not in the cursor
   231             if (type == ROW_HEADER) {
   232                 return null;
   233             }
   235             return super.getItem(position - getMostRecentSectionsCountBefore(position));
   236         }
   238         @Override
   239         public int getItemViewType(int position) {
   240             if (mMostRecentSections.get(position) != null) {
   241                 return ROW_HEADER;
   242             }
   244             return ROW_STANDARD;
   245         }
   247         @Override
   248         public boolean isEnabled(int position) {
   249             return (getItemViewType(position) == ROW_STANDARD);
   250         }
   252         @Override
   253         public int getCount() {
   254             // Add the history section headers to the number of reported results.
   255             return super.getCount() + mMostRecentSections.size();
   256         }
   258         @Override
   259         public Cursor swapCursor(Cursor cursor) {
   260             loadMostRecentSections(cursor);
   261             Cursor oldCursor = super.swapCursor(cursor);
   262             return oldCursor;
   263         }
   265         @Override
   266         public void bindView(View view, Context context, int position) {
   267             final int type = getItemViewType(position);
   269             if (type == ROW_HEADER) {
   270                 final MostRecentSection section = mMostRecentSections.get(position);
   271                 final TextView row = (TextView) view;
   272                 row.setText(getMostRecentSectionTitle(section));
   273             } else {
   274                 // Account for the most recent section headers
   275                 position -= getMostRecentSectionsCountBefore(position);
   276                 final Cursor c = getCursor(position);
   277                 final TwoLinePageRow row = (TwoLinePageRow) view;
   278                 row.updateFromCursor(c);
   279             }
   280         }
   282         private String getMostRecentSectionTitle(MostRecentSection section) {
   283             switch (section) {
   284             case TODAY:
   285                 return mContext.getString(R.string.history_today_section);
   286             case YESTERDAY:
   287                 return mContext.getString(R.string.history_yesterday_section);
   288             case WEEK:
   289                 return mContext.getString(R.string.history_week_section);
   290             case OLDER:
   291                 return mContext.getString(R.string.history_older_section);
   292             }
   294             throw new IllegalStateException("Unrecognized history section");
   295         }
   297         private int getMostRecentSectionsCountBefore(int position) {
   298             // Account for the number headers before the given position
   299             int sectionsBefore = 0;
   301             final int historySectionsCount = mMostRecentSections.size();
   302             for (int i = 0; i < historySectionsCount; i++) {
   303                 final int sectionPosition = mMostRecentSections.keyAt(i);
   304                 if (sectionPosition > position) {
   305                     break;
   306                 }
   308                 sectionsBefore++;
   309             }
   311             return sectionsBefore;
   312         }
   314         private static MostRecentSection getMostRecentSectionForTime(long from, long time) {
   315             long delta = from - time;
   317             if (delta < 0) {
   318                 return MostRecentSection.TODAY;
   319             }
   321             if (delta < MS_PER_DAY) {
   322                 return MostRecentSection.YESTERDAY;
   323             }
   325             if (delta < MS_PER_WEEK) {
   326                 return MostRecentSection.WEEK;
   327             }
   329             return MostRecentSection.OLDER;
   330         }
   332         private void loadMostRecentSections(Cursor c) {
   333             // Clear any history sections that may have been loaded before.
   334             mMostRecentSections.clear();
   336             if (c == null || !c.moveToFirst()) {
   337                 return;
   338             }
   340             final Date now = new Date();
   341             now.setHours(0);
   342             now.setMinutes(0);
   343             now.setSeconds(0);
   345             final long today = now.getTime();
   346             MostRecentSection section = null;
   348             do {
   349                 final int position = c.getPosition();
   350                 final long time = c.getLong(c.getColumnIndexOrThrow(URLColumns.DATE_LAST_VISITED));
   351                 final MostRecentSection itemSection = MostRecentAdapter.getMostRecentSectionForTime(today, time);
   353                 if (section != itemSection) {
   354                     section = itemSection;
   355                     mMostRecentSections.append(position + mMostRecentSections.size(), section);
   356                 }
   358                 // Reached the last section, no need to continue
   359                 if (section == MostRecentSection.OLDER) {
   360                     break;
   361                 }
   362             } while (c.moveToNext());
   363         }
   364     }
   366     private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
   367         @Override
   368         public Loader<Cursor> onCreateLoader(int id, Bundle args) {
   369             return new MostRecentCursorLoader(getActivity());
   370         }
   372         @Override
   373         public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
   374             mAdapter.swapCursor(c);
   375             updateUiFromCursor(c);
   376         }
   378         @Override
   379         public void onLoaderReset(Loader<Cursor> loader) {
   380             mAdapter.swapCursor(null);
   381         }
   382     }
   383 }

mercurial