PageRenderTime 44ms CodeModel.GetById 14ms app.highlight 25ms RepoModel.GetById 1ms app.codeStats 0ms

/WebVox/src/com/marvin/webvox/BrowserHistoryPage.java

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