OSDN Git Service

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