diff options
Diffstat (limited to 'src/com/android/browser/BrowserHistoryPage.java')
-rw-r--r-- | src/com/android/browser/BrowserHistoryPage.java | 468 |
1 files changed, 468 insertions, 0 deletions
diff --git a/src/com/android/browser/BrowserHistoryPage.java b/src/com/android/browser/BrowserHistoryPage.java new file mode 100644 index 0000000..42ca848 --- /dev/null +++ b/src/com/android/browser/BrowserHistoryPage.java @@ -0,0 +1,468 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.app.Activity; +import android.app.ExpandableListActivity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.database.ContentObserver; +import android.database.Cursor; +import android.database.DataSetObserver; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.os.Handler; +import android.os.ServiceManager; +import android.provider.Browser; +import android.text.IClipboard; +import android.util.Log; +import android.view.ContextMenu; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.view.ContextMenu.ContextMenuInfo; +import android.webkit.DateSorter; +import android.webkit.WebIconDatabase.IconListener; +import android.widget.AdapterView; +import android.widget.ExpandableListAdapter; +import android.widget.ExpandableListView; +import android.widget.ExpandableListView.ExpandableListContextMenuInfo; +import android.widget.TextView; + +import java.util.List; +import java.util.Vector; + +/** + * Activity for displaying the browser's history, divided into + * days of viewing. + */ +public class BrowserHistoryPage extends ExpandableListActivity { + private HistoryAdapter mAdapter; + private DateSorter mDateSorter; + private boolean mMaxTabsOpen; + + private final static String LOGTAG = "browser"; + + // Implementation of WebIconDatabase.IconListener + private class IconReceiver implements IconListener { + public void onReceivedIcon(String url, Bitmap icon) { + setListAdapter(mAdapter); + } + } + // Instance of IconReceiver + private final IconReceiver mIconReceiver = new IconReceiver(); + + /** + * Report back to the calling activity to load a site. + * @param url Site to load. + * @param newWindow True if the URL should be loaded in a new window + */ + private void loadUrl(String url, boolean newWindow) { + Intent intent = new Intent().setAction(url); + if (newWindow) { + Bundle b = new Bundle(); + b.putBoolean("new_window", true); + intent.putExtras(b); + } + setResultToParent(RESULT_OK, intent); + finish(); + } + + private void copy(CharSequence text) { + try { + IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard")); + if (clip != null) { + clip.setClipboardText(text); + } + } catch (android.os.RemoteException e) { + Log.e(LOGTAG, "Copy failed", e); + } + } + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + setTitle(R.string.browser_history); + + mDateSorter = new DateSorter(this); + + mAdapter = new HistoryAdapter(); + setListAdapter(mAdapter); + final ExpandableListView list = getExpandableListView(); + list.setOnCreateContextMenuListener(this); + LayoutInflater factory = LayoutInflater.from(this); + View v = factory.inflate(R.layout.empty_history, null); + addContentView(v, new LayoutParams(LayoutParams.FILL_PARENT, + LayoutParams.FILL_PARENT)); + list.setEmptyView(v); + // Do not post the runnable if there is nothing in the list. + if (list.getExpandableListAdapter().getGroupCount() > 0) { + list.post(new Runnable() { + public void run() { + // In case the history gets cleared before this event + // happens. + if (list.getExpandableListAdapter().getGroupCount() > 0) { + list.expandGroup(0); + } + } + }); + } + mMaxTabsOpen = getIntent().getBooleanExtra("maxTabsOpen", false); + CombinedBookmarkHistoryActivity.getIconListenerSet(getContentResolver()) + .addListener(mIconReceiver); + + // initialize the result to canceled, so that if the user just presses + // back then it will have the correct result + setResultToParent(RESULT_CANCELED, null); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.history, menu); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + menu.findItem(R.id.clear_history_menu_id).setVisible(Browser.canClearHistory(this.getContentResolver())); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.clear_history_menu_id: + // FIXME: Need to clear the tab control in browserActivity + // as well + Browser.clearHistory(getContentResolver()); + mAdapter.refreshData(); + return true; + + default: + break; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + ExpandableListContextMenuInfo i = + (ExpandableListContextMenuInfo) menuInfo; + // Do not allow a context menu to come up from the group views. + if (!(i.targetView instanceof HistoryItem)) { + return; + } + + // Inflate the menu + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.historycontext, menu); + + // Setup the header + menu.setHeaderTitle(((HistoryItem)i.targetView).getUrl()); + + // Only show open in new tab if we have not maxed out available tabs + menu.findItem(R.id.new_window_context_menu_id).setVisible(!mMaxTabsOpen); + + // decide whether to show the share link option + PackageManager pm = getPackageManager(); + Intent send = new Intent(Intent.ACTION_SEND); + send.setType("text/plain"); + ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY); + menu.findItem(R.id.share_link_context_menu_id).setVisible(ri != null); + + super.onCreateContextMenu(menu, v, menuInfo); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + ExpandableListContextMenuInfo i = + (ExpandableListContextMenuInfo) item.getMenuInfo(); + String url = ((HistoryItem)i.targetView).getUrl(); + String title = ((HistoryItem)i.targetView).getName(); + switch (item.getItemId()) { + case R.id.open_context_menu_id: + loadUrl(url, false); + return true; + case R.id.new_window_context_menu_id: + loadUrl(url, true); + return true; + case R.id.save_to_bookmarks_menu_id: + Browser.saveBookmark(this, title, url); + return true; + case R.id.share_link_context_menu_id: + Browser.sendString(this, url); + return true; + case R.id.copy_context_menu_id: + copy(url); + return true; + case R.id.delete_context_menu_id: + Browser.deleteFromHistory(getContentResolver(), url); + mAdapter.refreshData(); + return true; + default: + break; + } + return super.onContextItemSelected(item); + } + + @Override + public boolean onChildClick(ExpandableListView parent, View v, + int groupPosition, int childPosition, long id) { + if (v instanceof HistoryItem) { + loadUrl(((HistoryItem) v).getUrl(), false); + return true; + } + return false; + } + + // This Activity is generally a sub-Activity of CombinedHistoryActivity. In + // that situation, we need to pass our result code up to our parent. + // However, if someone calls this Activity directly, then this has no + // parent, and it needs to set it on itself. + private void setResultToParent(int resultCode, Intent data) { + Activity a = getParent() == null ? this : getParent(); + a.setResult(resultCode, data); + } + + private class ChangeObserver extends ContentObserver { + public ChangeObserver() { + super(new Handler()); + } + + @Override + public boolean deliverSelfNotifications() { + return true; + } + + @Override + public void onChange(boolean selfChange) { + mAdapter.refreshData(); + } + } + + private class HistoryAdapter implements ExpandableListAdapter { + + // Array for each of our bins. Each entry represents how many items are + // in that bin. + int mItemMap[]; + // This is our GroupCount. We will have at most DateSorter.DAY_COUNT + // bins, less if the user has no items in one or more bins. + int mNumberOfBins; + Vector<DataSetObserver> mObservers; + Cursor mCursor; + + HistoryAdapter() { + mObservers = new Vector<DataSetObserver>(); + + String whereClause = Browser.BookmarkColumns.VISITS + " > 0 "; + String orderBy = Browser.BookmarkColumns.DATE + " DESC"; + + mCursor = managedQuery( + Browser.BOOKMARKS_URI, + Browser.HISTORY_PROJECTION, + whereClause, null, orderBy); + + buildMap(); + mCursor.registerContentObserver(new ChangeObserver()); + } + + void refreshData() { + mCursor.requery(); + buildMap(); + for (DataSetObserver o : mObservers) { + o.onChanged(); + } + } + + private void buildMap() { + // The cursor is sorted by date + // The ItemMap will store the number of items in each bin. + int array[] = new int[DateSorter.DAY_COUNT]; + // Zero out the array. + for (int j = 0; j < DateSorter.DAY_COUNT; j++) { + array[j] = 0; + } + mNumberOfBins = 0; + int dateIndex = -1; + if (mCursor.moveToFirst() && mCursor.getCount() > 0) { + while (!mCursor.isAfterLast()) { + long date = mCursor.getLong(Browser.HISTORY_PROJECTION_DATE_INDEX); + int index = mDateSorter.getIndex(date); + if (index > dateIndex) { + mNumberOfBins++; + if (index == DateSorter.DAY_COUNT - 1) { + // We are already in the last bin, so it will + // include all the remaining items + array[index] = mCursor.getCount() + - mCursor.getPosition(); + break; + } + dateIndex = index; + } + array[dateIndex]++; + mCursor.moveToNext(); + } + } + mItemMap = array; + } + + // This translates from a group position in the Adapter to a position in + // our array. This is necessary because some positions in the array + // have no history items, so we simply do not present those positions + // to the Adapter. + private int groupPositionToArrayPosition(int groupPosition) { + if (groupPosition < 0 || groupPosition >= DateSorter.DAY_COUNT) { + throw new AssertionError("group position out of range"); + } + if (DateSorter.DAY_COUNT == mNumberOfBins || 0 == mNumberOfBins) { + // In the first case, we have exactly the same number of bins + // as our maximum possible, so there is no need to do a + // conversion + // The second statement is in case this method gets called when + // the array is empty, in which case the provided groupPosition + // will do fine. + return groupPosition; + } + int arrayPosition = -1; + while (groupPosition > -1) { + arrayPosition++; + if (mItemMap[arrayPosition] != 0) { + groupPosition--; + } + } + return arrayPosition; + } + + public View getChildView(int groupPosition, int childPosition, boolean isLastChild, + View convertView, ViewGroup parent) { + groupPosition = groupPositionToArrayPosition(groupPosition); + HistoryItem item; + if (null == convertView || !(convertView instanceof HistoryItem)) { + item = new HistoryItem(BrowserHistoryPage.this); + // Add padding on the left so it will be indented from the + // arrows on the group views. + item.setPadding(item.getPaddingLeft() + 10, + item.getPaddingTop(), + item.getPaddingRight(), + item.getPaddingBottom()); + } else { + item = (HistoryItem) convertView; + } + int index = childPosition; + for (int i = 0; i < groupPosition; i++) { + index += mItemMap[i]; + } + mCursor.moveToPosition(index); + item.setName(mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX)); + String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX); + item.setUrl(url); + item.setFavicon(CombinedBookmarkHistoryActivity.getIconListenerSet( + getContentResolver()).getFavicon(url)); + item.setIsBookmark(1 == + mCursor.getInt(Browser.HISTORY_PROJECTION_BOOKMARK_INDEX)); + return item; + } + + public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { + groupPosition = groupPositionToArrayPosition(groupPosition); + TextView item; + if (null == convertView || !(convertView instanceof TextView)) { + LayoutInflater factory = + LayoutInflater.from(BrowserHistoryPage.this); + item = (TextView) + factory.inflate(R.layout.history_header, null); + } else { + item = (TextView) convertView; + } + item.setText(mDateSorter.getLabel(groupPosition)); + return item; + } + + public boolean areAllItemsEnabled() { + return true; + } + + public boolean isChildSelectable(int groupPosition, int childPosition) { + return true; + } + + public int getGroupCount() { + return mNumberOfBins; + } + + public int getChildrenCount(int groupPosition) { + return mItemMap[groupPositionToArrayPosition(groupPosition)]; + } + + public Object getGroup(int groupPosition) { + return null; + } + + public Object getChild(int groupPosition, int childPosition) { + return null; + } + + public long getGroupId(int groupPosition) { + return groupPosition; + } + + public long getChildId(int groupPosition, int childPosition) { + return (childPosition << 3) + groupPosition; + } + + public boolean hasStableIds() { + return true; + } + + public void registerDataSetObserver(DataSetObserver observer) { + mObservers.add(observer); + } + + public void unregisterDataSetObserver(DataSetObserver observer) { + mObservers.remove(observer); + } + + public void onGroupExpanded(int groupPosition) { + + } + + public void onGroupCollapsed(int groupPosition) { + + } + + public long getCombinedChildId(long groupId, long childId) { + return childId; + } + + public long getCombinedGroupId(long groupId) { + return groupId; + } + + public boolean isEmpty() { + return mCursor.getCount() == 0; + } + } +} |