OSDN Git Service

don't create the fake title bar when getGlobalVisible fails
[android-x86/packages-apps-Browser.git] / src / com / android / browser / BrowserActivity.java
index 42bc1ab..15f986e 100644 (file)
@@ -45,6 +45,7 @@ import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteException;
 import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.DrawFilter;
 import android.graphics.Paint;
@@ -75,14 +76,15 @@ import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.provider.Browser;
-import android.provider.Contacts;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Intents.Insert;
 import android.provider.Downloads;
 import android.provider.MediaStore;
-import android.provider.Contacts.Intents.Insert;
 import android.text.IClipboard;
 import android.text.TextUtils;
 import android.text.format.DateFormat;
 import android.text.util.Regex;
+import android.util.AttributeSet;
 import android.util.Log;
 import android.view.ContextMenu;
 import android.view.Gravity;
@@ -148,8 +150,7 @@ import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
 public class BrowserActivity extends Activity
-    implements KeyTracker.OnKeyTracker,
-        View.OnCreateContextMenuListener,
+    implements View.OnCreateContextMenuListener,
         DownloadListener {
 
     /* Define some aliases to make these debugging flags easier to refer to.
@@ -305,6 +306,13 @@ public class BrowserActivity extends Activity
 
         mResolver = getContentResolver();
 
+        // If this was a web search request, pass it on to the default web
+        // search provider and finish this activity.
+        if (handleWebSearchIntent(getIntent())) {
+            finish();
+            return;
+        }
+
         //
         // start MASF proxy service
         //
@@ -412,12 +420,6 @@ public class BrowserActivity extends Activity
         };
         registerReceiver(mPackageInstallationReceiver, filter);
 
-        // If this was a web search request, pass it on to the default web search provider.
-        if (handleWebSearchIntent(getIntent())) {
-            moveTaskToBack(true);
-            return;
-        }
-
         if (!mTabControl.restoreState(icicle)) {
             // clear up the thumbnail directory if we can't restore the state as
             // none of the files in the directory are referenced any more.
@@ -617,7 +619,8 @@ public class BrowserActivity extends Activity
                 || Intent.ACTION_WEB_SEARCH.equals(action)) {
             url = intent.getStringExtra(SearchManager.QUERY);
         }
-        return handleWebSearchRequest(url, intent.getBundleExtra(SearchManager.APP_DATA));
+        return handleWebSearchRequest(url, intent.getBundleExtra(SearchManager.APP_DATA),
+                intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));
     }
 
     /**
@@ -625,7 +628,7 @@ public class BrowserActivity extends Activity
      * was identified as plain search terms and not URL/shortcut.
      * @return true if the request was handled and web search activity was launched, false if not.
      */
-    private boolean handleWebSearchRequest(String inUrl, Bundle appData) {
+    private boolean handleWebSearchRequest(String inUrl, Bundle appData, String extraData) {
         if (inUrl == null) return false;
 
         // In general, we shouldn't modify URL from Intent.
@@ -649,6 +652,9 @@ public class BrowserActivity extends Activity
         if (appData != null) {
             intent.putExtra(SearchManager.APP_DATA, appData);
         }
+        if (extraData != null) {
+            intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
+        }
         intent.putExtra(Browser.EXTRA_APPLICATION_ID, getPackageName());
         startActivity(intent);
 
@@ -949,9 +955,48 @@ public class BrowserActivity extends Activity
         return true;
     }
 
+    /**
+     * Special class used exclusively for the shadow drawn underneath the fake
+     * title bar.  The shadow does not need to be drawn if the WebView
+     * underneath is scrolled to the top, because it will draw directly on top
+     * of the embedded shadow.
+     */
+    private static class Shadow extends View {
+        private WebView mWebView;
+
+        public Shadow(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        public void setWebView(WebView view) {
+            mWebView = view;
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            // In general onDraw is the method to override, but we care about
+            // whether or not the background gets drawn, which happens in draw()
+            if (mWebView == null || mWebView.getScrollY() > getHeight()) {
+                super.draw(canvas);
+            }
+            // Need to invalidate so that if the scroll position changes, we
+            // still draw as appropriate.
+            invalidate();
+        }
+    }
+
     private void showFakeTitleBar() {
+        final View decor = getWindow().peekDecorView();
         if (mFakeTitleBar == null && mActiveTabsPage == null
-                && !mActivityInPause) {
+                && !mActivityInPause && decor != null
+                && decor.getWindowToken() != null) {
+            Rect visRect = new Rect();
+            if (!mBrowserFrameLayout.getGlobalVisibleRect(visRect)) {
+                if (LOGD_ENABLED) {
+                    Log.d(LOGTAG, "showFakeTitleBar visRect failed");
+                }
+                return;
+            }
             final WebView webView = getTopWindow();
             mFakeTitleBar = new TitleBar(this);
             mFakeTitleBar.setTitleAndUrl(null, webView.getUrl());
@@ -970,27 +1015,26 @@ public class BrowserActivity extends Activity
                     ViewGroup.LayoutParams.WRAP_CONTENT,
                     WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL,
                     WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
-                    PixelFormat.OPAQUE);
+                    PixelFormat.TRANSLUCENT);
             params.gravity = Gravity.TOP;
             WebView mainView = mTabControl.getCurrentWebView();
-            params.windowAnimations = mainView == null
-                    || mainView.getScrollY() != 0
-                    ? com.android.internal.R.style.Animation_DropDownDown : 0;
+            boolean atTop = mainView != null && mainView.getScrollY() == 0;
+            params.windowAnimations = atTop ? 0
+                    : com.android.internal.R.style.Animation_DropDownDown;
             // XXX : Without providing an offset, the fake title bar will be
             // placed underneath the status bar.  Use the global visible rect
             // of mBrowserFrameLayout to determine the bottom of the status bar
-            Rect rectangle = new Rect();
-            mBrowserFrameLayout.getGlobalVisibleRect(rectangle);
-            params.y = rectangle.top;
-            // Add a holder for the title bar.  It is a FrameLayout, which
-            // allows it to have an overlay shadow.  It also has a white
-            // background, which is the same as the background when it is
-            // placed in a WebView.
+            params.y = visRect.top;
+            // Add a holder for the title bar.  It also holds a shadow to show
+            // below the title bar.
             if (mFakeTitleBarHolder == null) {
                 mFakeTitleBarHolder = (ViewGroup) LayoutInflater.from(this)
                     .inflate(R.layout.title_bar_bg, null);
             }
-            mFakeTitleBarHolder.addView(mFakeTitleBar, mFakeTitleBarParams);
+            Shadow shadow = (Shadow) mFakeTitleBarHolder.findViewById(
+                    R.id.shadow);
+            shadow.setWebView(mainView);
+            mFakeTitleBarHolder.addView(mFakeTitleBar, 0, mFakeTitleBarParams);
             manager.addView(mFakeTitleBarHolder, params);
         }
     }
@@ -1009,14 +1053,36 @@ public class BrowserActivity extends Activity
     }
     private void hideFakeTitleBar() {
         if (mFakeTitleBar == null) return;
+        WindowManager.LayoutParams params = (WindowManager.LayoutParams)
+                mFakeTitleBarHolder.getLayoutParams();
+        WebView mainView = mTabControl.getCurrentWebView();
+        // Although we decided whether or not to animate based on the current
+        // scroll position, the scroll position may have changed since the
+        // fake title bar was displayed.  Make sure it has the appropriate
+        // animation/lack thereof before removing.
+        params.windowAnimations = mainView != null && mainView.getScrollY() == 0
+                ? 0 : com.android.internal.R.style.Animation_DropDownDown;
         WindowManager manager
                     = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
+        manager.updateViewLayout(mFakeTitleBarHolder, params);
         mFakeTitleBarHolder.removeView(mFakeTitleBar);
         manager.removeView(mFakeTitleBarHolder);
         mFakeTitleBar = null;
     }
 
     /**
+     * Special method for the fake title bar to call when displaying its context
+     * menu, since it is in its own Window, and its parent does not show a
+     * context menu.
+     */
+    /* package */ void showTitleBarContextMenu() {
+        if (null == mTitleBar.getParent()) {
+            return;
+        }
+        openContextMenu(mTitleBar);
+    }
+
+    /**
      *  onSaveInstanceState(Bundle map)
      *  onSaveInstanceState is called right before onStop(). The map contains
      *  the saved state.
@@ -1081,6 +1147,9 @@ public class BrowserActivity extends Activity
             Log.v(LOGTAG, "BrowserActivity.onDestroy: this=" + this);
         }
         super.onDestroy();
+
+        if (mTabControl == null) return;
+
         // Remove the current tab and sub window
         TabControl.Tab t = mTabControl.getCurrentTab();
         if (t != null) {
@@ -1293,15 +1362,20 @@ public class BrowserActivity extends Activity
         // options selector, so set mCanChord to true so we can access them.
         mCanChord = true;
         int id = item.getItemId();
-        final WebView webView = getTopWindow();
-        if (null == webView) {
-            return false;
-        }
-        final HashMap hrefMap = new HashMap();
-        hrefMap.put("webview", webView);
-        final Message msg = mHandler.obtainMessage(
-                FOCUS_NODE_HREF, id, 0, hrefMap);
         switch (id) {
+            // For the context menu from the title bar
+            case R.id.title_bar_share_page_url:
+            case R.id.title_bar_copy_page_url:
+                WebView mainView = mTabControl.getCurrentWebView();
+                if (null == mainView) {
+                    return false;
+                }
+                if (id == R.id.title_bar_share_page_url) {
+                    Browser.sendString(this, mainView.getUrl());
+                } else {
+                    copy(mainView.getUrl());
+                }
+                break;
             // -- Browser context menu
             case R.id.open_context_menu_id:
             case R.id.open_newtab_context_menu_id:
@@ -1309,6 +1383,14 @@ public class BrowserActivity extends Activity
             case R.id.save_link_context_menu_id:
             case R.id.share_link_context_menu_id:
             case R.id.copy_link_context_menu_id:
+                final WebView webView = getTopWindow();
+                if (null == webView) {
+                    return false;
+                }
+                final HashMap hrefMap = new HashMap();
+                hrefMap.put("webview", webView);
+                final Message msg = mHandler.obtainMessage(
+                        FOCUS_NODE_HREF, id, 0, hrefMap);
                 webView.requestFocusNodeHref(msg);
                 break;
 
@@ -1447,6 +1529,10 @@ public class BrowserActivity extends Activity
                 break;
 
             case R.id.goto_menu_id:
+                onSearchRequested();
+                break;
+
+            case R.id.bookmarks_menu_id:
                 bookmarksOrHistoryPicker(false);
                 break;
 
@@ -1466,6 +1552,7 @@ public class BrowserActivity extends Activity
                 i.putExtra("url", w.getUrl());
                 i.putExtra("title", w.getTitle());
                 i.putExtra("touch_icon_url", w.getTouchIconUrl());
+                i.putExtra("thumbnail", createScreenshot(w));
                 startActivity(i);
                 break;
 
@@ -1529,7 +1616,8 @@ public class BrowserActivity extends Activity
                 break;
 
             case R.id.share_page_menu_id:
-                Browser.sendString(this, getTopWindow().getUrl());
+                Browser.sendString(this, getTopWindow().getUrl(),
+                        getText(R.string.choosertitle_sharevia).toString());
                 break;
 
             case R.id.dump_nav_menu_id:
@@ -1696,7 +1784,7 @@ public class BrowserActivity extends Activity
                                 .parse(WebView.SCHEME_TEL + extra)));
                 Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
                 addIntent.putExtra(Insert.PHONE, Uri.decode(extra));
-                addIntent.setType(Contacts.People.CONTENT_ITEM_TYPE);
+                addIntent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
                 menu.findItem(R.id.add_contact_context_menu_id).setIntent(
                         addIntent);
                 menu.findItem(R.id.copy_phone_context_menu_id).setOnMenuItemClickListener(
@@ -1758,6 +1846,7 @@ public class BrowserActivity extends Activity
     }
 
     // Attach the given tab to the content view.
+    // this should only be called for the current tab.
     private void attachTabToContentView(TabControl.Tab t) {
         // Attach the container that contains the main WebView and any other UI
         // associated with the tab.
@@ -1776,9 +1865,12 @@ public class BrowserActivity extends Activity
                                                   ViewGroup.LayoutParams.WRAP_CONTENT));
         }
 
-        if (t == mTabControl.getCurrentTab()) {
-            setLockIconType(t.getLockIconType());
-            setPrevLockType(t.getPrevLockIconType());
+        setLockIconType(t.getLockIconType());
+        setPrevLockType(t.getPrevLockIconType());
+
+        // this is to match the code in removeTabFromContentView()
+        if (!mPageStarted && t.getTopWindow().getProgress() < 100) {
+            mPageStarted = true;
         }
 
         WebView view = t.getWebView();
@@ -1807,9 +1899,21 @@ public class BrowserActivity extends Activity
             view.setEmbeddedTitleBar(null);
         }
 
+        // unlike attachTabToContentView(), removeTabFromContentView() can be
+        // called for the non-current tab. Need to add the check.
         if (t == mTabControl.getCurrentTab()) {
             t.setLockIconType(getLockIconType());
             t.setPrevLockIconType(getPrevLockType());
+
+            // this is not a perfect solution. But currently there is one
+            // WebViewClient for all the WebView. if user switches from an
+            // in-load window to an already loaded window, mPageStarted will not
+            // be set to false. If user leaves the Browser, pauseWebViewTimers()
+            // won't do anything and leaves the timer running even Browser is in
+            // the background.
+            if (mPageStarted) {
+                mPageStarted = false;
+            }
         }
     }
 
@@ -2050,6 +2154,7 @@ public class BrowserActivity extends Activity
             currentIndex--;
         }
         mTabControl.setCurrentTab(mTabControl.getTab(currentIndex));
+        resetTitleIconAndProgress();
     }
 
     private void goBackOnePageOrQuit() {
@@ -2063,6 +2168,7 @@ public class BrowserActivity extends Activity
              * moveTaskToBack().
              */
             moveTaskToBack(true);
+            return;
         }
         WebView w = current.getWebView();
         if (w.canGoBack()) {
@@ -2113,83 +2219,76 @@ public class BrowserActivity extends Activity
         }
     }
 
-    public KeyTracker.State onKeyTracker(int keyCode,
-                                         KeyEvent event,
-                                         KeyTracker.Stage stage,
-                                         int duration) {
-        // if onKeyTracker() is called after activity onStop()
-        // because of accumulated key events,
-        // we should ignore it as browser is not active any more.
-        WebView topWindow = getTopWindow();
-        if (topWindow == null && mCustomView == null)
-            return KeyTracker.State.NOT_TRACKING;
-
-        if (keyCode == KeyEvent.KEYCODE_BACK) {
-            // Check if a custom view is currently showing and, if it is, hide it.
-            if (mCustomView != null) {
-                mWebChromeClient.onHideCustomView();
-                return KeyTracker.State.DONE_TRACKING;
-            }
-            if (stage == KeyTracker.Stage.LONG_REPEAT) {
-                bookmarksOrHistoryPicker(true);
-                return KeyTracker.State.DONE_TRACKING;
-            } else if (stage == KeyTracker.Stage.UP) {
-                // FIXME: Currently, we do not have a notion of the
-                // history picker for the subwindow, but maybe we
-                // should?
-                WebView subwindow = mTabControl.getCurrentSubWindow();
-                if (subwindow != null) {
-                    if (subwindow.canGoBack()) {
-                        subwindow.goBack();
-                    } else {
-                        dismissSubWindow(mTabControl.getCurrentTab());
-                    }
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is
+        // still down, we don't want to trigger the search. Pretend to consume
+        // the key and do nothing.
+        if (mMenuIsDown) return true;
+
+        switch(keyCode) {
+            case KeyEvent.KEYCODE_MENU:
+                mMenuIsDown = true;
+                break;
+            case KeyEvent.KEYCODE_SPACE:
+                // WebView/WebTextView handle the keys in the KeyDown. As
+                // the Activity's shortcut keys are only handled when WebView
+                // doesn't, have to do it in onKeyDown instead of onKeyUp.
+                if (event.isShiftPressed()) {
+                    getTopWindow().pageUp(false);
                 } else {
-                    goBackOnePageOrQuit();
+                    getTopWindow().pageDown(false);
                 }
-                return KeyTracker.State.DONE_TRACKING;
-            }
-            return KeyTracker.State.KEEP_TRACKING;
+                return true;
+            case KeyEvent.KEYCODE_BACK:
+                if (event.getRepeatCount() == 0) {
+                    event.startTracking();
+                    return true;
+                } else if (mCustomView == null && mActiveTabsPage == null
+                        && event.isLongPress()) {
+                    bookmarksOrHistoryPicker(true);
+                    return true;
+                }
+                break;
         }
-        return KeyTracker.State.NOT_TRACKING;
+        return super.onKeyDown(keyCode, event);
     }
 
-    @Override public boolean onKeyDown(int keyCode, KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_MENU) {
-            mMenuIsDown = true;
-        } else if (mMenuIsDown) {
-            // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is
-            // still down, we don't want to trigger the search. Pretend to
-            // consume the key and do nothing.
-            return true;
-        }
-        boolean handled =  mKeyTracker.doKeyDown(keyCode, event);
-        if (!handled) {
-            switch (keyCode) {
-                case KeyEvent.KEYCODE_SPACE:
-                    if (event.isShiftPressed()) {
-                        getTopWindow().pageUp(false);
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        switch(keyCode) {
+            case KeyEvent.KEYCODE_MENU:
+                mMenuIsDown = false;
+                break;
+            case KeyEvent.KEYCODE_BACK:
+                if (event.isTracking() && !event.isCanceled()) {
+                    if (mCustomView != null) {
+                        // if a custom view is showing, hide it
+                        mWebChromeClient.onHideCustomView();
+                    } else if (mActiveTabsPage != null) {
+                        // if tab page is showing, hide it
+                        removeActiveTabPage(true);
                     } else {
-                        getTopWindow().pageDown(false);
+                        WebView subwindow = mTabControl.getCurrentSubWindow();
+                        if (subwindow != null) {
+                            if (subwindow.canGoBack()) {
+                                subwindow.goBack();
+                            } else {
+                                dismissSubWindow(mTabControl.getCurrentTab());
+                            }
+                        } else {
+                            goBackOnePageOrQuit();
+                        }
                     }
-                    handled = true;
-                    break;
-
-                default:
-                    break;
-            }
-        }
-        return handled || super.onKeyDown(keyCode, event);
-    }
-
-    @Override public boolean onKeyUp(int keyCode, KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_MENU) {
-            mMenuIsDown = false;
+                    return true;
+                }
+                break;
         }
-        return mKeyTracker.doKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
+        return super.onKeyUp(keyCode, event);
     }
 
     /* package */ void stopLoading() {
+        mDidStopLoad = true;
         resetTitleAndRevertLockIcon();
         WebView w = getTopWindow();
         w.stopLoading();
@@ -2222,12 +2321,15 @@ public class BrowserActivity extends Activity
     private static final int CANCEL_CREDS_REQUEST    = 103;
     private static final int RELEASE_WAKELOCK        = 107;
 
+    private static final int UPDATE_BOOKMARK_THUMBNAIL = 108;
+
     // Private handler for handling javascript and saving passwords
     private Handler mHandler = new Handler() {
 
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case FOCUS_NODE_HREF:
+                {
                     String url = (String) msg.getData().get("url");
                     if (url == null || url.length() == 0) {
                         break;
@@ -2258,7 +2360,8 @@ public class BrowserActivity extends Activity
                             startActivity(intent);
                             break;
                         case R.id.share_link_context_menu_id:
-                            Browser.sendString(BrowserActivity.this, url);
+                            Browser.sendString(BrowserActivity.this, url,
+                                    getText(R.string.choosertitle_sharevia).toString());
                             break;
                         case R.id.copy_link_context_menu_id:
                             copy(url);
@@ -2269,6 +2372,7 @@ public class BrowserActivity extends Activity
                             break;
                     }
                     break;
+                }
 
                 case LOAD_URL:
                     loadURL(getTopWindow(), (String) msg.obj);
@@ -2287,6 +2391,13 @@ public class BrowserActivity extends Activity
                         mWakeLock.release();
                     }
                     break;
+
+                case UPDATE_BOOKMARK_THUMBNAIL:
+                    WebView view = (WebView) msg.obj;
+                    if (view != null) {
+                        updateScreenshot(view);
+                    }
+                    break;
             }
         }
     };
@@ -2297,9 +2408,10 @@ public class BrowserActivity extends Activity
         // FIXME: Would like to make sure there is actually something to
         // draw, but the API for that (WebViewCore.pictureReady()) is not
         // currently accessible here.
+
         ContentResolver cr = getContentResolver();
         final Cursor c = BrowserBookmarksAdapter.queryBookmarksForUrl(
-                cr, view.getOriginalUrl(), view.getUrl(), false);
+                cr, view.getOriginalUrl(), view.getUrl(), true);
         if (c != null) {
             boolean succeed = c.moveToFirst();
             ContentValues values = null;
@@ -2307,16 +2419,7 @@ public class BrowserActivity extends Activity
                 if (values == null) {
                     final ByteArrayOutputStream os
                             = new ByteArrayOutputStream();
-                    Picture thumbnail = view.capturePicture();
-                    // Keep width and height in sync with BrowserBookmarksPage
-                    // and bookmark_thumb
-                    Bitmap bm = Bitmap.createBitmap(100, 80,
-                            Bitmap.Config.ARGB_4444);
-                    Canvas canvas = new Canvas(bm);
-                    // May need to tweak these values to determine what is the
-                    // best scale factor
-                    canvas.scale(.5f, .5f);
-                    thumbnail.draw(canvas);
+                    Bitmap bm = createScreenshot(view);
                     bm.compress(Bitmap.CompressFormat.PNG, 100, os);
                     values = new ContentValues();
                     values.put(Browser.BookmarkColumns.THUMBNAIL,
@@ -2330,6 +2433,58 @@ public class BrowserActivity extends Activity
         }
     }
 
+    /**
+     * Values for the size of the thumbnail created when taking a screenshot.
+     * Lazily initialized.  Instead of using these directly, use
+     * getDesiredThumbnailWidth() or getDesiredThumbnailHeight().
+     */
+    private static int THUMBNAIL_WIDTH = 0;
+    private static int THUMBNAIL_HEIGHT = 0;
+
+    /**
+     * Return the desired width for thumbnail screenshots, which are stored in
+     * the database, and used on the bookmarks screen.
+     * @param context Context for finding out the density of the screen.
+     * @return int desired width for thumbnail screenshot.
+     */
+    /* package */ static int getDesiredThumbnailWidth(Context context) {
+        if (THUMBNAIL_WIDTH == 0) {
+            float density = context.getResources().getDisplayMetrics().density;
+            THUMBNAIL_WIDTH = (int) (90 * density);
+            THUMBNAIL_HEIGHT = (int) (80 * density);
+        }
+        return THUMBNAIL_WIDTH;
+    }
+
+    /**
+     * Return the desired height for thumbnail screenshots, which are stored in
+     * the database, and used on the bookmarks screen.
+     * @param context Context for finding out the density of the screen.
+     * @return int desired height for thumbnail screenshot.
+     */
+    /* package */ static int getDesiredThumbnailHeight(Context context) {
+        // To ensure that they are both initialized.
+        getDesiredThumbnailWidth(context);
+        return THUMBNAIL_HEIGHT;
+    }
+
+    private Bitmap createScreenshot(WebView view) {
+        Picture thumbnail = view.capturePicture();
+        Bitmap bm = Bitmap.createBitmap(getDesiredThumbnailWidth(this),
+                getDesiredThumbnailHeight(this), Bitmap.Config.ARGB_4444);
+        Canvas canvas = new Canvas(bm);
+        // May need to tweak these values to determine what is the
+        // best scale factor
+        int thumbnailWidth = thumbnail.getWidth();
+        if (thumbnailWidth > 0) {
+            float scaleFactor = (float) getDesiredThumbnailWidth(this) /
+                    (float)thumbnailWidth;
+            canvas.scale(scaleFactor, scaleFactor);
+        }
+        thumbnail.draw(canvas);
+        return bm;
+    }
+
     // -------------------------------------------------------------------------
     // WebViewClient implementation.
     //-------------------------------------------------------------------------
@@ -2347,7 +2502,15 @@ public class BrowserActivity extends Activity
     private void updateIcon(WebView view, Bitmap icon) {
         if (icon != null) {
             BrowserBookmarksAdapter.updateBookmarkFavicon(mResolver,
-                    view, icon);
+                    view.getOriginalUrl(), view.getUrl(), icon);
+        }
+        setFavicon(icon);
+    }
+
+    private void updateIcon(String url, Bitmap icon) {
+        if (icon != null) {
+            BrowserBookmarksAdapter.updateBookmarkFavicon(mResolver,
+                    null, url, icon);
         }
         setFavicon(icon);
     }
@@ -2358,6 +2521,20 @@ public class BrowserActivity extends Activity
             resetLockIcon(url);
             setUrlTitle(url, null);
 
+            // We've started to load a new page. If there was a pending message
+            // to save a screenshot then we will now take the new page and
+            // save an incorrect screenshot. Therefore, remove any pending
+            // thumbnail messages from the queue.
+            mHandler.removeMessages(UPDATE_BOOKMARK_THUMBNAIL);
+
+            // If we start a touch icon load and then load a new page, we don't
+            // want to cancel the current touch icon loader. But, we do want to
+            // create a new one when the touch icon url is known.
+            if (mTouchIconLoader != null) {
+                mTouchIconLoader.mActivity = null;
+                mTouchIconLoader = null;
+            }
+
             ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(false);
             if (errorConsole != null) {
                 errorConsole.clearErrorMessages();
@@ -2368,7 +2545,7 @@ public class BrowserActivity extends Activity
 
             // Call updateIcon instead of setFavicon so the bookmark
             // database can be updated.
-            updateIcon(view, favicon);
+            updateIcon(url, favicon);
 
             if (mSettings.isTracing()) {
                 String host;
@@ -2410,11 +2587,8 @@ public class BrowserActivity extends Activity
             CookieSyncManager.getInstance().resetSync();
 
             mInLoad = true;
-            WebView currentWebView = mTabControl.getCurrentWebView();
-            if (currentWebView == null || currentWebView.getScrollY() != 0) {
-                // This page has begun to load, so show the title bar
-                showFakeTitleBar();
-            }
+            mDidStopLoad = false;
+            showFakeTitleBar();
             updateInLoadMenuItems();
             if (!mIsNetworkUp) {
                 createAndShowNetworkDialog();
@@ -2430,9 +2604,15 @@ public class BrowserActivity extends Activity
             // load.
             resetTitleAndIcon(view);
 
+            if (!mDidStopLoad) {
+                // Only update the bookmark screenshot if the user did not
+                // cancel the load early.
+                Message updateScreenshot = Message.obtain(mHandler, UPDATE_BOOKMARK_THUMBNAIL, view);
+                mHandler.sendMessageDelayed(updateScreenshot, 500);
+            }
+
             // Update the lock icon image only once we are done loading
             updateLockIconToLatest();
-            updateScreenshot(view);
 
             // Performance probe
             if (false) {
@@ -3056,9 +3236,16 @@ public class BrowserActivity extends Activity
             }
 
             if (newProgress == 100) {
-                // onProgressChanged() is called for sub-frame too while
-                // onPageFinished() is only called for the main frame. sync
-                // cookie and cache promptly here.
+                // onProgressChanged() may continue to be called after the main
+                // frame has finished loading, as any remaining sub frames
+                // continue to load. We'll only get called once though with
+                // newProgress as 100 when everything is loaded.
+                // (onPageFinished is called once when the main frame completes
+                // loading regardless of the state of any sub frames so calls
+                // to onProgressChanges may continue after onPageFinished has
+                // executed)
+
+                // sync cookies and cache promptly here.
                 CookieSyncManager.getInstance().sync();
                 if (mInLoad) {
                     mInLoad = false;
@@ -3074,9 +3261,7 @@ public class BrowserActivity extends Activity
                 // and update the menu items.
                 mInLoad = true;
                 updateInLoadMenuItems();
-                WebView currentWebView = mTabControl.getCurrentWebView();
-                if ((currentWebView == null || currentWebView.getScrollY() != 0)
-                        && (!mOptionsMenuOpen || mIconView)) {
+                if (!mOptionsMenuOpen || mIconView) {
                     // This page has begun to load, so show the title bar
                     showFakeTitleBar();
                 }
@@ -3131,14 +3316,26 @@ public class BrowserActivity extends Activity
         }
 
         @Override
-        public void onReceivedTouchIconUrl(WebView view, String url) {
+        public void onReceivedTouchIconUrl(WebView view, String url,
+                boolean precomposed) {
             final ContentResolver cr = getContentResolver();
             final Cursor c =
                     BrowserBookmarksAdapter.queryBookmarksForUrl(cr,
                             view.getOriginalUrl(), view.getUrl(), true);
             if (c != null) {
                 if (c.getCount() > 0) {
-                    new DownloadTouchIcon(cr, c, view).execute(url);
+                    // Let precomposed icons take precedence over non-composed
+                    // icons.
+                    if (precomposed && mTouchIconLoader != null) {
+                        mTouchIconLoader.cancel(false);
+                        mTouchIconLoader = null;
+                    }
+                    // Have only one async task at a time.
+                    if (mTouchIconLoader == null) {
+                        mTouchIconLoader = new DownloadTouchIcon(
+                                BrowserActivity.this, cr, c, view);
+                        mTouchIconLoader.execute(url);
+                    }
                 } else {
                     c.close();
                 }
@@ -3258,6 +3455,38 @@ public class BrowserActivity extends Activity
                 }
             Log.w(LOGTAG, "Console: " + message + " " + sourceID + ":" + lineNumber);
         }
+
+        /**
+         * Ask the browser for an icon to represent a <video> element.
+         * This icon will be used if the Web page did not specify a poster attribute.
+         *
+         * @return Bitmap The icon or null if no such icon is available.
+         * @hide pending API Council approval
+         */
+        @Override
+        public Bitmap getDefaultVideoPoster() {
+            if (mDefaultVideoPoster == null) {
+                mDefaultVideoPoster = BitmapFactory.decodeResource(
+                        getResources(), R.drawable.default_video_poster);
+            }
+            return mDefaultVideoPoster;
+        }
+
+        /**
+         * Ask the host application for a custom progress view to show while
+         * a <video> is loading.
+         *
+         * @return View The progress view.
+         * @hide pending API Council approval
+         */
+        @Override
+        public View getVideoLoadingProgressView() {
+            if (mVideoProgressView == null) {
+                LayoutInflater inflater = LayoutInflater.from(BrowserActivity.this);
+                mVideoProgressView = inflater.inflate(R.layout.video_loading_progress, null);
+            }
+            return mVideoProgressView;
+        }
     };
 
     /**
@@ -4012,6 +4241,8 @@ public class BrowserActivity extends Activity
                 CombinedBookmarkHistoryActivity.class);
         String title = current.getTitle();
         String url = current.getUrl();
+        Bitmap thumbnail = createScreenshot(current);
+
         // Just in case the user opens bookmarks before a page finishes loading
         // so the current history item, and therefore the page, is null.
         if (null == url) {
@@ -4027,6 +4258,7 @@ public class BrowserActivity extends Activity
         }
         intent.putExtra("title", title);
         intent.putExtra("url", url);
+        intent.putExtra("thumbnail", thumbnail);
         // Disable opening in a new window if we have maxed out the windows
         intent.putExtra("disable_new_window", mTabControl.getTabCount()
                 >= TabControl.MAX_TABS);
@@ -4189,17 +4421,13 @@ public class BrowserActivity extends Activity
 
     private boolean mInLoad;
     private boolean mIsNetworkUp;
+    private boolean mDidStopLoad;
 
     private boolean mPageStarted;
     private boolean mActivityInPause = true;
 
     private boolean mMenuIsDown;
 
-    private final KeyTracker mKeyTracker = new KeyTracker(this);
-
-    // As trackball doesn't send repeat down, we have to track it ourselves
-    private boolean mTrackTrackball;
-
     private static boolean mInTrace;
 
     // Performance probe
@@ -4322,11 +4550,19 @@ public class BrowserActivity extends Activity
 
     private BroadcastReceiver mPackageInstallationReceiver;
 
+    // AsyncTask for downloading touch icons
+    /* package */ DownloadTouchIcon mTouchIconLoader;
+
     // activity requestCode
     final static int COMBO_PAGE                 = 1;
     final static int DOWNLOAD_PAGE              = 2;
     final static int PREFERENCES_PAGE           = 3;
 
+    // the default <video> poster
+    private Bitmap mDefaultVideoPoster;
+    // the video progress view
+    private View mVideoProgressView;
+
     /**
      * A UrlData class to abstract how the content will be set to WebView.
      * This base class uses loadUrl to show the content.