| /* |
| * 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.AlertDialog; |
| import android.app.Dialog; |
| import android.app.Fragment; |
| import android.app.FragmentBreadCrumbs; |
| import android.app.LoaderManager.LoaderCallbacks; |
| import android.content.ClipboardManager; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.CursorLoader; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.content.Loader; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ResolveInfo; |
| import android.database.Cursor; |
| import android.database.DataSetObserver; |
| import android.graphics.BitmapFactory; |
| import android.graphics.drawable.Drawable; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.provider.Browser; |
| import android.provider.BrowserContract; |
| import android.provider.BrowserContract.Combined; |
| import android.view.ContextMenu; |
| import android.view.ContextMenu.ContextMenuInfo; |
| 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.ViewStub; |
| import android.widget.AbsListView; |
| import android.widget.AdapterView; |
| import android.widget.AdapterView.AdapterContextMenuInfo; |
| import android.widget.AdapterView.OnItemClickListener; |
| import android.widget.BaseAdapter; |
| import android.widget.ExpandableListView; |
| import android.widget.ExpandableListView.ExpandableListContextMenuInfo; |
| import android.widget.ExpandableListView.OnChildClickListener; |
| import android.widget.ListView; |
| import android.widget.TextView; |
| import android.widget.Toast; |
| |
| /** |
| * Activity for displaying the browser's history, divided into |
| * days of viewing. |
| */ |
| public class BrowserHistoryPage extends Fragment |
| implements LoaderCallbacks<Cursor>, OnChildClickListener { |
| |
| static final int LOADER_HISTORY = 1; |
| static final int LOADER_MOST_VISITED = 2; |
| |
| CombinedBookmarksCallbacks mCallback; |
| HistoryAdapter mAdapter; |
| HistoryChildWrapper mChildWrapper; |
| boolean mDisableNewWindow; |
| HistoryItem mContextHeader; |
| String mMostVisitsLimit; |
| ListView mGroupList, mChildList; |
| private ViewGroup mPrefsContainer; |
| private FragmentBreadCrumbs mFragmentBreadCrumbs; |
| private ExpandableListView mHistoryList; |
| |
| private View mRoot; |
| |
| static interface HistoryQuery { |
| static final String[] PROJECTION = new String[] { |
| Combined._ID, // 0 |
| Combined.DATE_LAST_VISITED, // 1 |
| Combined.TITLE, // 2 |
| Combined.URL, // 3 |
| Combined.FAVICON, // 4 |
| Combined.VISITS, // 5 |
| Combined.IS_BOOKMARK, // 6 |
| }; |
| |
| static final int INDEX_ID = 0; |
| static final int INDEX_DATE_LAST_VISITED = 1; |
| static final int INDEX_TITE = 2; |
| static final int INDEX_URL = 3; |
| static final int INDEX_FAVICON = 4; |
| static final int INDEX_VISITS = 5; |
| static final int INDEX_IS_BOOKMARK = 6; |
| } |
| |
| private void copy(CharSequence text) { |
| ClipboardManager cm = (ClipboardManager) getActivity().getSystemService( |
| Context.CLIPBOARD_SERVICE); |
| cm.setText(text); |
| } |
| |
| @Override |
| public Loader<Cursor> onCreateLoader(int id, Bundle args) { |
| Uri.Builder combinedBuilder = Combined.CONTENT_URI.buildUpon(); |
| |
| switch (id) { |
| case LOADER_HISTORY: { |
| String sort = Combined.DATE_LAST_VISITED + " DESC"; |
| String where = Combined.VISITS + " > 0"; |
| CursorLoader loader = new CursorLoader(getActivity(), combinedBuilder.build(), |
| HistoryQuery.PROJECTION, where, null, sort); |
| return loader; |
| } |
| |
| case LOADER_MOST_VISITED: { |
| Uri uri = combinedBuilder |
| .appendQueryParameter(BrowserContract.PARAM_LIMIT, mMostVisitsLimit) |
| .build(); |
| String where = Combined.VISITS + " > 0"; |
| CursorLoader loader = new CursorLoader(getActivity(), uri, |
| HistoryQuery.PROJECTION, where, null, Combined.VISITS + " DESC"); |
| return loader; |
| } |
| |
| default: { |
| throw new IllegalArgumentException(); |
| } |
| } |
| } |
| |
| void selectGroup(int position) { |
| mGroupItemClickListener.onItemClick(null, |
| mAdapter.getGroupView(position, false, null, null), |
| position, position); |
| } |
| |
| void checkIfEmpty() { |
| if (mAdapter.mMostVisited != null && mAdapter.mHistoryCursor != null) { |
| // Both cursors have loaded - check to see if we have data |
| if (mAdapter.isEmpty()) { |
| mRoot.findViewById(R.id.history).setVisibility(View.GONE); |
| mRoot.findViewById(android.R.id.empty).setVisibility(View.VISIBLE); |
| } else { |
| mRoot.findViewById(R.id.history).setVisibility(View.VISIBLE); |
| mRoot.findViewById(android.R.id.empty).setVisibility(View.GONE); |
| } |
| } |
| } |
| |
| @Override |
| public void onLoadFinished(Loader<Cursor> loader, Cursor data) { |
| switch (loader.getId()) { |
| case LOADER_HISTORY: { |
| mAdapter.changeCursor(data); |
| if (!mAdapter.isEmpty() && mGroupList != null |
| && mGroupList.getCheckedItemPosition() == ListView.INVALID_POSITION) { |
| selectGroup(0); |
| } |
| |
| checkIfEmpty(); |
| break; |
| } |
| |
| case LOADER_MOST_VISITED: { |
| mAdapter.changeMostVisitedCursor(data); |
| |
| checkIfEmpty(); |
| break; |
| } |
| |
| default: { |
| throw new IllegalArgumentException(); |
| } |
| } |
| } |
| |
| @Override |
| public void onLoaderReset(Loader<Cursor> loader) { |
| } |
| |
| @Override |
| public void onCreate(Bundle icicle) { |
| super.onCreate(icicle); |
| |
| setHasOptionsMenu(true); |
| |
| Bundle args = getArguments(); |
| mDisableNewWindow = args.getBoolean(BrowserBookmarksPage.EXTRA_DISABLE_WINDOW, false); |
| int mvlimit = getResources().getInteger(R.integer.most_visits_limit); |
| mMostVisitsLimit = Integer.toString(mvlimit); |
| mCallback = (CombinedBookmarksCallbacks) getActivity(); |
| } |
| |
| @Override |
| public View onCreateView(LayoutInflater inflater, ViewGroup container, |
| Bundle savedInstanceState) { |
| mRoot = inflater.inflate(R.layout.history, container, false); |
| mAdapter = new HistoryAdapter(getActivity()); |
| ViewStub stub = (ViewStub) mRoot.findViewById(R.id.pref_stub); |
| if (stub != null) { |
| inflateTwoPane(stub); |
| } else { |
| inflateSinglePane(); |
| } |
| |
| // Start the loaders |
| getLoaderManager().restartLoader(LOADER_HISTORY, null, this); |
| getLoaderManager().restartLoader(LOADER_MOST_VISITED, null, this); |
| |
| return mRoot; |
| } |
| |
| private void inflateSinglePane() { |
| mHistoryList = (ExpandableListView) mRoot.findViewById(R.id.history); |
| mHistoryList.setAdapter(mAdapter); |
| mHistoryList.setOnChildClickListener(this); |
| registerForContextMenu(mHistoryList); |
| } |
| |
| private void inflateTwoPane(ViewStub stub) { |
| stub.setLayoutResource(R.layout.preference_list_content); |
| stub.inflate(); |
| mGroupList = (ListView) mRoot.findViewById(android.R.id.list); |
| mPrefsContainer = (ViewGroup) mRoot.findViewById(R.id.prefs_frame); |
| mFragmentBreadCrumbs = (FragmentBreadCrumbs) mRoot.findViewById(android.R.id.title); |
| mFragmentBreadCrumbs.setMaxVisible(1); |
| mFragmentBreadCrumbs.setActivity(getActivity()); |
| mPrefsContainer.setVisibility(View.VISIBLE); |
| mGroupList.setAdapter(new HistoryGroupWrapper(mAdapter)); |
| mGroupList.setOnItemClickListener(mGroupItemClickListener); |
| mGroupList.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE); |
| mChildWrapper = new HistoryChildWrapper(mAdapter); |
| mChildList = new ListView(getActivity()); |
| mChildList.setAdapter(mChildWrapper); |
| mChildList.setOnItemClickListener(mChildItemClickListener); |
| registerForContextMenu(mChildList); |
| ViewGroup prefs = (ViewGroup) mRoot.findViewById(R.id.prefs); |
| prefs.addView(mChildList); |
| } |
| |
| private OnItemClickListener mGroupItemClickListener = new OnItemClickListener() { |
| @Override |
| public void onItemClick( |
| AdapterView<?> parent, View view, int position, long id) { |
| CharSequence title = ((TextView) view).getText(); |
| mFragmentBreadCrumbs.setTitle(title, title); |
| mChildWrapper.setSelectedGroup(position); |
| mGroupList.setItemChecked(position, true); |
| } |
| }; |
| |
| private OnItemClickListener mChildItemClickListener = new OnItemClickListener() { |
| @Override |
| public void onItemClick( |
| AdapterView<?> parent, View view, int position, long id) { |
| mCallback.openUrl(((HistoryItem) view).getUrl()); |
| } |
| }; |
| |
| @Override |
| public boolean onChildClick(ExpandableListView parent, View view, |
| int groupPosition, int childPosition, long id) { |
| mCallback.openUrl(((HistoryItem) view).getUrl()); |
| return true; |
| } |
| |
| @Override |
| public void onDestroy() { |
| super.onDestroy(); |
| getLoaderManager().destroyLoader(LOADER_HISTORY); |
| getLoaderManager().destroyLoader(LOADER_MOST_VISITED); |
| } |
| |
| @Override |
| public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { |
| super.onCreateOptionsMenu(menu, inflater); |
| inflater.inflate(R.menu.history, menu); |
| } |
| |
| void promptToClearHistory() { |
| final ContentResolver resolver = getActivity().getContentResolver(); |
| final ClearHistoryTask clear = new ClearHistoryTask(resolver); |
| AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) |
| .setMessage(R.string.pref_privacy_clear_history_dlg) |
| .setIconAttribute(android.R.attr.alertDialogIcon) |
| .setNegativeButton(R.string.cancel, null) |
| .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| if (which == DialogInterface.BUTTON_POSITIVE) { |
| clear.start(); |
| } |
| } |
| }); |
| final Dialog dialog = builder.create(); |
| dialog.show(); |
| } |
| |
| @Override |
| public boolean onOptionsItemSelected(MenuItem item) { |
| if (item.getItemId() == R.id.clear_history_menu_id) { |
| promptToClearHistory(); |
| return true; |
| } |
| return super.onOptionsItemSelected(item); |
| } |
| |
| static class ClearHistoryTask extends Thread { |
| ContentResolver mResolver; |
| |
| public ClearHistoryTask(ContentResolver resolver) { |
| mResolver = resolver; |
| } |
| |
| @Override |
| public void run() { |
| Browser.clearHistory(mResolver); |
| } |
| } |
| |
| View getTargetView(ContextMenuInfo menuInfo) { |
| if (menuInfo instanceof AdapterContextMenuInfo) { |
| return ((AdapterContextMenuInfo) menuInfo).targetView; |
| } |
| if (menuInfo instanceof ExpandableListContextMenuInfo) { |
| return ((ExpandableListContextMenuInfo) menuInfo).targetView; |
| } |
| return null; |
| } |
| |
| @Override |
| public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { |
| |
| View targetView = getTargetView(menuInfo); |
| if (!(targetView instanceof HistoryItem)) { |
| return; |
| } |
| HistoryItem historyItem = (HistoryItem) targetView; |
| |
| // Inflate the menu |
| Activity parent = getActivity(); |
| MenuInflater inflater = parent.getMenuInflater(); |
| inflater.inflate(R.menu.historycontext, menu); |
| |
| // Setup the header |
| if (mContextHeader == null) { |
| mContextHeader = new HistoryItem(parent, false); |
| mContextHeader.setEnableScrolling(true); |
| } else if (mContextHeader.getParent() != null) { |
| ((ViewGroup) mContextHeader.getParent()).removeView(mContextHeader); |
| } |
| historyItem.copyTo(mContextHeader); |
| menu.setHeaderView(mContextHeader); |
| |
| // Only show open in new tab if it was not explicitly disabled |
| if (mDisableNewWindow) { |
| menu.findItem(R.id.new_window_context_menu_id).setVisible(false); |
| } |
| // For a bookmark, provide the option to remove it from bookmarks |
| if (historyItem.isBookmark()) { |
| MenuItem item = menu.findItem(R.id.save_to_bookmarks_menu_id); |
| item.setTitle(R.string.remove_from_bookmarks); |
| } |
| // decide whether to show the share link option |
| PackageManager pm = parent.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) { |
| ContextMenuInfo menuInfo = item.getMenuInfo(); |
| if (menuInfo == null) { |
| return false; |
| } |
| View targetView = getTargetView(menuInfo); |
| if (!(targetView instanceof HistoryItem)) { |
| return false; |
| } |
| HistoryItem historyItem = (HistoryItem) targetView; |
| String url = historyItem.getUrl(); |
| String title = historyItem.getName(); |
| Activity activity = getActivity(); |
| switch (item.getItemId()) { |
| case R.id.open_context_menu_id: |
| mCallback.openUrl(url); |
| return true; |
| case R.id.new_window_context_menu_id: |
| mCallback.openInNewTab(url); |
| return true; |
| case R.id.save_to_bookmarks_menu_id: |
| if (historyItem.isBookmark()) { |
| Bookmarks.removeFromBookmarks(activity, activity.getContentResolver(), |
| url, title); |
| } else { |
| Browser.saveBookmark(activity, title, url); |
| } |
| return true; |
| case R.id.share_link_context_menu_id: |
| Browser.sendString(activity, url, |
| activity.getText(R.string.choosertitle_sharevia).toString()); |
| return true; |
| case R.id.copy_url_context_menu_id: |
| copy(url); |
| return true; |
| case R.id.delete_context_menu_id: |
| Browser.deleteFromHistory(activity.getContentResolver(), url); |
| return true; |
| case R.id.homepage_context_menu_id: |
| BrowserSettings.getInstance().setHomePage(url); |
| Toast.makeText(activity, R.string.homepage_set, Toast.LENGTH_LONG).show(); |
| return true; |
| default: |
| break; |
| } |
| return super.onContextItemSelected(item); |
| } |
| |
| private static abstract class HistoryWrapper extends BaseAdapter { |
| |
| protected HistoryAdapter mAdapter; |
| private DataSetObserver mObserver = new DataSetObserver() { |
| @Override |
| public void onChanged() { |
| super.onChanged(); |
| notifyDataSetChanged(); |
| } |
| |
| @Override |
| public void onInvalidated() { |
| super.onInvalidated(); |
| notifyDataSetInvalidated(); |
| } |
| }; |
| |
| public HistoryWrapper(HistoryAdapter adapter) { |
| mAdapter = adapter; |
| mAdapter.registerDataSetObserver(mObserver); |
| } |
| |
| } |
| private static class HistoryGroupWrapper extends HistoryWrapper { |
| |
| public HistoryGroupWrapper(HistoryAdapter adapter) { |
| super(adapter); |
| } |
| |
| @Override |
| public int getCount() { |
| return mAdapter.getGroupCount(); |
| } |
| |
| @Override |
| public Object getItem(int position) { |
| return null; |
| } |
| |
| @Override |
| public long getItemId(int position) { |
| return position; |
| } |
| |
| @Override |
| public View getView(int position, View convertView, ViewGroup parent) { |
| return mAdapter.getGroupView(position, false, convertView, parent); |
| } |
| |
| } |
| |
| private static class HistoryChildWrapper extends HistoryWrapper { |
| |
| private int mSelectedGroup; |
| |
| public HistoryChildWrapper(HistoryAdapter adapter) { |
| super(adapter); |
| } |
| |
| void setSelectedGroup(int groupPosition) { |
| mSelectedGroup = groupPosition; |
| notifyDataSetChanged(); |
| } |
| |
| @Override |
| public int getCount() { |
| return mAdapter.getChildrenCount(mSelectedGroup); |
| } |
| |
| @Override |
| public Object getItem(int position) { |
| return null; |
| } |
| |
| @Override |
| public long getItemId(int position) { |
| return position; |
| } |
| |
| @Override |
| public View getView(int position, View convertView, ViewGroup parent) { |
| return mAdapter.getChildView(mSelectedGroup, position, |
| false, convertView, parent); |
| } |
| |
| } |
| |
| private class HistoryAdapter extends DateSortedExpandableListAdapter { |
| |
| private Cursor mMostVisited, mHistoryCursor; |
| Drawable mFaviconBackground; |
| |
| HistoryAdapter(Context context) { |
| super(context, HistoryQuery.INDEX_DATE_LAST_VISITED); |
| mFaviconBackground = BookmarkUtils.createListFaviconBackground(context); |
| } |
| |
| @Override |
| public void changeCursor(Cursor cursor) { |
| mHistoryCursor = cursor; |
| super.changeCursor(cursor); |
| } |
| |
| void changeMostVisitedCursor(Cursor cursor) { |
| if (mMostVisited == cursor) { |
| return; |
| } |
| if (mMostVisited != null) { |
| mMostVisited.unregisterDataSetObserver(mDataSetObserver); |
| mMostVisited.close(); |
| } |
| mMostVisited = cursor; |
| if (mMostVisited != null) { |
| mMostVisited.registerDataSetObserver(mDataSetObserver); |
| } |
| notifyDataSetChanged(); |
| } |
| |
| @Override |
| public long getChildId(int groupPosition, int childPosition) { |
| if (moveCursorToChildPosition(groupPosition, childPosition)) { |
| Cursor cursor = getCursor(groupPosition); |
| return cursor.getLong(HistoryQuery.INDEX_ID); |
| } |
| return 0; |
| } |
| |
| @Override |
| public int getGroupCount() { |
| return super.getGroupCount() + (!isMostVisitedEmpty() ? 1 : 0); |
| } |
| |
| @Override |
| public int getChildrenCount(int groupPosition) { |
| if (groupPosition >= super.getGroupCount()) { |
| if (isMostVisitedEmpty()) { |
| return 0; |
| } |
| return mMostVisited.getCount(); |
| } |
| return super.getChildrenCount(groupPosition); |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| if (!super.isEmpty()) { |
| return false; |
| } |
| return isMostVisitedEmpty(); |
| } |
| |
| private boolean isMostVisitedEmpty() { |
| return mMostVisited == null |
| || mMostVisited.isClosed() |
| || mMostVisited.getCount() == 0; |
| } |
| |
| Cursor getCursor(int groupPosition) { |
| if (groupPosition >= super.getGroupCount()) { |
| return mMostVisited; |
| } |
| return mHistoryCursor; |
| } |
| |
| @Override |
| public View getGroupView(int groupPosition, boolean isExpanded, |
| View convertView, ViewGroup parent) { |
| if (groupPosition >= super.getGroupCount()) { |
| if (mMostVisited == null || mMostVisited.isClosed()) { |
| throw new IllegalStateException("Data is not valid"); |
| } |
| TextView item; |
| if (null == convertView || !(convertView instanceof TextView)) { |
| LayoutInflater factory = LayoutInflater.from(getContext()); |
| item = (TextView) factory.inflate(R.layout.history_header, null); |
| } else { |
| item = (TextView) convertView; |
| } |
| item.setText(R.string.tab_most_visited); |
| return item; |
| } |
| return super.getGroupView(groupPosition, isExpanded, convertView, parent); |
| } |
| |
| @Override |
| boolean moveCursorToChildPosition( |
| int groupPosition, int childPosition) { |
| if (groupPosition >= super.getGroupCount()) { |
| if (mMostVisited != null && !mMostVisited.isClosed()) { |
| mMostVisited.moveToPosition(childPosition); |
| return true; |
| } |
| return false; |
| } |
| return super.moveCursorToChildPosition(groupPosition, childPosition); |
| } |
| |
| @Override |
| public View getChildView(int groupPosition, int childPosition, boolean isLastChild, |
| View convertView, ViewGroup parent) { |
| HistoryItem item; |
| if (null == convertView || !(convertView instanceof HistoryItem)) { |
| item = new HistoryItem(getContext()); |
| // 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()); |
| item.setFaviconBackground(mFaviconBackground); |
| } else { |
| item = (HistoryItem) convertView; |
| } |
| |
| // Bail early if the Cursor is closed. |
| if (!moveCursorToChildPosition(groupPosition, childPosition)) { |
| return item; |
| } |
| |
| Cursor cursor = getCursor(groupPosition); |
| item.setName(cursor.getString(HistoryQuery.INDEX_TITE)); |
| String url = cursor.getString(HistoryQuery.INDEX_URL); |
| item.setUrl(url); |
| byte[] data = cursor.getBlob(HistoryQuery.INDEX_FAVICON); |
| if (data != null) { |
| item.setFavicon(BitmapFactory.decodeByteArray(data, 0, |
| data.length)); |
| } |
| item.setIsBookmark(cursor.getInt(HistoryQuery.INDEX_IS_BOOKMARK) == 1); |
| return item; |
| } |
| } |
| } |