OSDN Git Service

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