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