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;
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
//
};
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.
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 && 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());
// 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;
+ 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);
}
+ Shadow shadow = (Shadow) mFakeTitleBarHolder.findViewById(
+ R.id.shadow);
+ shadow.setWebView(mainView);
mFakeTitleBarHolder.addView(mFakeTitleBar, 0, mFakeTitleBarParams);
manager.addView(mFakeTitleBarHolder, params);
}
}
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.
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) {
// 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:
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;
break;
case R.id.goto_menu_id:
+ onSearchRequested();
+ break;
+
+ case R.id.bookmarks_menu_id:
bookmarksOrHistoryPicker(false);
break;
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:
* moveTaskToBack().
*/
moveTaskToBack(true);
+ return;
}
WebView w = current.getWebView();
if (w.canGoBack()) {
mMenuIsDown = true;
break;
case KeyEvent.KEYCODE_SPACE:
- // Browser's hidden shortcut key. Don't call super so that
- // search won't be triggered.
+ // 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 {
+ getTopWindow().pageDown(false);
+ }
return true;
case KeyEvent.KEYCODE_BACK:
if (event.getRepeatCount() == 0) {
case KeyEvent.KEYCODE_MENU:
mMenuIsDown = false;
break;
- case KeyEvent.KEYCODE_SPACE:
- if (event.isShiftPressed()) {
- getTopWindow().pageUp(false);
- } else {
- getTopWindow().pageDown(false);
- }
- return true;
case KeyEvent.KEYCODE_BACK:
if (event.isTracking() && !event.isCanceled()) {
if (mCustomView != null) {
}
/* package */ void stopLoading() {
+ mDidStopLoad = true;
resetTitleAndRevertLockIcon();
WebView w = getTopWindow();
w.stopLoading();
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;
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);
break;
}
break;
+ }
case LOAD_URL:
loadURL(getTopWindow(), (String) msg.obj);
mWakeLock.release();
}
break;
+
+ case UPDATE_BOOKMARK_THUMBNAIL:
+ WebView view = (WebView) msg.obj;
+ if (view != null) {
+ updateScreenshot(view);
+ }
+ break;
}
}
};
// 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;
}
}
+ /**
+ * 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();
- // Keep width and height in sync with BrowserBookmarksPage
- // and bookmark_thumb
- Bitmap bm = Bitmap.createBitmap(100, 80,
- Bitmap.Config.ARGB_4444);
+ 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
- canvas.scale(.5f, .5f);
+ int thumbnailWidth = thumbnail.getWidth();
+ if (thumbnailWidth > 0) {
+ float scaleFactor = (float) getDesiredThumbnailWidth(this) /
+ (float)thumbnailWidth;
+ canvas.scale(scaleFactor, scaleFactor);
+ }
thumbnail.draw(canvas);
return bm;
}
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.
CookieSyncManager.getInstance().resetSync();
mInLoad = true;
+ mDidStopLoad = false;
showFakeTitleBar();
updateInLoadMenuItems();
if (!mIsNetworkUp) {
// 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) {
}
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;
private boolean mInLoad;
private boolean mIsNetworkUp;
+ private boolean mDidStopLoad;
private boolean mPageStarted;
private boolean mActivityInPause = true;