BrowserHistoryPage.java revision 2483745674f95f60b8c3b8c9e817f2df1776a0b5
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.browser;
18
19import android.app.Activity;
20import android.app.ExpandableListActivity;
21import android.content.Context;
22import android.content.Intent;
23import android.content.pm.PackageManager;
24import android.content.pm.ResolveInfo;
25import android.database.Cursor;
26import android.graphics.Bitmap;
27import android.graphics.BitmapFactory;
28import android.os.Bundle;
29import android.os.ServiceManager;
30import android.provider.Browser;
31import android.text.IClipboard;
32import android.util.Log;
33import android.view.ContextMenu;
34import android.view.KeyEvent;
35import android.view.Menu;
36import android.view.MenuInflater;
37import android.view.MenuItem;
38import android.view.View;
39import android.view.ViewGroup;
40import android.view.ViewGroup.LayoutParams;
41import android.view.ContextMenu.ContextMenuInfo;
42import android.view.ViewStub;
43import android.webkit.WebIconDatabase.IconListener;
44import android.widget.AdapterView;
45import android.widget.ExpandableListAdapter;
46import android.widget.ExpandableListView;
47import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
48import android.widget.Toast;
49
50/**
51 * Activity for displaying the browser's history, divided into
52 * days of viewing.
53 */
54public class BrowserHistoryPage extends ExpandableListActivity {
55    private HistoryAdapter          mAdapter;
56    private boolean                 mDisableNewWindow;
57    private HistoryItem             mContextHeader;
58
59    private final static String LOGTAG = "browser";
60
61    // Implementation of WebIconDatabase.IconListener
62    private class IconReceiver implements IconListener {
63        public void onReceivedIcon(String url, Bitmap icon) {
64            setListAdapter(mAdapter);
65        }
66    }
67    // Instance of IconReceiver
68    private final IconReceiver mIconReceiver = new IconReceiver();
69
70    /**
71     * Report back to the calling activity to load a site.
72     * @param url   Site to load.
73     * @param newWindow True if the URL should be loaded in a new window
74     */
75    private void loadUrl(String url, boolean newWindow) {
76        Intent intent = new Intent().setAction(url);
77        if (newWindow) {
78            Bundle b = new Bundle();
79            b.putBoolean("new_window", true);
80            intent.putExtras(b);
81        }
82        setResultToParent(RESULT_OK, intent);
83        finish();
84    }
85
86    private void copy(CharSequence text) {
87        try {
88            IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
89            if (clip != null) {
90                clip.setClipboardText(text);
91            }
92        } catch (android.os.RemoteException e) {
93            Log.e(LOGTAG, "Copy failed", e);
94        }
95    }
96
97    @Override
98    protected void onCreate(Bundle icicle) {
99        super.onCreate(icicle);
100        setTitle(R.string.browser_history);
101
102        final String whereClause = Browser.BookmarkColumns.VISITS + " > 0"
103                // In AddBookmarkPage, where we save new bookmarks, we add
104                // three visits to newly created bookmarks, so that
105                // bookmarks that have not been visited will show up in the
106                // most visited, and higher in the goto search box.
107                // However, this puts the site in the history, unless we
108                // ignore sites with a DATE of 0, which the next line does.
109                + " AND " + Browser.BookmarkColumns.DATE + " > 0";
110        final String orderBy = Browser.BookmarkColumns.DATE + " DESC";
111
112        Cursor cursor = managedQuery(
113                Browser.BOOKMARKS_URI,
114                Browser.HISTORY_PROJECTION,
115                whereClause, null, orderBy);
116
117        mAdapter = new HistoryAdapter(this, cursor,
118                Browser.HISTORY_PROJECTION_DATE_INDEX);
119        setListAdapter(mAdapter);
120        final ExpandableListView list = getExpandableListView();
121        list.setOnCreateContextMenuListener(this);
122        View v = new ViewStub(this, R.layout.empty_history);
123        addContentView(v, new LayoutParams(LayoutParams.FILL_PARENT,
124                LayoutParams.FILL_PARENT));
125        list.setEmptyView(v);
126        // Do not post the runnable if there is nothing in the list.
127        if (list.getExpandableListAdapter().getGroupCount() > 0) {
128            list.post(new Runnable() {
129                public void run() {
130                    // In case the history gets cleared before this event
131                    // happens.
132                    if (list.getExpandableListAdapter().getGroupCount() > 0) {
133                        list.expandGroup(0);
134                    }
135                }
136            });
137        }
138        mDisableNewWindow = getIntent().getBooleanExtra("disable_new_window",
139                false);
140        CombinedBookmarkHistoryActivity.getIconListenerSet()
141                .addListener(mIconReceiver);
142        Activity parent = getParent();
143        if (null == parent
144                || !(parent instanceof CombinedBookmarkHistoryActivity)) {
145            throw new AssertionError("history page can only be viewed as a tab"
146                    + "in CombinedBookmarkHistoryActivity");
147        }
148        // initialize the result to canceled, so that if the user just presses
149        // back then it will have the correct result
150        setResultToParent(RESULT_CANCELED, null);
151    }
152
153    @Override
154    protected void onDestroy() {
155        super.onDestroy();
156        CombinedBookmarkHistoryActivity.getIconListenerSet()
157                .removeListener(mIconReceiver);
158    }
159
160    @Override
161    public boolean onCreateOptionsMenu(Menu menu) {
162        super.onCreateOptionsMenu(menu);
163        MenuInflater inflater = getMenuInflater();
164        inflater.inflate(R.menu.history, menu);
165        return true;
166    }
167
168    @Override
169    public boolean onPrepareOptionsMenu(Menu menu) {
170        menu.findItem(R.id.clear_history_menu_id).setVisible(Browser.canClearHistory(this.getContentResolver()));
171        return true;
172    }
173
174    @Override
175    public boolean onOptionsItemSelected(MenuItem item) {
176        switch (item.getItemId()) {
177            case R.id.clear_history_menu_id:
178                Browser.clearHistory(getContentResolver());
179                // BrowserHistoryPage is always a child of
180                // CombinedBookmarkHistoryActivity
181                ((CombinedBookmarkHistoryActivity) getParent())
182                        .removeParentChildRelationShips();
183                mAdapter.refreshData();
184                return true;
185
186            default:
187                break;
188        }
189        return super.onOptionsItemSelected(item);
190    }
191
192    @Override
193    public void onCreateContextMenu(ContextMenu menu, View v,
194            ContextMenuInfo menuInfo) {
195        ExpandableListContextMenuInfo i =
196            (ExpandableListContextMenuInfo) menuInfo;
197        // Do not allow a context menu to come up from the group views.
198        if (!(i.targetView instanceof HistoryItem)) {
199            return;
200        }
201
202        // Inflate the menu
203        MenuInflater inflater = getMenuInflater();
204        inflater.inflate(R.menu.historycontext, menu);
205
206        HistoryItem historyItem = (HistoryItem) i.targetView;
207
208        // Setup the header
209        if (mContextHeader == null) {
210            mContextHeader = new HistoryItem(this);
211        } else if (mContextHeader.getParent() != null) {
212            ((ViewGroup) mContextHeader.getParent()).removeView(mContextHeader);
213        }
214        historyItem.copyTo(mContextHeader);
215        menu.setHeaderView(mContextHeader);
216
217        // Only show open in new tab if it was not explicitly disabled
218        if (mDisableNewWindow) {
219            menu.findItem(R.id.new_window_context_menu_id).setVisible(false);
220        }
221        // For a bookmark, provide the option to remove it from bookmarks
222        if (historyItem.isBookmark()) {
223            MenuItem item = menu.findItem(R.id.save_to_bookmarks_menu_id);
224            item.setTitle(R.string.remove_from_bookmarks);
225        }
226        // decide whether to show the share link option
227        PackageManager pm = getPackageManager();
228        Intent send = new Intent(Intent.ACTION_SEND);
229        send.setType("text/plain");
230        ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
231        menu.findItem(R.id.share_link_context_menu_id).setVisible(ri != null);
232
233        super.onCreateContextMenu(menu, v, menuInfo);
234    }
235
236    @Override
237    public boolean onContextItemSelected(MenuItem item) {
238        ExpandableListContextMenuInfo i =
239            (ExpandableListContextMenuInfo) item.getMenuInfo();
240        HistoryItem historyItem = (HistoryItem) i.targetView;
241        String url = historyItem.getUrl();
242        String title = historyItem.getName();
243        switch (item.getItemId()) {
244            case R.id.open_context_menu_id:
245                loadUrl(url, false);
246                return true;
247            case R.id.new_window_context_menu_id:
248                loadUrl(url, true);
249                return true;
250            case R.id.save_to_bookmarks_menu_id:
251                if (historyItem.isBookmark()) {
252                    Bookmarks.removeFromBookmarks(this, getContentResolver(),
253                            url, title);
254                } else {
255                    Browser.saveBookmark(this, title, url);
256                }
257                return true;
258            case R.id.share_link_context_menu_id:
259                Browser.sendString(this, url,
260                        getText(R.string.choosertitle_sharevia).toString());
261                return true;
262            case R.id.copy_url_context_menu_id:
263                copy(url);
264                return true;
265            case R.id.delete_context_menu_id:
266                Browser.deleteFromHistory(getContentResolver(), url);
267                mAdapter.refreshData();
268                return true;
269            case R.id.homepage_context_menu_id:
270                BrowserSettings.getInstance().setHomePage(this, url);
271                Toast.makeText(this, R.string.homepage_set,
272                    Toast.LENGTH_LONG).show();
273                return true;
274            default:
275                break;
276        }
277        return super.onContextItemSelected(item);
278    }
279
280    @Override
281    public boolean onChildClick(ExpandableListView parent, View v,
282            int groupPosition, int childPosition, long id) {
283        if (v instanceof HistoryItem) {
284            loadUrl(((HistoryItem) v).getUrl(), false);
285            return true;
286        }
287        return false;
288    }
289
290    // This Activity is always a sub-Activity of
291    // CombinedBookmarkHistoryActivity. Therefore, we need to pass our
292    // result code up to our parent.
293    private void setResultToParent(int resultCode, Intent data) {
294        ((CombinedBookmarkHistoryActivity) getParent()).setResultFromChild(
295                resultCode, data);
296    }
297
298    private class HistoryAdapter extends DateSortedExpandableListAdapter {
299        HistoryAdapter(Context context, Cursor cursor, int index) {
300            super(context, cursor, index);
301
302        }
303
304        public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
305                View convertView, ViewGroup parent) {
306            HistoryItem item;
307            if (null == convertView || !(convertView instanceof HistoryItem)) {
308                item = new HistoryItem(BrowserHistoryPage.this);
309                // Add padding on the left so it will be indented from the
310                // arrows on the group views.
311                item.setPadding(item.getPaddingLeft() + 10,
312                        item.getPaddingTop(),
313                        item.getPaddingRight(),
314                        item.getPaddingBottom());
315            } else {
316                item = (HistoryItem) convertView;
317            }
318            // Bail early if the Cursor is closed.
319            if (!moveCursorToChildPosition(groupPosition, childPosition)) {
320                return item;
321            }
322            item.setName(getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
323            String url = getString(Browser.HISTORY_PROJECTION_URL_INDEX);
324            item.setUrl(url);
325            byte[] data = getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
326            if (data != null) {
327                item.setFavicon(BitmapFactory.decodeByteArray(data, 0,
328                        data.length));
329            } else {
330                item.setFavicon(CombinedBookmarkHistoryActivity
331                        .getIconListenerSet().getFavicon(url));
332            }
333            item.setIsBookmark(1 ==
334                    getInt(Browser.HISTORY_PROJECTION_BOOKMARK_INDEX));
335            return item;
336        }
337    }
338}
339