OSDN Git Service

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