OSDN Git Service

am a0b0e2a2: (-s ours) Do not merge When opening a new tab from the context menu...
[android-x86/packages-apps-Browser.git] / src / com / android / browser / BrowserHistoryPage.java
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
17 package com.android.browser;
18
19 import android.app.Activity;
20 import android.app.ExpandableListActivity;
21 import android.content.Intent;
22 import android.content.pm.PackageManager;
23 import android.content.pm.ResolveInfo;
24 import android.database.ContentObserver;
25 import android.database.Cursor;
26 import android.database.DataSetObserver;
27 import android.graphics.Bitmap;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.ServiceManager;
31 import android.provider.Browser;
32 import android.text.IClipboard;
33 import android.util.Log;
34 import android.view.ContextMenu;
35 import android.view.KeyEvent;
36 import android.view.LayoutInflater;
37 import android.view.Menu;
38 import android.view.MenuInflater;
39 import android.view.MenuItem;
40 import android.view.View;
41 import android.view.ViewGroup;
42 import android.view.ViewGroup.LayoutParams;
43 import android.view.ContextMenu.ContextMenuInfo;
44 import android.view.ViewStub;
45 import android.webkit.DateSorter;
46 import android.webkit.WebIconDatabase.IconListener;
47 import android.widget.AdapterView;
48 import android.widget.ExpandableListAdapter;
49 import android.widget.ExpandableListView;
50 import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
51 import android.widget.TextView;
52 import android.widget.Toast;
53
54 import java.util.List;
55 import java.util.Vector;
56
57 /**
58  * Activity for displaying the browser's history, divided into
59  * days of viewing.
60  */
61 public class BrowserHistoryPage extends ExpandableListActivity {
62     private HistoryAdapter          mAdapter;
63     private DateSorter              mDateSorter;
64     private boolean                 mMaxTabsOpen;
65
66     private final static String LOGTAG = "browser";
67
68     // Implementation of WebIconDatabase.IconListener
69     private class IconReceiver implements IconListener {
70         public void onReceivedIcon(String url, Bitmap icon) {
71             setListAdapter(mAdapter);
72         }
73     }
74     // Instance of IconReceiver
75     private final IconReceiver mIconReceiver = new IconReceiver();
76
77     /**
78      * Report back to the calling activity to load a site.
79      * @param url   Site to load.
80      * @param newWindow True if the URL should be loaded in a new window
81      */
82     private void loadUrl(String url, boolean newWindow) {
83         Intent intent = new Intent().setAction(url);
84         if (newWindow) {
85             Bundle b = new Bundle();
86             b.putBoolean("new_window", true);
87             intent.putExtras(b);
88         }
89         setResultToParent(RESULT_OK, intent);
90         finish();
91     }
92     
93     private void copy(CharSequence text) {
94         try {
95             IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
96             if (clip != null) {
97                 clip.setClipboardText(text);
98             }
99         } catch (android.os.RemoteException e) {
100             Log.e(LOGTAG, "Copy failed", e);
101         }
102     }
103
104     @Override
105     protected void onCreate(Bundle icicle) {
106         super.onCreate(icicle);
107         setTitle(R.string.browser_history);
108         
109         mDateSorter = new DateSorter(this);
110
111         mAdapter = new HistoryAdapter();
112         setListAdapter(mAdapter);
113         final ExpandableListView list = getExpandableListView();
114         list.setOnCreateContextMenuListener(this);
115         View v = new ViewStub(this, R.layout.empty_history);
116         addContentView(v, new LayoutParams(LayoutParams.FILL_PARENT,
117                 LayoutParams.FILL_PARENT));
118         list.setEmptyView(v);
119         // Do not post the runnable if there is nothing in the list.
120         if (list.getExpandableListAdapter().getGroupCount() > 0) {
121             list.post(new Runnable() {
122                 public void run() {
123                     // In case the history gets cleared before this event
124                     // happens.
125                     if (list.getExpandableListAdapter().getGroupCount() > 0) {
126                         list.expandGroup(0);
127                     }
128                 }
129             });
130         }
131         mMaxTabsOpen = getIntent().getBooleanExtra("maxTabsOpen", false);
132         CombinedBookmarkHistoryActivity.getIconListenerSet(getContentResolver())
133                 .addListener(mIconReceiver);
134         
135         // initialize the result to canceled, so that if the user just presses
136         // back then it will have the correct result
137         setResultToParent(RESULT_CANCELED, null);
138     }
139
140     @Override
141     public boolean onCreateOptionsMenu(Menu menu) {
142         super.onCreateOptionsMenu(menu);
143         MenuInflater inflater = getMenuInflater();
144         inflater.inflate(R.menu.history, menu);
145         return true;
146     }
147
148     @Override
149     public boolean onPrepareOptionsMenu(Menu menu) {
150         menu.findItem(R.id.clear_history_menu_id).setVisible(Browser.canClearHistory(this.getContentResolver()));
151         return true;
152     }
153     
154     @Override
155     public boolean onOptionsItemSelected(MenuItem item) {
156         switch (item.getItemId()) {
157             case R.id.clear_history_menu_id:
158                 // FIXME: Need to clear the tab control in browserActivity 
159                 // as well
160                 Browser.clearHistory(getContentResolver());
161                 mAdapter.refreshData();
162                 return true;
163                 
164             default:
165                 break;
166         }  
167         return super.onOptionsItemSelected(item);
168     }
169     
170     @Override
171     public void onCreateContextMenu(ContextMenu menu, View v,
172             ContextMenuInfo menuInfo) {
173         ExpandableListContextMenuInfo i = 
174             (ExpandableListContextMenuInfo) menuInfo;
175         // Do not allow a context menu to come up from the group views.
176         if (!(i.targetView instanceof HistoryItem)) {
177             return;
178         }
179
180         // Inflate the menu
181         MenuInflater inflater = getMenuInflater();
182         inflater.inflate(R.menu.historycontext, menu);
183
184         // Setup the header
185         menu.setHeaderTitle(((HistoryItem)i.targetView).getUrl());
186
187         // Only show open in new tab if we have not maxed out available tabs
188         menu.findItem(R.id.new_window_context_menu_id).setVisible(!mMaxTabsOpen);
189         
190         // decide whether to show the share link option
191         PackageManager pm = getPackageManager();
192         Intent send = new Intent(Intent.ACTION_SEND);
193         send.setType("text/plain");
194         ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
195         menu.findItem(R.id.share_link_context_menu_id).setVisible(ri != null);
196         
197         super.onCreateContextMenu(menu, v, menuInfo);
198     }
199     
200     @Override
201     public boolean onContextItemSelected(MenuItem item) {
202         ExpandableListContextMenuInfo i = 
203             (ExpandableListContextMenuInfo) item.getMenuInfo();
204         String url = ((HistoryItem)i.targetView).getUrl();
205         String title = ((HistoryItem)i.targetView).getName();
206         switch (item.getItemId()) {
207             case R.id.open_context_menu_id:
208                 loadUrl(url, false);
209                 return true;
210             case R.id.new_window_context_menu_id:
211                 loadUrl(url, true);
212                 return true;
213             case R.id.save_to_bookmarks_menu_id:
214                 Browser.saveBookmark(this, title, url);
215                 return true;
216             case R.id.share_link_context_menu_id:
217                 Browser.sendString(this, url);
218                 return true;
219             case R.id.copy_context_menu_id:
220                 copy(url);
221                 return true;
222             case R.id.delete_context_menu_id:
223                 Browser.deleteFromHistory(getContentResolver(), url);
224                 mAdapter.refreshData();
225                 return true;
226             case R.id.homepage_context_menu_id:
227                 BrowserSettings.getInstance().setHomePage(this, url);
228                 Toast.makeText(this, R.string.homepage_set,
229                     Toast.LENGTH_LONG).show();
230                 return true;
231             default:
232                 break;
233         }
234         return super.onContextItemSelected(item);
235     }
236     
237     @Override
238     public boolean onChildClick(ExpandableListView parent, View v,
239             int groupPosition, int childPosition, long id) {
240         if (v instanceof HistoryItem) {
241             loadUrl(((HistoryItem) v).getUrl(), false);
242             return true;
243         }
244         return false;
245     }
246
247     // This Activity is generally a sub-Activity of CombinedHistoryActivity. In
248     // that situation, we need to pass our result code up to our parent.
249     // However, if someone calls this Activity directly, then this has no
250     // parent, and it needs to set it on itself.
251     private void setResultToParent(int resultCode, Intent data) {
252         Activity a = getParent() == null ? this : getParent();
253         a.setResult(resultCode, data);
254     }
255
256     private class ChangeObserver extends ContentObserver {
257         public ChangeObserver() {
258             super(new Handler());
259         }
260
261         @Override
262         public boolean deliverSelfNotifications() {
263             return true;
264         }
265
266         @Override
267         public void onChange(boolean selfChange) {
268             mAdapter.refreshData();
269         }
270     }
271     
272     private class HistoryAdapter implements ExpandableListAdapter {
273         
274         // Array for each of our bins.  Each entry represents how many items are
275         // in that bin.
276         private int mItemMap[];
277         // This is our GroupCount.  We will have at most DateSorter.DAY_COUNT
278         // bins, less if the user has no items in one or more bins.
279         private int mNumberOfBins;
280         private Vector<DataSetObserver> mObservers;
281         private Cursor mCursor;
282         
283         HistoryAdapter() {
284             mObservers = new Vector<DataSetObserver>();
285             
286             final String whereClause = Browser.BookmarkColumns.VISITS + " > 0"
287                     // In AddBookmarkPage, where we save new bookmarks, we add
288                     // three visits to newly created bookmarks, so that
289                     // bookmarks that have not been visited will show up in the
290                     // most visited, and higher in the goto search box.
291                     // However, this puts the site in the history, unless we
292                     // ignore sites with a DATE of 0, which the next line does.
293                     + " AND " + Browser.BookmarkColumns.DATE + " > 0";
294             final String orderBy = Browser.BookmarkColumns.DATE + " DESC";
295            
296             mCursor = managedQuery(
297                     Browser.BOOKMARKS_URI,
298                     Browser.HISTORY_PROJECTION,
299                     whereClause, null, orderBy);
300             
301             buildMap();
302             mCursor.registerContentObserver(new ChangeObserver());
303         }
304         
305         void refreshData() {
306             if (mCursor.isClosed()) {
307                 return;
308             }
309             mCursor.requery();
310             buildMap();
311             for (DataSetObserver o : mObservers) {
312                 o.onChanged();
313             }
314         }
315         
316         private void buildMap() {
317             // The cursor is sorted by date
318             // The ItemMap will store the number of items in each bin.
319             int array[] = new int[DateSorter.DAY_COUNT];
320             // Zero out the array.
321             for (int j = 0; j < DateSorter.DAY_COUNT; j++) {
322                 array[j] = 0;
323             }
324             mNumberOfBins = 0;
325             int dateIndex = -1;
326             if (mCursor.moveToFirst() && mCursor.getCount() > 0) {
327                 while (!mCursor.isAfterLast()) {
328                     long date = mCursor.getLong(Browser.HISTORY_PROJECTION_DATE_INDEX);
329                     int index = mDateSorter.getIndex(date);
330                     if (index > dateIndex) {
331                         mNumberOfBins++;
332                         if (index == DateSorter.DAY_COUNT - 1) {
333                             // We are already in the last bin, so it will
334                             // include all the remaining items
335                             array[index] = mCursor.getCount()
336                                     - mCursor.getPosition();
337                             break;
338                         }
339                         dateIndex = index;
340                     }
341                     array[dateIndex]++;
342                     mCursor.moveToNext();
343                 }
344             }
345             mItemMap = array;
346         }
347
348         // This translates from a group position in the Adapter to a position in
349         // our array.  This is necessary because some positions in the array
350         // have no history items, so we simply do not present those positions
351         // to the Adapter.
352         private int groupPositionToArrayPosition(int groupPosition) {
353             if (groupPosition < 0 || groupPosition >= DateSorter.DAY_COUNT) {
354                 throw new AssertionError("group position out of range");
355             }
356             if (DateSorter.DAY_COUNT == mNumberOfBins || 0 == mNumberOfBins) {
357                 // In the first case, we have exactly the same number of bins
358                 // as our maximum possible, so there is no need to do a
359                 // conversion
360                 // The second statement is in case this method gets called when
361                 // the array is empty, in which case the provided groupPosition
362                 // will do fine.
363                 return groupPosition;
364             }
365             int arrayPosition = -1;
366             while (groupPosition > -1) {
367                 arrayPosition++;
368                 if (mItemMap[arrayPosition] != 0) {
369                     groupPosition--;
370                 }
371             }
372             return arrayPosition;
373         }
374
375         public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
376                 View convertView, ViewGroup parent) {
377             groupPosition = groupPositionToArrayPosition(groupPosition);
378             HistoryItem item;
379             if (null == convertView || !(convertView instanceof HistoryItem)) {
380                 item = new HistoryItem(BrowserHistoryPage.this);
381                 // Add padding on the left so it will be indented from the
382                 // arrows on the group views.
383                 item.setPadding(item.getPaddingLeft() + 10,
384                         item.getPaddingTop(),
385                         item.getPaddingRight(),
386                         item.getPaddingBottom());
387             } else {
388                 item = (HistoryItem) convertView;
389             }
390             int index = childPosition;
391             for (int i = 0; i < groupPosition; i++) {
392                 index += mItemMap[i];
393             }
394             mCursor.moveToPosition(index);
395             item.setName(mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
396             String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
397             item.setUrl(url);
398             item.setFavicon(CombinedBookmarkHistoryActivity.getIconListenerSet(
399                     getContentResolver()).getFavicon(url));
400             item.setIsBookmark(1 ==
401                     mCursor.getInt(Browser.HISTORY_PROJECTION_BOOKMARK_INDEX));
402             return item;
403         }
404         
405         public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
406             groupPosition = groupPositionToArrayPosition(groupPosition);
407             TextView item;
408             if (null == convertView || !(convertView instanceof TextView)) {
409                 LayoutInflater factory = 
410                         LayoutInflater.from(BrowserHistoryPage.this);
411                 item = (TextView) 
412                         factory.inflate(R.layout.history_header, null);
413             } else {
414                 item = (TextView) convertView;
415             }
416             item.setText(mDateSorter.getLabel(groupPosition));
417             return item;
418         }
419
420         public boolean areAllItemsEnabled() {
421             return true;
422         }
423
424         public boolean isChildSelectable(int groupPosition, int childPosition) {
425             return true;
426         }
427
428         public int getGroupCount() {
429             return mNumberOfBins;
430         }
431
432         public int getChildrenCount(int groupPosition) {
433             return mItemMap[groupPositionToArrayPosition(groupPosition)];
434         }
435
436         public Object getGroup(int groupPosition) {
437             return null;
438         }
439
440         public Object getChild(int groupPosition, int childPosition) {
441             return null;
442         }
443
444         public long getGroupId(int groupPosition) {
445             return groupPosition;
446         }
447
448         public long getChildId(int groupPosition, int childPosition) {
449             return (childPosition << 3) + groupPosition;
450         }
451
452         public boolean hasStableIds() {
453             return true;
454         }
455
456         public void registerDataSetObserver(DataSetObserver observer) {
457             mObservers.add(observer);
458         }
459
460         public void unregisterDataSetObserver(DataSetObserver observer) {
461             mObservers.remove(observer);
462         }
463
464         public void onGroupExpanded(int groupPosition) {
465         
466         }
467
468         public void onGroupCollapsed(int groupPosition) {
469         
470         }
471
472         public long getCombinedChildId(long groupId, long childId) {
473             return childId;
474         }
475
476         public long getCombinedGroupId(long groupId) {
477             return groupId;
478         }
479
480         public boolean isEmpty() {
481             return mCursor.getCount() == 0;
482         }
483     }
484 }