/* -*- 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; import org.mozilla.gecko.AwesomeBar.ContextMenuSubject; import org.mozilla.gecko.db.BrowserContract.Combined; import org.mozilla.gecko.db.BrowserDB; import org.mozilla.gecko.db.BrowserDB.URLColumns; import org.mozilla.gecko.gfx.BitmapUtils; import org.mozilla.gecko.util.GamepadUtils; import org.mozilla.gecko.util.ThreadUtils; import org.mozilla.gecko.widget.FaviconView; import android.app.Activity; import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.database.ContentObserver; import android.database.Cursor; import android.graphics.Bitmap; import android.os.AsyncTask; import android.text.TextUtils; import android.util.Log; import android.util.Pair; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.KeyEvent; import android.view.MenuInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ExpandableListView; import android.widget.ImageView; import android.widget.ListView; import android.widget.SimpleExpandableListAdapter; import android.widget.TextView; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; public class HistoryTab extends AwesomeBarTab { public static final String LOGTAG = "HISTORY_TAB"; public static final String TAG = "history"; private static enum HistorySection { TODAY, YESTERDAY, WEEK, OLDER }; private ContentObserver mContentObserver; private ContentResolver mContentResolver; private HistoryQueryTask mQueryTask = null; private HistoryListAdapter mCursorAdapter = null; public HistoryTab(Context context) { super(context); mContentObserver = null; } @Override public int getTitleStringId() { return R.string.awesomebar_history_title; } @Override public String getTag() { return TAG; } @Override public ListView getView() { if (mView == null) { mView = new ExpandableListView(mContext, null); ((Activity)mContext).registerForContextMenu(mView); mView.setTag(TAG); ExpandableListView list = (ExpandableListView)mView; list.setOnChildClickListener(new ExpandableListView.OnChildClickListener() { @Override public boolean onChildClick(ExpandableListView parent, View view, int groupPosition, int childPosition, long id) { return handleItemClick(groupPosition, childPosition); } }); // This is to disallow collapsing the expandable groups in the // history expandable list view to mimic simpler sections. We should // Remove this if we decide to allow expanding/collapsing groups. list.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() { @Override public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) { return true; } }); list.setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { if (GamepadUtils.isActionKeyDown(event)) { ExpandableListView expando = (ExpandableListView)v; long selected = expando.getSelectedPosition(); switch (ExpandableListView.getPackedPositionType(selected)) { case ExpandableListView.PACKED_POSITION_TYPE_CHILD: return handleItemClick(ExpandableListView.getPackedPositionGroup(selected), ExpandableListView.getPackedPositionChild(selected)); case ExpandableListView.PACKED_POSITION_TYPE_GROUP: int group = ExpandableListView.getPackedPositionGroup(selected); return (expando.isGroupExpanded(group) ? expando.collapseGroup(group) : expando.expandGroup(group)); } } return false; } }); mView.setOnTouchListener(mListListener); // We need to add the header before we set the adapter, hence make it null list.setAdapter(getCursorAdapter()); HistoryQueryTask task = new HistoryQueryTask(); task.execute(); } return (ListView)mView; } @Override public void destroy() { super.destroy(); if (mContentObserver != null) BrowserDB.unregisterContentObserver(getContentResolver(), mContentObserver); } @Override public boolean onBackPressed() { // If the soft keyboard is visible in the bookmarks or history tab, the user // must have explictly brought it up, so we should try hiding it instead of // exiting the activity or going up a bookmarks folder level. View view = getView(); if (hideSoftInput(view)) return true; return false; } protected HistoryListAdapter getCursorAdapter() { return mCursorAdapter; } private class HistoryListAdapter extends SimpleExpandableListAdapter { public HistoryListAdapter(Context context, List> groupData, int groupLayout, String[] groupFrom, int[] groupTo, List>> childData) { super(context, groupData, groupLayout, groupFrom, groupTo, childData, -1, new String[] {}, new int[] {}); } @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { AwesomeEntryViewHolder viewHolder = null; if (convertView == null) { convertView = getInflater().inflate(R.layout.awesomebar_row, null); viewHolder = new AwesomeEntryViewHolder(); viewHolder.titleView = (TextView) convertView.findViewById(R.id.title); viewHolder.urlView = (TextView) convertView.findViewById(R.id.url); viewHolder.faviconView = (FaviconView) convertView.findViewById(R.id.favicon); viewHolder.bookmarkIconView = (ImageView) convertView.findViewById(R.id.bookmark_icon); convertView.setTag(viewHolder); } else { viewHolder = (AwesomeEntryViewHolder) convertView.getTag(); } HistoryListAdapter adapter = getCursorAdapter(); if (adapter == null) return null; @SuppressWarnings("unchecked") Map historyItem = (Map) adapter.getChild(groupPosition, childPosition); String title = (String) historyItem.get(URLColumns.TITLE); String url = (String) historyItem.get(URLColumns.URL); updateTitle(viewHolder.titleView, title, url); updateUrl(viewHolder, url); byte[] b = (byte[]) historyItem.get(URLColumns.FAVICON); Bitmap favicon = null; if (b != null) { Bitmap bitmap = BitmapUtils.decodeByteArray(b); if (bitmap != null) { favicon = Favicons.getInstance().scaleImage(bitmap); } } updateFavicon(viewHolder.faviconView, favicon, url); Integer bookmarkId = (Integer) historyItem.get(Combined.BOOKMARK_ID); Integer display = (Integer) historyItem.get(Combined.DISPLAY); // The bookmark id will be 0 (null in database) when the url // is not a bookmark. Reading list items are irrelevant in history // tab. We should never show any sign or them. int visibility = (bookmarkId != 0 && display != Combined.DISPLAY_READER ? View.VISIBLE : View.GONE); viewHolder.bookmarkIconView.setVisibility(visibility); viewHolder.bookmarkIconView.setImageResource(R.drawable.ic_awesomebar_star); return convertView; } } private static class GroupList extends LinkedList> { private static final long serialVersionUID = 0L; } private static class ChildrenList extends LinkedList> { private static final long serialVersionUID = 0L; } private class HistoryQueryTask extends AsyncTask>> { private static final long MS_PER_DAY = 86400000; private static final long MS_PER_WEEK = MS_PER_DAY * 7; @Override protected Pair> doInBackground(Void... arg0) { Cursor cursor = BrowserDB.getRecentHistory(getContentResolver(), MAX_RESULTS); Date now = new Date(); now.setHours(0); now.setMinutes(0); now.setSeconds(0); long today = now.getTime(); // Split the list of urls into separate date range groups // and show it in an expandable list view. List childrenLists = new LinkedList(); ChildrenList children = null; GroupList groups = new GroupList(); HistorySection section = null; // Move cursor before the first row in preparation // for the iteration. cursor.moveToPosition(-1); // Split the history query results into adapters per time // section (today, yesterday, week, older). Queries on content // Browser content provider don't support limitting the number // of returned rows so we limit it here. while (cursor.moveToNext()) { long time = cursor.getLong(cursor.getColumnIndexOrThrow(URLColumns.DATE_LAST_VISITED)); HistorySection itemSection = getSectionForTime(time, today); if (section != itemSection) { if (section != null) { groups.add(createGroupItem(section)); childrenLists.add(children); } section = itemSection; children = new ChildrenList(); } children.add(createHistoryItem(cursor)); } // Add any remaining section to the list if it hasn't // been added to the list after the loop. if (section != null && children != null) { groups.add(createGroupItem(section)); childrenLists.add(children); } // Close the query cursor as we won't use it anymore cursor.close(); // groups and childrenLists will be empty lists if there's no history return Pair.>create(groups, childrenLists); } public Map createHistoryItem(Cursor cursor) { Map historyItem = new HashMap(); String url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL)); String title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE)); byte[] favicon = cursor.getBlob(cursor.getColumnIndexOrThrow(URLColumns.FAVICON)); Integer bookmarkId = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.BOOKMARK_ID)); Integer historyId = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.HISTORY_ID)); Integer display = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.DISPLAY)); // Use the URL instead of an empty title for consistency with the normal URL // bar view - this is the equivalent of getDisplayTitle() in Tab.java if (title == null || title.length() == 0) title = url; historyItem.put(URLColumns.URL, url); historyItem.put(URLColumns.TITLE, title); if (favicon != null) historyItem.put(URLColumns.FAVICON, favicon); historyItem.put(Combined.BOOKMARK_ID, bookmarkId); historyItem.put(Combined.HISTORY_ID, historyId); historyItem.put(Combined.DISPLAY, display); return historyItem; } public Map createGroupItem(HistorySection section) { Map groupItem = new HashMap(); groupItem.put(URLColumns.TITLE, getSectionName(section)); return groupItem; } private String getSectionName(HistorySection section) { Resources resources = mContext.getResources(); switch (section) { case TODAY: return resources.getString(R.string.history_today_section); case YESTERDAY: return resources.getString(R.string.history_yesterday_section); case WEEK: return resources.getString(R.string.history_week_section); case OLDER: return resources.getString(R.string.history_older_section); } return null; } private void expandAllGroups(ExpandableListView historyList) { int groupCount = mCursorAdapter.getGroupCount(); for (int i = 0; i < groupCount; i++) { historyList.expandGroup(i); } } private HistorySection getSectionForTime(long time, long today) { long delta = today - time; if (delta < 0) { return HistorySection.TODAY; } if (delta < MS_PER_DAY) { return HistorySection.YESTERDAY; } if (delta < MS_PER_WEEK) { return HistorySection.WEEK; } return HistorySection.OLDER; } @Override protected void onPostExecute(Pair> result) { mCursorAdapter = new HistoryListAdapter( mContext, result.first, R.layout.awesomebar_header_row, new String[] { URLColumns.TITLE }, new int[] { R.id.title }, result.second ); if (mContentObserver == null) { // Register an observer to update the history tab contents if they change. mContentObserver = new ContentObserver(ThreadUtils.getBackgroundHandler()) { @Override public void onChange(boolean selfChange) { mQueryTask = new HistoryQueryTask(); mQueryTask.execute(); } }; BrowserDB.registerHistoryObserver(getContentResolver(), mContentObserver); } final ExpandableListView historyList = (ExpandableListView)getView(); // Hack: force this to the main thread, even though it should already be on it ThreadUtils.postToUiThread(new Runnable() { @Override public void run() { historyList.setAdapter(mCursorAdapter); expandAllGroups(historyList); } }); mQueryTask = null; } } public boolean handleItemClick(int groupPosition, int childPosition) { HistoryListAdapter adapter = getCursorAdapter(); if (adapter == null) return false; @SuppressWarnings("unchecked") Map historyItem = (Map) adapter.getChild(groupPosition, childPosition); String url = (String) historyItem.get(URLColumns.URL); String title = (String) historyItem.get(URLColumns.TITLE); sendToListener(url, title); return true; } @Override public ContextMenuSubject getSubject(ContextMenu menu, View view, ContextMenuInfo menuInfo) { ContextMenuSubject subject = null; if (!(menuInfo instanceof ExpandableListView.ExpandableListContextMenuInfo)) { Log.e(LOGTAG, "menuInfo is not ExpandableListContextMenuInfo"); return subject; } ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) menuInfo; int childPosition = ExpandableListView.getPackedPositionChild(info.packedPosition); int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition); // Check if long tap is on a header row if (groupPosition < 0 || childPosition < 0) return subject; ExpandableListView exList = (ExpandableListView) view; // The history list is backed by a SimpleExpandableListAdapter @SuppressWarnings("rawtypes") Map map = (Map) exList.getExpandableListAdapter().getChild(groupPosition, childPosition); subject = new AwesomeBar.ContextMenuSubject((Integer) map.get(Combined.HISTORY_ID), (String) map.get(URLColumns.URL), (byte[]) map.get(URLColumns.FAVICON), (String) map.get(URLColumns.TITLE), null); setupMenu(menu, subject); menu.findItem(R.id.remove_bookmark).setVisible(false); menu.findItem(R.id.edit_bookmark).setVisible(false); menu.findItem(R.id.open_in_reader).setVisible(false); return subject; } }