diff options
author | Michael Kolb <kolby@google.com> | 2010-10-26 16:08:53 -0700 |
---|---|---|
committer | Michael Kolb <kolby@google.com> | 2010-11-17 13:27:05 -0800 |
commit | 8233facddcc51865d612a919d450db6954aa48e3 (patch) | |
tree | 049b5f9ac93146d842aa1c28a168e93783ea578a /src/com/android | |
parent | d9fb57958a0bea503b43d740057e3a4623c91d12 (diff) | |
download | packages_apps_browser-8233facddcc51865d612a919d450db6954aa48e3.zip packages_apps_browser-8233facddcc51865d612a919d450db6954aa48e3.tar.gz packages_apps_browser-8233facddcc51865d612a919d450db6954aa48e3.tar.bz2 |
Controller Refactor
Bug: 3170671
First step towards a model/view/control design in Browser
introduced Controller object
started separating UI code
represent state of the app in one place only
Change-Id: Ica387d6bde2dcf1a4993c3db0cce498cf34ff60f
Diffstat (limited to 'src/com/android')
31 files changed, 5904 insertions, 4856 deletions
diff --git a/src/com/android/browser/ActiveTabsPage.java b/src/com/android/browser/ActiveTabsPage.java index e450a99..fb5ed3b 100644 --- a/src/com/android/browser/ActiveTabsPage.java +++ b/src/com/android/browser/ActiveTabsPage.java @@ -21,7 +21,6 @@ import android.graphics.Bitmap; import android.os.Handler; import android.util.AttributeSet; import android.util.Log; -import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -33,17 +32,19 @@ import android.widget.ListView; import android.widget.TextView; public class ActiveTabsPage extends LinearLayout { + private static final String LOGTAG = "TabPicker"; - private final BrowserActivity mBrowserActivity; - private final LayoutInflater mFactory; - private final TabControl mControl; - private final TabsListAdapter mAdapter; - private final ListView mListView; - public ActiveTabsPage(BrowserActivity context, TabControl control) { + private final LayoutInflater mFactory; + private final UiController mUiController; + private final TabControl mControl; + private final TabsListAdapter mAdapter; + private final ListView mListView; + + public ActiveTabsPage(Context context, UiController control) { super(context); - mBrowserActivity = context; - mControl = control; + mUiController = control; + mControl = control.getTabControl(); mFactory = LayoutInflater.from(context); mFactory.inflate(R.layout.active_tabs, this); mListView = (ListView) findViewById(R.id.list); @@ -58,19 +59,19 @@ public class ActiveTabsPage extends LinearLayout { boolean needToAttach = false; if (position == -2) { // Create a new tab - mBrowserActivity.openTabToHomePage(); + mUiController.openTabToHomePage(); } else if (position == -1) { // Create a new incognito tab - mBrowserActivity.openIncognitoTab(); + mUiController.openIncognitoTab(); } else { // Open the corresponding tab // If the tab is the current one, switchToTab will // do nothing and return, so we need to make sure // it gets attached back to its mContentView in // removeActiveTabPage - needToAttach = !mBrowserActivity.switchToTab(position); + needToAttach = !mUiController.switchToTab(position); } - mBrowserActivity.removeActiveTabPage(needToAttach); + mUiController.removeActiveTabsPage(needToAttach); } }); } @@ -186,11 +187,11 @@ public class ActiveTabsPage extends LinearLayout { final int closePosition = position; close.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { - mBrowserActivity.closeTab( + mUiController.closeTab( mControl.getTab(closePosition)); if (tabCount == 1) { - mBrowserActivity.openTabToHomePage(); - mBrowserActivity.removeActiveTabPage(false); + mUiController.openTabToHomePage(); + mUiController.removeActiveTabsPage(false); } else { mNotified = true; notifyDataSetChanged(); diff --git a/src/com/android/browser/AddBookmarkPage.java b/src/com/android/browser/AddBookmarkPage.java index 2b745b1..5670881 100644 --- a/src/com/android/browser/AddBookmarkPage.java +++ b/src/com/android/browser/AddBookmarkPage.java @@ -671,7 +671,7 @@ public class AddBookmarkPage extends Activity String title = mTitle.getText().toString().trim(); String unfilteredUrl; - unfilteredUrl = BrowserActivity.fixUrl(mAddress.getText().toString()); + unfilteredUrl = UrlUtils.fixUrl(mAddress.getText().toString()); boolean emptyTitle = title.length() == 0; boolean emptyUrl = unfilteredUrl.trim().length() == 0; diff --git a/src/com/android/browser/BaseUi.java b/src/com/android/browser/BaseUi.java new file mode 100644 index 0000000..052c9c5 --- /dev/null +++ b/src/com/android/browser/BaseUi.java @@ -0,0 +1,829 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.app.ActionBar; +import android.app.Activity; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.PixelFormat; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import android.view.ActionMode; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.webkit.WebChromeClient; +import android.webkit.WebHistoryItem; +import android.webkit.WebView; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.Toast; + +/** + * UI interface definitions + */ +public class BaseUi implements UI, WebViewFactory { + + private static final String LOGTAG = "BaseUi"; + + private static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS = + new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + + private static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER = + new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + Gravity.CENTER); + + Activity mActivity; + UiController mUiController; + TabControl mTabControl; + + private Drawable mSecLockIcon; + private Drawable mMixLockIcon; + + private boolean mXLargeScreenSize; + private FrameLayout mBrowserFrameLayout; + private FrameLayout mContentView; + private FrameLayout mCustomViewContainer; + private TitleBarBase mTitleBar; + private TitleBarBase mFakeTitleBar; + private TabBar mTabBar; + + private View mCustomView; + private WebChromeClient.CustomViewCallback mCustomViewCallback; + + private CombinedBookmarkHistoryView mComboView; + + private LinearLayout mErrorConsoleContainer = null; + + private Toast mStopToast; + private ActiveTabsPage mActiveTabsPage; + + // the default <video> poster + private Bitmap mDefaultVideoPoster; + // the video progress view + private View mVideoProgressView; + + boolean mExtendedMenuOpen; + boolean mOptionsMenuOpen; + + private boolean mActivityPaused; + + public BaseUi(Activity browser, UiController controller) { + mActivity = browser; + mUiController = controller; + mTabControl = controller.getTabControl(); + Resources res = mActivity.getResources(); + mSecLockIcon = res.getDrawable(R.drawable.ic_secure); + mMixLockIcon = res.getDrawable(R.drawable.ic_partial_secure); + + + mXLargeScreenSize = (res.getConfiguration().screenLayout + & Configuration.SCREENLAYOUT_SIZE_MASK) + == Configuration.SCREENLAYOUT_SIZE_XLARGE; + + FrameLayout frameLayout = (FrameLayout) mActivity.getWindow() + .getDecorView().findViewById(android.R.id.content); + mBrowserFrameLayout = (FrameLayout) LayoutInflater.from(mActivity) + .inflate(R.layout.custom_screen, null); + mContentView = (FrameLayout) mBrowserFrameLayout.findViewById( + R.id.main_content); + mErrorConsoleContainer = (LinearLayout) mBrowserFrameLayout + .findViewById(R.id.error_console); + mCustomViewContainer = (FrameLayout) mBrowserFrameLayout + .findViewById(R.id.fullscreen_custom_content); + frameLayout.addView(mBrowserFrameLayout, COVER_SCREEN_PARAMS); + + if (mXLargeScreenSize) { + mTitleBar = new TitleBarXLarge(mActivity, mUiController); + mTitleBar.setProgress(100); + mFakeTitleBar = new TitleBarXLarge(mActivity, mUiController); + ActionBar actionBar = mActivity.getActionBar(); + mTabBar = new TabBar(mActivity, mUiController, this); + actionBar.setCustomNavigationMode(mTabBar); + } else { + mTitleBar = new TitleBar(mActivity, mUiController); + // mTitleBar will be always be shown in the fully loaded mode on + // phone + mTitleBar.setProgress(100); + mFakeTitleBar = new TitleBar(mActivity, mUiController); + } + } + + // webview factory + + @Override + public WebView createWebView(boolean privateBrowsing) { + // Create a new WebView + ScrollWebView w = new ScrollWebView(mActivity, null, + android.R.attr.webViewStyle, privateBrowsing); + w.setScrollbarFadingEnabled(true); + w.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY); + w.setMapTrackballToArrowKeys(false); // use trackball directly + // Enable the built-in zoom + w.getSettings().setBuiltInZoomControls(true); + if (mXLargeScreenSize) { + w.setScrollListener(mTabBar); + w.getSettings().setDisplayZoomControls(false); + } + + // Add this WebView to the settings observer list and update the + // settings + final BrowserSettings s = BrowserSettings.getInstance(); + s.addObserver(w.getSettings()).update(s, null); + return w; + } + + void stopWebViewScrolling() { + ScrollWebView web = (ScrollWebView) mUiController.getCurrentWebView(); + if (web != null) { + web.stopScroll(); + } + } + + private void cancelStopToast() { + if (mStopToast != null) { + mStopToast.cancel(); + mStopToast = null; + } + } + + // lifecycle + + public void onPause() { + // FIXME: This removes the active tabs page and resets the menu to + // MAIN_MENU. A better solution might be to do this work in onNewIntent + // but then we would need to save it in onSaveInstanceState and restore + // it in onCreate/onRestoreInstanceState + if (mActiveTabsPage != null) { + mUiController.removeActiveTabsPage(true); + } + cancelStopToast(); + mActivityPaused = true; + } + + public void onResume() { + mActivityPaused = false; + } + + public void onDestroy() { + hideFakeTitleBar(); + } + + public void onConfigurationChanged(Configuration config) { + } + + // key handling + + @Override + public boolean onBackKey() { + if (mActiveTabsPage != null) { + // if tab page is showing, hide it + mUiController.removeActiveTabsPage(true); + return true; + } + if (mComboView != null) { + if (!mComboView.onBackPressed()) { + mUiController.removeComboView(); + } + return true; + } + if (mCustomView != null) { + mUiController.hideCustomView(); + return true; + } + return false; + } + + // WebView callbacks + + @Override + public void onPageStarted(Tab tab, String url, Bitmap favicon) { + if (mXLargeScreenSize) { + mTabBar.onPageStarted(tab, url, favicon); + } + if (tab.inForeground()) { + resetLockIcon(tab, url); + setUrlTitle(tab, url, null); + setFavicon(tab, favicon); + } + + } + + @Override + public void onPageFinished(Tab tab, String url) { + if (mXLargeScreenSize) { + mTabBar.onPageFinished(tab); + } + if (tab.inForeground()) { + // Reset the title and icon in case we stopped a provisional load. + resetTitleAndIcon(tab); + // Update the lock icon image only once we are done loading + updateLockIconToLatest(tab); + } + } + + @Override + public void onPageStopped(Tab tab) { + cancelStopToast(); + if (tab.inForeground()) { + mStopToast = Toast + .makeText(mActivity, R.string.stopping, Toast.LENGTH_SHORT); + mStopToast.show(); + } + } + + @Override + public void onProgressChanged(Tab tab, int progress) { + if (mXLargeScreenSize) { + mTabBar.onProgress(tab, progress); + } + if (tab.inForeground()) { + mFakeTitleBar.setProgress(progress); + if (progress == 100) { + if (!mOptionsMenuOpen || !mExtendedMenuOpen) { + hideFakeTitleBar(); + } + } else { + if (!mOptionsMenuOpen || mExtendedMenuOpen) { + showFakeTitleBar(); + } + } + } + } + + @Override + public void addTab(Tab tab) { + if (mXLargeScreenSize) { + mTabBar.onNewTab(tab); + } + } + + @Override + public void setActiveTab(Tab tab) { + Tab current = mTabControl.getCurrentTab(); + if ((tab != current) && (current != null)) { + removeTabFromContentView(current); + } + attachTabToContentView(tab); + setShouldShowErrorConsole(tab, mUiController.shouldShowErrorConsole()); + + WebView view = tab.getWebView(); + view.setEmbeddedTitleBar(mTitleBar); + if (tab.isInVoiceSearchMode()) { + showVoiceTitleBar(tab.getVoiceDisplayTitle()); + } else { + revertVoiceTitleBar(tab); + } + + if (mXLargeScreenSize) { + // Request focus on the top window. + mTabBar.onSetActiveTab(tab); + } + resetTitleIconAndProgress(tab); + updateLockIconToLatest(tab); + tab.getTopWindow().requestFocus(); + } + + @Override + public void removeTab(Tab tab) { + if (mTabControl.getCurrentTab() == tab) { + removeTabFromContentView(tab); + } + if (mXLargeScreenSize) { + mTabBar.onRemoveTab(tab); + } + } + + @Override + public void detachTab(Tab tab) { + removeTabFromContentView(tab); + } + + @Override + public void attachTab(Tab tab) { + attachTabToContentView(tab); + } + + private void attachTabToContentView(Tab tab) { + if (tab.getWebView() == null) { + return; + } + View container = tab.getViewContainer(); + WebView mainView = tab.getWebView(); + // Attach the WebView to the container and then attach the + // container to the content view. + FrameLayout wrapper = + (FrameLayout) container.findViewById(R.id.webview_wrapper); + ViewGroup parent = (ViewGroup) mainView.getParent(); + if (parent != wrapper) { + if (parent != null) { + Log.w(LOGTAG, "mMainView already has a parent in" + + " attachTabToContentView!"); + parent.removeView(mainView); + } + wrapper.addView(mainView); + } else { + Log.w(LOGTAG, "mMainView is already attached to wrapper in" + + " attachTabToContentView!"); + } + parent = (ViewGroup) container.getParent(); + if (parent != mContentView) { + if (parent != null) { + Log.w(LOGTAG, "mContainer already has a parent in" + + " attachTabToContentView!"); + parent.removeView(container); + } + mContentView.addView(container, COVER_SCREEN_PARAMS); + } else { + Log.w(LOGTAG, "mContainer is already attached to content in" + + " attachTabToContentView!"); + } + mUiController.attachSubWindow(tab); + } + + private void removeTabFromContentView(Tab tab) { + // Remove the container that contains the main WebView. + WebView mainView = tab.getWebView(); + View container = tab.getViewContainer(); + if (mainView == null) { + return; + } + // Remove the container from the content and then remove the + // WebView from the container. This will trigger a focus change + // needed by WebView. + FrameLayout wrapper = + (FrameLayout) container.findViewById(R.id.webview_wrapper); + wrapper.removeView(mainView); + mContentView.removeView(container); + mUiController.endActionMode(); + mUiController.removeSubWindow(tab); + ErrorConsoleView errorConsole = tab.getErrorConsole(false); + if (errorConsole != null) { + mErrorConsoleContainer.removeView(errorConsole); + } + mainView.setEmbeddedTitleBar(null); + } + + /** + * Remove the sub window from the content view. + */ + @Override + public void removeSubWindow(View subviewContainer) { + mContentView.removeView(subviewContainer); + mUiController.endActionMode(); + } + + /** + * Attach the sub window to the content view. + */ + @Override + public void attachSubWindow(View container) { + mContentView.addView(container, COVER_SCREEN_PARAMS); + } + + void showFakeTitleBar() { + if (!isFakeTitleBarShowing() && mActiveTabsPage == null && + !mActivityPaused) { + WebView mainView = mUiController.getCurrentWebView(); + // if there is no current WebView, don't show the faked title bar; + if (mainView == null) { + return; + } + // Do not need to check for null, since the current tab will have + // at least a main WebView, or we would have returned above. + if (mUiController.isInCustomActionMode()) { + // Do not show the fake title bar, while a custom ActionMode + // (i.e. find or select) is showing. + return; + } + if (mXLargeScreenSize) { + mContentView.addView(mFakeTitleBar); + mTabBar.onShowTitleBar(); + } else { + WindowManager manager = (WindowManager) + mActivity.getSystemService(Context.WINDOW_SERVICE); + + // Add the title bar to the window manager so it can receive + // touches + // while the menu is up + WindowManager.LayoutParams params = + new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_APPLICATION, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + params.gravity = Gravity.TOP; + boolean atTop = mainView.getScrollY() == 0; + params.windowAnimations = atTop ? 0 : R.style.TitleBar; + manager.addView(mFakeTitleBar, params); + } + } + } + + void hideFakeTitleBar() { + if (!isFakeTitleBarShowing()) return; + if (mXLargeScreenSize) { + mContentView.removeView(mFakeTitleBar); + mTabBar.onHideTitleBar(); + } else { + WindowManager.LayoutParams params = + (WindowManager.LayoutParams) mFakeTitleBar.getLayoutParams(); + WebView mainView = mUiController.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 : R.style.TitleBar; + WindowManager manager = (WindowManager) mActivity + .getSystemService(Context.WINDOW_SERVICE); + manager.updateViewLayout(mFakeTitleBar, params); + manager.removeView(mFakeTitleBar); + } + } + + boolean isFakeTitleBarShowing() { + return (mFakeTitleBar.getParent() != null); + } + + @Override + public void showComboView(boolean startWithHistory, Bundle extras) { + mComboView = new CombinedBookmarkHistoryView(mActivity, + mUiController, + startWithHistory ? + CombinedBookmarkHistoryView.FRAGMENT_ID_HISTORY + : CombinedBookmarkHistoryView.FRAGMENT_ID_BOOKMARKS, + extras); + mTitleBar.setVisibility(View.GONE); + hideFakeTitleBar(); + mContentView.addView(mComboView, COVER_SCREEN_PARAMS); + } + + /** + * dismiss the ComboPage + */ + @Override + public void hideComboView() { + if (mComboView != null) { + mContentView.removeView(mComboView); + mTitleBar.setVisibility(View.VISIBLE); + mComboView = null; + } + } + + @Override + public void showCustomView(View view, + WebChromeClient.CustomViewCallback callback) { + // if a view already exists then immediately terminate the new one + if (mCustomView != null) { + callback.onCustomViewHidden(); + return; + } + + // Add the custom view to its container. + mCustomViewContainer.addView(view, COVER_SCREEN_GRAVITY_CENTER); + mCustomView = view; + mCustomViewCallback = callback; + // Hide the content view. + mContentView.setVisibility(View.GONE); + // Finally show the custom view container. + setStatusBarVisibility(false); + mCustomViewContainer.setVisibility(View.VISIBLE); + mCustomViewContainer.bringToFront(); + } + + @Override + public void onHideCustomView() { + if (mCustomView == null) + return; + + // Hide the custom view. + mCustomView.setVisibility(View.GONE); + // Remove the custom view from its container. + mCustomViewContainer.removeView(mCustomView); + mCustomView = null; + mCustomViewContainer.setVisibility(View.GONE); + mCustomViewCallback.onCustomViewHidden(); + // Show the content view. + setStatusBarVisibility(true); + mContentView.setVisibility(View.VISIBLE); + } + + @Override + public boolean isCustomViewShowing() { + return mCustomView != null; + } + + @Override + public void showVoiceTitleBar(String title) { + mTitleBar.setInVoiceMode(true); + mTitleBar.setDisplayTitle(title); + mFakeTitleBar.setInVoiceMode(true); + mFakeTitleBar.setDisplayTitle(title); + } + + @Override + public void revertVoiceTitleBar(Tab tab) { + mTitleBar.setInVoiceMode(false); + String url = tab.getCurrentUrl(); + mTitleBar.setDisplayTitle(url); + mFakeTitleBar.setInVoiceMode(false); + mFakeTitleBar.setDisplayTitle(url); + } + + // ------------------------------------------------------------------------- + + @Override + public void resetTitleAndRevertLockIcon(Tab tab) { + tab.revertLockIcon(); + updateLockIconToLatest(tab); + resetTitleIconAndProgress(tab); + } + + /** + * Resets the lock icon. This method is called when we start a new load and + * know the url to be loaded. + */ + private void resetLockIcon(Tab tab, String url) { + // Save the lock-icon state (we revert to it if the load gets cancelled) + tab.resetLockIcon(url); + updateLockIconImage(Tab.LOCK_ICON_UNSECURE); + } + + /** + * Update the lock icon to correspond to our latest state. + */ + private void updateLockIconToLatest(Tab t) { + if (t != null) { + updateLockIconImage(t.getLockIconType()); + } + } + + /** + * Reset the title, favicon, and progress. + */ + private void resetTitleIconAndProgress(Tab tab) { + WebView current = tab.getWebView(); + if (current == null) { + return; + } + resetTitleAndIcon(current); + int progress = current.getProgress(); + current.getWebChromeClient().onProgressChanged(current, progress); + } + + @Override + public void resetTitleAndIcon(Tab tab) { + WebView current = tab.getWebView(); + if (current != null) { + resetTitleAndIcon(current); + } + } + + // Reset the title and the icon based on the given item. + private void resetTitleAndIcon(WebView view) { + WebHistoryItem item = view.copyBackForwardList().getCurrentItem(); + Tab tab = mTabControl.getTabFromView(view); + if (item != null) { + setUrlTitle(tab, item.getUrl(), item.getTitle()); + setFavicon(tab, item.getFavicon()); + } else { + setUrlTitle(tab, null, null); + setFavicon(tab, null); + } + } + + /** + * Updates the lock-icon image in the title-bar. + */ + private void updateLockIconImage(int lockIconType) { + Drawable d = null; + if (lockIconType == Tab.LOCK_ICON_SECURE) { + d = mSecLockIcon; + } else if (lockIconType == Tab.LOCK_ICON_MIXED) { + d = mMixLockIcon; + } + mTitleBar.setLock(d); + mFakeTitleBar.setLock(d); + } + + // active tabs page + + public void showActiveTabsPage() { + mActiveTabsPage = new ActiveTabsPage(mActivity, mUiController); + mTitleBar.setVisibility(View.GONE); + hideFakeTitleBar(); + mContentView.addView(mActiveTabsPage, COVER_SCREEN_PARAMS); + mActiveTabsPage.requestFocus(); + } + + /** + * Remove the active tabs page. + * @param needToAttach If true, the active tabs page did not attach a tab + * to the content view, so we need to do that here. + */ + public void removeActiveTabsPage() { + mContentView.removeView(mActiveTabsPage); + mTitleBar.setVisibility(View.VISIBLE); + mActiveTabsPage = null; + } + + // action mode callbacks + + @Override + public void onActionModeStarted(ActionMode mode) { + // hide the fake title bar when CAB is shown + hideFakeTitleBar(); + } + + @Override + public void onActionModeFinished(boolean inLoad) { + if (inLoad) { + // the titlebar was removed when the CAB was shown + // if the page is loading, show it again + showFakeTitleBar(); + } + } + + // menu handling callbacks + + @Override + public void onOptionsMenuOpened() { + mOptionsMenuOpen = true; + // options menu opened, show fake title bar + showFakeTitleBar(); + } + + @Override + public void onExtendedMenuOpened() { + // Switching the menu to expanded view, so hide the + // title bar. + mExtendedMenuOpen = true; + hideFakeTitleBar(); + } + + @Override + public void onOptionsMenuClosed(boolean inLoad) { + mOptionsMenuOpen = false; + if (!inLoad) { + hideFakeTitleBar(); + } + } + + @Override + public void onExtendedMenuClosed(boolean inLoad) { + mExtendedMenuOpen = false; + if (inLoad) { + showFakeTitleBar(); + } + } + + @Override + public void onContextMenuCreated(Menu menu) { + hideFakeTitleBar(); + } + + @Override + public void onContextMenuClosed(Menu menu, boolean inLoad) { + if (inLoad) { + showFakeTitleBar(); + } + } + + @Override + public void onScroll(boolean titleVisible) { + if (mTabBar != null) { + mTabBar.onScroll(titleVisible); + } + } + + // error console + + @Override + public void setShouldShowErrorConsole(Tab tab, boolean flag) { + ErrorConsoleView errorConsole = tab.getErrorConsole(true); + if (flag) { + // Setting the show state of the console will cause it's the layout + // to be inflated. + if (errorConsole.numberOfErrors() > 0) { + errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED); + } else { + errorConsole.showConsole(ErrorConsoleView.SHOW_NONE); + } + if (errorConsole.getParent() != null) { + mErrorConsoleContainer.removeView(errorConsole); + } + // Now we can add it to the main view. + mErrorConsoleContainer.addView(errorConsole, + new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + } else { + mErrorConsoleContainer.removeView(errorConsole); + } + } + + private void setStatusBarVisibility(boolean visible) { + int flag = visible ? 0 : WindowManager.LayoutParams.FLAG_FULLSCREEN; + mActivity.getWindow().setFlags(flag, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + + @Override + public void setUrlTitle(Tab tab, String url, String title) { + if (TextUtils.isEmpty(title)) { + if (TextUtils.isEmpty(url)) { + title = mActivity.getResources() + .getString(R.string.title_bar_loading); + } else { + title = url; + } + } + if (tab.isInVoiceSearchMode()) return; + if (tab.inForeground()) { + mTitleBar.setDisplayTitle(url); + mFakeTitleBar.setDisplayTitle(url); + } + if (mXLargeScreenSize) { + mTabBar.onUrlAndTitle(tab, url, title); + } + } + + // Set the favicon in the title bar. + @Override + public void setFavicon(Tab tab, Bitmap icon) { + mTitleBar.setFavicon(icon); + mFakeTitleBar.setFavicon(icon); + if (mXLargeScreenSize) { + mTabBar.onFavicon(tab, icon); + } + } + @Override + public boolean showsWeb() { + return mCustomView == null && mActiveTabsPage == null + && mComboView == null; + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + if (!mXLargeScreenSize) { + final MenuItem newtab = menu.findItem(R.id.new_tab_menu_id); + newtab.setEnabled(mUiController.getTabControl().canCreateNewTab()); + } + } + + // ------------------------------------------------------------------------- + // Helper function for WebChromeClient + // ------------------------------------------------------------------------- + + @Override + public Bitmap getDefaultVideoPoster() { + if (mDefaultVideoPoster == null) { + mDefaultVideoPoster = BitmapFactory.decodeResource( + mActivity.getResources(), R.drawable.default_video_poster); + } + return mDefaultVideoPoster; + } + + @Override + public View getVideoLoadingProgressView() { + if (mVideoProgressView == null) { + LayoutInflater inflater = LayoutInflater.from(mActivity); + mVideoProgressView = inflater.inflate( + R.layout.video_loading_progress, null); + } + return mVideoProgressView; + } + +} diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java index ef26145..97da401 100644 --- a/src/com/android/browser/BrowserActivity.java +++ b/src/com/android/browser/BrowserActivity.java @@ -16,151 +16,34 @@ package com.android.browser; -import com.android.browser.ScrollWebView.ScrollListener; -import com.android.browser.search.SearchEngine; -import com.android.common.Search; -import com.android.common.speech.LoggingEvents; - -import android.app.ActionBar; import android.app.Activity; -import android.app.AlertDialog; -import android.app.DownloadManager; -import android.app.ProgressDialog; -import android.app.SearchManager; -import android.content.ActivityNotFoundException; -import android.content.BroadcastReceiver; -import android.content.ClipboardManager; -import android.content.ComponentName; -import android.content.ContentProvider; -import android.content.ContentProviderClient; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.res.Configuration; -import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Picture; import android.graphics.PixelFormat; -import android.graphics.drawable.Drawable; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.Uri; -import android.net.WebAddress; -import android.net.http.SslCertificate; -import android.net.http.SslError; -import android.os.AsyncTask; import android.os.Bundle; -import android.os.Debug; -import android.os.Environment; -import android.os.Handler; -import android.os.Message; -import android.os.PowerManager; -import android.os.Process; -import android.os.SystemClock; -import android.provider.Browser; -import android.provider.BrowserContract; -import android.provider.BrowserContract.Images; -import android.provider.ContactsContract; -import android.provider.ContactsContract.Intents.Insert; -import android.provider.MediaStore; -import android.speech.RecognizerResultsIntent; -import android.text.TextUtils; -import android.text.format.DateFormat; import android.util.Log; -import android.util.Patterns; import android.view.ActionMode; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; -import android.view.Gravity; import android.view.KeyEvent; -import android.view.LayoutInflater; import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; -import android.view.MenuItem.OnMenuItemClickListener; import android.view.View; -import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; -import android.webkit.CookieManager; -import android.webkit.CookieSyncManager; -import android.webkit.DownloadListener; -import android.webkit.HttpAuthHandler; -import android.webkit.SslErrorHandler; -import android.webkit.URLUtil; -import android.webkit.ValueCallback; -import android.webkit.WebChromeClient; -import android.webkit.WebHistoryItem; -import android.webkit.WebIconDatabase; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.Toast; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLEncoder; -import java.util.Calendar; -import java.util.Date; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Vector; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -public class BrowserActivity extends Activity - implements View.OnCreateContextMenuListener, DownloadListener, - BookmarksHistoryCallbacks { - - /* Define some aliases to make these debugging flags easier to refer to. - * This file imports android.provider.Browser, so we can't just refer to "Browser.DEBUG". - */ - private final static boolean DEBUG = com.android.browser.Browser.DEBUG; - private final static boolean LOGV_ENABLED = com.android.browser.Browser.LOGV_ENABLED; - private final static boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED; - - private static class ClearThumbnails extends AsyncTask<File, Void, Void> { - @Override - public Void doInBackground(File... files) { - if (files != null) { - for (File f : files) { - if (!f.delete()) { - Log.e(LOGTAG, f.getPath() + " was not deleted"); - } - } - } - return null; - } - } +public class BrowserActivity extends Activity { - /** - * This layout holds everything you see below the status bar, including the - * error console, the custom view container, and the webviews. - */ - private FrameLayout mBrowserFrameLayout; - - private CombinedBookmarkHistoryView mComboView; + private final static String LOGTAG = "browser"; - private boolean mXLargeScreenSize; + private final static boolean LOGV_ENABLED = + com.android.browser.Browser.LOGV_ENABLED; - private Boolean mIsProviderPresent = null; - private Uri mRlzUri = null; + private Controller mController; + private UI mUi; @Override public void onCreate(Bundle icicle) { @@ -169,11 +52,8 @@ public class BrowserActivity extends Activity } super.onCreate(icicle); - // Keep a settings instance handy. - mSettings = BrowserSettings.getInstance(); - // render the browser in OpenGL - if (mSettings.isHardwareAccelerated()) { + if (BrowserSettings.getInstance().isHardwareAccelerated()) { // Set the flag in the activity's window this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); @@ -188,527 +68,41 @@ public class BrowserActivity extends Activity BitmapFactory.setDefaultConfig(Bitmap.Config.ARGB_8888); } - if (((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE)).isEnabled()) { - setDefaultKeyMode(DEFAULT_KEYS_DISABLE); - } else { - setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); - } - - 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())) { + if (IntentHandler.handleWebSearchIntent(this, null, getIntent())) { finish(); return; } - mSecLockIcon = getResources().getDrawable(R.drawable.ic_secure); - mMixLockIcon = getResources().getDrawable(R.drawable.ic_partial_secure); - - // Create the tab control and our initial tab - mTabControl = new TabControl(this); - - mXLargeScreenSize = (getResources().getConfiguration().screenLayout - & Configuration.SCREENLAYOUT_SIZE_MASK) - == Configuration.SCREENLAYOUT_SIZE_XLARGE; - - FrameLayout frameLayout = (FrameLayout) getWindow().getDecorView() - .findViewById(android.R.id.content); - mBrowserFrameLayout = (FrameLayout) LayoutInflater.from(this) - .inflate(R.layout.custom_screen, null); - mContentView = (FrameLayout) mBrowserFrameLayout.findViewById( - R.id.main_content); - mErrorConsoleContainer = (LinearLayout) mBrowserFrameLayout - .findViewById(R.id.error_console); - mCustomViewContainer = (FrameLayout) mBrowserFrameLayout - .findViewById(R.id.fullscreen_custom_content); - frameLayout.addView(mBrowserFrameLayout, COVER_SCREEN_PARAMS); - - if (mXLargeScreenSize) { - mTitleBar = new TitleBarXLarge(this); - mTitleBar.setProgress(100); - mFakeTitleBar = new TitleBarXLarge(this); - ActionBar actionBar = getActionBar(); - mTabBar = new TabBar(this, mTabControl, (TitleBarXLarge) mFakeTitleBar); - actionBar.setCustomNavigationMode(mTabBar); - // disable built in zoom controls - mTabControl.setDisplayZoomControls(false); - } else { - mTitleBar = new TitleBar(this); - // mTitleBar will be always be shown in the fully loaded mode on - // phone - mTitleBar.setProgress(100); - mFakeTitleBar = new TitleBar(this); - } - - // Open the icon database and retain all the bookmark urls for favicons - retainIconsOnStartup(); - - mSettings.setTabControl(mTabControl); - - PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Browser"); - - // Find out if the network is currently up. - ConnectivityManager cm = (ConnectivityManager) getSystemService( - Context.CONNECTIVITY_SERVICE); - NetworkInfo info = cm.getActiveNetworkInfo(); - if (info != null) { - mIsNetworkUp = info.isAvailable(); - } - - /* enables registration for changes in network status from - http stack */ - mNetworkStateChangedFilter = new IntentFilter(); - mNetworkStateChangedFilter.addAction( - ConnectivityManager.CONNECTIVITY_ACTION); - mNetworkStateIntentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals( - ConnectivityManager.CONNECTIVITY_ACTION)) { - - NetworkInfo info = intent.getParcelableExtra( - ConnectivityManager.EXTRA_NETWORK_INFO); - String typeName = info.getTypeName(); - String subtypeName = info.getSubtypeName(); - sendNetworkType(typeName.toLowerCase(), - (subtypeName != null ? subtypeName.toLowerCase() : "")); - - onNetworkToggle(info.isAvailable()); - } - } - }; - - // Unless the last browser usage was within 24 hours, destroy any - // remaining incognito tabs. - - Calendar lastActiveDate = icicle != null ? (Calendar) icicle.getSerializable("lastActiveDate") : null; - Calendar today = Calendar.getInstance(); - Calendar yesterday = Calendar.getInstance(); - yesterday.add(Calendar.DATE, -1); - - boolean dontRestoreIncognitoTabs = lastActiveDate == null - || lastActiveDate.before(yesterday) - || lastActiveDate.after(today); - - if (!mTabControl.restoreState(icicle, dontRestoreIncognitoTabs)) { - // clear up the thumbnail directory if we can't restore the state as - // none of the files in the directory are referenced any more. - new ClearThumbnails().execute( - mTabControl.getThumbnailDir().listFiles()); - // there is no quit on Android. But if we can't restore the state, - // we can treat it as a new Browser, remove the old session cookies. - CookieManager.getInstance().removeSessionCookie(); - // remove any incognito files - WebView.cleanupPrivateBrowsingFiles(this); - final Intent intent = getIntent(); - final Bundle extra = intent.getExtras(); - // Create an initial tab. - // If the intent is ACTION_VIEW and data is not null, the Browser is - // invoked to view the content by another application. In this case, - // the tab will be close when exit. - UrlData urlData = getUrlDataFromIntent(intent); - - String action = intent.getAction(); - final Tab t = mTabControl.createNewTab( - (Intent.ACTION_VIEW.equals(action) && - intent.getData() != null) - || RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS - .equals(action), - intent.getStringExtra(Browser.EXTRA_APPLICATION_ID), - urlData.mUrl, false); - mTabControl.setCurrentTab(t); - attachTabToContentView(t); - WebView webView = t.getWebView(); - if (extra != null) { - int scale = extra.getInt(Browser.INITIAL_ZOOM_LEVEL, 0); - if (scale > 0 && scale <= 1000) { - webView.setInitialScale(scale); - } - } - - if (urlData.isEmpty()) { - loadUrl(webView, mSettings.getHomePage()); - } else { - loadUrlDataIn(t, urlData); - } + if (((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE)) + .isEnabled()) { + setDefaultKeyMode(DEFAULT_KEYS_DISABLE); } else { - if (dontRestoreIncognitoTabs) { - WebView.cleanupPrivateBrowsingFiles(this); - } - - // TabControl.restoreState() will create a new tab even if - // restoring the state fails. - attachTabToContentView(mTabControl.getCurrentTab()); + setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); } - // Delete old thumbnails to save space - File dir = mTabControl.getThumbnailDir(); - if (dir.exists()) { - for (String child : dir.list()) { - File f = new File(dir, child); - f.delete(); - } - } + mController = new Controller(this); + mUi = new BaseUi(this, mController); + mController.setUi(mUi); + mController.setWebViewFactory((BaseUi) mUi); - // Read JavaScript flags if it exists. - String jsFlags = mSettings.getJsFlags(); - if (jsFlags.trim().length() != 0) { - mTabControl.getCurrentWebView().setJsFlags(jsFlags); - } - - // Start watching the default geolocation permissions - mSystemAllowGeolocationOrigins - = new SystemAllowGeolocationOrigins(getApplicationContext()); - mSystemAllowGeolocationOrigins.start(); + mController.start(icicle, getIntent()); } - ScrollListener getScrollListener() { - return mTabBar; + Controller getController() { + return mController; } - /** - * Feed the previously stored results strings to the BrowserProvider so that - * the SearchDialog will show them instead of the standard searches. - * @param result String to show on the editable line of the SearchDialog. - */ - /* package */ void showVoiceSearchResults(String result) { - ContentProviderClient client = mResolver.acquireContentProviderClient( - Browser.BOOKMARKS_URI); - ContentProvider prov = client.getLocalContentProvider(); - BrowserProvider bp = (BrowserProvider) prov; - bp.setQueryResults(mTabControl.getCurrentTab().getVoiceSearchResults()); - client.release(); - - Bundle bundle = createGoogleSearchSourceBundle( - GOOGLE_SEARCH_SOURCE_SEARCHKEY); - bundle.putBoolean(SearchManager.CONTEXT_IS_VOICE, true); - startSearch(result, false, bundle, false); + // TODO: this is here for the test classes + // remove once tests are fixed + TabControl getTabControl() { + return mController.getTabControl(); } @Override protected void onNewIntent(Intent intent) { - Tab current = mTabControl.getCurrentTab(); - // When a tab is closed on exit, the current tab index is set to -1. - // Reset before proceed as Browser requires the current tab to be set. - if (current == null) { - // Try to reset the tab in case the index was incorrect. - current = mTabControl.getTab(0); - if (current == null) { - // No tabs at all so just ignore this intent. - return; - } - mTabControl.setCurrentTab(current); - attachTabToContentView(current); - resetTitleAndIcon(current.getWebView()); - } - final String action = intent.getAction(); - final int flags = intent.getFlags(); - if (Intent.ACTION_MAIN.equals(action) || - (flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) { - // just resume the browser - return; - } - // In case the SearchDialog is open. - ((SearchManager) getSystemService(Context.SEARCH_SERVICE)) - .stopSearch(); - boolean activateVoiceSearch = RecognizerResultsIntent - .ACTION_VOICE_SEARCH_RESULTS.equals(action); - if (Intent.ACTION_VIEW.equals(action) - || Intent.ACTION_SEARCH.equals(action) - || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action) - || Intent.ACTION_WEB_SEARCH.equals(action) - || activateVoiceSearch) { - if (current.isInVoiceSearchMode()) { - String title = current.getVoiceDisplayTitle(); - if (title != null && title.equals(intent.getStringExtra( - SearchManager.QUERY))) { - // The user submitted the same search as the last voice - // search, so do nothing. - return; - } - if (Intent.ACTION_SEARCH.equals(action) - && current.voiceSearchSourceIsGoogle()) { - Intent logIntent = new Intent( - LoggingEvents.ACTION_LOG_EVENT); - logIntent.putExtra(LoggingEvents.EXTRA_EVENT, - LoggingEvents.VoiceSearch.QUERY_UPDATED); - logIntent.putExtra( - LoggingEvents.VoiceSearch.EXTRA_QUERY_UPDATED_VALUE, - intent.getDataString()); - sendBroadcast(logIntent); - // Note, onPageStarted will revert the voice title bar - // When http://b/issue?id=2379215 is fixed, we should update - // the title bar here. - } - } - // If this was a search request (e.g. search query directly typed into the address bar), - // pass it on to the default web search provider. - if (handleWebSearchIntent(intent)) { - return; - } - - UrlData urlData = getUrlDataFromIntent(intent); - if (urlData.isEmpty()) { - urlData = new UrlData(mSettings.getHomePage()); - } - - final String appId = intent - .getStringExtra(Browser.EXTRA_APPLICATION_ID); - if ((Intent.ACTION_VIEW.equals(action) - // If a voice search has no appId, it means that it came - // from the browser. In that case, reuse the current tab. - || (activateVoiceSearch && appId != null)) - && !getPackageName().equals(appId) - && (flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) { - Tab appTab = mTabControl.getTabFromId(appId); - if (appTab != null) { - Log.i(LOGTAG, "Reusing tab for " + appId); - // Dismiss the subwindow if applicable. - dismissSubWindow(appTab); - // Since we might kill the WebView, remove it from the - // content view first. - removeTabFromContentView(appTab); - // Recreate the main WebView after destroying the old one. - // If the WebView has the same original url and is on that - // page, it can be reused. - boolean needsLoad = - mTabControl.recreateWebView(appTab, urlData); - - if (current != appTab) { - switchToTab(mTabControl.getTabIndex(appTab)); - if (needsLoad) { - loadUrlDataIn(appTab, urlData); - } - } else { - // If the tab was the current tab, we have to attach - // it to the view system again. - attachTabToContentView(appTab); - if (needsLoad) { - loadUrlDataIn(appTab, urlData); - } - } - return; - } else { - // No matching application tab, try to find a regular tab - // with a matching url. - appTab = mTabControl.findUnusedTabWithUrl(urlData.mUrl); - if (appTab != null) { - if (current != appTab) { - switchToTab(mTabControl.getTabIndex(appTab)); - } - // Otherwise, we are already viewing the correct tab. - } else { - // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url - // will be opened in a new tab unless we have reached - // MAX_TABS. Then the url will be opened in the current - // tab. If a new tab is created, it will have "true" for - // exit on close. - openTabAndShow(urlData, true, appId); - } - } - } else { - if (!urlData.isEmpty() - && urlData.mUrl.startsWith("about:debug")) { - if ("about:debug.dom".equals(urlData.mUrl)) { - current.getWebView().dumpDomTree(false); - } else if ("about:debug.dom.file".equals(urlData.mUrl)) { - current.getWebView().dumpDomTree(true); - } else if ("about:debug.render".equals(urlData.mUrl)) { - current.getWebView().dumpRenderTree(false); - } else if ("about:debug.render.file".equals(urlData.mUrl)) { - current.getWebView().dumpRenderTree(true); - } else if ("about:debug.display".equals(urlData.mUrl)) { - current.getWebView().dumpDisplayTree(); - } else { - mSettings.toggleDebugSettings(); - } - return; - } - // Get rid of the subwindow if it exists - dismissSubWindow(current); - // If the current Tab is being used as an application tab, - // remove the association, since the new Intent means that it is - // no longer associated with that application. - current.setAppId(null); - loadUrlDataIn(current, urlData); - } - } - } - - /** - * Launches the default web search activity with the query parameters if the given intent's data - * are identified as plain search terms and not URLs/shortcuts. - * @return true if the intent was handled and web search activity was launched, false if not. - */ - private boolean handleWebSearchIntent(Intent intent) { - if (intent == null) return false; - - String url = null; - final String action = intent.getAction(); - if (RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS.equals( - action)) { - return false; - } - if (Intent.ACTION_VIEW.equals(action)) { - Uri data = intent.getData(); - if (data != null) url = data.toString(); - } else if (Intent.ACTION_SEARCH.equals(action) - || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action) - || Intent.ACTION_WEB_SEARCH.equals(action)) { - url = intent.getStringExtra(SearchManager.QUERY); - } - return handleWebSearchRequest(url, intent.getBundleExtra(SearchManager.APP_DATA), - intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); - } - - /** - * Launches the default web search activity with the query parameters if the given url string - * 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, String extraData) { - if (inUrl == null) return false; - - // In general, we shouldn't modify URL from Intent. - // But currently, we get the user-typed URL from search box as well. - String url = fixUrl(inUrl).trim(); - - // URLs are handled by the regular flow of control, so - // return early. - if (Patterns.WEB_URL.matcher(url).matches() - || ACCEPTED_URI_SCHEMA.matcher(url).matches()) { - return false; - } - - final ContentResolver cr = mResolver; - final String newUrl = url; - if (mTabControl == null || !mTabControl.getCurrentWebView().isPrivateBrowsingEnabled()) { - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... unused) { - Browser.updateVisitedHistory(cr, newUrl, false); - Browser.addSearchUrl(cr, newUrl); - return null; - } - }.execute(); - } - - SearchEngine searchEngine = mSettings.getSearchEngine(); - if (searchEngine == null) return false; - searchEngine.startSearch(this, url, appData, extraData); - - return true; - } - - private UrlData getUrlDataFromIntent(Intent intent) { - String url = ""; - Map<String, String> headers = null; - if (intent != null) { - final String action = intent.getAction(); - if (Intent.ACTION_VIEW.equals(action)) { - url = smartUrlFilter(intent.getData()); - if (url != null && url.startsWith("content:")) { - /* Append mimetype so webview knows how to display */ - String mimeType = intent.resolveType(getContentResolver()); - if (mimeType != null) { - url += "?" + mimeType; - } - } - if (url != null && url.startsWith("http")) { - final Bundle pairs = intent - .getBundleExtra(Browser.EXTRA_HEADERS); - if (pairs != null && !pairs.isEmpty()) { - Iterator<String> iter = pairs.keySet().iterator(); - headers = new HashMap<String, String>(); - while (iter.hasNext()) { - String key = iter.next(); - headers.put(key, pairs.getString(key)); - } - } - } - } else if (Intent.ACTION_SEARCH.equals(action) - || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action) - || Intent.ACTION_WEB_SEARCH.equals(action)) { - url = intent.getStringExtra(SearchManager.QUERY); - if (url != null) { - mLastEnteredUrl = url; - // In general, we shouldn't modify URL from Intent. - // But currently, we get the user-typed URL from search box as well. - url = fixUrl(url); - url = smartUrlFilter(url); - final ContentResolver cr = mResolver; - final String newUrl = url; - if (mTabControl == null - || mTabControl.getCurrentWebView() == null - || !mTabControl.getCurrentWebView().isPrivateBrowsingEnabled()) { - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... unused) { - Browser.updateVisitedHistory(cr, newUrl, false); - return null; - } - }.execute(); - } - String searchSource = "&source=android-" + GOOGLE_SEARCH_SOURCE_SUGGEST + "&"; - if (url.contains(searchSource)) { - String source = null; - final Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA); - if (appData != null) { - source = appData.getString(Search.SOURCE); - } - if (TextUtils.isEmpty(source)) { - source = GOOGLE_SEARCH_SOURCE_UNKNOWN; - } - url = url.replace(searchSource, "&source=android-"+source+"&"); - } - } - } - } - return new UrlData(url, headers, intent); - } - /* package */ void showVoiceTitleBar(String title) { - mTitleBar.setInVoiceMode(true); - mTitleBar.setDisplayTitle(title); - mFakeTitleBar.setInVoiceMode(true); - mFakeTitleBar.setDisplayTitle(title); - } - /* package */ void revertVoiceTitleBar() { - mTitleBar.setInVoiceMode(false); - mTitleBar.setDisplayTitle(mUrl); - mFakeTitleBar.setInVoiceMode(false); - mFakeTitleBar.setDisplayTitle(mUrl); - } - /* package */ static String fixUrl(String inUrl) { - // FIXME: Converting the url to lower case - // duplicates functionality in smartUrlFilter(). - // However, changing all current callers of fixUrl to - // call smartUrlFilter in addition may have unwanted - // consequences, and is deferred for now. - int colon = inUrl.indexOf(':'); - boolean allLower = true; - for (int index = 0; index < colon; index++) { - char ch = inUrl.charAt(index); - if (!Character.isLetter(ch)) { - break; - } - allLower &= Character.isLowerCase(ch); - if (index == colon - 1 && !allLower) { - inUrl = inUrl.substring(0, colon).toLowerCase() - + inUrl.substring(colon); - } - } - if (inUrl.startsWith("http://") || inUrl.startsWith("https://")) - return inUrl; - if (inUrl.startsWith("http:") || - inUrl.startsWith("https:")) { - if (inUrl.startsWith("http:/") || inUrl.startsWith("https:/")) { - inUrl = inUrl.replaceFirst("/", "//"); - } else inUrl = inUrl.replaceFirst(":", "://"); - } - return inUrl; + mController.handleNewIntent(intent); } @Override @@ -717,185 +111,26 @@ public class BrowserActivity extends Activity if (LOGV_ENABLED) { Log.v(LOGTAG, "BrowserActivity.onResume: this=" + this); } - - if (!mActivityInPause) { - Log.e(LOGTAG, "BrowserActivity is already resumed."); - return; - } - - mTabControl.resumeCurrentTab(); - mActivityInPause = false; - resumeWebViewTimers(); - - if (mWakeLock.isHeld()) { - mHandler.removeMessages(RELEASE_WAKELOCK); - mWakeLock.release(); - } - - registerReceiver(mNetworkStateIntentReceiver, - mNetworkStateChangedFilter); - WebView.enablePlatformNotifications(); + mController.onResume(); } - /** - * Since the actual title bar is embedded in the WebView, and removing it - * would change its appearance, use a different TitleBar to show overlayed - * at the top of the screen, when the menu is open or the page is loading. - */ - private TitleBarBase mFakeTitleBar; - - /** - * Keeps track of whether the options menu is open. This is important in - * determining whether to show or hide the title bar overlay. - */ - private boolean mOptionsMenuOpen; - - /** - * Only meaningful when mOptionsMenuOpen is true. This variable keeps track - * of whether the configuration has changed. The first onMenuOpened call - * after a configuration change is simply a reopening of the same menu - * (i.e. mIconView did not change). - */ - private boolean mConfigChanged; - - /** - * Whether or not the options menu is in its smaller, icon menu form. When - * true, we want the title bar overlay to be up. When false, we do not. - * Only meaningful if mOptionsMenuOpen is true. - */ - private boolean mIconView; - @Override public boolean onMenuOpened(int featureId, Menu menu) { if (Window.FEATURE_OPTIONS_PANEL == featureId) { - if (mOptionsMenuOpen) { - if (mConfigChanged) { - // We do not need to make any changes to the state of the - // title bar, since the only thing that happened was a - // change in orientation - mConfigChanged = false; - } else { - if (mIconView) { - // Switching the menu to expanded view, so hide the - // title bar. - hideFakeTitleBar(); - mIconView = false; - } else { - // Switching the menu back to icon view, so show the - // title bar once again. - showFakeTitleBar(); - mIconView = true; - } - } - } else { - // The options menu is closed, so open it, and show the title - showFakeTitleBar(); - mOptionsMenuOpen = true; - mConfigChanged = false; - mIconView = true; - } + mController.onMenuOpened(featureId, menu); } return true; } - void showFakeTitleBar() { - if (!isFakeTitleBarShowing() && mActiveTabsPage == null && !mActivityInPause) { - WebView mainView = mTabControl.getCurrentWebView(); - // if there is no current WebView, don't show the faked title bar; - if (mainView == null) { - return; - } - // Do not need to check for null, since the current tab will have - // at least a main WebView, or we would have returned above. - if (isInCustomActionMode()) { - // Do not show the fake title bar, while a custom ActionMode - // (i.e. find or select) is showing. - return; - } - if (mXLargeScreenSize) { - mContentView.addView(mFakeTitleBar); - mTabBar.onShowTitleBar(); - } else { - WindowManager manager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); - - // Add the title bar to the window manager so it can receive - // touches - // while the menu is up - WindowManager.LayoutParams params = - new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT, - WindowManager.LayoutParams.TYPE_APPLICATION, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, - PixelFormat.TRANSLUCENT); - params.gravity = Gravity.TOP; - boolean atTop = mainView.getScrollY() == 0; - params.windowAnimations = atTop ? 0 : R.style.TitleBar; - manager.addView(mFakeTitleBar, params); - } - } - } - @Override public void onOptionsMenuClosed(Menu menu) { - mOptionsMenuOpen = false; - if (!mInLoad) { - hideFakeTitleBar(); - } else if (!mIconView) { - // The page is currently loading, and we are in expanded mode, so - // we were not showing the menu. Show it once again. It will be - // removed when the page finishes. - showFakeTitleBar(); - } - } - - void stopScrolling() { - ((ScrollWebView) mTabControl.getCurrentWebView()).stopScroll(); - } - - void hideFakeTitleBar() { - if (!isFakeTitleBarShowing()) return; - if (mXLargeScreenSize) { - mContentView.removeView(mFakeTitleBar); - mTabBar.onHideTitleBar(); - } else { - WindowManager.LayoutParams params = - (WindowManager.LayoutParams) mFakeTitleBar.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 : R.style.TitleBar; - WindowManager manager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); - manager.updateViewLayout(mFakeTitleBar, params); - manager.removeView(mFakeTitleBar); - } - } - - boolean isFakeTitleBarShowing() { - return (mFakeTitleBar.getParent() != 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); + mController.onOptionsMenuClosed(menu); } @Override public void onContextMenuClosed(Menu menu) { super.onContextMenuClosed(menu); - if (mInLoad) { - showFakeTitleBar(); - } + mController.onContextMenuClosed(menu); } /** @@ -908,49 +143,13 @@ public class BrowserActivity extends Activity if (LOGV_ENABLED) { Log.v(LOGTAG, "BrowserActivity.onSaveInstanceState: this=" + this); } - // the default implementation requires each view to have an id. As the - // browser handles the state itself and it doesn't use id for the views, - // don't call the default implementation. Otherwise it will trigger the - // warning like this, "couldn't save which view has focus because the - // focused view XXX has no id". - - // Save all the tabs - mTabControl.saveState(outState); - - // Save time so that we know how old incognito tabs (if any) are. - outState.putSerializable("lastActiveDate", Calendar.getInstance()); + mController.onSaveInstanceState(outState); } @Override protected void onPause() { + mController.onPause(); super.onPause(); - - if (mActivityInPause) { - Log.e(LOGTAG, "BrowserActivity is already paused."); - return; - } - - mTabControl.pauseCurrentTab(); - mActivityInPause = true; - if (mTabControl.getCurrentIndex() >= 0 && !pauseWebViewTimers()) { - mWakeLock.acquire(); - mHandler.sendMessageDelayed(mHandler - .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT); - } - - // FIXME: This removes the active tabs page and resets the menu to - // MAIN_MENU. A better solution might be to do this work in onNewIntent - // but then we would need to save it in onSaveInstanceState and restore - // it in onCreate/onRestoreInstanceState - if (mActiveTabsPage != null) { - removeActiveTabPage(true); - } - - cancelStopToast(); - - // unregister network state listener - unregisterReceiver(mNetworkStateIntentReceiver); - WebView.disablePlatformNotifications(); } @Override @@ -959,3204 +158,83 @@ public class BrowserActivity extends Activity Log.v(LOGTAG, "BrowserActivity.onDestroy: this=" + this); } super.onDestroy(); - - if (mUploadMessage != null) { - mUploadMessage.onReceiveValue(null); - mUploadMessage = null; - } - - if (mTabControl == null) return; - - // Remove the fake title bar if it is there - hideFakeTitleBar(); - - // Remove the current tab and sub window - Tab t = mTabControl.getCurrentTab(); - if (t != null) { - dismissSubWindow(t); - removeTabFromContentView(t); - } - // Destroy all the tabs - mTabControl.destroy(); - WebIconDatabase.getInstance().close(); - - // Stop watching the default geolocation permissions - mSystemAllowGeolocationOrigins.stop(); - mSystemAllowGeolocationOrigins = null; + mController.onDestroy(); + mUi = null; + mController = null; } @Override public void onConfigurationChanged(Configuration newConfig) { - mConfigChanged = true; super.onConfigurationChanged(newConfig); - - if (mPageInfoDialog != null) { - mPageInfoDialog.dismiss(); - showPageInfo( - mPageInfoView, - mPageInfoFromShowSSLCertificateOnError); - } - if (mSSLCertificateDialog != null) { - mSSLCertificateDialog.dismiss(); - showSSLCertificate( - mSSLCertificateView); - } - if (mSSLCertificateOnErrorDialog != null) { - mSSLCertificateOnErrorDialog.dismiss(); - showSSLCertificateOnError( - mSSLCertificateOnErrorView, - mSSLCertificateOnErrorHandler, - mSSLCertificateOnErrorError); - } - if (mHttpAuthenticationDialog != null) { - mHttpAuthenticationDialog.reshow(); - } + mController.onConfgurationChanged(newConfig); } @Override public void onLowMemory() { super.onLowMemory(); - mTabControl.freeMemory(); - } - - private void resumeWebViewTimers() { - Tab tab = mTabControl.getCurrentTab(); - if (tab == null) return; // monkey can trigger this - boolean inLoad = tab.inLoad(); - if ((!mActivityInPause && !inLoad) || (mActivityInPause && inLoad)) { - CookieSyncManager.getInstance().startSync(); - WebView w = tab.getWebView(); - if (w != null) { - w.resumeTimers(); - } - } - } - - private boolean pauseWebViewTimers() { - Tab tab = mTabControl.getCurrentTab(); - boolean inLoad = tab.inLoad(); - if (mActivityInPause && !inLoad) { - CookieSyncManager.getInstance().stopSync(); - WebView w = mTabControl.getCurrentWebView(); - if (w != null) { - w.pauseTimers(); - } - return true; - } else { - return false; - } - } - - // Open the icon database and retain all the icons for visited sites. - private void retainIconsOnStartup() { - final WebIconDatabase db = WebIconDatabase.getInstance(); - db.open(getDir("icons", 0).getPath()); - Cursor c = null; - try { - c = Browser.getAllBookmarks(mResolver); - if (c.moveToFirst()) { - int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL); - do { - String url = c.getString(urlIndex); - db.retainIconForPageUrl(url); - } while (c.moveToNext()); - } - } catch (IllegalStateException e) { - Log.e(LOGTAG, "retainIconsOnStartup", e); - } finally { - if (c!= null) c.close(); - } - } - - // Helper method for getting the top window. - WebView getTopWindow() { - return mTabControl.getCurrentTopWebView(); - } - - TabControl getTabControl() { - return mTabControl; + mController.onLowMemory(); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); - - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.browser, menu); - mMenu = menu; - updateInLoadMenuItems(); - return true; - } - - /** - * As the menu can be open when loading state changes - * we must manually update the state of the stop/reload menu - * item - */ - private void updateInLoadMenuItems() { - if (mMenu == null) { - return; - } - MenuItem dest = mMenu.findItem(R.id.stop_reload_menu_id); - MenuItem src = mInLoad ? - mMenu.findItem(R.id.stop_menu_id): - mMenu.findItem(R.id.reload_menu_id); - if (src != null) { - dest.setIcon(src.getIcon()); - dest.setTitle(src.getTitle()); - } - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - // chording is not an issue with context menus, but we use the same - // options selector, so set mCanChord to true so we can access them. - mCanChord = true; - int id = item.getItemId(); - boolean result = true; - switch (id) { - // For the context menu from the title bar - case R.id.title_bar_copy_page_url: - Tab currentTab = mTabControl.getCurrentTab(); - if (null == currentTab) { - result = false; - break; - } - WebView mainView = currentTab.getWebView(); - if (null == mainView) { - result = false; - break; - } - copy(mainView.getUrl()); - break; - // -- Browser context menu - case R.id.open_context_menu_id: - case R.id.bookmark_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) { - result = false; - break; - } - final HashMap hrefMap = new HashMap(); - hrefMap.put("webview", webView); - final Message msg = mHandler.obtainMessage( - FOCUS_NODE_HREF, id, 0, hrefMap); - webView.requestFocusNodeHref(msg); - break; - - default: - // For other context menus - result = onOptionsItemSelected(item); - } - mCanChord = false; - return result; - } - - private Bundle createGoogleSearchSourceBundle(String source) { - Bundle bundle = new Bundle(); - bundle.putString(Search.SOURCE, source); - return bundle; - } - - /* package */ void editUrl() { - if (mOptionsMenuOpen) closeOptionsMenu(); - String url = (getTopWindow() == null) ? null : getTopWindow().getUrl(); - startSearch(mSettings.getHomePage().equals(url) ? null : url, true, - null, false); - } - - /** - * Overriding this to insert a local information bundle - */ - @Override - public void startSearch(String initialQuery, boolean selectInitialQuery, - Bundle appSearchData, boolean globalSearch) { - if (appSearchData == null) { - appSearchData = createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_TYPE); - } - - SearchEngine searchEngine = mSettings.getSearchEngine(); - if (searchEngine != null && !searchEngine.supportsVoiceSearch()) { - appSearchData.putBoolean(SearchManager.DISABLE_VOICE_SEARCH, true); - } - - super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch); - } - - /** - * Switch tabs. Called by the TitleBarSet when sliding the title bar - * results in changing tabs. - * @param index Index of the tab to change to, as defined by - * mTabControl.getTabIndex(Tab t). - * @return boolean True if we successfully switched to a different tab. If - * the indexth tab is null, or if that tab is the same as - * the current one, return false. - */ - /* package */ boolean switchToTab(int index) { - Tab tab = mTabControl.getTab(index); - Tab currentTab = mTabControl.getCurrentTab(); - if (tab == null || tab == currentTab) { - return false; - } - if (currentTab != null) { - // currentTab may be null if it was just removed. In that case, - // we do not need to remove it - removeTabFromContentView(currentTab); - } - mTabControl.setCurrentTab(tab); - attachTabToContentView(tab); - resetTitleIconAndProgress(); - updateLockIconToLatest(); - return true; - } - - /* package */ Tab openTabToHomePage() { - return openTabAndShow(mSettings.getHomePage(), false, null); - } - - /* package */ void closeCurrentWindow() { - final Tab current = mTabControl.getCurrentTab(); - if (mTabControl.getTabCount() == 1) { - // This is the last tab. Open a new one, with the home - // page and close the current one. - openTabToHomePage(); - closeTab(current); - return; - } - final Tab parent = current.getParentTab(); - int indexToShow = -1; - if (parent != null) { - indexToShow = mTabControl.getTabIndex(parent); - } else { - final int currentIndex = mTabControl.getCurrentIndex(); - // Try to move to the tab to the right - indexToShow = currentIndex + 1; - if (indexToShow > mTabControl.getTabCount() - 1) { - // Try to move to the tab to the left - indexToShow = currentIndex - 1; - } - } - if (switchToTab(indexToShow)) { - // Close window - closeTab(current); - } - } - - private ActiveTabsPage mActiveTabsPage; - - /** - * Remove the active tabs page. - * @param needToAttach If true, the active tabs page did not attach a tab - * to the content view, so we need to do that here. - */ - /* package */ void removeActiveTabPage(boolean needToAttach) { - mContentView.removeView(mActiveTabsPage); - mTitleBar.setVisibility(View.VISIBLE); - mActiveTabsPage = null; - mMenuState = R.id.MAIN_MENU; - if (needToAttach) { - attachTabToContentView(mTabControl.getCurrentTab()); - } - getTopWindow().requestFocus(); + return mController.onCreateOptionsMenu(menu); } @Override - public void onActionModeStarted(ActionMode mode) { - super.onActionModeStarted(mode); - mActionMode = mode; - hideFakeTitleBar(); - // Would like to change the MENU, but onEndActionMode may not be called - // TODO onActionModeFinished will notify when an action mode ends + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + return mController.prepareOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { - if (item.getGroupId() != R.id.CONTEXT_MENU) { - // menu remains active, so ensure comboview is dismissed - // if main menu option is selected - removeComboView(); - } - // check the action bar button before mCanChord check, as the prepare call - // doesn't come for action bar buttons - if (item.getItemId() == R.id.newtab) { - openTabToHomePage(); - return true; - } - if (!mCanChord) { - // The user has already fired a shortcut with this hold down of the - // menu key. - return false; - } - if (null == getTopWindow()) { - return false; - } - if (mMenuIsDown) { - // The shortcut action consumes the MENU. Even if it is still down, - // it won't trigger the next shortcut action. In the case of the - // shortcut action triggering a new activity, like Bookmarks, we - // won't get onKeyUp for MENU. So it is important to reset it here. - mMenuIsDown = false; - } - switch (item.getItemId()) { - // -- Main menu - case R.id.new_tab_menu_id: - openTabToHomePage(); - break; - - case R.id.incognito_menu_id: - openIncognitoTab(); - break; - - case R.id.goto_menu_id: - editUrl(); - break; - - case R.id.bookmarks_menu_id: - bookmarksOrHistoryPicker(false); - break; - - case R.id.active_tabs_menu_id: - mActiveTabsPage = new ActiveTabsPage(this, mTabControl); - removeTabFromContentView(mTabControl.getCurrentTab()); - mTitleBar.setVisibility(View.GONE); - hideFakeTitleBar(); - mContentView.addView(mActiveTabsPage, COVER_SCREEN_PARAMS); - mActiveTabsPage.requestFocus(); - mMenuState = EMPTY_MENU; - break; - - case R.id.add_bookmark_menu_id: - bookmarkCurrentPage(AddBookmarkPage.DEFAULT_FOLDER_ID); - break; - - case R.id.stop_reload_menu_id: - if (mInLoad) { - stopLoading(); - } else { - getTopWindow().reload(); - } - break; - - case R.id.back_menu_id: - getTopWindow().goBack(); - break; - - case R.id.forward_menu_id: - getTopWindow().goForward(); - break; - - case R.id.close_menu_id: - // Close the subwindow if it exists. - if (mTabControl.getCurrentSubWindow() != null) { - dismissSubWindow(mTabControl.getCurrentTab()); - break; - } - closeCurrentWindow(); - break; - - case R.id.homepage_menu_id: - Tab current = mTabControl.getCurrentTab(); - if (current != null) { - dismissSubWindow(current); - loadUrl(current.getWebView(), mSettings.getHomePage()); - } - break; - - case R.id.preferences_menu_id: - Intent intent = new Intent(this, - BrowserPreferencesPage.class); - intent.putExtra(BrowserPreferencesPage.CURRENT_PAGE, - getTopWindow().getUrl()); - startActivityForResult(intent, PREFERENCES_PAGE); - break; - - case R.id.find_menu_id: - getTopWindow().showFindDialog(null); - break; - - case R.id.page_info_menu_id: - showPageInfo(mTabControl.getCurrentTab(), false); - break; - - case R.id.classic_history_menu_id: - bookmarksOrHistoryPicker(true); - break; - - case R.id.title_bar_share_page_url: - case R.id.share_page_menu_id: - Tab currentTab = mTabControl.getCurrentTab(); - if (null == currentTab) { - mCanChord = false; - return false; - } - currentTab.populatePickerData(); - sharePage(this, currentTab.getTitle(), - currentTab.getUrl(), currentTab.getFavicon(), - createScreenshot(currentTab.getWebView(), getDesiredThumbnailWidth(this), - getDesiredThumbnailHeight(this))); - break; - - case R.id.dump_nav_menu_id: - getTopWindow().debugDump(); - break; - - case R.id.dump_counters_menu_id: - getTopWindow().dumpV8Counters(); - break; - - case R.id.zoom_in_menu_id: - getTopWindow().zoomIn(); - break; - - case R.id.zoom_out_menu_id: - getTopWindow().zoomOut(); - break; - - case R.id.view_downloads_menu_id: - viewDownloads(); - break; - - case R.id.window_one_menu_id: - case R.id.window_two_menu_id: - case R.id.window_three_menu_id: - case R.id.window_four_menu_id: - case R.id.window_five_menu_id: - case R.id.window_six_menu_id: - case R.id.window_seven_menu_id: - case R.id.window_eight_menu_id: - { - int menuid = item.getItemId(); - for (int id = 0; id < WINDOW_SHORTCUT_ID_ARRAY.length; id++) { - if (WINDOW_SHORTCUT_ID_ARRAY[id] == menuid) { - Tab desiredTab = mTabControl.getTab(id); - if (desiredTab != null && - desiredTab != mTabControl.getCurrentTab()) { - switchToTab(id); - } - break; - } - } - } - break; - - default: - if (!super.onOptionsItemSelected(item)) { - return false; - } - // Otherwise fall through. + if (!mController.onOptionsItemSelected(item)) { + return super.onOptionsItemSelected(item); } - mCanChord = false; - return true; - } - - /** - * add the current page as a bookmark to the given folder id - * @param folderId use -1 for the default folder - */ - /* package */ void bookmarkCurrentPage(long folderId) { - Intent i = new Intent(BrowserActivity.this, - AddBookmarkPage.class); - WebView w = getTopWindow(); - i.putExtra(BrowserContract.Bookmarks.URL, w.getUrl()); - i.putExtra(BrowserContract.Bookmarks.TITLE, w.getTitle()); - String touchIconUrl = w.getTouchIconUrl(); - if (touchIconUrl != null) { - i.putExtra(AddBookmarkPage.TOUCH_ICON_URL, touchIconUrl); - WebSettings settings = w.getSettings(); - if (settings != null) { - i.putExtra(AddBookmarkPage.USER_AGENT, - settings.getUserAgentString()); - } - } - i.putExtra(BrowserContract.Bookmarks.THUMBNAIL, - createScreenshot(w, getDesiredThumbnailWidth(this), - getDesiredThumbnailHeight(this))); - i.putExtra(BrowserContract.Bookmarks.FAVICON, w.getFavicon()); - i.putExtra(BrowserContract.Bookmarks.PARENT, - folderId); - // Put the dialog at the upper right of the screen, covering the - // star on the title bar. - i.putExtra("gravity", Gravity.RIGHT | Gravity.TOP); - startActivity(i); - } - - /* - * True if a custom ActionMode (i.e. find or select) is in use. - */ - private boolean isInCustomActionMode() { - return mActionMode != null; - } - - /* - * End the current ActionMode. - */ - void endActionMode() { - if (mActionMode != null) { - ActionMode mode = mActionMode; - onEndActionMode(); - mode.finish(); - } - } - - /* - * Called by find and select when they are finished. Replace title bars - * as necessary. - */ - public void onEndActionMode() { - if (!isInCustomActionMode()) return; - if (mInLoad) { - // The title bar was hidden, because otherwise it would cover up the - // find or select dialog. Now that the dialog has been removed, - // show the fake title bar once again. - showFakeTitleBar(); - } - // Would like to return the menu state to normal, but this does not - // necessarily get called. - mActionMode = null; - } - - // For select and find, we keep track of the ActionMode so that - // finish() can be called as desired. - private ActionMode mActionMode; - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - // This happens when the user begins to hold down the menu key, so - // allow them to chord to get a shortcut. - mCanChord = true; - // Note: setVisible will decide whether an item is visible; while - // setEnabled() will decide whether an item is enabled, which also means - // whether the matching shortcut key will function. - super.onPrepareOptionsMenu(menu); - switch (mMenuState) { - case EMPTY_MENU: - if (mCurrentMenuState != mMenuState) { - menu.setGroupVisible(R.id.MAIN_MENU, false); - menu.setGroupEnabled(R.id.MAIN_MENU, false); - menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false); - } - break; - default: - if (mCurrentMenuState != mMenuState) { - menu.setGroupVisible(R.id.MAIN_MENU, true); - menu.setGroupEnabled(R.id.MAIN_MENU, true); - menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true); - } - final WebView w = getTopWindow(); - boolean canGoBack = false; - boolean canGoForward = false; - boolean isHome = false; - if (w != null) { - canGoBack = w.canGoBack(); - canGoForward = w.canGoForward(); - isHome = mSettings.getHomePage().equals(w.getUrl()); - } - final MenuItem back = menu.findItem(R.id.back_menu_id); - back.setEnabled(canGoBack); - - final MenuItem home = menu.findItem(R.id.homepage_menu_id); - home.setEnabled(!isHome); - - final MenuItem forward = menu.findItem(R.id.forward_menu_id); - forward.setEnabled(canGoForward); - - if (!mXLargeScreenSize) { - final MenuItem newtab = menu.findItem(R.id.new_tab_menu_id); - newtab.setEnabled(mTabControl.canCreateNewTab()); - } - // decide whether to show the share link option - PackageManager pm = getPackageManager(); - Intent send = new Intent(Intent.ACTION_SEND); - send.setType("text/plain"); - ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY); - menu.findItem(R.id.share_page_menu_id).setVisible(ri != null); - - boolean isNavDump = mSettings.isNavDump(); - final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id); - nav.setVisible(isNavDump); - nav.setEnabled(isNavDump); - - boolean showDebugSettings = mSettings.showDebugSettings(); - final MenuItem counter = menu.findItem(R.id.dump_counters_menu_id); - counter.setVisible(showDebugSettings); - counter.setEnabled(showDebugSettings); - - break; - } - mCurrentMenuState = mMenuState; return true; } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - if (v instanceof TitleBarBase) { - return; - } - if (!(v instanceof WebView)) { - return; - } - WebView webview = (WebView) v; - WebView.HitTestResult result = webview.getHitTestResult(); - if (result == null) { - return; - } - - int type = result.getType(); - if (type == WebView.HitTestResult.UNKNOWN_TYPE) { - Log.w(LOGTAG, - "We should not show context menu when nothing is touched"); - return; - } - if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) { - // let TextView handles context menu - return; - } - - // Note, http://b/issue?id=1106666 is requesting that - // an inflated menu can be used again. This is not available - // yet, so inflate each time (yuk!) - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.browsercontext, menu); - - // Show the correct menu group - final String extra = result.getExtra(); - menu.setGroupVisible(R.id.PHONE_MENU, - type == WebView.HitTestResult.PHONE_TYPE); - menu.setGroupVisible(R.id.EMAIL_MENU, - type == WebView.HitTestResult.EMAIL_TYPE); - menu.setGroupVisible(R.id.GEO_MENU, - type == WebView.HitTestResult.GEO_TYPE); - menu.setGroupVisible(R.id.IMAGE_MENU, - type == WebView.HitTestResult.IMAGE_TYPE - || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE); - menu.setGroupVisible(R.id.ANCHOR_MENU, - type == WebView.HitTestResult.SRC_ANCHOR_TYPE - || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE); - - // Setup custom handling depending on the type - switch (type) { - case WebView.HitTestResult.PHONE_TYPE: - menu.setHeaderTitle(Uri.decode(extra)); - menu.findItem(R.id.dial_context_menu_id).setIntent( - new Intent(Intent.ACTION_VIEW, Uri - .parse(WebView.SCHEME_TEL + extra))); - Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT); - addIntent.putExtra(Insert.PHONE, Uri.decode(extra)); - 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( - new Copy(extra)); - break; - - case WebView.HitTestResult.EMAIL_TYPE: - menu.setHeaderTitle(extra); - menu.findItem(R.id.email_context_menu_id).setIntent( - new Intent(Intent.ACTION_VIEW, Uri - .parse(WebView.SCHEME_MAILTO + extra))); - menu.findItem(R.id.copy_mail_context_menu_id).setOnMenuItemClickListener( - new Copy(extra)); - break; - - case WebView.HitTestResult.GEO_TYPE: - menu.setHeaderTitle(extra); - menu.findItem(R.id.map_context_menu_id).setIntent( - new Intent(Intent.ACTION_VIEW, Uri - .parse(WebView.SCHEME_GEO - + URLEncoder.encode(extra)))); - menu.findItem(R.id.copy_geo_context_menu_id).setOnMenuItemClickListener( - new Copy(extra)); - break; - - case WebView.HitTestResult.SRC_ANCHOR_TYPE: - case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE: - TextView titleView = (TextView) LayoutInflater.from(this) - .inflate(android.R.layout.browser_link_context_header, - null); - titleView.setText(extra); - menu.setHeaderView(titleView); - // decide whether to show the open link in new tab option - boolean showNewTab = mTabControl.canCreateNewTab(); - MenuItem newTabItem - = menu.findItem(R.id.open_newtab_context_menu_id); - newTabItem.setVisible(showNewTab); - if (showNewTab) { - newTabItem.setOnMenuItemClickListener( - new MenuItem.OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - final Tab parent = mTabControl.getCurrentTab(); - final Tab newTab = openTab(extra, false); - if (newTab != parent) { - parent.addChildTab(newTab); - } - return true; - } - }); - } - menu.findItem(R.id.bookmark_context_menu_id).setVisible( - Bookmarks.urlHasAcceptableScheme(extra)); - PackageManager pm = getPackageManager(); - Intent send = new Intent(Intent.ACTION_SEND); - send.setType("text/plain"); - ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY); - menu.findItem(R.id.share_link_context_menu_id).setVisible(ri != null); - if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE) { - break; - } - // otherwise fall through to handle image part - case WebView.HitTestResult.IMAGE_TYPE: - if (type == WebView.HitTestResult.IMAGE_TYPE) { - menu.setHeaderTitle(extra); - } - menu.findItem(R.id.view_image_context_menu_id).setIntent( - new Intent(Intent.ACTION_VIEW, Uri.parse(extra))); - menu.findItem(R.id.download_context_menu_id). - setOnMenuItemClickListener(new Download(extra)); - menu.findItem(R.id.set_wallpaper_context_menu_id). - setOnMenuItemClickListener(new SetAsWallpaper(extra)); - break; - - default: - Log.w(LOGTAG, "We should not get here."); - break; - } - hideFakeTitleBar(); - } - - // Attach the given tab to the content view. - // this should only be called for the current tab. - private void attachTabToContentView(Tab t) { - // Attach the container that contains the main WebView and any other UI - // associated with the tab. - t.attachTabToContentView(mContentView); - - if (mShouldShowErrorConsole) { - ErrorConsoleView errorConsole = t.getErrorConsole(true); - if (errorConsole.numberOfErrors() == 0) { - errorConsole.showConsole(ErrorConsoleView.SHOW_NONE); - } else { - errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED); - } - - mErrorConsoleContainer.addView(errorConsole, - new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); - } - - WebView view = t.getWebView(); - view.setEmbeddedTitleBar(mTitleBar); - if (t.isInVoiceSearchMode()) { - showVoiceTitleBar(t.getVoiceDisplayTitle()); - } else { - revertVoiceTitleBar(); - } - // Request focus on the top window. - t.getTopWindow().requestFocus(); - if (mTabControl.getTabChangeListener() != null) { - mTabControl.getTabChangeListener().onCurrentTab(t); - } - } - - // Attach a sub window to the main WebView of the given tab. - void attachSubWindow(Tab t) { - t.attachSubWindow(mContentView); - getTopWindow().requestFocus(); - } - - // Remove the given tab from the content view. - private void removeTabFromContentView(Tab t) { - // Remove the container that contains the main WebView. - t.removeTabFromContentView(mContentView); - - ErrorConsoleView errorConsole = t.getErrorConsole(false); - if (errorConsole != null) { - mErrorConsoleContainer.removeView(errorConsole); - } - - WebView view = t.getWebView(); - if (view != null) { - view.setEmbeddedTitleBar(null); - } - } - - // Remove the sub window if it exists. Also called by TabControl when the - // user clicks the 'X' to dismiss a sub window. - /* package */ void dismissSubWindow(Tab t) { - t.removeSubWindow(mContentView); - // dismiss the subwindow. This will destroy the WebView. - t.dismissSubWindow(); - getTopWindow().requestFocus(); + mController.onCreateContextMenu(menu, v, menuInfo); } - // A wrapper function of {@link #openTabAndShow(UrlData, boolean, String)} - // that accepts url as string. - private Tab openTabAndShow(String url, boolean closeOnExit, String appId) { - return openTabAndShow(new UrlData(url), closeOnExit, appId); - } - - // This method does a ton of stuff. It will attempt to create a new tab - // if we haven't reached MAX_TABS. Otherwise it uses the current tab. If - // url isn't null, it will load the given url. - /* package */Tab openTabAndShow(UrlData urlData, boolean closeOnExit, - String appId) { - final Tab currentTab = mTabControl.getCurrentTab(); - if (mTabControl.canCreateNewTab()) { - final Tab tab = mTabControl.createNewTab(closeOnExit, appId, - urlData.mUrl, false); - WebView webview = tab.getWebView(); - // If the last tab was removed from the active tabs page, currentTab - // will be null. - if (currentTab != null) { - removeTabFromContentView(currentTab); - } - // We must set the new tab as the current tab to reflect the old - // animation behavior. - mTabControl.setCurrentTab(tab); - attachTabToContentView(tab); - if (!urlData.isEmpty()) { - loadUrlDataIn(tab, urlData); - } - return tab; - } else { - // Get rid of the subwindow if it exists - dismissSubWindow(currentTab); - if (!urlData.isEmpty()) { - // Load the given url. - loadUrlDataIn(currentTab, urlData); - } - return currentTab; - } - } - - private Tab openTab(String url, boolean forceForeground) { - if (mSettings.openInBackground() && !forceForeground) { - Tab t = mTabControl.createNewTab(); - if (t != null) { - WebView view = t.getWebView(); - loadUrl(view, url); - } - return t; - } else { - return openTabAndShow(url, false, null); - } - } - - /* package */ Tab openIncognitoTab() { - if (mTabControl.canCreateNewTab()) { - Tab currentTab = mTabControl.getCurrentTab(); - Tab tab = mTabControl.createNewTab(false, null, null, true); - if (currentTab != null) { - removeTabFromContentView(currentTab); - } - mTabControl.setCurrentTab(tab); - attachTabToContentView(tab); - return tab; - } - return null; - } - - private class Copy implements OnMenuItemClickListener { - private CharSequence mText; - - public boolean onMenuItemClick(MenuItem item) { - copy(mText); - return true; - } - - public Copy(CharSequence toCopy) { - mText = toCopy; - } - } - - private class Download implements OnMenuItemClickListener { - private String mText; - - public boolean onMenuItemClick(MenuItem item) { - onDownloadStartNoStream(mText, null, null, null, -1); - return true; - } - - public Download(String toDownload) { - mText = toDownload; - } - } - - private class SetAsWallpaper extends Thread implements - OnMenuItemClickListener, DialogInterface.OnCancelListener { - private URL mUrl; - private ProgressDialog mWallpaperProgress; - private boolean mCanceled = false; - - public SetAsWallpaper(String url) { - try { - mUrl = new URL(url); - } catch (MalformedURLException e) { - mUrl = null; - } - } - - public void onCancel(DialogInterface dialog) { - mCanceled = true; - } - - public boolean onMenuItemClick(MenuItem item) { - if (mUrl != null) { - // The user may have tried to set a image with a large file size as their - // background so it may take a few moments to perform the operation. Display - // a progress spinner while it is working. - mWallpaperProgress = new ProgressDialog(BrowserActivity.this); - mWallpaperProgress.setIndeterminate(true); - mWallpaperProgress.setMessage(getText(R.string.progress_dialog_setting_wallpaper)); - mWallpaperProgress.setCancelable(true); - mWallpaperProgress.setOnCancelListener(this); - mWallpaperProgress.show(); - start(); - } - return true; - } - - @Override - public void run() { - Drawable oldWallpaper = BrowserActivity.this.getWallpaper(); - try { - // TODO: This will cause the resource to be downloaded again, when we - // should in most cases be able to grab it from the cache. To fix this - // we should query WebCore to see if we can access a cached version and - // instead open an input stream on that. This pattern could also be used - // in the download manager where the same problem exists. - InputStream inputstream = mUrl.openStream(); - if (inputstream != null) { - setWallpaper(inputstream); - } - } catch (IOException e) { - Log.e(LOGTAG, "Unable to set new wallpaper"); - // Act as though the user canceled the operation so we try to - // restore the old wallpaper. - mCanceled = true; - } - - if (mCanceled) { - // Restore the old wallpaper if the user cancelled whilst we were setting - // the new wallpaper. - int width = oldWallpaper.getIntrinsicWidth(); - int height = oldWallpaper.getIntrinsicHeight(); - Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); - Canvas canvas = new Canvas(bm); - oldWallpaper.setBounds(0, 0, width, height); - oldWallpaper.draw(canvas); - try { - setWallpaper(bm); - } catch (IOException e) { - Log.e(LOGTAG, "Unable to restore old wallpaper."); - } - mCanceled = false; - } - - if (mWallpaperProgress.isShowing()) { - mWallpaperProgress.dismiss(); - } - } - } - - private void copy(CharSequence text) { - ClipboardManager cm = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE); - cm.setText(text); - } - - /** - * Resets the browser title-view to whatever it must be - * (for example, if we had a loading error) - * When we have a new page, we call resetTitle, when we - * have to reset the titlebar to whatever it used to be - * (for example, if the user chose to stop loading), we - * call resetTitleAndRevertLockIcon. - */ - /* package */ void resetTitleAndRevertLockIcon() { - mTabControl.getCurrentTab().revertLockIcon(); - updateLockIconToLatest(); - resetTitleIconAndProgress(); - } - - /** - * Reset the title, favicon, and progress. - */ - private void resetTitleIconAndProgress() { - WebView current = mTabControl.getCurrentWebView(); - if (current == null) { - return; - } - resetTitleAndIcon(current); - int progress = current.getProgress(); - current.getWebChromeClient().onProgressChanged(current, progress); - } - - // Reset the title and the icon based on the given item. - private void resetTitleAndIcon(WebView view) { - WebHistoryItem item = view.copyBackForwardList().getCurrentItem(); - if (item != null) { - setUrlTitle(item.getUrl(), item.getTitle()); - setFavicon(item.getFavicon()); - } else { - setUrlTitle(null, null); - setFavicon(null); - } - } - - /** - * Sets a title composed of the URL and the title string. - * @param url The URL of the site being loaded. - * @param title The title of the site being loaded. - */ - void setUrlTitle(String url, String title) { - mUrl = url; - mTitle = title; - - // If we are in voice search mode, the title has already been set. - if (mTabControl.getCurrentTab().isInVoiceSearchMode()) return; - mTitleBar.setDisplayTitle(url); - mFakeTitleBar.setDisplayTitle(url); - } - - /** - * @param url The URL to build a title version of the URL from. - * @return The title version of the URL or null if fails. - * The title version of the URL can be either the URL hostname, - * or the hostname with an "https://" prefix (for secure URLs), - * or an empty string if, for example, the URL in question is a - * file:// URL with no hostname. - */ - /* package */ static String buildTitleUrl(String url) { - String titleUrl = null; - - if (url != null) { - try { - // parse the url string - URL urlObj = new URL(url); - if (urlObj != null) { - titleUrl = ""; - - String protocol = urlObj.getProtocol(); - String host = urlObj.getHost(); - - if (host != null && 0 < host.length()) { - titleUrl = host; - if (protocol != null) { - // if a secure site, add an "https://" prefix! - if (protocol.equalsIgnoreCase("https")) { - titleUrl = protocol + "://" + host; - } - } - } - } - } catch (MalformedURLException e) {} - } - - return titleUrl; - } - - // Set the favicon in the title bar. - void setFavicon(Bitmap icon) { - mTitleBar.setFavicon(icon); - mFakeTitleBar.setFavicon(icon); - } - - /** - * Close the tab, remove its associated title bar, and adjust mTabControl's - * current tab to a valid value. - */ - /* package */ void closeTab(Tab t) { - int currentIndex = mTabControl.getCurrentIndex(); - int removeIndex = mTabControl.getTabIndex(t); - mTabControl.removeTab(t); - if (currentIndex >= removeIndex && currentIndex != 0) { - currentIndex--; - } - mTabControl.setCurrentTab(mTabControl.getTab(currentIndex)); - resetTitleIconAndProgress(); - updateLockIconToLatest(); - - if (!mTabControl.hasAnyOpenIncognitoTabs()) { - WebView.cleanupPrivateBrowsingFiles(this); - } - } - - /* package */ void goBackOnePageOrQuit() { - Tab current = mTabControl.getCurrentTab(); - if (current == null) { - /* - * Instead of finishing the activity, simply push this to the back - * of the stack and let ActivityManager to choose the foreground - * activity. As BrowserActivity is singleTask, it will be always the - * root of the task. So we can use either true or false for - * moveTaskToBack(). - */ - moveTaskToBack(true); - return; - } - WebView w = current.getWebView(); - if (w.canGoBack()) { - w.goBack(); - } else { - // Check to see if we are closing a window that was created by - // another window. If so, we switch back to that window. - Tab parent = current.getParentTab(); - if (parent != null) { - switchToTab(mTabControl.getTabIndex(parent)); - // Now we close the other tab - closeTab(current); - } else { - if (current.closeOnExit()) { - // force the tab's inLoad() to be false as we are going to - // either finish the activity or remove the tab. This will - // ensure pauseWebViewTimers() taking action. - mTabControl.getCurrentTab().clearInLoad(); - if (mTabControl.getTabCount() == 1) { - finish(); - return; - } - // call pauseWebViewTimers() now, we won't be able to call - // it in onPause() as the WebView won't be valid. - // Temporarily change mActivityInPause to be true as - // pauseWebViewTimers() will do nothing if mActivityInPause - // is false. - boolean savedState = mActivityInPause; - if (savedState) { - Log.e(LOGTAG, "BrowserActivity is already paused " - + "while handing goBackOnePageOrQuit."); - } - mActivityInPause = true; - pauseWebViewTimers(); - mActivityInPause = savedState; - removeTabFromContentView(current); - mTabControl.removeTab(current); - } - /* - * Instead of finishing the activity, simply push this to the back - * of the stack and let ActivityManager to choose the foreground - * activity. As BrowserActivity is singleTask, it will be always the - * root of the task. So we can use either true or false for - * moveTaskToBack(). - */ - moveTaskToBack(true); - } - } - } - - boolean isMenuDown() { - return mMenuIsDown; + @Override + public boolean onContextItemSelected(MenuItem item) { + return mController.onContextItemSelected(item); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - // Even if MENU is already held down, we need to call to super to open - // the IME on long press. - if (KeyEvent.KEYCODE_MENU == keyCode) { - mMenuIsDown = true; - return super.onKeyDown(keyCode, 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_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 { - getTopWindow().pageDown(false); - } - return true; - case KeyEvent.KEYCODE_BACK: - if (event.getRepeatCount() == 0) { - event.startTracking(); - return true; - } else if (mCustomView == null && mActiveTabsPage == null - && mComboView == null - && event.isLongPress()) { - bookmarksOrHistoryPicker(true); - return true; - } - break; - } - return super.onKeyDown(keyCode, event); + return mController.onKeyDown(keyCode, event) || + super.onKeyDown(keyCode, event); } @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 - mTabControl.getCurrentWebView().getWebChromeClient() - .onHideCustomView(); - } else if (mActiveTabsPage != null) { - // if tab page is showing, hide it - removeActiveTabPage(true); - } else if (mComboView != null) { - if (!mComboView.onBackPressed()) { - removeComboView(); - } - } else { - WebView subwindow = mTabControl.getCurrentSubWindow(); - if (subwindow != null) { - if (subwindow.canGoBack()) { - subwindow.goBack(); - } else { - dismissSubWindow(mTabControl.getCurrentTab()); - } - } else { - goBackOnePageOrQuit(); - } - } - return true; - } - break; - } - return super.onKeyUp(keyCode, event); - } - - /* package */ void stopLoading() { - mDidStopLoad = true; - resetTitleAndRevertLockIcon(); - WebView w = getTopWindow(); - w.stopLoading(); - // FIXME: before refactor, it is using mWebViewClient. So I keep the - // same logic here. But for subwindow case, should we call into the main - // WebView's onPageFinished as we never call its onPageStarted and if - // the page finishes itself, we don't call onPageFinished. - mTabControl.getCurrentWebView().getWebViewClient().onPageFinished(w, - w.getUrl()); - - cancelStopToast(); - mStopToast = Toast - .makeText(this, R.string.stopping, Toast.LENGTH_SHORT); - mStopToast.show(); - } - - boolean didUserStopLoading() { - return mDidStopLoad; - } - - private void cancelStopToast() { - if (mStopToast != null) { - mStopToast.cancel(); - mStopToast = null; - } - } - - // called by a UI or non-UI thread to post the message - public void postMessage(int what, int arg1, int arg2, Object obj, - long delayMillis) { - mHandler.sendMessageDelayed(mHandler.obtainMessage(what, arg1, arg2, - obj), delayMillis); - } - - // called by a UI or non-UI thread to remove the message - void removeMessages(int what, Object object) { - mHandler.removeMessages(what, object); - } - - // public message ids - public final static int LOAD_URL = 1001; - public final static int STOP_LOAD = 1002; - - // Message Ids - private static final int FOCUS_NODE_HREF = 102; - private static final int RELEASE_WAKELOCK = 107; - - static final int UPDATE_BOOKMARK_THUMBNAIL = 108; - - private static final int OPEN_BOOKMARKS = 201; - - // Private handler for handling javascript and saving passwords - private Handler mHandler = new Handler() { - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case OPEN_BOOKMARKS: - bookmarksOrHistoryPicker(false); - break; - case FOCUS_NODE_HREF: - { - String url = (String) msg.getData().get("url"); - String title = (String) msg.getData().get("title"); - if (url == null || url.length() == 0) { - break; - } - HashMap focusNodeMap = (HashMap) msg.obj; - WebView view = (WebView) focusNodeMap.get("webview"); - // Only apply the action if the top window did not change. - if (getTopWindow() != view) { - break; - } - switch (msg.arg1) { - case R.id.open_context_menu_id: - case R.id.view_image_context_menu_id: - loadUrlFromContext(getTopWindow(), url); - break; - case R.id.bookmark_context_menu_id: - Intent intent = new Intent(BrowserActivity.this, - AddBookmarkPage.class); - intent.putExtra(BrowserContract.Bookmarks.URL, url); - intent.putExtra(BrowserContract.Bookmarks.TITLE, - title); - startActivity(intent); - break; - case R.id.share_link_context_menu_id: - sharePage(BrowserActivity.this, title, url, null, - null); - break; - case R.id.copy_link_context_menu_id: - copy(url); - break; - case R.id.save_link_context_menu_id: - case R.id.download_context_menu_id: - onDownloadStartNoStream(url, null, null, null, -1); - break; - } - break; - } - - case LOAD_URL: - loadUrlFromContext(getTopWindow(), (String) msg.obj); - break; - - case STOP_LOAD: - stopLoading(); - break; - - case RELEASE_WAKELOCK: - if (mWakeLock.isHeld()) { - mWakeLock.release(); - // if we reach here, Browser should be still in the - // background loading after WAKELOCK_TIMEOUT (5-min). - // To avoid burning the battery, stop loading. - mTabControl.stopAllLoading(); - } - break; - - case UPDATE_BOOKMARK_THUMBNAIL: - WebView view = (WebView) msg.obj; - if (view != null) { - updateScreenshot(view); - } - break; - } - } - }; - - /** - * Share a page, providing the title, url, favicon, and a screenshot. Uses - * an {@link Intent} to launch the Activity chooser. - * @param c Context used to launch a new Activity. - * @param title Title of the page. Stored in the Intent with - * {@link Intent#EXTRA_SUBJECT} - * @param url URL of the page. Stored in the Intent with - * {@link Intent#EXTRA_TEXT} - * @param favicon Bitmap of the favicon for the page. Stored in the Intent - * with {@link Browser#EXTRA_SHARE_FAVICON} - * @param screenshot Bitmap of a screenshot of the page. Stored in the - * Intent with {@link Browser#EXTRA_SHARE_SCREENSHOT} - */ - public static final void sharePage(Context c, String title, String url, - Bitmap favicon, Bitmap screenshot) { - Intent send = new Intent(Intent.ACTION_SEND); - send.setType("text/plain"); - send.putExtra(Intent.EXTRA_TEXT, url); - send.putExtra(Intent.EXTRA_SUBJECT, title); - send.putExtra(Browser.EXTRA_SHARE_FAVICON, favicon); - send.putExtra(Browser.EXTRA_SHARE_SCREENSHOT, screenshot); - try { - c.startActivity(Intent.createChooser(send, c.getString( - R.string.choosertitle_sharevia))); - } catch(android.content.ActivityNotFoundException ex) { - // if no app handles it, do nothing - } - } - - private void updateScreenshot(WebView view) { - // If this is a bookmarked site, add a screenshot to the database. - // FIXME: When should we update? Every time? - // FIXME: Would like to make sure there is actually something to - // draw, but the API for that (WebViewCore.pictureReady()) is not - // currently accessible here. - - final Bitmap bm = createScreenshot(view, getDesiredThumbnailWidth(this), - getDesiredThumbnailHeight(this)); - if (bm == null) { - return; - } - - final ContentResolver cr = getContentResolver(); - final String url = view.getUrl(); - final String originalUrl = view.getOriginalUrl(); - - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... unused) { - Cursor cursor = null; - try { - cursor = Bookmarks.queryCombinedForUrl(cr, originalUrl, url); - if (cursor != null && cursor.moveToFirst()) { - final ByteArrayOutputStream os = new ByteArrayOutputStream(); - bm.compress(Bitmap.CompressFormat.PNG, 100, os); - - ContentValues values = new ContentValues(); - values.put(Images.THUMBNAIL, os.toByteArray()); - values.put(Images.URL, cursor.getString(0)); - - do { - cr.update(Images.CONTENT_URI, values, null, null); - } while (cursor.moveToNext()); - } - } catch (IllegalStateException e) { - // Ignore - } finally { - if (cursor != null) cursor.close(); - } - return null; - } - }.execute(); - } - - /** - * 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 desired width for thumbnail screenshot. - */ - /* package */ static int getDesiredThumbnailWidth(Context context) { - return context.getResources().getDimensionPixelOffset(R.dimen.bookmarkThumbnailWidth); - } - - /** - * 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 desired height for thumbnail screenshot. - */ - /* package */ static int getDesiredThumbnailHeight(Context context) { - return context.getResources().getDimensionPixelOffset(R.dimen.bookmarkThumbnailHeight); - } - - private Bitmap createScreenshot(WebView view, int width, int height) { - Picture thumbnail = view.capturePicture(); - if (thumbnail == null) { - return null; - } - Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); - Canvas canvas = new Canvas(bm); - // May need to tweak these values to determine what is the - // best scale factor - int thumbnailWidth = thumbnail.getWidth(); - int thumbnailHeight = thumbnail.getHeight(); - float scaleFactorX = 1.0f; - float scaleFactorY = 1.0f; - if (thumbnailWidth > 0) { - scaleFactorX = (float) width / (float)thumbnailWidth; - } else { - return null; - } - - if (view.getWidth() > view.getHeight() && - thumbnailHeight < view.getHeight() && thumbnailHeight > 0) { - // If the device is in landscape and the page is shorter - // than the height of the view, stretch the thumbnail to fill the - // space. - scaleFactorY = (float) height / (float)thumbnailHeight; - } else { - // In the portrait case, this looks nice. - scaleFactorY = scaleFactorX; - } - - canvas.scale(scaleFactorX, scaleFactorY); - - thumbnail.draw(canvas); - return bm; - } - - // ------------------------------------------------------------------------- - // Helper function for WebViewClient. - //------------------------------------------------------------------------- - - // Use in overrideUrlLoading - /* package */ final static String SCHEME_WTAI = "wtai://wp/"; - /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;"; - /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;"; - /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;"; - - // Keep this initial progress in sync with initialProgressValue (* 100) - // in ProgressTracker.cpp - private final static int INITIAL_PROGRESS = 10; - - void onPageStarted(WebView view, String url, Bitmap favicon) { - // when BrowserActivity just starts, onPageStarted may be called before - // onResume as it is triggered from onCreate. Call resumeWebViewTimers - // to start the timer. As we won't switch tabs while an activity is in - // pause state, we can ensure calling resume and pause in pair. - if (mActivityInPause) resumeWebViewTimers(); - - resetLockIcon(url); - setUrlTitle(url, null); - setFavicon(favicon); - // Show some progress so that the user knows the page is beginning to - // load - onProgressChanged(view, INITIAL_PROGRESS); - mDidStopLoad = false; - if (!mIsNetworkUp) createAndShowNetworkDialog(); - endActionMode(); - if (mSettings.isTracing()) { - String host; - try { - WebAddress uri = new WebAddress(url); - host = uri.getHost(); - } catch (android.net.ParseException ex) { - host = "browser"; - } - host = host.replace('.', '_'); - host += ".trace"; - mInTrace = true; - Debug.startMethodTracing(host, 20 * 1024 * 1024); - } - - // Performance probe - if (false) { - mStart = SystemClock.uptimeMillis(); - mProcessStart = Process.getElapsedCpuTime(); - long[] sysCpu = new long[7]; - if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null, - sysCpu, null)) { - mUserStart = sysCpu[0] + sysCpu[1]; - mSystemStart = sysCpu[2]; - mIdleStart = sysCpu[3]; - mIrqStart = sysCpu[4] + sysCpu[5] + sysCpu[6]; - } - mUiStart = SystemClock.currentThreadTimeMillis(); - } - } - - void onPageFinished(WebView view, String url) { - // Reset the title and icon in case we stopped a provisional load. - resetTitleAndIcon(view); - // Update the lock icon image only once we are done loading - updateLockIconToLatest(); - // pause the WebView timer and release the wake lock if it is finished - // while BrowserActivity is in pause state. - if (mActivityInPause && pauseWebViewTimers()) { - if (mWakeLock.isHeld()) { - mHandler.removeMessages(RELEASE_WAKELOCK); - mWakeLock.release(); - } - } - - // Performance probe - if (false) { - long[] sysCpu = new long[7]; - if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null, - sysCpu, null)) { - String uiInfo = "UI thread used " - + (SystemClock.currentThreadTimeMillis() - mUiStart) - + " ms"; - if (LOGD_ENABLED) { - Log.d(LOGTAG, uiInfo); - } - //The string that gets written to the log - String performanceString = "It took total " - + (SystemClock.uptimeMillis() - mStart) - + " ms clock time to load the page." - + "\nbrowser process used " - + (Process.getElapsedCpuTime() - mProcessStart) - + " ms, user processes used " - + (sysCpu[0] + sysCpu[1] - mUserStart) * 10 - + " ms, kernel used " - + (sysCpu[2] - mSystemStart) * 10 - + " ms, idle took " + (sysCpu[3] - mIdleStart) * 10 - + " ms and irq took " - + (sysCpu[4] + sysCpu[5] + sysCpu[6] - mIrqStart) - * 10 + " ms, " + uiInfo; - if (LOGD_ENABLED) { - Log.d(LOGTAG, performanceString + "\nWebpage: " + url); - } - if (url != null) { - // strip the url to maintain consistency - String newUrl = new String(url); - if (newUrl.startsWith("http://www.")) { - newUrl = newUrl.substring(11); - } else if (newUrl.startsWith("http://")) { - newUrl = newUrl.substring(7); - } else if (newUrl.startsWith("https://www.")) { - newUrl = newUrl.substring(12); - } else if (newUrl.startsWith("https://")) { - newUrl = newUrl.substring(8); - } - if (LOGD_ENABLED) { - Log.d(LOGTAG, newUrl + " loaded"); - } - } - } - } - - if (mInTrace) { - mInTrace = false; - Debug.stopMethodTracing(); - } - } - - private void closeEmptyChildTab() { - Tab current = mTabControl.getCurrentTab(); - if (current != null - && current.getWebView().copyBackForwardList().getSize() == 0) { - Tab parent = current.getParentTab(); - if (parent != null) { - switchToTab(mTabControl.getTabIndex(parent)); - closeTab(current); - } - } - } - - boolean shouldOverrideUrlLoading(WebView view, String url) { - if (view.isPrivateBrowsingEnabled()) { - // Don't allow urls to leave the browser app when in private browsing mode - loadUrl(view, url); - return true; - } - - if (url.startsWith(SCHEME_WTAI)) { - // wtai://wp/mc;number - // number=string(phone-number) - if (url.startsWith(SCHEME_WTAI_MC)) { - Intent intent = new Intent(Intent.ACTION_VIEW, - Uri.parse(WebView.SCHEME_TEL + - url.substring(SCHEME_WTAI_MC.length()))); - startActivity(intent); - // before leaving BrowserActivity, close the empty child tab. - // If a new tab is created through JavaScript open to load this - // url, we would like to close it as we will load this url in a - // different Activity. - closeEmptyChildTab(); - return true; - } - // wtai://wp/sd;dtmf - // dtmf=string(dialstring) - if (url.startsWith(SCHEME_WTAI_SD)) { - // TODO: only send when there is active voice connection - return false; - } - // wtai://wp/ap;number;name - // number=string(phone-number) - // name=string - if (url.startsWith(SCHEME_WTAI_AP)) { - // TODO - return false; - } - } - - // The "about:" schemes are internal to the browser; don't want these to - // be dispatched to other apps. - if (url.startsWith("about:")) { - return false; - } - - // If this is a Google search, attempt to add an RLZ string (if one isn't already present). - if (rlzProviderPresent()) { - Uri siteUri = Uri.parse(url); - if (needsRlzString(siteUri)) { - String rlz = null; - Cursor cur = null; - try { - cur = getContentResolver().query(getRlzUri(), null, null, null, null); - if (cur != null && cur.moveToFirst() && !cur.isNull(0)) { - url = siteUri.buildUpon() - .appendQueryParameter("rlz", cur.getString(0)) - .build().toString(); - } - } finally { - if (cur != null) { - cur.close(); - } - } - loadUrl(view, url); - return true; - } - } - - Intent intent; - // perform generic parsing of the URI to turn it into an Intent. - try { - intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); - } catch (URISyntaxException ex) { - Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage()); - return false; - } - - // check whether the intent can be resolved. If not, we will see - // whether we can download it from the Market. - if (getPackageManager().resolveActivity(intent, 0) == null) { - String packagename = intent.getPackage(); - if (packagename != null) { - intent = new Intent(Intent.ACTION_VIEW, Uri - .parse("market://search?q=pname:" + packagename)); - intent.addCategory(Intent.CATEGORY_BROWSABLE); - startActivity(intent); - // before leaving BrowserActivity, close the empty child tab. - // If a new tab is created through JavaScript open to load this - // url, we would like to close it as we will load this url in a - // different Activity. - closeEmptyChildTab(); - return true; - } else { - return false; - } - } - - // sanitize the Intent, ensuring web pages can not bypass browser - // security (only access to BROWSABLE activities). - intent.addCategory(Intent.CATEGORY_BROWSABLE); - intent.setComponent(null); - try { - if (startActivityIfNeeded(intent, -1)) { - // before leaving BrowserActivity, close the empty child tab. - // If a new tab is created through JavaScript open to load this - // url, we would like to close it as we will load this url in a - // different Activity. - closeEmptyChildTab(); - return true; - } - } catch (ActivityNotFoundException ex) { - // ignore the error. If no application can handle the URL, - // eg about:blank, assume the browser can handle it. - } - - if (mMenuIsDown) { - openTab(url, false); - closeOptionsMenu(); - return true; - } - return false; - } - - // Determine whether the RLZ provider is present on the system. - private boolean rlzProviderPresent() { - if (mIsProviderPresent == null) { - PackageManager pm = getPackageManager(); - mIsProviderPresent = pm.resolveContentProvider(BrowserSettings.RLZ_PROVIDER, 0) != null; - } - return mIsProviderPresent; - } - - // Retrieve the RLZ access point string and cache the URI used to retrieve RLZ values. - private Uri getRlzUri() { - if (mRlzUri == null) { - String ap = getResources().getString(R.string.rlz_access_point); - mRlzUri = Uri.withAppendedPath(BrowserSettings.RLZ_PROVIDER_URI, ap); - } - return mRlzUri; - } - - // Determine if this URI appears to be for a Google search and does not have an RLZ parameter. - // Taken largely from Chrome source, src/chrome/browser/google_url_tracker.cc - private static boolean needsRlzString(Uri uri) { - String scheme = uri.getScheme(); - if (("http".equals(scheme) || "https".equals(scheme)) && - (uri.getQueryParameter("q") != null) && (uri.getQueryParameter("rlz") == null)) { - String host = uri.getHost(); - if (host == null) { - return false; - } - String[] hostComponents = host.split("\\."); - - if (hostComponents.length < 2) { - return false; - } - int googleComponent = hostComponents.length - 2; - String component = hostComponents[googleComponent]; - if (!"google".equals(component)) { - if (hostComponents.length < 3 || - (!"co".equals(component) && !"com".equals(component))) { - return false; - } - googleComponent = hostComponents.length - 3; - if (!"google".equals(hostComponents[googleComponent])) { - return false; - } - } - - // Google corp network handling. - if (googleComponent > 0 && "corp".equals(hostComponents[googleComponent - 1])) { - return false; - } - - return true; - } - return false; - } - - // ------------------------------------------------------------------------- - // Helper function for WebChromeClient - // ------------------------------------------------------------------------- - - void onProgressChanged(WebView view, int newProgress) { - - // On the phone, the fake title bar will always cover up the - // regular title bar (or the regular one is offscreen), so only the - // fake title bar needs to change its progress - mFakeTitleBar.setProgress(newProgress); - - if (newProgress == 100) { - // 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) - if (mInLoad) { - mInLoad = false; - updateInLoadMenuItems(); - // If the options menu is open, leave the title bar - if (!mOptionsMenuOpen || !mIconView) { - hideFakeTitleBar(); - } - } - } else { - if (!mInLoad) { - // onPageFinished may have already been called but a subframe is - // still loading and updating the progress. Reset mInLoad and - // update the menu items. - mInLoad = true; - updateInLoadMenuItems(); - } - // When the page first begins to load, the Activity may still be - // paused, in which case showFakeTitleBar will do nothing. Call - // again as the page continues to load so that it will be shown. - // (Calling it will the fake title bar is already showing will also - // do nothing. - if (!mOptionsMenuOpen || mIconView) { - // This page has begun to load, so show the title bar - showFakeTitleBar(); - } - } - } - - void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) { - // if a view already exists then immediately terminate the new one - if (mCustomView != null) { - callback.onCustomViewHidden(); - return; - } - - // Add the custom view to its container. - mCustomViewContainer.addView(view, COVER_SCREEN_GRAVITY_CENTER); - mCustomView = view; - mCustomViewCallback = callback; - // Save the menu state and set it to empty while the custom - // view is showing. - mOldMenuState = mMenuState; - mMenuState = EMPTY_MENU; - // Hide the content view. - mContentView.setVisibility(View.GONE); - // Finally show the custom view container. - setStatusBarVisibility(false); - mCustomViewContainer.setVisibility(View.VISIBLE); - mCustomViewContainer.bringToFront(); - } - - void onHideCustomView() { - if (mCustomView == null) - return; - - // Hide the custom view. - mCustomView.setVisibility(View.GONE); - // Remove the custom view from its container. - mCustomViewContainer.removeView(mCustomView); - mCustomView = null; - // Reset the old menu state. - mMenuState = mOldMenuState; - mOldMenuState = EMPTY_MENU; - mCustomViewContainer.setVisibility(View.GONE); - mCustomViewCallback.onCustomViewHidden(); - // Show the content view. - setStatusBarVisibility(true); - mContentView.setVisibility(View.VISIBLE); - } - - Bitmap getDefaultVideoPoster() { - if (mDefaultVideoPoster == null) { - mDefaultVideoPoster = BitmapFactory.decodeResource( - getResources(), R.drawable.default_video_poster); - } - return mDefaultVideoPoster; - } - - View getVideoLoadingProgressView() { - if (mVideoProgressView == null) { - LayoutInflater inflater = LayoutInflater.from(BrowserActivity.this); - mVideoProgressView = inflater.inflate( - R.layout.video_loading_progress, null); - } - return mVideoProgressView; - } - - /* - * The Object used to inform the WebView of the file to upload. - */ - private ValueCallback<Uri> mUploadMessage; - private String mCameraFilePath; - - void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) { - - final String imageMimeType = "image/*"; - final String videoMimeType = "video/*"; - final String audioMimeType = "audio/*"; - final String mediaSourceKey = "capture"; - final String mediaSourceValueCamera = "camera"; - final String mediaSourceValueFileSystem = "filesystem"; - final String mediaSourceValueCamcorder = "camcorder"; - final String mediaSourceValueMicrophone = "microphone"; - - // media source can be 'filesystem' or 'camera' or 'camcorder' or 'microphone'. - String mediaSource = ""; - - // We add the camera intent if there was no accept type (or '*/*' or 'image/*'). - boolean addCameraIntent = true; - // We add the camcorder intent if there was no accept type (or '*/*' or 'video/*'). - boolean addCamcorderIntent = true; - - if (mUploadMessage != null) { - // Already a file picker operation in progress. - return; - } - - mUploadMessage = uploadMsg; - - // Parse the accept type. - String params[] = acceptType.split(";"); - String mimeType = params[0]; - - for (String p : params) { - String[] keyValue = p.split("="); - if (keyValue.length == 2) { - // Process key=value parameters. - if (mediaSourceKey.equals(keyValue[0])) { - mediaSource = keyValue[1]; - } - } - } - - // This intent will display the standard OPENABLE file picker. - Intent i = new Intent(Intent.ACTION_GET_CONTENT); - i.addCategory(Intent.CATEGORY_OPENABLE); - - // Create an intent to add to the standard file picker that will - // capture an image from the camera. We'll combine this intent with - // the standard OPENABLE picker unless the web developer specifically - // requested the camera or gallery be opened by passing a parameter - // in the accept type. - Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - File externalDataDir = Environment.getExternalStoragePublicDirectory( - Environment.DIRECTORY_DCIM); - File cameraDataDir = new File(externalDataDir.getAbsolutePath() + - File.separator + "browser-photos"); - cameraDataDir.mkdirs(); - mCameraFilePath = cameraDataDir.getAbsolutePath() + File.separator + - System.currentTimeMillis() + ".jpg"; - cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(mCameraFilePath))); - - Intent camcorderIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); - - Intent soundRecIntent = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION); - - if (mimeType.equals(imageMimeType)) { - i.setType(imageMimeType); - addCamcorderIntent = false; - if (mediaSource.equals(mediaSourceValueCamera)) { - // Specified 'image/*' and requested the camera, so go ahead and launch the camera - // directly. - BrowserActivity.this.startActivityForResult(cameraIntent, FILE_SELECTED); - return; - } else if (mediaSource.equals(mediaSourceValueFileSystem)) { - // Specified filesytem as the source, so don't want to consider the camera. - addCameraIntent = false; - } - } else if (mimeType.equals(videoMimeType)) { - i.setType(videoMimeType); - addCameraIntent = false; - // The camcorder saves it's own file and returns it to us in the intent, so - // we don't need to generate one here. - mCameraFilePath = null; - - if (mediaSource.equals(mediaSourceValueCamcorder)) { - // Specified 'video/*' and requested the camcorder, so go ahead and launch the - // camcorder directly. - BrowserActivity.this.startActivityForResult(camcorderIntent, FILE_SELECTED); - return; - } else if (mediaSource.equals(mediaSourceValueFileSystem)) { - // Specified filesystem as the source, so don't want to consider the camcorder. - addCamcorderIntent = false; - } - } else if (mimeType.equals(audioMimeType)) { - i.setType(audioMimeType); - addCameraIntent = false; - addCamcorderIntent = false; - if (mediaSource.equals(mediaSourceValueMicrophone)) { - // Specified 'audio/*' and requested microphone, so go ahead and launch the sound - // recorder. - BrowserActivity.this.startActivityForResult(soundRecIntent, FILE_SELECTED); - return; - } - // On a default system, there is no single option to open an audio "gallery". Both the - // sound recorder and music browser respond to the OPENABLE/audio/* intent unlike the - // image/* and video/* OPENABLE intents where the image / video gallery are the only - // respondants (and so the user is not prompted by default). - } else { - i.setType("*/*"); - } - - // Combine the chooser and the extra choices (like camera or camcorder) - Intent chooser = new Intent(Intent.ACTION_CHOOSER); - chooser.putExtra(Intent.EXTRA_INTENT, i); - - Vector<Intent> extraInitialIntents = new Vector<Intent>(0); - - if (addCameraIntent) { - extraInitialIntents.add(cameraIntent); - } - - if (addCamcorderIntent) { - extraInitialIntents.add(camcorderIntent); - } - - if (extraInitialIntents.size() > 0) { - Intent[] extraIntents = new Intent[extraInitialIntents.size()]; - chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraInitialIntents.toArray(extraIntents)); - } - - chooser.putExtra(Intent.EXTRA_TITLE, getString(R.string.choose_upload)); - BrowserActivity.this.startActivityForResult(chooser, FILE_SELECTED); - } - - // ------------------------------------------------------------------------- - // Implement functions for DownloadListener - // ------------------------------------------------------------------------- - - /** - * Notify the host application a download should be done, or that - * the data should be streamed if a streaming viewer is available. - * @param url The full url to the content that should be downloaded - * @param contentDisposition Content-disposition http header, if - * present. - * @param mimetype The mimetype of the content reported by the server - * @param contentLength The file size reported by the server - */ - public void onDownloadStart(String url, String userAgent, - String contentDisposition, String mimetype, long contentLength) { - // if we're dealing wih A/V content that's not explicitly marked - // for download, check if it's streamable. - if (contentDisposition == null - || !contentDisposition.regionMatches( - true, 0, "attachment", 0, 10)) { - // query the package manager to see if there's a registered handler - // that matches. - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(Uri.parse(url), mimetype); - ResolveInfo info = getPackageManager().resolveActivity(intent, - PackageManager.MATCH_DEFAULT_ONLY); - if (info != null) { - ComponentName myName = getComponentName(); - // If we resolved to ourselves, we don't want to attempt to - // load the url only to try and download it again. - if (!myName.getPackageName().equals( - info.activityInfo.packageName) - || !myName.getClassName().equals( - info.activityInfo.name)) { - // someone (other than us) knows how to handle this mime - // type with this scheme, don't download. - try { - startActivity(intent); - return; - } catch (ActivityNotFoundException ex) { - if (LOGD_ENABLED) { - Log.d(LOGTAG, "activity not found for " + mimetype - + " over " + Uri.parse(url).getScheme(), - ex); - } - // Best behavior is to fall back to a download in this - // case - } - } - } - } - onDownloadStartNoStream(url, userAgent, contentDisposition, mimetype, contentLength); - } - - // This is to work around the fact that java.net.URI throws Exceptions - // instead of just encoding URL's properly - // Helper method for onDownloadStartNoStream - private static String encodePath(String path) { - char[] chars = path.toCharArray(); - - boolean needed = false; - for (char c : chars) { - if (c == '[' || c == ']') { - needed = true; - break; - } - } - if (needed == false) { - return path; - } - - StringBuilder sb = new StringBuilder(""); - for (char c : chars) { - if (c == '[' || c == ']') { - sb.append('%'); - sb.append(Integer.toHexString(c)); - } else { - sb.append(c); - } - } - - return sb.toString(); - } - - /** - * Notify the host application a download should be done, even if there - * is a streaming viewer available for thise type. - * @param url The full url to the content that should be downloaded - * @param contentDisposition Content-disposition http header, if - * present. - * @param mimetype The mimetype of the content reported by the server - * @param contentLength The file size reported by the server - */ - /*package */ void onDownloadStartNoStream(String url, String userAgent, - String contentDisposition, String mimetype, long contentLength) { - - String filename = URLUtil.guessFileName(url, - contentDisposition, mimetype); - - // Check to see if we have an SDCard - String status = Environment.getExternalStorageState(); - if (!status.equals(Environment.MEDIA_MOUNTED)) { - int title; - String msg; - - // Check to see if the SDCard is busy, same as the music app - if (status.equals(Environment.MEDIA_SHARED)) { - msg = getString(R.string.download_sdcard_busy_dlg_msg); - title = R.string.download_sdcard_busy_dlg_title; - } else { - msg = getString(R.string.download_no_sdcard_dlg_msg, filename); - title = R.string.download_no_sdcard_dlg_title; - } - - new AlertDialog.Builder(this) - .setTitle(title) - .setIcon(android.R.drawable.ic_dialog_alert) - .setMessage(msg) - .setPositiveButton(R.string.ok, null) - .show(); - return; - } - - // java.net.URI is a lot stricter than KURL so we have to encode some - // extra characters. Fix for b 2538060 and b 1634719 - WebAddress webAddress; - try { - webAddress = new WebAddress(url); - webAddress.setPath(encodePath(webAddress.getPath())); - } catch (Exception e) { - // This only happens for very bad urls, we want to chatch the - // exception here - Log.e(LOGTAG, "Exception trying to parse url:" + url); - return; - } - - String addressString = webAddress.toString(); - Uri uri = Uri.parse(addressString); - DownloadManager.Request request = new DownloadManager.Request(uri); - request.setMimeType(mimetype); - request.setDestinationInExternalFilesDir(this, null, filename); - // let this downloaded file be scanned by MediaScanner - so that it can show up - // in Gallery app, for example. - request.allowScanningByMediaScanner(); - request.setDescription(webAddress.getHost()); - String cookies = CookieManager.getInstance().getCookie(url); - request.addRequestHeader("cookie", cookies); - request.setNotificationVisibility( - DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); - if (mimetype == null) { - ContentValues values = new ContentValues(); - values.put(FetchUrlMimeType.URI, addressString); - // XXX: Have to use the old url since the cookies were stored using the - // old percent-encoded url. - values.put(FetchUrlMimeType.COOKIE_DATA, cookies); - values.put(FetchUrlMimeType.USER_AGENT, userAgent); - - // We must have long pressed on a link or image to download it. We - // are not sure of the mimetype in this case, so do a head request - new FetchUrlMimeType(this, request).execute(values); - } else { - DownloadManager manager - = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE); - manager.enqueue(request); - } - Toast.makeText(this, R.string.download_pending, Toast.LENGTH_SHORT) - .show(); - } - - // ------------------------------------------------------------------------- - - /** - * Resets the lock icon. This method is called when we start a new load and - * know the url to be loaded. - */ - private void resetLockIcon(String url) { - // Save the lock-icon state (we revert to it if the load gets cancelled) - mTabControl.getCurrentTab().resetLockIcon(url); - updateLockIconImage(LOCK_ICON_UNSECURE); - } - - /** - * Update the lock icon to correspond to our latest state. - */ - private void updateLockIconToLatest() { - Tab t = mTabControl.getCurrentTab(); - if (t != null) { - updateLockIconImage(t.getLockIconType()); - } - } - - /** - * Updates the lock-icon image in the title-bar. - */ - private void updateLockIconImage(int lockIconType) { - Drawable d = null; - if (lockIconType == LOCK_ICON_SECURE) { - d = mSecLockIcon; - } else if (lockIconType == LOCK_ICON_MIXED) { - d = mMixLockIcon; - } - mTitleBar.setLock(d); - mFakeTitleBar.setLock(d); - } - - /** - * Displays a page-info dialog. - * @param tab The tab to show info about - * @param fromShowSSLCertificateOnError The flag that indicates whether - * this dialog was opened from the SSL-certificate-on-error dialog or - * not. This is important, since we need to know whether to return to - * the parent dialog or simply dismiss. - */ - private void showPageInfo(final Tab tab, - final boolean fromShowSSLCertificateOnError) { - final LayoutInflater factory = LayoutInflater - .from(this); - - final View pageInfoView = factory.inflate(R.layout.page_info, null); - - final WebView view = tab.getWebView(); - - String url = null; - String title = null; - - if (view == null) { - url = tab.getUrl(); - title = tab.getTitle(); - } else if (view == mTabControl.getCurrentWebView()) { - // Use the cached title and url if this is the current WebView - url = mUrl; - title = mTitle; - } else { - url = view.getUrl(); - title = view.getTitle(); - } - - if (url == null) { - url = ""; - } - if (title == null) { - title = ""; - } - - ((TextView) pageInfoView.findViewById(R.id.address)).setText(url); - ((TextView) pageInfoView.findViewById(R.id.title)).setText(title); - - mPageInfoView = tab; - mPageInfoFromShowSSLCertificateOnError = fromShowSSLCertificateOnError; - - AlertDialog.Builder alertDialogBuilder = - new AlertDialog.Builder(this) - .setTitle(R.string.page_info).setIcon(android.R.drawable.ic_dialog_info) - .setView(pageInfoView) - .setPositiveButton( - R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, - int whichButton) { - mPageInfoDialog = null; - mPageInfoView = null; - - // if we came here from the SSL error dialog - if (fromShowSSLCertificateOnError) { - // go back to the SSL error dialog - showSSLCertificateOnError( - mSSLCertificateOnErrorView, - mSSLCertificateOnErrorHandler, - mSSLCertificateOnErrorError); - } - } - }) - .setOnCancelListener( - new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - mPageInfoDialog = null; - mPageInfoView = null; - - // if we came here from the SSL error dialog - if (fromShowSSLCertificateOnError) { - // go back to the SSL error dialog - showSSLCertificateOnError( - mSSLCertificateOnErrorView, - mSSLCertificateOnErrorHandler, - mSSLCertificateOnErrorError); - } - } - }); - - // if we have a main top-level page SSL certificate set or a certificate - // error - if (fromShowSSLCertificateOnError || - (view != null && view.getCertificate() != null)) { - // add a 'View Certificate' button - alertDialogBuilder.setNeutralButton( - R.string.view_certificate, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, - int whichButton) { - mPageInfoDialog = null; - mPageInfoView = null; - - // if we came here from the SSL error dialog - if (fromShowSSLCertificateOnError) { - // go back to the SSL error dialog - showSSLCertificateOnError( - mSSLCertificateOnErrorView, - mSSLCertificateOnErrorHandler, - mSSLCertificateOnErrorError); - } else { - // otherwise, display the top-most certificate from - // the chain - if (view.getCertificate() != null) { - showSSLCertificate(tab); - } - } - } - }); - } - - mPageInfoDialog = alertDialogBuilder.show(); - } - - /** - * Displays the main top-level page SSL certificate dialog - * (accessible from the Page-Info dialog). - * @param tab The tab to show certificate for. - */ - private void showSSLCertificate(final Tab tab) { - final View certificateView = - inflateCertificateView(tab.getWebView().getCertificate()); - if (certificateView == null) { - return; - } - - LayoutInflater factory = LayoutInflater.from(this); - - final LinearLayout placeholder = - (LinearLayout)certificateView.findViewById(R.id.placeholder); - - LinearLayout ll = (LinearLayout) factory.inflate( - R.layout.ssl_success, placeholder); - ((TextView)ll.findViewById(R.id.success)) - .setText(R.string.ssl_certificate_is_valid); - - mSSLCertificateView = tab; - mSSLCertificateDialog = - new AlertDialog.Builder(this) - .setTitle(R.string.ssl_certificate).setIcon( - R.drawable.ic_dialog_browser_certificate_secure) - .setView(certificateView) - .setPositiveButton(R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, - int whichButton) { - mSSLCertificateDialog = null; - mSSLCertificateView = null; - - showPageInfo(tab, false); - } - }) - .setOnCancelListener( - new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - mSSLCertificateDialog = null; - mSSLCertificateView = null; - - showPageInfo(tab, false); - } - }) - .show(); - } - - /** - * Displays the SSL error certificate dialog. - * @param view The target web-view. - * @param handler The SSL error handler responsible for cancelling the - * connection that resulted in an SSL error or proceeding per user request. - * @param error The SSL error object. - */ - void showSSLCertificateOnError( - final WebView view, final SslErrorHandler handler, final SslError error) { - - final View certificateView = - inflateCertificateView(error.getCertificate()); - if (certificateView == null) { - return; - } - - LayoutInflater factory = LayoutInflater.from(this); - - final LinearLayout placeholder = - (LinearLayout)certificateView.findViewById(R.id.placeholder); - - if (error.hasError(SslError.SSL_UNTRUSTED)) { - LinearLayout ll = (LinearLayout)factory - .inflate(R.layout.ssl_warning, placeholder); - ((TextView)ll.findViewById(R.id.warning)) - .setText(R.string.ssl_untrusted); - } - - if (error.hasError(SslError.SSL_IDMISMATCH)) { - LinearLayout ll = (LinearLayout)factory - .inflate(R.layout.ssl_warning, placeholder); - ((TextView)ll.findViewById(R.id.warning)) - .setText(R.string.ssl_mismatch); - } - - if (error.hasError(SslError.SSL_EXPIRED)) { - LinearLayout ll = (LinearLayout)factory - .inflate(R.layout.ssl_warning, placeholder); - ((TextView)ll.findViewById(R.id.warning)) - .setText(R.string.ssl_expired); - } - - if (error.hasError(SslError.SSL_NOTYETVALID)) { - LinearLayout ll = (LinearLayout)factory - .inflate(R.layout.ssl_warning, placeholder); - ((TextView)ll.findViewById(R.id.warning)) - .setText(R.string.ssl_not_yet_valid); - } - - mSSLCertificateOnErrorHandler = handler; - mSSLCertificateOnErrorView = view; - mSSLCertificateOnErrorError = error; - mSSLCertificateOnErrorDialog = - new AlertDialog.Builder(this) - .setTitle(R.string.ssl_certificate).setIcon( - R.drawable.ic_dialog_browser_certificate_partially_secure) - .setView(certificateView) - .setPositiveButton(R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, - int whichButton) { - mSSLCertificateOnErrorDialog = null; - mSSLCertificateOnErrorView = null; - mSSLCertificateOnErrorHandler = null; - mSSLCertificateOnErrorError = null; - - view.getWebViewClient().onReceivedSslError( - view, handler, error); - } - }) - .setNeutralButton(R.string.page_info_view, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, - int whichButton) { - mSSLCertificateOnErrorDialog = null; - - // do not clear the dialog state: we will - // need to show the dialog again once the - // user is done exploring the page-info details - - showPageInfo(mTabControl.getTabFromView(view), - true); - } - }) - .setOnCancelListener( - new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - mSSLCertificateOnErrorDialog = null; - mSSLCertificateOnErrorView = null; - mSSLCertificateOnErrorHandler = null; - mSSLCertificateOnErrorError = null; - - view.getWebViewClient().onReceivedSslError( - view, handler, error); - } - }) - .show(); + return mController.onKeyUp(keyCode, event) || + super.onKeyUp(keyCode, event); } - /** - * Inflates the SSL certificate view (helper method). - * @param certificate The SSL certificate. - * @return The resultant certificate view with issued-to, issued-by, - * issued-on, expires-on, and possibly other fields set. - * If the input certificate is null, returns null. - */ - private View inflateCertificateView(SslCertificate certificate) { - if (certificate == null) { - return null; - } - - LayoutInflater factory = LayoutInflater.from(this); - - View certificateView = factory.inflate( - R.layout.ssl_certificate, null); - - // issued to: - SslCertificate.DName issuedTo = certificate.getIssuedTo(); - if (issuedTo != null) { - ((TextView) certificateView.findViewById(R.id.to_common)) - .setText(issuedTo.getCName()); - ((TextView) certificateView.findViewById(R.id.to_org)) - .setText(issuedTo.getOName()); - ((TextView) certificateView.findViewById(R.id.to_org_unit)) - .setText(issuedTo.getUName()); - } - - // issued by: - SslCertificate.DName issuedBy = certificate.getIssuedBy(); - if (issuedBy != null) { - ((TextView) certificateView.findViewById(R.id.by_common)) - .setText(issuedBy.getCName()); - ((TextView) certificateView.findViewById(R.id.by_org)) - .setText(issuedBy.getOName()); - ((TextView) certificateView.findViewById(R.id.by_org_unit)) - .setText(issuedBy.getUName()); - } - - // issued on: - String issuedOn = formatCertificateDate( - certificate.getValidNotBeforeDate()); - ((TextView) certificateView.findViewById(R.id.issued_on)) - .setText(issuedOn); - - // expires on: - String expiresOn = formatCertificateDate( - certificate.getValidNotAfterDate()); - ((TextView) certificateView.findViewById(R.id.expires_on)) - .setText(expiresOn); - - return certificateView; - } - - /** - * Formats the certificate date to a properly localized date string. - * @return Properly localized version of the certificate date string and - * the "" if it fails to localize. - */ - private String formatCertificateDate(Date certificateDate) { - if (certificateDate == null) { - return ""; - } - String formattedDate = DateFormat.getDateFormat(this).format(certificateDate); - if (formattedDate == null) { - return ""; - } - return formattedDate; - } - - /** - * Displays an http-authentication dialog. - */ - void showHttpAuthentication(final HttpAuthHandler handler, String host, String realm) { - mHttpAuthenticationDialog = new HttpAuthenticationDialog(this, host, realm); - mHttpAuthenticationDialog.setOkListener(new HttpAuthenticationDialog.OkListener() { - public void onOk(String host, String realm, String username, String password) { - BrowserActivity.this.setHttpAuthUsernamePassword(host, realm, username, password); - handler.proceed(username, password); - mHttpAuthenticationDialog = null; - } - }); - mHttpAuthenticationDialog.setCancelListener(new HttpAuthenticationDialog.CancelListener() { - public void onCancel() { - handler.cancel(); - BrowserActivity.this.resetTitleAndRevertLockIcon(); - mHttpAuthenticationDialog = null; - } - }); - mHttpAuthenticationDialog.show(); - } - - public int getProgress() { - WebView w = mTabControl.getCurrentWebView(); - if (w != null) { - return w.getProgress(); - } else { - return 100; - } - } - - /** - * Set HTTP authentication password. - * - * @param host The host for the password - * @param realm The realm for the password - * @param username The username for the password. If it is null, it means - * password can't be saved. - * @param password The password - */ - public void setHttpAuthUsernamePassword(String host, String realm, - String username, - String password) { - WebView w = getTopWindow(); - if (w != null) { - w.setHttpAuthUsernamePassword(host, realm, username, password); - } - } - - /** - * connectivity manager says net has come or gone... inform the user - * @param up true if net has come up, false if net has gone down - */ - public void onNetworkToggle(boolean up) { - if (up == mIsNetworkUp) { - return; - } else if (up) { - mIsNetworkUp = true; - if (mAlertDialog != null) { - mAlertDialog.cancel(); - mAlertDialog = null; - } - } else { - mIsNetworkUp = false; - if (mInLoad) { - createAndShowNetworkDialog(); - } - } - WebView w = mTabControl.getCurrentWebView(); - if (w != null) { - w.setNetworkAvailable(up); - } - } - - boolean isNetworkUp() { - return mIsNetworkUp; - } - - // This method shows the network dialog alerting the user that the net is - // down. It will only show the dialog if mAlertDialog is null. - private void createAndShowNetworkDialog() { - if (mAlertDialog == null) { - mAlertDialog = new AlertDialog.Builder(this) - .setTitle(R.string.loadSuspendedTitle) - .setMessage(R.string.loadSuspended) - .setPositiveButton(R.string.ok, null) - .show(); - } - } - - /** - * callback from ComboPage when bookmark/history selection - */ @Override - public void onUrlSelected(String url, boolean newTab) { - removeComboView(); - if (!TextUtils.isEmpty(url)) { - if (newTab) { - openTab(url, false); - } else { - final Tab currentTab = mTabControl.getCurrentTab(); - dismissSubWindow(currentTab); - loadUrl(getTopWindow(), url); - } - } + public void onActionModeStarted(ActionMode mode) { + super.onActionModeStarted(mode); + mController.onActionModeStarted(mode); } - /** - * callback from ComboPage when dismissed - */ @Override - public void onComboCanceled() { - removeComboView(); - } - - /** - * dismiss the ComboPage - */ - /* package */ void removeComboView() { - if (mComboView != null) { - mContentView.removeView(mComboView); - mTitleBar.setVisibility(View.VISIBLE); - mMenuState = R.id.MAIN_MENU; - attachTabToContentView(mTabControl.getCurrentTab()); - getTopWindow().requestFocus(); - mComboView = null; - } - } - - /** - * callback from ComboPage when clear history is requested - */ - public void onRemoveParentChildRelationships() { - mTabControl.removeParentChildRelationShips(); + public void onActionModeFinished(ActionMode mode) { + super.onActionModeFinished(mode); + mController.onActionModeFinished(mode); } @Override protected void onActivityResult(int requestCode, int resultCode, - Intent intent) { - if (getTopWindow() == null) return; - switch (requestCode) { - case PREFERENCES_PAGE: - if (resultCode == RESULT_OK && intent != null) { - String action = intent.getStringExtra(Intent.EXTRA_TEXT); - if (BrowserSettings.PREF_CLEAR_HISTORY.equals(action)) { - mTabControl.removeParentChildRelationShips(); - } - } - break; - // Choose a file from the file picker. - case FILE_SELECTED: - if (null == mUploadMessage) break; - Uri result = intent == null || resultCode != RESULT_OK ? null - : intent.getData(); - - // As we ask the camera to save the result of the user taking - // a picture, the camera application does not return anything other - // than RESULT_OK. So we need to check whether the file we expected - // was written to disk in the in the case that we - // did not get an intent returned but did get a RESULT_OK. If it was, - // we assume that this result has came back from the camera. - if (result == null && intent == null && resultCode == RESULT_OK) { - File cameraFile = new File(mCameraFilePath); - if (cameraFile.exists()) { - result = Uri.fromFile(cameraFile); - // Broadcast to the media scanner that we have a new photo - // so it will be added into the gallery for the user. - sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result)); - } - } - mUploadMessage.onReceiveValue(result); - mUploadMessage = null; - mCameraFilePath = null; - break; - default: - break; - } - getTopWindow().requestFocus(); + Intent intent) { + mController.onActivityResult(requestCode, resultCode, intent); } - /* - * This method is called as a result of the user selecting the options - * menu to see the download window. It shows the download window on top of - * the current window. - */ - private void viewDownloads() { - Intent intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS); - startActivity(intent); - } - - /** - * Open the Go page. - * @param startWithHistory If true, open starting on the history tab. - * Otherwise, start with the bookmarks tab. - */ - /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory) { - if (mTabControl.getCurrentWebView() == null) { - return; - } - Bundle extras = new Bundle(); - // Disable opening in a new window if we have maxed out the windows - extras.putBoolean(BrowserBookmarksPage.EXTRA_DISABLE_WINDOW, - !mTabControl.canCreateNewTab()); - - mComboView = new CombinedBookmarkHistoryView(this, - startWithHistory ? CombinedBookmarkHistoryView.FRAGMENT_ID_HISTORY - : CombinedBookmarkHistoryView.FRAGMENT_ID_BOOKMARKS, - extras); - removeTabFromContentView(mTabControl.getCurrentTab()); - mTitleBar.setVisibility(View.GONE); - hideFakeTitleBar(); - mContentView.addView(mComboView, COVER_SCREEN_PARAMS); - } - - // Called when loading from context menu or LOAD_URL message - private void loadUrlFromContext(WebView view, String url) { - // In case the user enters nothing. - if (url != null && url.length() != 0 && view != null) { - url = smartUrlFilter(url); - if (!view.getWebViewClient().shouldOverrideUrlLoading(view, url)) { - loadUrl(view, url); - } - } - } - - /** - * Load the URL into the given WebView and update the title bar - * to reflect the new load. Call this instead of WebView.loadUrl - * directly. - * @param view The WebView used to load url. - * @param url The URL to load. - */ - private void loadUrl(WebView view, String url) { - updateTitleBarForNewLoad(view, url); - view.loadUrl(url); - } - - /** - * Load UrlData into a Tab and update the title bar to reflect the new - * load. Call this instead of UrlData.loadIn directly. - * @param t The Tab used to load. - * @param data The UrlData being loaded. - */ - private void loadUrlDataIn(Tab t, UrlData data) { - updateTitleBarForNewLoad(t.getWebView(), data.mUrl); - data.loadIn(t); - } - - /** - * If the WebView is the top window, update the title bar to reflect - * loading the new URL. i.e. set its text, clear the favicon (which - * will be set once the page begins loading), and set the progress to - * INITIAL_PROGRESS to show that the page has begun to load. Called - * by loadUrl and loadUrlDataIn. - * @param view The WebView that is starting a load. - * @param url The URL that is being loaded. - */ - private void updateTitleBarForNewLoad(WebView view, String url) { - if (view == getTopWindow()) { - setUrlTitle(url, null); - setFavicon(null); - onProgressChanged(view, INITIAL_PROGRESS); - } - } - - private String smartUrlFilter(Uri inUri) { - if (inUri != null) { - return smartUrlFilter(inUri.toString()); - } - return null; - } - - protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile( - "(?i)" + // switch on case insensitive matching - "(" + // begin group for schema - "(?:http|https|file):\\/\\/" + - "|(?:inline|data|about|content|javascript):" + - ")" + - "(.*)" ); - - /** - * Attempts to determine whether user input is a URL or search - * terms. Anything with a space is passed to search. - * - * Converts to lowercase any mistakenly uppercased schema (i.e., - * "Http://" converts to "http://" - * - * @return Original or modified URL - * - */ - String smartUrlFilter(String url) { - - String inUrl = url.trim(); - boolean hasSpace = inUrl.indexOf(' ') != -1; - - Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl); - if (matcher.matches()) { - // force scheme to lowercase - String scheme = matcher.group(1); - String lcScheme = scheme.toLowerCase(); - if (!lcScheme.equals(scheme)) { - inUrl = lcScheme + matcher.group(2); - } - if (hasSpace) { - inUrl = inUrl.replace(" ", "%20"); - } - return inUrl; - } - if (!hasSpace) { - if (Patterns.WEB_URL.matcher(inUrl).matches()) { - return URLUtil.guessUrl(inUrl); - } - } - - // FIXME: Is this the correct place to add to searches? - // what if someone else calls this function? - - Browser.addSearchUrl(mResolver, inUrl); - return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER); - } - - /* package */ void setShouldShowErrorConsole(boolean flag) { - if (flag == mShouldShowErrorConsole) { - // Nothing to do. - return; - } - Tab t = mTabControl.getCurrentTab(); - if (t == null) { - // There is no current tab so we cannot toggle the error console - return; - } - - mShouldShowErrorConsole = flag; - - ErrorConsoleView errorConsole = t.getErrorConsole(true); - - if (flag) { - // Setting the show state of the console will cause it's the layout to be inflated. - if (errorConsole.numberOfErrors() > 0) { - errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED); - } else { - errorConsole.showConsole(ErrorConsoleView.SHOW_NONE); - } - - // Now we can add it to the main view. - mErrorConsoleContainer.addView(errorConsole, - new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); - } else { - mErrorConsoleContainer.removeView(errorConsole); - } - - } - - boolean shouldShowErrorConsole() { - return mShouldShowErrorConsole; - } - - private void setStatusBarVisibility(boolean visible) { - int flag = visible ? 0 : WindowManager.LayoutParams.FLAG_FULLSCREEN; - getWindow().setFlags(flag, WindowManager.LayoutParams.FLAG_FULLSCREEN); - } - - - private void sendNetworkType(String type, String subtype) { - WebView w = mTabControl.getCurrentWebView(); - if (w != null) { - w.setNetworkType(type, subtype); - } - } - - final static int LOCK_ICON_UNSECURE = 0; - final static int LOCK_ICON_SECURE = 1; - final static int LOCK_ICON_MIXED = 2; - - private BrowserSettings mSettings; - private TabControl mTabControl; - private ContentResolver mResolver; - private FrameLayout mContentView; - private View mCustomView; - private FrameLayout mCustomViewContainer; - private WebChromeClient.CustomViewCallback mCustomViewCallback; - - // FIXME, temp address onPrepareMenu performance problem. When we move everything out of - // view, we should rewrite this. - private int mCurrentMenuState = 0; - private int mMenuState = R.id.MAIN_MENU; - private int mOldMenuState = EMPTY_MENU; - private static final int EMPTY_MENU = -1; - private Menu mMenu; - - // Used to prevent chording to result in firing two shortcuts immediately - // one after another. Fixes bug 1211714. - boolean mCanChord; - - private boolean mInLoad; - private boolean mIsNetworkUp; - private boolean mDidStopLoad; - - /* package */ boolean mActivityInPause = true; - - private boolean mMenuIsDown; - - private static boolean mInTrace; - - // Performance probe - private static final int[] SYSTEM_CPU_FORMAT = new int[] { - Process.PROC_SPACE_TERM | Process.PROC_COMBINE, - Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 1: user time - Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 2: nice time - Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 3: sys time - Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 4: idle time - Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 5: iowait time - Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 6: irq time - Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG // 7: softirq time - }; - - private long mStart; - private long mProcessStart; - private long mUserStart; - private long mSystemStart; - private long mIdleStart; - private long mIrqStart; - - private long mUiStart; - - private Drawable mMixLockIcon; - private Drawable mSecLockIcon; - - /* hold a ref so we can auto-cancel if necessary */ - private AlertDialog mAlertDialog; - - // The up-to-date URL and title (these can be different from those stored - // in WebView, since it takes some time for the information in WebView to - // get updated) - private String mUrl; - private String mTitle; - - // As PageInfo has different style for landscape / portrait, we have - // to re-open it when configuration changed - private AlertDialog mPageInfoDialog; - private Tab mPageInfoView; - // If the Page-Info dialog is launched from the SSL-certificate-on-error - // dialog, we should not just dismiss it, but should get back to the - // SSL-certificate-on-error dialog. This flag is used to store this state - private boolean mPageInfoFromShowSSLCertificateOnError; - - // as SSLCertificateOnError has different style for landscape / portrait, - // we have to re-open it when configuration changed - private AlertDialog mSSLCertificateOnErrorDialog; - private WebView mSSLCertificateOnErrorView; - private SslErrorHandler mSSLCertificateOnErrorHandler; - private SslError mSSLCertificateOnErrorError; - - // as SSLCertificate has different style for landscape / portrait, we - // have to re-open it when configuration changed - private AlertDialog mSSLCertificateDialog; - private Tab mSSLCertificateView; - - // as HttpAuthentication has different style for landscape / portrait, we - // have to re-open it when configuration changed - private HttpAuthenticationDialog mHttpAuthenticationDialog; - - /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS = - new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT); - /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER = - new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, - Gravity.CENTER); - // Google search - final static String QuickSearch_G = "http://www.google.com/m?q=%s"; - - final static String QUERY_PLACE_HOLDER = "%s"; - - // "source" parameter for Google search through search key - final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key"; - // "source" parameter for Google search through goto menu - final static String GOOGLE_SEARCH_SOURCE_GOTO = "browser-goto"; - // "source" parameter for Google search through simplily type - final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type"; - // "source" parameter for Google search suggested by the browser - final static String GOOGLE_SEARCH_SOURCE_SUGGEST = "browser-suggest"; - // "source" parameter for Google search from unknown source - final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown"; - - private final static String LOGTAG = "browser"; - - private String mLastEnteredUrl; - - private PowerManager.WakeLock mWakeLock; - private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes - - private Toast mStopToast; - - private TitleBarBase mTitleBar; - private TabBar mTabBar; - - private LinearLayout mErrorConsoleContainer = null; - private boolean mShouldShowErrorConsole = false; - - // As the ids are dynamically created, we can't guarantee that they will - // be in sequence, so this static array maps ids to a window number. - final static private int[] WINDOW_SHORTCUT_ID_ARRAY = - { R.id.window_one_menu_id, R.id.window_two_menu_id, R.id.window_three_menu_id, - R.id.window_four_menu_id, R.id.window_five_menu_id, R.id.window_six_menu_id, - R.id.window_seven_menu_id, R.id.window_eight_menu_id }; - - // monitor platform changes - private IntentFilter mNetworkStateChangedFilter; - private BroadcastReceiver mNetworkStateIntentReceiver; - - private SystemAllowGeolocationOrigins mSystemAllowGeolocationOrigins; - - // activity requestCode - final static int PREFERENCES_PAGE = 3; - final static int FILE_SELECTED = 4; - - // 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. - */ - /* package */ static class UrlData { - final String mUrl; - final Map<String, String> mHeaders; - final Intent mVoiceIntent; - - UrlData(String url) { - this.mUrl = url; - this.mHeaders = null; - this.mVoiceIntent = null; - } - - UrlData(String url, Map<String, String> headers, Intent intent) { - this.mUrl = url; - this.mHeaders = headers; - if (RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS - .equals(intent.getAction())) { - this.mVoiceIntent = intent; - } else { - this.mVoiceIntent = null; - } - } - - boolean isEmpty() { - return mVoiceIntent == null && (mUrl == null || mUrl.length() == 0); - } - - /** - * Load this UrlData into the given Tab. Use loadUrlDataIn to update - * the title bar as well. - */ - public void loadIn(Tab t) { - if (mVoiceIntent != null) { - t.activateVoiceSearchMode(mVoiceIntent); - } else { - t.getWebView().loadUrl(mUrl, mHeaders); - } - } - }; - /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null); } diff --git a/src/com/android/browser/BrowserBookmarksPage.java b/src/com/android/browser/BrowserBookmarksPage.java index 88927bd..c7392cc 100644 --- a/src/com/android/browser/BrowserBookmarksPage.java +++ b/src/com/android/browser/BrowserBookmarksPage.java @@ -269,7 +269,7 @@ public class BrowserBookmarksPage extends Fragment implements View.OnCreateConte break; case R.id.share_link_context_menu_id: { Cursor cursor = (Cursor) mAdapter.getItem(i.position); - BrowserActivity.sharePage(activity, + Controller.sharePage(activity, cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE), cursor.getString(BookmarksLoader.COLUMN_INDEX_URL), getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON), @@ -368,7 +368,7 @@ public class BrowserBookmarksPage extends Fragment implements View.OnCreateConte mGrid = (GridView) root.findViewById(R.id.grid); mGrid.setOnItemClickListener(this); - mGrid.setColumnWidth(BrowserActivity.getDesiredThumbnailWidth(getActivity())); + mGrid.setColumnWidth(Controller.getDesiredThumbnailWidth(getActivity())); if (!mCreateShortcut) { mGrid.setOnCreateContextMenuListener(this); } diff --git a/src/com/android/browser/BrowserHomepagePreference.java b/src/com/android/browser/BrowserHomepagePreference.java index fefe101..9886053 100644 --- a/src/com/android/browser/BrowserHomepagePreference.java +++ b/src/com/android/browser/BrowserHomepagePreference.java @@ -21,16 +21,16 @@ import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; import android.preference.EditTextPreference; -import android.widget.Button; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.Toast; +import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; -import android.util.AttributeSet; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.Toast; public class BrowserHomepagePreference extends EditTextPreference { private String mCurrentPage; @@ -102,7 +102,9 @@ public class BrowserHomepagePreference extends EditTextPreference { protected void onDialogClosed(boolean positiveResult) { if (positiveResult) { String url = getEditText().getText().toString(); - if (!BrowserActivity.ACCEPTED_URI_SCHEMA.matcher(url).matches()) { + if (url.length() > 0 + && !UrlUtils.ACCEPTED_URI_SCHEMA.matcher(url) + .matches()) { int colon = url.indexOf(':'); int space = url.indexOf(' '); if (colon == -1 && space == -1 && url.length() > 0) { diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java index 44842be..693ad00 100644 --- a/src/com/android/browser/BrowserSettings.java +++ b/src/com/android/browser/BrowserSettings.java @@ -189,7 +189,7 @@ public class BrowserSettings extends Observable { public static final Uri RLZ_PROVIDER_URI = Uri.parse("content://" + RLZ_PROVIDER + "/"); - private TabControl mTabControl; + private Controller mController; // Single instance of the BrowserSettings for use in the Browser app. private static BrowserSettings sSingleton; @@ -375,8 +375,8 @@ public class BrowserSettings extends Observable { // One or more tabs could have been in voice search mode. // Clear it, since the new SearchEngine may not support // it, or may handle it differently. - for (int i = 0; i < mTabControl.getTabCount(); i++) { - mTabControl.getTab(i).revertVoiceSearchMode(); + for (int i = 0; i < mController.getTabControl().getTabCount(); i++) { + mController.getTabControl().getTab(i).revertVoiceSearchMode(); } } searchEngine.close(); @@ -609,8 +609,8 @@ public class BrowserSettings extends Observable { /* * Package level method for associating the BrowserSettings with TabControl */ - /* package */void setTabControl(TabControl tabControl) { - mTabControl = tabControl; + /* package */void setController(Controller ctrl) { + mController = ctrl; updateTabControlSettings(); } @@ -624,8 +624,8 @@ public class BrowserSettings extends Observable { /*package*/ void clearCache(Context context) { WebIconDatabase.getInstance().removeAllIcons(); - if (mTabControl != null) { - WebView current = mTabControl.getCurrentWebView(); + if (mController != null) { + WebView current = mController.getCurrentWebView(); if (current != null) { current.clearCache(true); } @@ -644,8 +644,8 @@ public class BrowserSettings extends Observable { /* package */ void clearFormData(Context context) { WebViewDatabase.getInstance(context).clearFormData(); - if (mTabControl != null) { - WebView currentTopView = mTabControl.getCurrentTopWebView(); + if (mController!= null) { + WebView currentTopView = mController.getCurrentTopWebView(); if (currentTopView != null) { currentTopView.clearFormData(); } @@ -660,7 +660,7 @@ public class BrowserSettings extends Observable { private void updateTabControlSettings() { // Enable/disable the error console. - mTabControl.getBrowserActivity().setShouldShowErrorConsole( + mController.setShouldShowErrorConsole( showDebugSettings && showConsole); } diff --git a/src/com/android/browser/CircularProgressView.java b/src/com/android/browser/CircularProgressView.java deleted file mode 100644 index 48f293a..0000000 --- a/src/com/android/browser/CircularProgressView.java +++ /dev/null @@ -1,140 +0,0 @@ - -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.browser; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.RectF; -import android.util.AttributeSet; -import android.widget.ImageButton; - -/** - * - */ -public class CircularProgressView extends ImageButton { - - private static final int[] ALPHAS = { - 64, 96, 128, 160, 192, 192, 160, 128, 96, 64 - }; - - // 100 ms delay between frames, 10fps - private static int ALPHA_REFRESH_DELAY = 100; - - private int mEndAngle; - private int mProgress; - private Paint mPaint; - private int mAlpha; - private boolean mAnimated; - private RectF mRect; - private int mMaxProgress; - - /** - * @param context - * @param attrs - * @param defStyle - */ - public CircularProgressView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(context); - } - - /** - * @param context - * @param attrs - */ - public CircularProgressView(Context context, AttributeSet attrs) { - super(context, attrs); - init(context); - } - - /** - * @param context - */ - public CircularProgressView(Context context) { - super(context); - init(context); - } - - private void init(Context ctx) { - mEndAngle = 0; - mProgress = 0; - mMaxProgress = 100; - mPaint = new Paint(); - mPaint.setAntiAlias(true); - mPaint.setColor(Color.BLACK); - mRect = new RectF(); - } - - void setMaxProgress(int max) { - mMaxProgress = max; - } - - private synchronized boolean isAnimated() { - return mAnimated; - } - - private synchronized void setAnimated(boolean animated) { - mAnimated = animated; - } - - void setProgress(int progress) { - mProgress = progress; - mEndAngle = 360 * progress / mMaxProgress; - invalidate(); - if (!isAnimated() && (progress > 0) && (progress < mMaxProgress)) { - setAnimated(true); - mAlpha = 0; - post(new Runnable() { - @Override - public void run() { - if (isAnimated()) { - mAlpha = (mAlpha + 1) % ALPHAS.length; - mPaint.setAlpha(ALPHAS[mAlpha]); - invalidate(); - postDelayed(this, ALPHA_REFRESH_DELAY); - } - } - }); - } else if ((progress <= 0) || (progress >= mMaxProgress)) { - setAnimated(false); - } - } - - @Override - public void onDraw(Canvas canvas) { - int w = getWidth(); - int h = getHeight(); - float cx = w * 0.5f; - float cy = h * 0.5f; - mRect.set(0, 0, w, h); - if ((mProgress > 0) && (mProgress < mMaxProgress)) { - Path p = new Path(); - p.moveTo(cx, cy); - p.lineTo(cx, 0); - p.arcTo(mRect, 270, mEndAngle); - p.lineTo(cx, cy); - int state = canvas.save(); - canvas.drawPath(p, mPaint); - canvas.restoreToCount(state); - } - super.onDraw(canvas); - } - -} diff --git a/src/com/android/browser/CombinedBookmarkHistoryView.java b/src/com/android/browser/CombinedBookmarkHistoryView.java index c829079..c078b51 100644 --- a/src/com/android/browser/CombinedBookmarkHistoryView.java +++ b/src/com/android/browser/CombinedBookmarkHistoryView.java @@ -16,12 +16,11 @@ package com.android.browser; -import com.android.browser.BreadCrumbView.Controller; +import android.app.Activity; import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; -import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.os.AsyncTask; @@ -45,14 +44,15 @@ interface BookmarksHistoryCallbacks { } public class CombinedBookmarkHistoryView extends LinearLayout - implements OnClickListener, Controller { + implements OnClickListener, BreadCrumbView.Controller { final static String STARTING_FRAGMENT = "fragment"; final static int FRAGMENT_ID_BOOKMARKS = 1; final static int FRAGMENT_ID_HISTORY = 2; - private BrowserActivity mBrowserActivity; + private UiController mUiController; + private Activity mActivity; private Bundle mExtras; @@ -104,12 +104,14 @@ public class CombinedBookmarkHistoryView extends LinearLayout return sIconListenerSet; } - public CombinedBookmarkHistoryView(Context context, int startingFragment, Bundle extras) { - super(context); - mBrowserActivity = (BrowserActivity) context; + public CombinedBookmarkHistoryView(Activity activity, UiController controller, + int startingFragment, Bundle extras) { + super(activity); + mUiController = controller; + mActivity = activity; mExtras = extras; - View v = LayoutInflater.from(context).inflate(R.layout.bookmarks_history, this); - Resources res = context.getResources(); + View v = LayoutInflater.from(activity).inflate(R.layout.bookmarks_history, this); + Resources res = activity.getResources(); // setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); @@ -136,7 +138,7 @@ public class CombinedBookmarkHistoryView extends LinearLayout (new AsyncTask<Void, Void, Void>() { @Override public Void doInBackground(Void... v) { - Browser.requestAllIcons(mBrowserActivity.getContentResolver(), + Browser.requestAllIcons(mActivity.getContentResolver(), Browser.BookmarkColumns.FAVICON + " is NULL", getIconListenerSet()); return null; } @@ -145,8 +147,8 @@ public class CombinedBookmarkHistoryView extends LinearLayout } private void initFragments(Bundle extras) { - mBookmarks = BrowserBookmarksPage.newInstance(mBrowserActivity, mCrumbs, extras); - mHistory = BrowserHistoryPage.newInstance(mBrowserActivity, extras); + mBookmarks = BrowserBookmarksPage.newInstance(mUiController, mCrumbs, extras); + mHistory = BrowserHistoryPage.newInstance(mUiController, extras); } private void loadFragment(int id, Bundle extras, boolean notify) { @@ -169,7 +171,7 @@ public class CombinedBookmarkHistoryView extends LinearLayout } mCurrentFragment = id; - FragmentManager fm = mBrowserActivity.getFragmentManager(); + FragmentManager fm = mActivity.getFragmentManager(); FragmentTransaction transaction = fm.openTransaction(); transaction.replace(R.id.fragment, fragment); transaction.commit(); @@ -186,7 +188,7 @@ public class CombinedBookmarkHistoryView extends LinearLayout mCrumbs.clear(); } } else if (mAddBookmark == view) { - mBrowserActivity.bookmarkCurrentPage(mBookmarks.getFolderId()); + mUiController.bookmarkCurrentPage(mBookmarks.getFolderId()); } } diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java new file mode 100644 index 0000000..6b0ab0e --- /dev/null +++ b/src/com/android/browser/Controller.java @@ -0,0 +1,2340 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import com.android.browser.IntentHandler.UrlData; +import com.android.browser.search.SearchEngine; +import com.android.common.Search; + +import android.app.Activity; +import android.app.DownloadManager; +import android.app.SearchManager; +import android.content.ClipboardManager; +import android.content.ContentProvider; +import android.content.ContentProviderClient; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Configuration; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Picture; +import android.net.Uri; +import android.net.http.SslError; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.provider.Browser; +import android.provider.BrowserContract; +import android.provider.BrowserContract.History; +import android.provider.BrowserContract.Images; +import android.provider.ContactsContract; +import android.provider.ContactsContract.Intents.Insert; +import android.speech.RecognizerResultsIntent; +import android.text.TextUtils; +import android.util.Log; +import android.view.ActionMode; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.View; +import android.webkit.CookieManager; +import android.webkit.CookieSyncManager; +import android.webkit.HttpAuthHandler; +import android.webkit.SslErrorHandler; +import android.webkit.ValueCallback; +import android.webkit.WebChromeClient; +import android.webkit.WebIconDatabase; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.widget.TextView; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.net.URLEncoder; +import java.util.Calendar; +import java.util.HashMap; + +/** + * Controller for browser + */ +public class Controller + implements WebViewController, UiController { + + private static final String LOGTAG = "Controller"; + + // public message ids + public final static int LOAD_URL = 1001; + public final static int STOP_LOAD = 1002; + + // Message Ids + private static final int FOCUS_NODE_HREF = 102; + private static final int RELEASE_WAKELOCK = 107; + + static final int UPDATE_BOOKMARK_THUMBNAIL = 108; + + private static final int OPEN_BOOKMARKS = 201; + + private static final int EMPTY_MENU = -1; + + // Keep this initial progress in sync with initialProgressValue (* 100) + // in ProgressTracker.cpp + private final static int INITIAL_PROGRESS = 10; + + // activity requestCode + final static int PREFERENCES_PAGE = 3; + final static int FILE_SELECTED = 4; + private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes + + // As the ids are dynamically created, we can't guarantee that they will + // be in sequence, so this static array maps ids to a window number. + final static private int[] WINDOW_SHORTCUT_ID_ARRAY = + { R.id.window_one_menu_id, R.id.window_two_menu_id, + R.id.window_three_menu_id, R.id.window_four_menu_id, + R.id.window_five_menu_id, R.id.window_six_menu_id, + R.id.window_seven_menu_id, R.id.window_eight_menu_id }; + + // "source" parameter for Google search through search key + final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key"; + // "source" parameter for Google search through simplily type + final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type"; + + private Activity mActivity; + private UI mUi; + private TabControl mTabControl; + private BrowserSettings mSettings; + private WebViewFactory mFactory; + + private WakeLock mWakeLock; + + private UrlHandler mUrlHandler; + private UploadHandler mUploadHandler; + private IntentHandler mIntentHandler; + private DownloadHandler mDownloadHandler; + private PageDialogsHandler mPageDialogsHandler; + private NetworkStateHandler mNetworkHandler; + + private boolean mShouldShowErrorConsole; + + private SystemAllowGeolocationOrigins mSystemAllowGeolocationOrigins; + + // FIXME, temp address onPrepareMenu performance problem. + // When we move everything out of view, we should rewrite this. + private int mCurrentMenuState = 0; + private int mMenuState = R.id.MAIN_MENU; + private int mOldMenuState = EMPTY_MENU; + private Menu mCachedMenu; + + // Used to prevent chording to result in firing two shortcuts immediately + // one after another. Fixes bug 1211714. + boolean mCanChord; + private boolean mMenuIsDown; + + // For select and find, we keep track of the ActionMode so that + // finish() can be called as desired. + private ActionMode mActionMode; + + /** + * Only meaningful when mOptionsMenuOpen is true. This variable keeps track + * of whether the configuration has changed. The first onMenuOpened call + * after a configuration change is simply a reopening of the same menu + * (i.e. mIconView did not change). + */ + private boolean mConfigChanged; + + /** + * Keeps track of whether the options menu is open. This is important in + * determining whether to show or hide the title bar overlay + */ + private boolean mOptionsMenuOpen; + + /** + * Whether or not the options menu is in its bigger, popup menu form. When + * true, we want the title bar overlay to be gone. When false, we do not. + * Only meaningful if mOptionsMenuOpen is true. + */ + private boolean mExtendedMenuOpen; + + private boolean mInLoad; + + private boolean mActivityPaused = true; + private boolean mLoadStopped; + + private Handler mHandler; + + private static class ClearThumbnails extends AsyncTask<File, Void, Void> { + @Override + public Void doInBackground(File... files) { + if (files != null) { + for (File f : files) { + if (!f.delete()) { + Log.e(LOGTAG, f.getPath() + " was not deleted"); + } + } + } + return null; + } + } + + public Controller(Activity browser) { + mActivity = browser; + mSettings = BrowserSettings.getInstance(); + mTabControl = new TabControl(this); + mSettings.setController(this); + + mUrlHandler = new UrlHandler(this); + mIntentHandler = new IntentHandler(mActivity, this); + mDownloadHandler = new DownloadHandler(mActivity); + mPageDialogsHandler = new PageDialogsHandler(mActivity, this); + + PowerManager pm = (PowerManager) mActivity + .getSystemService(Context.POWER_SERVICE); + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Browser"); + + startHandler(); + + mNetworkHandler = new NetworkStateHandler(mActivity, this); + // Start watching the default geolocation permissions + mSystemAllowGeolocationOrigins = + new SystemAllowGeolocationOrigins(mActivity.getApplicationContext()); + mSystemAllowGeolocationOrigins.start(); + + retainIconsOnStartup(); + } + + void start(Bundle icicle, Intent intent) { + // Unless the last browser usage was within 24 hours, destroy any + // remaining incognito tabs. + + Calendar lastActiveDate = icicle != null ? + (Calendar) icicle.getSerializable("lastActiveDate") : null; + Calendar today = Calendar.getInstance(); + Calendar yesterday = Calendar.getInstance(); + yesterday.add(Calendar.DATE, -1); + + boolean dontRestoreIncognitoTabs = lastActiveDate == null + || lastActiveDate.before(yesterday) + || lastActiveDate.after(today); + + if (!mTabControl.restoreState(icicle, dontRestoreIncognitoTabs)) { + // there is no quit on Android. But if we can't restore the state, + // we can treat it as a new Browser, remove the old session cookies. + CookieManager.getInstance().removeSessionCookie(); + // remove any incognito files + WebView.cleanupPrivateBrowsingFiles(mActivity); + final Bundle extra = intent.getExtras(); + // Create an initial tab. + // If the intent is ACTION_VIEW and data is not null, the Browser is + // invoked to view the content by another application. In this case, + // the tab will be close when exit. + UrlData urlData = mIntentHandler.getUrlDataFromIntent(intent); + + String action = intent.getAction(); + final Tab t = mTabControl.createNewTab( + (Intent.ACTION_VIEW.equals(action) && + intent.getData() != null) + || RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS + .equals(action), + intent.getStringExtra(Browser.EXTRA_APPLICATION_ID), + urlData.mUrl, false); + addTab(t); + setActiveTab(t); + WebView webView = t.getWebView(); + if (extra != null) { + int scale = extra.getInt(Browser.INITIAL_ZOOM_LEVEL, 0); + if (scale > 0 && scale <= 1000) { + webView.setInitialScale(scale); + } + } + + if (urlData.isEmpty()) { + loadUrl(webView, mSettings.getHomePage()); + } else { + loadUrlDataIn(t, urlData); + } + } else { + if (dontRestoreIncognitoTabs) { + WebView.cleanupPrivateBrowsingFiles(mActivity); + } + // TabControl.restoreState() will create a new tab even if + // restoring the state fails. + setActiveTab(mTabControl.getCurrentTab()); + } + // clear up the thumbnail directory, which is no longer used; + // ideally this should only be run once after an upgrade from + // a previous version of the browser + new ClearThumbnails().execute(mTabControl.getThumbnailDir() + .listFiles()); + // Read JavaScript flags if it exists. + String jsFlags = getSettings().getJsFlags(); + if (jsFlags.trim().length() != 0) { + getCurrentWebView().setJsFlags(jsFlags); + } + } + + void setWebViewFactory(WebViewFactory factory) { + mFactory = factory; + } + + WebViewFactory getWebViewFactory() { + return mFactory; + } + + @Override + public Activity getActivity() { + return mActivity; + } + + void setUi(UI ui) { + mUi = ui; + } + + BrowserSettings getSettings() { + return mSettings; + } + + IntentHandler getIntentHandler() { + return mIntentHandler; + } + + @Override + public UI getUi() { + return mUi; + } + + int getMaxTabs() { + return mActivity.getResources().getInteger(R.integer.max_tabs); + } + + @Override + public TabControl getTabControl() { + return mTabControl; + } + + // Open the icon database and retain all the icons for visited sites. + private void retainIconsOnStartup() { + final WebIconDatabase db = WebIconDatabase.getInstance(); + db.open(mActivity.getDir("icons", 0).getPath()); + Cursor c = null; + try { + c = Browser.getAllBookmarks(mActivity.getContentResolver()); + if (c.moveToFirst()) { + int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL); + do { + String url = c.getString(urlIndex); + db.retainIconForPageUrl(url); + } while (c.moveToNext()); + } + } catch (IllegalStateException e) { + Log.e(LOGTAG, "retainIconsOnStartup", e); + } finally { + if (c!= null) c.close(); + } + } + + private void startHandler() { + mHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case OPEN_BOOKMARKS: + bookmarksOrHistoryPicker(false); + break; + case FOCUS_NODE_HREF: + { + String url = (String) msg.getData().get("url"); + String title = (String) msg.getData().get("title"); + if (TextUtils.isEmpty(url)) { + break; + } + HashMap focusNodeMap = (HashMap) msg.obj; + WebView view = (WebView) focusNodeMap.get("webview"); + // Only apply the action if the top window did not change. + if (getCurrentTopWebView() != view) { + break; + } + switch (msg.arg1) { + case R.id.open_context_menu_id: + case R.id.view_image_context_menu_id: + loadUrlFromContext(getCurrentTopWebView(), url); + break; + case R.id.bookmark_context_menu_id: + Intent intent = new Intent(mActivity, + AddBookmarkPage.class); + intent.putExtra(BrowserContract.Bookmarks.URL, url); + intent.putExtra(BrowserContract.Bookmarks.TITLE, + title); + mActivity.startActivity(intent); + break; + case R.id.share_link_context_menu_id: + sharePage(mActivity, title, url, null, + null); + break; + case R.id.copy_link_context_menu_id: + copy(url); + break; + case R.id.save_link_context_menu_id: + case R.id.download_context_menu_id: + mDownloadHandler + .onDownloadStartNoStream(url, null, null, null, -1); + break; + } + break; + } + + case LOAD_URL: + loadUrlFromContext(getCurrentTopWebView(), (String) msg.obj); + break; + + case STOP_LOAD: + stopLoading(); + break; + + case RELEASE_WAKELOCK: + if (mWakeLock.isHeld()) { + mWakeLock.release(); + // if we reach here, Browser should be still in the + // background loading after WAKELOCK_TIMEOUT (5-min). + // To avoid burning the battery, stop loading. + mTabControl.stopAllLoading(); + } + break; + + case UPDATE_BOOKMARK_THUMBNAIL: + WebView view = (WebView) msg.obj; + if (view != null) { + updateScreenshot(view); + } + break; + } + } + }; + + } + + /** + * Share a page, providing the title, url, favicon, and a screenshot. Uses + * an {@link Intent} to launch the Activity chooser. + * @param c Context used to launch a new Activity. + * @param title Title of the page. Stored in the Intent with + * {@link Intent#EXTRA_SUBJECT} + * @param url URL of the page. Stored in the Intent with + * {@link Intent#EXTRA_TEXT} + * @param favicon Bitmap of the favicon for the page. Stored in the Intent + * with {@link Browser#EXTRA_SHARE_FAVICON} + * @param screenshot Bitmap of a screenshot of the page. Stored in the + * Intent with {@link Browser#EXTRA_SHARE_SCREENSHOT} + */ + static final void sharePage(Context c, String title, String url, + Bitmap favicon, Bitmap screenshot) { + Intent send = new Intent(Intent.ACTION_SEND); + send.setType("text/plain"); + send.putExtra(Intent.EXTRA_TEXT, url); + send.putExtra(Intent.EXTRA_SUBJECT, title); + send.putExtra(Browser.EXTRA_SHARE_FAVICON, favicon); + send.putExtra(Browser.EXTRA_SHARE_SCREENSHOT, screenshot); + try { + c.startActivity(Intent.createChooser(send, c.getString( + R.string.choosertitle_sharevia))); + } catch(android.content.ActivityNotFoundException ex) { + // if no app handles it, do nothing + } + } + + private void copy(CharSequence text) { + ClipboardManager cm = (ClipboardManager) mActivity + .getSystemService(Context.CLIPBOARD_SERVICE); + cm.setText(text); + } + + // lifecycle + + protected void onConfgurationChanged(Configuration config) { + mConfigChanged = true; + if (mPageDialogsHandler != null) { + mPageDialogsHandler.onConfigurationChanged(config); + } + mUi.onConfigurationChanged(config); + } + + @Override + public void handleNewIntent(Intent intent) { + mIntentHandler.onNewIntent(intent); + } + + protected void onPause() { + if (mActivityPaused) { + Log.e(LOGTAG, "BrowserActivity is already paused."); + return; + } + mTabControl.pauseCurrentTab(); + mActivityPaused = true; + if (mTabControl.getCurrentIndex() >= 0 && + !pauseWebViewTimers(mActivityPaused)) { + mWakeLock.acquire(); + mHandler.sendMessageDelayed(mHandler + .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT); + } + mUi.onPause(); + mNetworkHandler.onPause(); + + WebView.disablePlatformNotifications(); + } + + void onSaveInstanceState(Bundle outState) { + // the default implementation requires each view to have an id. As the + // browser handles the state itself and it doesn't use id for the views, + // don't call the default implementation. Otherwise it will trigger the + // warning like this, "couldn't save which view has focus because the + // focused view XXX has no id". + + // Save all the tabs + mTabControl.saveState(outState); + // Save time so that we know how old incognito tabs (if any) are. + outState.putSerializable("lastActiveDate", Calendar.getInstance()); + } + + void onResume() { + if (!mActivityPaused) { + Log.e(LOGTAG, "BrowserActivity is already resumed."); + return; + } + mTabControl.resumeCurrentTab(); + mActivityPaused = false; + resumeWebViewTimers(); + + if (mWakeLock.isHeld()) { + mHandler.removeMessages(RELEASE_WAKELOCK); + mWakeLock.release(); + } + mUi.onResume(); + mNetworkHandler.onResume(); + WebView.enablePlatformNotifications(); + } + + private void resumeWebViewTimers() { + Tab tab = mTabControl.getCurrentTab(); + if (tab == null) return; // monkey can trigger this + boolean inLoad = tab.inPageLoad(); + if ((!mActivityPaused && !inLoad) || (mActivityPaused && inLoad)) { + CookieSyncManager.getInstance().startSync(); + WebView w = tab.getWebView(); + if (w != null) { + w.resumeTimers(); + } + } + } + + private boolean pauseWebViewTimers(boolean activityPaused) { + Tab tab = mTabControl.getCurrentTab(); + boolean inLoad = tab.inPageLoad(); + if (activityPaused && !inLoad) { + CookieSyncManager.getInstance().stopSync(); + WebView w = getCurrentWebView(); + if (w != null) { + w.pauseTimers(); + } + return true; + } else { + return false; + } + } + + void onDestroy() { + if (mUploadHandler != null) { + mUploadHandler.onResult(Activity.RESULT_CANCELED, null); + mUploadHandler = null; + } + if (mTabControl == null) return; + mUi.onDestroy(); + // Remove the current tab and sub window + Tab t = mTabControl.getCurrentTab(); + if (t != null) { + dismissSubWindow(t); + removeTab(t); + } + // Destroy all the tabs + mTabControl.destroy(); + WebIconDatabase.getInstance().close(); + // Stop watching the default geolocation permissions + mSystemAllowGeolocationOrigins.stop(); + mSystemAllowGeolocationOrigins = null; + } + + protected boolean isActivityPaused() { + return mActivityPaused; + } + + protected void onLowMemory() { + mTabControl.freeMemory(); + } + + @Override + public boolean shouldShowErrorConsole() { + return mShouldShowErrorConsole; + } + + protected void setShouldShowErrorConsole(boolean show) { + if (show == mShouldShowErrorConsole) { + // Nothing to do. + return; + } + mShouldShowErrorConsole = show; + Tab t = mTabControl.getCurrentTab(); + if (t == null) { + // There is no current tab so we cannot toggle the error console + return; + } + mUi.setShouldShowErrorConsole(t, show); + } + + @Override + public void stopLoading() { + mLoadStopped = true; + Tab tab = mTabControl.getCurrentTab(); + resetTitleAndRevertLockIcon(tab); + WebView w = getCurrentTopWebView(); + w.stopLoading(); + // FIXME: before refactor, it is using mWebViewClient. So I keep the + // same logic here. But for subwindow case, should we call into the main + // WebView's onPageFinished as we never call its onPageStarted and if + // the page finishes itself, we don't call onPageFinished. + mTabControl.getCurrentWebView().getWebViewClient().onPageFinished(w, + w.getUrl()); + mUi.onPageStopped(tab); + } + + boolean didUserStopLoading() { + return mLoadStopped; + } + + // WebViewController + + @Override + public void onPageStarted(Tab tab, WebView view, String url, Bitmap favicon) { + + // 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(Controller.UPDATE_BOOKMARK_THUMBNAIL, + view); + + // reset sync timer to avoid sync starts during loading a page + CookieSyncManager.getInstance().resetSync(); + + if (!mNetworkHandler.isNetworkUp()) { + view.setNetworkAvailable(false); + } + + // when BrowserActivity just starts, onPageStarted may be called before + // onResume as it is triggered from onCreate. Call resumeWebViewTimers + // to start the timer. As we won't switch tabs while an activity is in + // pause state, we can ensure calling resume and pause in pair. + if (mActivityPaused) { + resumeWebViewTimers(); + } + mLoadStopped = false; + if (!mNetworkHandler.isNetworkUp()) { + mNetworkHandler.createAndShowNetworkDialog(); + } + endActionMode(); + + mUi.onPageStarted(tab, url, favicon); + + // Show some progress so that the user knows the page is beginning to + // load + onProgressChanged(tab, INITIAL_PROGRESS); + + // update the bookmark database for favicon + maybeUpdateFavicon(tab, null, url, favicon); + + Performance.tracePageStart(url); + + // Performance probe + if (false) { + Performance.onPageStarted(); + } + + } + + @Override + public void onPageFinished(Tab tab, String url) { + mUi.onPageFinished(tab, url); + if (!tab.isPrivateBrowsingEnabled()) { + if (tab.inForeground() && !didUserStopLoading() + || !tab.inForeground()) { + // Only update the bookmark screenshot if the user did not + // cancel the load early. + mHandler.sendMessageDelayed(mHandler.obtainMessage( + UPDATE_BOOKMARK_THUMBNAIL, 0, 0, tab.getWebView()), + 500); + } + } + // pause the WebView timer and release the wake lock if it is finished + // while BrowserActivity is in pause state. + if (mActivityPaused && pauseWebViewTimers(mActivityPaused)) { + if (mWakeLock.isHeld()) { + mHandler.removeMessages(RELEASE_WAKELOCK); + mWakeLock.release(); + } + } + // Performance probe + if (false) { + Performance.onPageFinished(url); + } + + Performance.tracePageFinished(); + } + + @Override + public void onProgressChanged(Tab tab, int newProgress) { + + if (newProgress == 100) { + CookieSyncManager.getInstance().sync(); + // 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) + if (mInLoad) { + mInLoad = false; + updateInLoadMenuItems(mCachedMenu); + } + } else { + if (!mInLoad) { + // onPageFinished may have already been called but a subframe is + // still loading and updating the progress. Reset mInLoad and + // update the menu items. + mInLoad = true; + updateInLoadMenuItems(mCachedMenu); + } + } + mUi.onProgressChanged(tab, newProgress); + } + + @Override + public void onReceivedTitle(Tab tab, final String title) { + final String pageUrl = tab.getWebView().getUrl(); + setUrlTitle(tab, pageUrl, title); + if (pageUrl == null || pageUrl.length() + >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) { + return; + } + // Update the title in the history database if not in private browsing mode + if (!tab.isPrivateBrowsingEnabled()) { + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... unused) { + // See if we can find the current url in our history + // database and add the new title to it. + String url = pageUrl; + if (url.startsWith("http://www.")) { + url = url.substring(11); + } else if (url.startsWith("http://")) { + url = url.substring(4); + } + // Escape wildcards for LIKE operator. + url = url.replace("\\", "\\\\").replace("%", "\\%") + .replace("_", "\\_"); + Cursor c = null; + try { + final ContentResolver cr = + getActivity().getContentResolver(); + String selection = History.URL + " LIKE ? ESCAPE '\\'"; + String [] selectionArgs = new String[] { "%" + url }; + ContentValues values = new ContentValues(); + values.put(History.TITLE, title); + cr.update(History.CONTENT_URI, values, selection, + selectionArgs); + } catch (IllegalStateException e) { + Log.e(LOGTAG, "Tab onReceived title", e); + } catch (SQLiteException ex) { + Log.e(LOGTAG, + "onReceivedTitle() caught SQLiteException: ", + ex); + } finally { + if (c != null) c.close(); + } + return null; + } + }.execute(); + } + } + + @Override + public void onFavicon(Tab tab, WebView view, Bitmap icon) { + mUi.setFavicon(tab, icon); + maybeUpdateFavicon(tab, view.getOriginalUrl(), view.getUrl(), icon); + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + return mUrlHandler.shouldOverrideUrlLoading(view, url); + } + + @Override + public boolean shouldOverrideKeyEvent(KeyEvent event) { + if (mMenuIsDown) { + // only check shortcut key when MENU is held + return mActivity.getWindow().isShortcutKey(event.getKeyCode(), + event); + } else { + return false; + } + } + + @Override + public void onUnhandledKeyEvent(KeyEvent event) { + if (!isActivityPaused()) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + mActivity.onKeyDown(event.getKeyCode(), event); + } else { + mActivity.onKeyUp(event.getKeyCode(), event); + } + } + } + + @Override + public void doUpdateVisitedHistory(Tab tab, String url, + boolean isReload) { + // Don't save anything in private browsing mode + if (tab.isPrivateBrowsingEnabled()) return; + + if (url.regionMatches(true, 0, "about:", 0, 6)) { + return; + } + // remove "client" before updating it to the history so that it wont + // show up in the auto-complete list. + int index = url.indexOf("client=ms-"); + if (index > 0 && url.contains(".google.")) { + int end = url.indexOf('&', index); + if (end > 0) { + url = url.substring(0, index) + .concat(url.substring(end + 1)); + } else { + // the url.charAt(index-1) should be either '?' or '&' + url = url.substring(0, index-1); + } + } + final ContentResolver cr = getActivity().getContentResolver(); + final String newUrl = url; + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... unused) { + Browser.updateVisitedHistory(cr, newUrl, true); + return null; + } + }.execute(); + WebIconDatabase.getInstance().retainIconForPageUrl(url); + } + + @Override + public void getVisitedHistory(final ValueCallback<String[]> callback) { + AsyncTask<Void, Void, String[]> task = + new AsyncTask<Void, Void, String[]>() { + @Override + public String[] doInBackground(Void... unused) { + return Browser.getVisitedHistory(mActivity.getContentResolver()); + } + @Override + public void onPostExecute(String[] result) { + callback.onReceiveValue(result); + } + }; + task.execute(); + } + + @Override + public void onReceivedHttpAuthRequest(Tab tab, WebView view, + final HttpAuthHandler handler, final String host, + final String realm) { + String username = null; + String password = null; + + boolean reuseHttpAuthUsernamePassword + = handler.useHttpAuthUsernamePassword(); + + if (reuseHttpAuthUsernamePassword && view != null) { + String[] credentials = view.getHttpAuthUsernamePassword(host, realm); + if (credentials != null && credentials.length == 2) { + username = credentials[0]; + password = credentials[1]; + } + } + + if (username != null && password != null) { + handler.proceed(username, password); + } else { + if (tab.inForeground()) { + mPageDialogsHandler.showHttpAuthentication(tab, handler, host, realm); + } else { + handler.cancel(); + } + } + } + + @Override + public void onDownloadStart(Tab tab, String url, String userAgent, + String contentDisposition, String mimetype, long contentLength) { + mDownloadHandler.onDownloadStart(url, userAgent, contentDisposition, + mimetype, contentLength); + if (tab.getWebView().copyBackForwardList().getSize() == 0) { + // This Tab was opened for the sole purpose of downloading a + // file. Remove it. + if (tab == mTabControl.getCurrentTab()) { + // In this case, the Tab is still on top. + goBackOnePageOrQuit(); + } else { + // In this case, it is not. + closeTab(tab); + } + } + } + + @Override + public Bitmap getDefaultVideoPoster() { + return mUi.getDefaultVideoPoster(); + } + + @Override + public View getVideoLoadingProgressView() { + return mUi.getVideoLoadingProgressView(); + } + + @Override + public void showSslCertificateOnError(WebView view, SslErrorHandler handler, + SslError error) { + mPageDialogsHandler.showSSLCertificateOnError(view, handler, error); + } + + // helper method + + /* + * Update the favorites icon if the private browsing isn't enabled and the + * icon is valid. + */ + private void maybeUpdateFavicon(Tab tab, final String originalUrl, + final String url, Bitmap favicon) { + if (favicon == null) { + return; + } + if (!tab.isPrivateBrowsingEnabled()) { + Bookmarks.updateFavicon(mActivity + .getContentResolver(), originalUrl, url, favicon); + } + } + + // end WebViewController + + protected void pageUp() { + getCurrentTopWebView().pageUp(false); + } + + protected void pageDown() { + getCurrentTopWebView().pageDown(false); + } + + // callback from phone title bar + public void editUrl() { + if (mOptionsMenuOpen) mActivity.closeOptionsMenu(); + String url = (getCurrentTopWebView() == null) ? null : getCurrentTopWebView().getUrl(); + startSearch(mSettings.getHomePage().equals(url) ? null : url, true, + null, false); + } + + public void activateVoiceSearchMode(String title) { + mUi.showVoiceTitleBar(title); + } + + public void revertVoiceSearchMode(Tab tab) { + mUi.revertVoiceTitleBar(tab); + } + + public void showCustomView(Tab tab, View view, + WebChromeClient.CustomViewCallback callback) { + if (tab.inForeground()) { + if (mUi.isCustomViewShowing()) { + callback.onCustomViewHidden(); + return; + } + mUi.showCustomView(view, callback); + // Save the menu state and set it to empty while the custom + // view is showing. + mOldMenuState = mMenuState; + mMenuState = EMPTY_MENU; + } + } + + @Override + public void hideCustomView() { + if (mUi.isCustomViewShowing()) { + mUi.onHideCustomView(); + // Reset the old menu state. + mMenuState = mOldMenuState; + mOldMenuState = EMPTY_MENU; + } + } + + protected void onActivityResult(int requestCode, int resultCode, + Intent intent) { + if (getCurrentTopWebView() == null) return; + switch (requestCode) { + case PREFERENCES_PAGE: + if (resultCode == Activity.RESULT_OK && intent != null) { + String action = intent.getStringExtra(Intent.EXTRA_TEXT); + if (BrowserSettings.PREF_CLEAR_HISTORY.equals(action)) { + mTabControl.removeParentChildRelationShips(); + } + } + break; + case FILE_SELECTED: + // Choose a file from the file picker. + if (null == mUploadHandler) break; + mUploadHandler.onResult(resultCode, intent); + mUploadHandler = null; + break; + default: + break; + } + getCurrentTopWebView().requestFocus(); + } + + /** + * Open the Go page. + * @param startWithHistory If true, open starting on the history tab. + * Otherwise, start with the bookmarks tab. + */ + @Override + public void bookmarksOrHistoryPicker(boolean startWithHistory) { + if (mTabControl.getCurrentWebView() == null) { + return; + } + Bundle extras = new Bundle(); + // Disable opening in a new window if we have maxed out the windows + extras.putBoolean(BrowserBookmarksPage.EXTRA_DISABLE_WINDOW, + !mTabControl.canCreateNewTab()); + mUi.showComboView(startWithHistory, extras); + } + + // combo view callbacks + + /** + * callback from ComboPage when clear history is requested + */ + public void onRemoveParentChildRelationships() { + mTabControl.removeParentChildRelationShips(); + } + + /** + * callback from ComboPage when bookmark/history selection + */ + @Override + public void onUrlSelected(String url, boolean newTab) { + removeComboView(); + if (!TextUtils.isEmpty(url)) { + if (newTab) { + openTab(url, false); + } else { + final Tab currentTab = mTabControl.getCurrentTab(); + dismissSubWindow(currentTab); + loadUrl(getCurrentTopWebView(), url); + } + } + } + + /** + * callback from ComboPage when dismissed + */ + @Override + public void onComboCanceled() { + removeComboView(); + } + + /** + * dismiss the ComboPage + */ + @Override + public void removeComboView() { + mUi.hideComboView(); + } + + // active tabs page handling + + protected void showActiveTabsPage() { + mMenuState = EMPTY_MENU; + mUi.showActiveTabsPage(); + } + + /** + * Remove the active tabs page. + * @param needToAttach If true, the active tabs page did not attach a tab + * to the content view, so we need to do that here. + */ + @Override + public void removeActiveTabsPage(boolean needToAttach) { + mMenuState = R.id.MAIN_MENU; + mUi.removeActiveTabsPage(); + if (needToAttach) { + setActiveTab(mTabControl.getCurrentTab()); + } + getCurrentTopWebView().requestFocus(); + } + + // key handling + protected void onBackKey() { + if (!mUi.onBackKey()) { + WebView subwindow = mTabControl.getCurrentSubWindow(); + if (subwindow != null) { + if (subwindow.canGoBack()) { + subwindow.goBack(); + } else { + dismissSubWindow(mTabControl.getCurrentTab()); + } + } else { + goBackOnePageOrQuit(); + } + } + } + + // menu handling and state + // TODO: maybe put into separate handler + + protected boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = mActivity.getMenuInflater(); + inflater.inflate(R.menu.browser, menu); + updateInLoadMenuItems(menu); + // hold on to the menu reference here; it is used by the page callbacks + // to update the menu based on loading state + mCachedMenu = menu; + return true; + } + + protected void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + if (v instanceof TitleBarBase) { + return; + } + if (!(v instanceof WebView)) { + return; + } + WebView webview = (WebView) v; + WebView.HitTestResult result = webview.getHitTestResult(); + if (result == null) { + return; + } + + int type = result.getType(); + if (type == WebView.HitTestResult.UNKNOWN_TYPE) { + Log.w(LOGTAG, + "We should not show context menu when nothing is touched"); + return; + } + if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) { + // let TextView handles context menu + return; + } + + // Note, http://b/issue?id=1106666 is requesting that + // an inflated menu can be used again. This is not available + // yet, so inflate each time (yuk!) + MenuInflater inflater = mActivity.getMenuInflater(); + inflater.inflate(R.menu.browsercontext, menu); + + // Show the correct menu group + final String extra = result.getExtra(); + menu.setGroupVisible(R.id.PHONE_MENU, + type == WebView.HitTestResult.PHONE_TYPE); + menu.setGroupVisible(R.id.EMAIL_MENU, + type == WebView.HitTestResult.EMAIL_TYPE); + menu.setGroupVisible(R.id.GEO_MENU, + type == WebView.HitTestResult.GEO_TYPE); + menu.setGroupVisible(R.id.IMAGE_MENU, + type == WebView.HitTestResult.IMAGE_TYPE + || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE); + menu.setGroupVisible(R.id.ANCHOR_MENU, + type == WebView.HitTestResult.SRC_ANCHOR_TYPE + || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE); + + // Setup custom handling depending on the type + switch (type) { + case WebView.HitTestResult.PHONE_TYPE: + menu.setHeaderTitle(Uri.decode(extra)); + menu.findItem(R.id.dial_context_menu_id).setIntent( + new Intent(Intent.ACTION_VIEW, Uri + .parse(WebView.SCHEME_TEL + extra))); + Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT); + addIntent.putExtra(Insert.PHONE, Uri.decode(extra)); + 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( + new Copy(extra)); + break; + + case WebView.HitTestResult.EMAIL_TYPE: + menu.setHeaderTitle(extra); + menu.findItem(R.id.email_context_menu_id).setIntent( + new Intent(Intent.ACTION_VIEW, Uri + .parse(WebView.SCHEME_MAILTO + extra))); + menu.findItem(R.id.copy_mail_context_menu_id) + .setOnMenuItemClickListener( + new Copy(extra)); + break; + + case WebView.HitTestResult.GEO_TYPE: + menu.setHeaderTitle(extra); + menu.findItem(R.id.map_context_menu_id).setIntent( + new Intent(Intent.ACTION_VIEW, Uri + .parse(WebView.SCHEME_GEO + + URLEncoder.encode(extra)))); + menu.findItem(R.id.copy_geo_context_menu_id) + .setOnMenuItemClickListener( + new Copy(extra)); + break; + + case WebView.HitTestResult.SRC_ANCHOR_TYPE: + case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE: + TextView titleView = (TextView) LayoutInflater.from(mActivity) + .inflate(android.R.layout.browser_link_context_header, + null); + titleView.setText(extra); + menu.setHeaderView(titleView); + // decide whether to show the open link in new tab option + boolean showNewTab = mTabControl.canCreateNewTab(); + MenuItem newTabItem + = menu.findItem(R.id.open_newtab_context_menu_id); + newTabItem.setVisible(showNewTab); + if (showNewTab) { + newTabItem.setOnMenuItemClickListener( + new MenuItem.OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + final Tab parent = mTabControl.getCurrentTab(); + final Tab newTab = openTab(extra, false); + if (newTab != parent) { + parent.addChildTab(newTab); + } + return true; + } + }); + } + menu.findItem(R.id.bookmark_context_menu_id).setVisible( + Bookmarks.urlHasAcceptableScheme(extra)); + PackageManager pm = mActivity.getPackageManager(); + Intent send = new Intent(Intent.ACTION_SEND); + send.setType("text/plain"); + ResolveInfo ri = pm.resolveActivity(send, + PackageManager.MATCH_DEFAULT_ONLY); + menu.findItem(R.id.share_link_context_menu_id) + .setVisible(ri != null); + if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE) { + break; + } + // otherwise fall through to handle image part + case WebView.HitTestResult.IMAGE_TYPE: + if (type == WebView.HitTestResult.IMAGE_TYPE) { + menu.setHeaderTitle(extra); + } + menu.findItem(R.id.view_image_context_menu_id).setIntent( + new Intent(Intent.ACTION_VIEW, Uri.parse(extra))); + menu.findItem(R.id.download_context_menu_id). + setOnMenuItemClickListener(new Download(extra)); + menu.findItem(R.id.set_wallpaper_context_menu_id). + setOnMenuItemClickListener(new WallpaperHandler(mActivity, + extra)); + break; + + default: + Log.w(LOGTAG, "We should not get here."); + break; + } + //update the ui + mUi.onContextMenuCreated(menu); + } + + /** + * As the menu can be open when loading state changes + * we must manually update the state of the stop/reload menu + * item + */ + private void updateInLoadMenuItems(Menu menu) { + if (menu == null) { + return; + } + MenuItem dest = menu.findItem(R.id.stop_reload_menu_id); + MenuItem src = mInLoad ? + menu.findItem(R.id.stop_menu_id): + menu.findItem(R.id.reload_menu_id); + if (src != null) { + dest.setIcon(src.getIcon()); + dest.setTitle(src.getTitle()); + } + } + + boolean prepareOptionsMenu(Menu menu) { + // This happens when the user begins to hold down the menu key, so + // allow them to chord to get a shortcut. + mCanChord = true; + // Note: setVisible will decide whether an item is visible; while + // setEnabled() will decide whether an item is enabled, which also means + // whether the matching shortcut key will function. + switch (mMenuState) { + case EMPTY_MENU: + if (mCurrentMenuState != mMenuState) { + menu.setGroupVisible(R.id.MAIN_MENU, false); + menu.setGroupEnabled(R.id.MAIN_MENU, false); + menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false); + } + break; + default: + if (mCurrentMenuState != mMenuState) { + menu.setGroupVisible(R.id.MAIN_MENU, true); + menu.setGroupEnabled(R.id.MAIN_MENU, true); + menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true); + } + final WebView w = getCurrentTopWebView(); + boolean canGoBack = false; + boolean canGoForward = false; + boolean isHome = false; + if (w != null) { + canGoBack = w.canGoBack(); + canGoForward = w.canGoForward(); + isHome = mSettings.getHomePage().equals(w.getUrl()); + } + final MenuItem back = menu.findItem(R.id.back_menu_id); + back.setEnabled(canGoBack); + + final MenuItem home = menu.findItem(R.id.homepage_menu_id); + home.setEnabled(!isHome); + + final MenuItem forward = menu.findItem(R.id.forward_menu_id); + forward.setEnabled(canGoForward); + + // decide whether to show the share link option + PackageManager pm = mActivity.getPackageManager(); + Intent send = new Intent(Intent.ACTION_SEND); + send.setType("text/plain"); + ResolveInfo ri = pm.resolveActivity(send, + PackageManager.MATCH_DEFAULT_ONLY); + menu.findItem(R.id.share_page_menu_id).setVisible(ri != null); + + boolean isNavDump = mSettings.isNavDump(); + final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id); + nav.setVisible(isNavDump); + nav.setEnabled(isNavDump); + + boolean showDebugSettings = mSettings.showDebugSettings(); + final MenuItem counter = menu.findItem(R.id.dump_counters_menu_id); + counter.setVisible(showDebugSettings); + counter.setEnabled(showDebugSettings); + + // allow the ui to adjust state based settings + mUi.onPrepareOptionsMenu(menu); + + break; + } + mCurrentMenuState = mMenuState; + return true; + } + + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getGroupId() != R.id.CONTEXT_MENU) { + // menu remains active, so ensure comboview is dismissed + // if main menu option is selected + removeComboView(); + } + // check the action bar button before mCanChord check, as the prepare call + // doesn't come for action bar buttons + if (item.getItemId() == R.id.newtab) { + openTabToHomePage(); + return true; + } + if (!mCanChord) { + // The user has already fired a shortcut with this hold down of the + // menu key. + return false; + } + if (null == getCurrentTopWebView()) { + return false; + } + if (mMenuIsDown) { + // The shortcut action consumes the MENU. Even if it is still down, + // it won't trigger the next shortcut action. In the case of the + // shortcut action triggering a new activity, like Bookmarks, we + // won't get onKeyUp for MENU. So it is important to reset it here. + mMenuIsDown = false; + } + switch (item.getItemId()) { + // -- Main menu + case R.id.new_tab_menu_id: + openTabToHomePage(); + break; + + case R.id.incognito_menu_id: + openIncognitoTab(); + break; + + case R.id.goto_menu_id: + editUrl(); + break; + + case R.id.bookmarks_menu_id: + bookmarksOrHistoryPicker(false); + break; + + case R.id.active_tabs_menu_id: + showActiveTabsPage(); + break; + + case R.id.add_bookmark_menu_id: + bookmarkCurrentPage(AddBookmarkPage.DEFAULT_FOLDER_ID); + break; + + case R.id.stop_reload_menu_id: + if (mInLoad) { + stopLoading(); + } else { + getCurrentTopWebView().reload(); + } + break; + + case R.id.back_menu_id: + getCurrentTopWebView().goBack(); + break; + + case R.id.forward_menu_id: + getCurrentTopWebView().goForward(); + break; + + case R.id.close_menu_id: + // Close the subwindow if it exists. + if (mTabControl.getCurrentSubWindow() != null) { + dismissSubWindow(mTabControl.getCurrentTab()); + break; + } + closeCurrentTab(); + break; + + case R.id.homepage_menu_id: + Tab current = mTabControl.getCurrentTab(); + if (current != null) { + dismissSubWindow(current); + loadUrl(current.getWebView(), mSettings.getHomePage()); + } + break; + + case R.id.preferences_menu_id: + Intent intent = new Intent(mActivity, BrowserPreferencesPage.class); + intent.putExtra(BrowserPreferencesPage.CURRENT_PAGE, + getCurrentTopWebView().getUrl()); + mActivity.startActivityForResult(intent, PREFERENCES_PAGE); + break; + + case R.id.find_menu_id: + getCurrentTopWebView().showFindDialog(null); + break; + + case R.id.page_info_menu_id: + mPageDialogsHandler.showPageInfo(mTabControl.getCurrentTab(), + false); + break; + + case R.id.classic_history_menu_id: + bookmarksOrHistoryPicker(true); + break; + + case R.id.title_bar_share_page_url: + case R.id.share_page_menu_id: + Tab currentTab = mTabControl.getCurrentTab(); + if (null == currentTab) { + mCanChord = false; + return false; + } + currentTab.populatePickerData(); + sharePage(mActivity, currentTab.getTitle(), + currentTab.getUrl(), currentTab.getFavicon(), + createScreenshot(currentTab.getWebView(), + getDesiredThumbnailWidth(mActivity), + getDesiredThumbnailHeight(mActivity))); + break; + + case R.id.dump_nav_menu_id: + getCurrentTopWebView().debugDump(); + break; + + case R.id.dump_counters_menu_id: + getCurrentTopWebView().dumpV8Counters(); + break; + + case R.id.zoom_in_menu_id: + getCurrentTopWebView().zoomIn(); + break; + + case R.id.zoom_out_menu_id: + getCurrentTopWebView().zoomOut(); + break; + + case R.id.view_downloads_menu_id: + viewDownloads(); + break; + + case R.id.window_one_menu_id: + case R.id.window_two_menu_id: + case R.id.window_three_menu_id: + case R.id.window_four_menu_id: + case R.id.window_five_menu_id: + case R.id.window_six_menu_id: + case R.id.window_seven_menu_id: + case R.id.window_eight_menu_id: + { + int menuid = item.getItemId(); + for (int id = 0; id < WINDOW_SHORTCUT_ID_ARRAY.length; id++) { + if (WINDOW_SHORTCUT_ID_ARRAY[id] == menuid) { + Tab desiredTab = mTabControl.getTab(id); + if (desiredTab != null && + desiredTab != mTabControl.getCurrentTab()) { + switchToTab(id); + } + break; + } + } + } + break; + + default: + return false; + } + mCanChord = false; + return true; + } + + public boolean onContextItemSelected(MenuItem item) { + // chording is not an issue with context menus, but we use the same + // options selector, so set mCanChord to true so we can access them. + mCanChord = true; + int id = item.getItemId(); + boolean result = true; + switch (id) { + // For the context menu from the title bar + case R.id.title_bar_copy_page_url: + Tab currentTab = mTabControl.getCurrentTab(); + if (null == currentTab) { + result = false; + break; + } + WebView mainView = currentTab.getWebView(); + if (null == mainView) { + result = false; + break; + } + copy(mainView.getUrl()); + break; + // -- Browser context menu + case R.id.open_context_menu_id: + case R.id.bookmark_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 = getCurrentTopWebView(); + if (null == webView) { + result = false; + break; + } + final HashMap<String, WebView> hrefMap = + new HashMap<String, WebView>(); + hrefMap.put("webview", webView); + final Message msg = mHandler.obtainMessage( + FOCUS_NODE_HREF, id, 0, hrefMap); + webView.requestFocusNodeHref(msg); + break; + + default: + // For other context menus + result = onOptionsItemSelected(item); + } + mCanChord = false; + return result; + } + + /** + * support programmatically opening the context menu + */ + public void openContextMenu(View view) { + mActivity.openContextMenu(view); + } + + /** + * programmatically open the options menu + */ + public void openOptionsMenu() { + mActivity.openOptionsMenu(); + } + + public boolean onMenuOpened(int featureId, Menu menu) { + if (mOptionsMenuOpen) { + if (mConfigChanged) { + // We do not need to make any changes to the state of the + // title bar, since the only thing that happened was a + // change in orientation + mConfigChanged = false; + } else { + if (!mExtendedMenuOpen) { + mExtendedMenuOpen = true; + mUi.onExtendedMenuOpened(); + } else { + // Switching the menu back to icon view, so show the + // title bar once again. + mExtendedMenuOpen = false; + mUi.onExtendedMenuClosed(mInLoad); + mUi.onOptionsMenuOpened(); + } + } + } else { + // The options menu is closed, so open it, and show the title + mOptionsMenuOpen = true; + mConfigChanged = false; + mExtendedMenuOpen = false; + mUi.onOptionsMenuOpened(); + } + return true; + } + + public void onOptionsMenuClosed(Menu menu) { + mOptionsMenuOpen = false; + mUi.onOptionsMenuClosed(mInLoad); + } + + public void onContextMenuClosed(Menu menu) { + mUi.onContextMenuClosed(menu, mInLoad); + } + + // Helper method for getting the top window. + @Override + public WebView getCurrentTopWebView() { + return mTabControl.getCurrentTopWebView(); + } + + @Override + public WebView getCurrentWebView() { + return mTabControl.getCurrentWebView(); + } + + /* + * This method is called as a result of the user selecting the options + * menu to see the download window. It shows the download window on top of + * the current window. + */ + void viewDownloads() { + Intent intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS); + mActivity.startActivity(intent); + } + + // action mode + + void onActionModeStarted(ActionMode mode) { + mUi.onActionModeStarted(mode); + mActionMode = mode; + } + + /* + * True if a custom ActionMode (i.e. find or select) is in use. + */ + @Override + public boolean isInCustomActionMode() { + return mActionMode != null; + } + + /* + * End the current ActionMode. + */ + @Override + public void endActionMode() { + if (mActionMode != null) { + mActionMode.finish(); + } + } + + /* + * Called by find and select when they are finished. Replace title bars + * as necessary. + */ + public void onActionModeFinished(ActionMode mode) { + if (!isInCustomActionMode()) return; + mUi.onActionModeFinished(mInLoad); + mActionMode = null; + } + + boolean isInLoad() { + return mInLoad; + } + + // bookmark handling + + /** + * add the current page as a bookmark to the given folder id + * @param folderId use -1 for the default folder + */ + @Override + public void bookmarkCurrentPage(long folderId) { + Intent i = new Intent(mActivity, + AddBookmarkPage.class); + WebView w = getCurrentTopWebView(); + i.putExtra(BrowserContract.Bookmarks.URL, w.getUrl()); + i.putExtra(BrowserContract.Bookmarks.TITLE, w.getTitle()); + String touchIconUrl = w.getTouchIconUrl(); + if (touchIconUrl != null) { + i.putExtra(AddBookmarkPage.TOUCH_ICON_URL, touchIconUrl); + WebSettings settings = w.getSettings(); + if (settings != null) { + i.putExtra(AddBookmarkPage.USER_AGENT, + settings.getUserAgentString()); + } + } + i.putExtra(BrowserContract.Bookmarks.THUMBNAIL, + createScreenshot(w, getDesiredThumbnailWidth(mActivity), + getDesiredThumbnailHeight(mActivity))); + i.putExtra(BrowserContract.Bookmarks.FAVICON, w.getFavicon()); + i.putExtra(BrowserContract.Bookmarks.PARENT, + folderId); + // Put the dialog at the upper right of the screen, covering the + // star on the title bar. + i.putExtra("gravity", Gravity.RIGHT | Gravity.TOP); + mActivity.startActivity(i); + } + + // file chooser + public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) { + mUploadHandler = new UploadHandler(this); + mUploadHandler.openFileChooser(uploadMsg, acceptType); + } + + // thumbnails + + /** + * 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 desired width for thumbnail screenshot. + */ + static int getDesiredThumbnailWidth(Context context) { + return context.getResources().getDimensionPixelOffset( + R.dimen.bookmarkThumbnailWidth); + } + + /** + * 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 desired height for thumbnail screenshot. + */ + static int getDesiredThumbnailHeight(Context context) { + return context.getResources().getDimensionPixelOffset( + R.dimen.bookmarkThumbnailHeight); + } + + private static Bitmap createScreenshot(WebView view, int width, int height) { + Picture thumbnail = view.capturePicture(); + if (thumbnail == null) { + return null; + } + Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); + Canvas canvas = new Canvas(bm); + // May need to tweak these values to determine what is the + // best scale factor + int thumbnailWidth = thumbnail.getWidth(); + int thumbnailHeight = thumbnail.getHeight(); + float scaleFactorX = 1.0f; + float scaleFactorY = 1.0f; + if (thumbnailWidth > 0) { + scaleFactorX = (float) width / (float)thumbnailWidth; + } else { + return null; + } + if (view.getWidth() > view.getHeight() && + thumbnailHeight < view.getHeight() && thumbnailHeight > 0) { + // If the device is in landscape and the page is shorter + // than the height of the view, stretch the thumbnail to fill the + // space. + scaleFactorY = (float) height / (float)thumbnailHeight; + } else { + // In the portrait case, this looks nice. + scaleFactorY = scaleFactorX; + } + + canvas.scale(scaleFactorX, scaleFactorY); + + thumbnail.draw(canvas); + return bm; + } + + private void updateScreenshot(WebView view) { + // If this is a bookmarked site, add a screenshot to the database. + // FIXME: When should we update? Every time? + // FIXME: Would like to make sure there is actually something to + // draw, but the API for that (WebViewCore.pictureReady()) is not + // currently accessible here. + + final Bitmap bm = createScreenshot(view, getDesiredThumbnailWidth(mActivity), + getDesiredThumbnailHeight(mActivity)); + if (bm == null) { + return; + } + + final ContentResolver cr = mActivity.getContentResolver(); + final String url = view.getUrl(); + final String originalUrl = view.getOriginalUrl(); + + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... unused) { + Cursor cursor = null; + try { + cursor = Bookmarks.queryCombinedForUrl(cr, originalUrl, url); + if (cursor != null && cursor.moveToFirst()) { + final ByteArrayOutputStream os = + new ByteArrayOutputStream(); + bm.compress(Bitmap.CompressFormat.PNG, 100, os); + + ContentValues values = new ContentValues(); + values.put(Images.THUMBNAIL, os.toByteArray()); + values.put(Images.URL, cursor.getString(0)); + + do { + cr.update(Images.CONTENT_URI, values, null, null); + } while (cursor.moveToNext()); + } + } catch (IllegalStateException e) { + // Ignore + } finally { + if (cursor != null) cursor.close(); + } + return null; + } + }.execute(); + } + + private class Copy implements OnMenuItemClickListener { + private CharSequence mText; + + public boolean onMenuItemClick(MenuItem item) { + copy(mText); + return true; + } + + public Copy(CharSequence toCopy) { + mText = toCopy; + } + } + + private class Download implements OnMenuItemClickListener { + private String mText; + + public boolean onMenuItemClick(MenuItem item) { + mDownloadHandler.onDownloadStartNoStream(mText, null, null, null, -1); + return true; + } + + public Download(String toDownload) { + mText = toDownload; + } + } + + /********************** TODO: UI stuff *****************************/ + + // these methods have been copied, they still need to be cleaned up + + /****************** tabs ***************************************************/ + + // basic tab interactions: + + // it is assumed that tabcontrol already knows about the tab + protected void addTab(Tab tab) { + mUi.addTab(tab); + } + + protected void removeTab(Tab tab) { + mUi.removeTab(tab); + mTabControl.removeTab(tab); + } + + protected void setActiveTab(Tab tab) { + // Update the UI before setting the current tab in TabControl + // so the UI can access the old tab to switch over from + mUi.setActiveTab(tab); + mTabControl.setCurrentTab(tab); + } + + protected void closeEmptyChildTab() { + Tab current = mTabControl.getCurrentTab(); + if (current != null + && current.getWebView().copyBackForwardList().getSize() == 0) { + Tab parent = current.getParentTab(); + if (parent != null) { + switchToTab(mTabControl.getTabIndex(parent)); + closeTab(current); + } + } + } + + protected void reuseTab(Tab appTab, String appId, UrlData urlData) { + Log.i(LOGTAG, "Reusing tab for " + appId); + // Dismiss the subwindow if applicable. + dismissSubWindow(appTab); + // Since we might kill the WebView, remove it from the + // content view first. + mUi.detachTab(appTab); + // Recreate the main WebView after destroying the old one. + // If the WebView has the same original url and is on that + // page, it can be reused. + boolean needsLoad = + mTabControl.recreateWebView(appTab, urlData); + // TODO: analyze why the remove and add are necessary + mUi.attachTab(appTab); + if (mTabControl.getCurrentTab() != appTab) { + switchToTab(mTabControl.getTabIndex(appTab)); + if (needsLoad) { + loadUrlDataIn(appTab, urlData); + } + } else { + // If the tab was the current tab, we have to attach + // it to the view system again. + setActiveTab(appTab); + if (needsLoad) { + loadUrlDataIn(appTab, urlData); + } + } + } + + // Remove the sub window if it exists. Also called by TabControl when the + // user clicks the 'X' to dismiss a sub window. + public void dismissSubWindow(Tab tab) { + removeSubWindow(tab); + // dismiss the subwindow. This will destroy the WebView. + tab.dismissSubWindow(); + getCurrentTopWebView().requestFocus(); + } + + @Override + public void removeSubWindow(Tab t) { + if (t.getSubWebView() != null) { + mUi.removeSubWindow(t.getSubViewContainer()); + } + } + + @Override + public void attachSubWindow(Tab tab) { + if (tab.getSubWebView() != null) { + mUi.attachSubWindow(tab.getSubViewContainer()); + getCurrentTopWebView().requestFocus(); + } + } + + // A wrapper function of {@link #openTabAndShow(UrlData, boolean, String)} + // that accepts url as string. + + protected Tab openTabAndShow(String url, boolean closeOnExit, String appId) { + return openTabAndShow(new UrlData(url), closeOnExit, appId); + } + + // This method does a ton of stuff. It will attempt to create a new tab + // if we haven't reached MAX_TABS. Otherwise it uses the current tab. If + // url isn't null, it will load the given url. + + public Tab openTabAndShow(UrlData urlData, boolean closeOnExit, + String appId) { + final Tab currentTab = mTabControl.getCurrentTab(); + if (mTabControl.canCreateNewTab()) { + final Tab tab = mTabControl.createNewTab(closeOnExit, appId, + urlData.mUrl, false); + WebView webview = tab.getWebView(); + // We must set the new tab as the current tab to reflect the old + // animation behavior. + addTab(tab); + setActiveTab(tab); + if (!urlData.isEmpty()) { + loadUrlDataIn(tab, urlData); + } + return tab; + } else { + // Get rid of the subwindow if it exists + dismissSubWindow(currentTab); + if (!urlData.isEmpty()) { + // Load the given url. + loadUrlDataIn(currentTab, urlData); + } + return currentTab; + } + } + + protected Tab openTab(String url, boolean forceForeground) { + if (mSettings.openInBackground() && !forceForeground) { + Tab tab = mTabControl.createNewTab(); + if (tab != null) { + addTab(tab); + WebView view = tab.getWebView(); + loadUrl(view, url); + } + return tab; + } else { + return openTabAndShow(url, false, null); + } + } + + @Override + public Tab openIncognitoTab() { + if (mTabControl.canCreateNewTab()) { + Tab currentTab = mTabControl.getCurrentTab(); + Tab tab = mTabControl.createNewTab(false, null, null, true); + addTab(tab); + setActiveTab(tab); + return tab; + } + return null; + } + + /** + * @param index Index of the tab to change to, as defined by + * mTabControl.getTabIndex(Tab t). + * @return boolean True if we successfully switched to a different tab. If + * the indexth tab is null, or if that tab is the same as + * the current one, return false. + */ + @Override + public boolean switchToTab(int index) { + Tab tab = mTabControl.getTab(index); + Tab currentTab = mTabControl.getCurrentTab(); + if (tab == null || tab == currentTab) { + return false; + } + setActiveTab(tab); + return true; + } + + @Override + public Tab openTabToHomePage() { + return openTabAndShow(mSettings.getHomePage(), false, null); + } + + @Override + public void closeCurrentTab() { + final Tab current = mTabControl.getCurrentTab(); + if (mTabControl.getTabCount() == 1) { + // This is the last tab. Open a new one, with the home + // page and close the current one. + openTabToHomePage(); + closeTab(current); + return; + } + final Tab parent = current.getParentTab(); + int indexToShow = -1; + if (parent != null) { + indexToShow = mTabControl.getTabIndex(parent); + } else { + final int currentIndex = mTabControl.getCurrentIndex(); + // Try to move to the tab to the right + indexToShow = currentIndex + 1; + if (indexToShow > mTabControl.getTabCount() - 1) { + // Try to move to the tab to the left + indexToShow = currentIndex - 1; + } + } + if (switchToTab(indexToShow)) { + // Close window + closeTab(current); + } + } + + /** + * Close the tab, remove its associated title bar, and adjust mTabControl's + * current tab to a valid value. + */ + @Override + public void closeTab(Tab tab) { + int currentIndex = mTabControl.getCurrentIndex(); + int removeIndex = mTabControl.getTabIndex(tab); + removeTab(tab); + if (currentIndex >= removeIndex && currentIndex != 0) { + currentIndex--; + } + Tab newtab = mTabControl.getTab(currentIndex); + setActiveTab(newtab); + if (!mTabControl.hasAnyOpenIncognitoTabs()) { + WebView.cleanupPrivateBrowsingFiles(mActivity); + } + } + + /**************** TODO: Url loading clean up *******************************/ + + // Called when loading from context menu or LOAD_URL message + protected void loadUrlFromContext(WebView view, String url) { + // In case the user enters nothing. + if (url != null && url.length() != 0 && view != null) { + url = UrlUtils.smartUrlFilter(url); + if (!view.getWebViewClient().shouldOverrideUrlLoading(view, url)) { + loadUrl(view, url); + } + } + } + + /** + * Load the URL into the given WebView and update the title bar + * to reflect the new load. Call this instead of WebView.loadUrl + * directly. + * @param view The WebView used to load url. + * @param url The URL to load. + */ + protected void loadUrl(WebView view, String url) { + updateTitleBarForNewLoad(view, url); + view.loadUrl(url); + } + + /** + * Load UrlData into a Tab and update the title bar to reflect the new + * load. Call this instead of UrlData.loadIn directly. + * @param t The Tab used to load. + * @param data The UrlData being loaded. + */ + protected void loadUrlDataIn(Tab t, UrlData data) { + updateTitleBarForNewLoad(t.getWebView(), data.mUrl); + data.loadIn(t); + } + + /** + * Resets the browser title-view to whatever it must be + * (for example, if we had a loading error) + * When we have a new page, we call resetTitle, when we + * have to reset the titlebar to whatever it used to be + * (for example, if the user chose to stop loading), we + * call resetTitleAndRevertLockIcon. + */ + public void resetTitleAndRevertLockIcon(Tab tab) { + mUi.resetTitleAndRevertLockIcon(tab); + } + + void resetTitleAndIcon(Tab tab) { + mUi.resetTitleAndIcon(tab); + } + + /** + * If the WebView is the top window, update the title bar to reflect + * loading the new URL. i.e. set its text, clear the favicon (which + * will be set once the page begins loading), and set the progress to + * INITIAL_PROGRESS to show that the page has begun to load. Called + * by loadUrl and loadUrlDataIn. + * @param view The WebView that is starting a load. + * @param url The URL that is being loaded. + */ + private void updateTitleBarForNewLoad(WebView view, String url) { + if (view == getCurrentTopWebView()) { + // TODO we should come with a tab and not with a view + Tab tab = mTabControl.getTabFromView(view); + setUrlTitle(tab, url, null); + mUi.setFavicon(tab, null); + onProgressChanged(tab, INITIAL_PROGRESS); + } + } + + /** + * Sets a title composed of the URL and the title string. + * @param url The URL of the site being loaded. + * @param title The title of the site being loaded. + */ + void setUrlTitle(Tab tab, String url, String title) { + tab.setCurrentUrl(url); + tab.setCurrentTitle(title); + // If we are in voice search mode, the title has already been set. + if (tab.isInVoiceSearchMode()) return; + mUi.setUrlTitle(tab, url, title); + } + + void goBackOnePageOrQuit() { + Tab current = mTabControl.getCurrentTab(); + if (current == null) { + /* + * Instead of finishing the activity, simply push this to the back + * of the stack and let ActivityManager to choose the foreground + * activity. As BrowserActivity is singleTask, it will be always the + * root of the task. So we can use either true or false for + * moveTaskToBack(). + */ + mActivity.moveTaskToBack(true); + return; + } + WebView w = current.getWebView(); + if (w.canGoBack()) { + w.goBack(); + } else { + // Check to see if we are closing a window that was created by + // another window. If so, we switch back to that window. + Tab parent = current.getParentTab(); + if (parent != null) { + switchToTab(mTabControl.getTabIndex(parent)); + // Now we close the other tab + closeTab(current); + } else { + if (current.closeOnExit()) { + // force the tab's inLoad() to be false as we are going to + // either finish the activity or remove the tab. This will + // ensure pauseWebViewTimers() taking action. + mTabControl.getCurrentTab().clearInPageLoad(); + if (mTabControl.getTabCount() == 1) { + mActivity.finish(); + return; + } + if (mActivityPaused) { + Log.e(LOGTAG, "BrowserActivity is already paused " + + "while handing goBackOnePageOrQuit."); + } + pauseWebViewTimers(true); + removeTab(current); + } + /* + * Instead of finishing the activity, simply push this to the back + * of the stack and let ActivityManager to choose the foreground + * activity. As BrowserActivity is singleTask, it will be always the + * root of the task. So we can use either true or false for + * moveTaskToBack(). + */ + mActivity.moveTaskToBack(true); + } + } + } + + /** + * Feed the previously stored results strings to the BrowserProvider so that + * the SearchDialog will show them instead of the standard searches. + * @param result String to show on the editable line of the SearchDialog. + */ + @Override + public void showVoiceSearchResults(String result) { + ContentProviderClient client = mActivity.getContentResolver() + .acquireContentProviderClient(Browser.BOOKMARKS_URI); + ContentProvider prov = client.getLocalContentProvider(); + BrowserProvider bp = (BrowserProvider) prov; + bp.setQueryResults(mTabControl.getCurrentTab().getVoiceSearchResults()); + client.release(); + + Bundle bundle = createGoogleSearchSourceBundle( + GOOGLE_SEARCH_SOURCE_SEARCHKEY); + bundle.putBoolean(SearchManager.CONTEXT_IS_VOICE, true); + startSearch(result, false, bundle, false); + } + + private void startSearch(String initialQuery, boolean selectInitialQuery, + Bundle appSearchData, boolean globalSearch) { + if (appSearchData == null) { + appSearchData = createGoogleSearchSourceBundle( + GOOGLE_SEARCH_SOURCE_TYPE); + } + + SearchEngine searchEngine = mSettings.getSearchEngine(); + if (searchEngine != null && !searchEngine.supportsVoiceSearch()) { + appSearchData.putBoolean(SearchManager.DISABLE_VOICE_SEARCH, true); + } + mActivity.startSearch(initialQuery, selectInitialQuery, appSearchData, + globalSearch); + } + + private Bundle createGoogleSearchSourceBundle(String source) { + Bundle bundle = new Bundle(); + bundle.putString(Search.SOURCE, source); + return bundle; + } + + /** + * handle key events in browser + * + * @param keyCode + * @param event + * @return true if handled, false to pass to super + */ + boolean onKeyDown(int keyCode, KeyEvent event) { + // Even if MENU is already held down, we need to call to super to open + // the IME on long press. + if (KeyEvent.KEYCODE_MENU == keyCode) { + mMenuIsDown = true; + return false; + } + // 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_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()) { + pageUp(); + } else { + pageDown(); + } + return true; + case KeyEvent.KEYCODE_BACK: + if (event.getRepeatCount() == 0) { + event.startTracking(); + return true; + } else if (mUi.showsWeb() + && event.isLongPress()) { + bookmarksOrHistoryPicker(true); + return true; + } + break; + } + return false; + } + + boolean onKeyUp(int keyCode, KeyEvent event) { + switch(keyCode) { + case KeyEvent.KEYCODE_MENU: + mMenuIsDown = false; + break; + case KeyEvent.KEYCODE_BACK: + if (event.isTracking() && !event.isCanceled()) { + onBackKey(); + return true; + } + break; + } + return false; + } + + public boolean isMenuDown() { + return mMenuIsDown; + } + +} diff --git a/src/com/android/browser/Dots.java b/src/com/android/browser/Dots.java deleted file mode 100644 index e28d868..0000000 --- a/src/com/android/browser/Dots.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.browser; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.Gravity; -import android.widget.ImageView; -import android.widget.LinearLayout; - -import java.util.Map; - -/** - * Displays a series of dots. The selected one is highlighted. - * No animations yet. Nothing fancy. - */ -class Dots extends LinearLayout { - - private static final int MAX_DOTS = 8; - private int mSelected = -1; - - public Dots(Context context) { - this(context, null); - } - - public Dots(Context context, AttributeSet attrs) { - super(context, attrs); - - setGravity(Gravity.CENTER); - setPadding(0, 4, 0, 4); - - LayoutParams lp = - new LayoutParams(LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT); - - for (int i = 0; i < MAX_DOTS; i++) { - ImageView dotView = new ImageView(getContext()); - dotView.setImageResource(R.drawable.page_indicator_unselected2); - addView(dotView, lp); - } - } - - /** - * @param dotCount if less than 1 or greater than MAX_DOTS, Dots - * disappears - */ - public void setDotCount(int dotCount) { - if (dotCount > 1 && dotCount <= MAX_DOTS) { - setVisibility(VISIBLE); - for (int i = 0; i < MAX_DOTS; i++) { - getChildAt(i).setVisibility(i < dotCount? VISIBLE : GONE); - } - } else { - setVisibility(GONE); - } - } - - public void setSelected(int index) { - if (index < 0 || index >= MAX_DOTS) return; - - if (mSelected >= 0) { - // Unselect old - ((ImageView)getChildAt(mSelected)).setImageResource( - R.drawable.page_indicator_unselected2); - } - ((ImageView)getChildAt(index)).setImageResource(R.drawable.page_indicator); - mSelected = index; - } -} diff --git a/src/com/android/browser/DownloadHandler.java b/src/com/android/browser/DownloadHandler.java new file mode 100644 index 0000000..b833bd7 --- /dev/null +++ b/src/com/android/browser/DownloadHandler.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.DownloadManager; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.net.WebAddress; +import android.os.Environment; +import android.util.Log; +import android.webkit.CookieManager; +import android.webkit.URLUtil; +import android.widget.Toast; + +/** + * Handle download requests + */ +public class DownloadHandler { + + private static final boolean LOGD_ENABLED = + com.android.browser.Browser.LOGD_ENABLED; + + private static final String LOGTAG = "DLHandler"; + + Activity mActivity; + + public DownloadHandler(Activity activity) { + mActivity = activity; + } + + /** + * Notify the host application a download should be done, or that + * the data should be streamed if a streaming viewer is available. + * @param url The full url to the content that should be downloaded + * @param contentDisposition Content-disposition http header, if + * present. + * @param mimetype The mimetype of the content reported by the server + * @param contentLength The file size reported by the server + */ + public void onDownloadStart(String url, String userAgent, + String contentDisposition, String mimetype, long contentLength) { + // if we're dealing wih A/V content that's not explicitly marked + // for download, check if it's streamable. + if (contentDisposition == null + || !contentDisposition.regionMatches( + true, 0, "attachment", 0, 10)) { + // query the package manager to see if there's a registered handler + // that matches. + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(Uri.parse(url), mimetype); + ResolveInfo info = mActivity.getPackageManager().resolveActivity(intent, + PackageManager.MATCH_DEFAULT_ONLY); + if (info != null) { + ComponentName myName = mActivity.getComponentName(); + // If we resolved to ourselves, we don't want to attempt to + // load the url only to try and download it again. + if (!myName.getPackageName().equals( + info.activityInfo.packageName) + || !myName.getClassName().equals( + info.activityInfo.name)) { + // someone (other than us) knows how to handle this mime + // type with this scheme, don't download. + try { + mActivity.startActivity(intent); + return; + } catch (ActivityNotFoundException ex) { + if (LOGD_ENABLED) { + Log.d(LOGTAG, "activity not found for " + mimetype + + " over " + Uri.parse(url).getScheme(), + ex); + } + // Best behavior is to fall back to a download in this + // case + } + } + } + } + onDownloadStartNoStream(url, userAgent, contentDisposition, mimetype, contentLength); + } + + // This is to work around the fact that java.net.URI throws Exceptions + // instead of just encoding URL's properly + // Helper method for onDownloadStartNoStream + private static String encodePath(String path) { + char[] chars = path.toCharArray(); + + boolean needed = false; + for (char c : chars) { + if (c == '[' || c == ']') { + needed = true; + break; + } + } + if (needed == false) { + return path; + } + + StringBuilder sb = new StringBuilder(""); + for (char c : chars) { + if (c == '[' || c == ']') { + sb.append('%'); + sb.append(Integer.toHexString(c)); + } else { + sb.append(c); + } + } + + return sb.toString(); + } + + /** + * Notify the host application a download should be done, even if there + * is a streaming viewer available for thise type. + * @param url The full url to the content that should be downloaded + * @param contentDisposition Content-disposition http header, if + * present. + * @param mimetype The mimetype of the content reported by the server + * @param contentLength The file size reported by the server + */ + /*package */ void onDownloadStartNoStream(String url, String userAgent, + String contentDisposition, String mimetype, long contentLength) { + + String filename = URLUtil.guessFileName(url, + contentDisposition, mimetype); + + // Check to see if we have an SDCard + String status = Environment.getExternalStorageState(); + if (!status.equals(Environment.MEDIA_MOUNTED)) { + int title; + String msg; + + // Check to see if the SDCard is busy, same as the music app + if (status.equals(Environment.MEDIA_SHARED)) { + msg = mActivity.getString(R.string.download_sdcard_busy_dlg_msg); + title = R.string.download_sdcard_busy_dlg_title; + } else { + msg = mActivity.getString(R.string.download_no_sdcard_dlg_msg, filename); + title = R.string.download_no_sdcard_dlg_title; + } + + new AlertDialog.Builder(mActivity) + .setTitle(title) + .setIcon(android.R.drawable.ic_dialog_alert) + .setMessage(msg) + .setPositiveButton(R.string.ok, null) + .show(); + return; + } + + // java.net.URI is a lot stricter than KURL so we have to encode some + // extra characters. Fix for b 2538060 and b 1634719 + WebAddress webAddress; + try { + webAddress = new WebAddress(url); + webAddress.setPath(encodePath(webAddress.getPath())); + } catch (Exception e) { + // This only happens for very bad urls, we want to chatch the + // exception here + Log.e(LOGTAG, "Exception trying to parse url:" + url); + return; + } + + String addressString = webAddress.toString(); + Uri uri = Uri.parse(addressString); + DownloadManager.Request request = new DownloadManager.Request(uri); + request.setMimeType(mimetype); + request.setDestinationInExternalFilesDir(mActivity, null, filename); + // let this downloaded file be scanned by MediaScanner - so that it can + // show up in Gallery app, for example. + request.allowScanningByMediaScanner(); + request.setDescription(webAddress.getHost()); + String cookies = CookieManager.getInstance().getCookie(url); + request.addRequestHeader("cookie", cookies); + request.setNotificationVisibility( + DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); + if (mimetype == null) { + ContentValues values = new ContentValues(); + values.put(FetchUrlMimeType.URI, addressString); + // XXX: Have to use the old url since the cookies were stored using the + // old percent-encoded url. + values.put(FetchUrlMimeType.COOKIE_DATA, cookies); + values.put(FetchUrlMimeType.USER_AGENT, userAgent); + + // We must have long pressed on a link or image to download it. We + // are not sure of the mimetype in this case, so do a head request + new FetchUrlMimeType(mActivity, request).execute(values); + } else { + DownloadManager manager + = (DownloadManager) mActivity.getSystemService(Context.DOWNLOAD_SERVICE); + manager.enqueue(request); + } + Toast.makeText(mActivity, R.string.download_pending, Toast.LENGTH_SHORT) + .show(); + } + +} diff --git a/src/com/android/browser/FetchUrlMimeType.java b/src/com/android/browser/FetchUrlMimeType.java index 89ac13d..0481806 100644 --- a/src/com/android/browser/FetchUrlMimeType.java +++ b/src/com/android/browser/FetchUrlMimeType.java @@ -16,25 +16,24 @@ package com.android.browser; -import android.app.DownloadManager; -import android.content.ContentValues; -import android.content.Context; -import android.net.Proxy; -import android.net.Uri; -import android.net.http.AndroidHttpClient; - +import org.apache.http.Header; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; -import org.apache.http.Header; import org.apache.http.client.methods.HttpHead; import org.apache.http.conn.params.ConnRouteParams; -import java.io.IOException; - +import android.app.Activity; +import android.app.DownloadManager; +import android.content.ContentValues; +import android.content.Context; +import android.net.Proxy; +import android.net.http.AndroidHttpClient; import android.os.AsyncTask; import android.webkit.MimeTypeMap; import android.webkit.URLUtil; +import java.io.IOException; + /** * This class is used to pull down the http headers of a given URL so that * we can analyse the mimetype and make any correction needed before we give @@ -53,11 +52,12 @@ class FetchUrlMimeType extends AsyncTask<ContentValues, String, ContentValues> { public static final String URI = "uri"; public static final String USER_AGENT = "user_agent"; public static final String COOKIE_DATA = "cookie_data"; - BrowserActivity mActivity; + + Activity mActivity; ContentValues mValues; DownloadManager.Request mRequest; - public FetchUrlMimeType(BrowserActivity activity, + public FetchUrlMimeType(Activity activity, DownloadManager.Request request) { mActivity = activity; mRequest = request; diff --git a/src/com/android/browser/IntentHandler.java b/src/com/android/browser/IntentHandler.java new file mode 100644 index 0000000..040af81 --- /dev/null +++ b/src/com/android/browser/IntentHandler.java @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.android.browser; + +import com.android.browser.search.SearchEngine; +import com.android.common.Search; +import com.android.common.speech.LoggingEvents; + +import android.app.Activity; +import android.app.SearchManager; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.provider.Browser; +import android.provider.MediaStore; +import android.speech.RecognizerResultsIntent; +import android.text.TextUtils; +import android.util.Patterns; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * Handle all browser related intents + */ +public class IntentHandler { + + // "source" parameter for Google search suggested by the browser + final static String GOOGLE_SEARCH_SOURCE_SUGGEST = "browser-suggest"; + // "source" parameter for Google search from unknown source + final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown"; + + /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null); + + private Activity mActivity; + private Controller mController; + private TabControl mTabControl; + private BrowserSettings mSettings; + + public IntentHandler(Activity browser, Controller controller) { + mActivity = browser; + mController = controller; + mTabControl = mController.getTabControl(); + mSettings = controller.getSettings(); + } + + void onNewIntent(Intent intent) { + Tab current = mTabControl.getCurrentTab(); + // When a tab is closed on exit, the current tab index is set to -1. + // Reset before proceed as Browser requires the current tab to be set. + if (current == null) { + // Try to reset the tab in case the index was incorrect. + current = mTabControl.getTab(0); + if (current == null) { + // No tabs at all so just ignore this intent. + return; + } + mController.setActiveTab(current); + mController.resetTitleAndIcon(current); + } + final String action = intent.getAction(); + final int flags = intent.getFlags(); + if (Intent.ACTION_MAIN.equals(action) || + (flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) { + // just resume the browser + return; + } + // In case the SearchDialog is open. + ((SearchManager) mActivity.getSystemService(Context.SEARCH_SERVICE)) + .stopSearch(); + boolean activateVoiceSearch = RecognizerResultsIntent + .ACTION_VOICE_SEARCH_RESULTS.equals(action); + if (Intent.ACTION_VIEW.equals(action) + || Intent.ACTION_SEARCH.equals(action) + || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action) + || Intent.ACTION_WEB_SEARCH.equals(action) + || activateVoiceSearch) { + if (current.isInVoiceSearchMode()) { + String title = current.getVoiceDisplayTitle(); + if (title != null && title.equals(intent.getStringExtra( + SearchManager.QUERY))) { + // The user submitted the same search as the last voice + // search, so do nothing. + return; + } + if (Intent.ACTION_SEARCH.equals(action) + && current.voiceSearchSourceIsGoogle()) { + Intent logIntent = new Intent( + LoggingEvents.ACTION_LOG_EVENT); + logIntent.putExtra(LoggingEvents.EXTRA_EVENT, + LoggingEvents.VoiceSearch.QUERY_UPDATED); + logIntent.putExtra( + LoggingEvents.VoiceSearch.EXTRA_QUERY_UPDATED_VALUE, + intent.getDataString()); + mActivity.sendBroadcast(logIntent); + // Note, onPageStarted will revert the voice title bar + // When http://b/issue?id=2379215 is fixed, we should update + // the title bar here. + } + } + // If this was a search request (e.g. search query directly typed into the address bar), + // pass it on to the default web search provider. + if (handleWebSearchIntent(mActivity, mController, intent)) { + return; + } + + UrlData urlData = getUrlDataFromIntent(intent); + if (urlData.isEmpty()) { + urlData = new UrlData(mSettings.getHomePage()); + } + + final String appId = intent + .getStringExtra(Browser.EXTRA_APPLICATION_ID); + if ((Intent.ACTION_VIEW.equals(action) + // If a voice search has no appId, it means that it came + // from the browser. In that case, reuse the current tab. + || (activateVoiceSearch && appId != null)) + && !mActivity.getPackageName().equals(appId) + && (flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) { + Tab appTab = mTabControl.getTabFromId(appId); + if (appTab != null) { + mController.reuseTab(appTab, appId, urlData); + return; + } else { + // No matching application tab, try to find a regular tab + // with a matching url. + appTab = mTabControl.findUnusedTabWithUrl(urlData.mUrl); + if (appTab != null) { + if (current != appTab) { + mController.switchToTab(mTabControl.getTabIndex(appTab)); + } + // Otherwise, we are already viewing the correct tab. + } else { + // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url + // will be opened in a new tab unless we have reached + // MAX_TABS. Then the url will be opened in the current + // tab. If a new tab is created, it will have "true" for + // exit on close. + mController.openTabAndShow(urlData, true, appId); + } + } + } else { + if (!urlData.isEmpty() + && urlData.mUrl.startsWith("about:debug")) { + if ("about:debug.dom".equals(urlData.mUrl)) { + current.getWebView().dumpDomTree(false); + } else if ("about:debug.dom.file".equals(urlData.mUrl)) { + current.getWebView().dumpDomTree(true); + } else if ("about:debug.render".equals(urlData.mUrl)) { + current.getWebView().dumpRenderTree(false); + } else if ("about:debug.render.file".equals(urlData.mUrl)) { + current.getWebView().dumpRenderTree(true); + } else if ("about:debug.display".equals(urlData.mUrl)) { + current.getWebView().dumpDisplayTree(); + } else { + mSettings.toggleDebugSettings(); + } + return; + } + // Get rid of the subwindow if it exists + mController.dismissSubWindow(current); + // If the current Tab is being used as an application tab, + // remove the association, since the new Intent means that it is + // no longer associated with that application. + current.setAppId(null); + mController.loadUrlDataIn(current, urlData); + } + } + } + + protected UrlData getUrlDataFromIntent(Intent intent) { + String url = ""; + Map<String, String> headers = null; + if (intent != null) { + final String action = intent.getAction(); + if (Intent.ACTION_VIEW.equals(action)) { + url = UrlUtils.smartUrlFilter(intent.getData()); + if (url != null && url.startsWith("content:")) { + /* Append mimetype so webview knows how to display */ + String mimeType = intent.resolveType(mActivity.getContentResolver()); + if (mimeType != null) { + url += "?" + mimeType; + } + } + if (url != null && url.startsWith("http")) { + final Bundle pairs = intent + .getBundleExtra(Browser.EXTRA_HEADERS); + if (pairs != null && !pairs.isEmpty()) { + Iterator<String> iter = pairs.keySet().iterator(); + headers = new HashMap<String, String>(); + while (iter.hasNext()) { + String key = iter.next(); + headers.put(key, pairs.getString(key)); + } + } + } + } else if (Intent.ACTION_SEARCH.equals(action) + || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action) + || Intent.ACTION_WEB_SEARCH.equals(action)) { + url = intent.getStringExtra(SearchManager.QUERY); + if (url != null) { + // In general, we shouldn't modify URL from Intent. + // But currently, we get the user-typed URL from search box as well. + url = UrlUtils.fixUrl(url); + url = UrlUtils.smartUrlFilter(url); + final ContentResolver cr = mActivity.getContentResolver(); + final String newUrl = url; + if (mTabControl == null + || mTabControl.getCurrentWebView() == null + || !mTabControl.getCurrentWebView().isPrivateBrowsingEnabled()) { + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... unused) { + Browser.updateVisitedHistory(cr, newUrl, false); + return null; + } + }.execute(); + } + String searchSource = "&source=android-" + GOOGLE_SEARCH_SOURCE_SUGGEST + "&"; + if (url.contains(searchSource)) { + String source = null; + final Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA); + if (appData != null) { + source = appData.getString(Search.SOURCE); + } + if (TextUtils.isEmpty(source)) { + source = GOOGLE_SEARCH_SOURCE_UNKNOWN; + } + url = url.replace(searchSource, "&source=android-"+source+"&"); + } + } + } + } + return new UrlData(url, headers, intent); + } + + /** + * Launches the default web search activity with the query parameters if the given intent's data + * are identified as plain search terms and not URLs/shortcuts. + * @return true if the intent was handled and web search activity was launched, false if not. + */ + static boolean handleWebSearchIntent(Activity activity, + Controller controller, Intent intent) { + if (intent == null) return false; + + String url = null; + final String action = intent.getAction(); + if (RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS.equals( + action)) { + return false; + } + if (Intent.ACTION_VIEW.equals(action)) { + Uri data = intent.getData(); + if (data != null) url = data.toString(); + } else if (Intent.ACTION_SEARCH.equals(action) + || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action) + || Intent.ACTION_WEB_SEARCH.equals(action)) { + url = intent.getStringExtra(SearchManager.QUERY); + } + return handleWebSearchRequest(activity, controller, url, + intent.getBundleExtra(SearchManager.APP_DATA), + intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); + } + + /** + * Launches the default web search activity with the query parameters if the given url string + * 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 static boolean handleWebSearchRequest(Activity activity, + Controller controller, String inUrl, Bundle appData, + String extraData) { + if (inUrl == null) return false; + + // In general, we shouldn't modify URL from Intent. + // But currently, we get the user-typed URL from search box as well. + String url = UrlUtils.fixUrl(inUrl).trim(); + + // URLs are handled by the regular flow of control, so + // return early. + if (Patterns.WEB_URL.matcher(url).matches() + || UrlUtils.ACCEPTED_URI_SCHEMA.matcher(url).matches()) { + return false; + } + + final ContentResolver cr = activity.getContentResolver(); + final String newUrl = url; + if (controller == null || controller.getTabControl() == null + || controller.getTabControl().getCurrentWebView() == null + || !controller.getTabControl().getCurrentWebView() + .isPrivateBrowsingEnabled()) { + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... unused) { + Browser.updateVisitedHistory(cr, newUrl, false); + Browser.addSearchUrl(cr, newUrl); + return null; + } + }.execute(); + } + + SearchEngine searchEngine = BrowserSettings.getInstance().getSearchEngine(); + if (searchEngine == null) return false; + searchEngine.startSearch(activity, url, appData, extraData); + + return true; + } + + /** + * A UrlData class to abstract how the content will be set to WebView. + * This base class uses loadUrl to show the content. + */ + static class UrlData { + final String mUrl; + final Map<String, String> mHeaders; + final Intent mVoiceIntent; + + UrlData(String url) { + this.mUrl = url; + this.mHeaders = null; + this.mVoiceIntent = null; + } + + UrlData(String url, Map<String, String> headers, Intent intent) { + this.mUrl = url; + this.mHeaders = headers; + if (RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS + .equals(intent.getAction())) { + this.mVoiceIntent = intent; + } else { + this.mVoiceIntent = null; + } + } + + boolean isEmpty() { + return mVoiceIntent == null && (mUrl == null || mUrl.length() == 0); + } + + /** + * Load this UrlData into the given Tab. Use loadUrlDataIn to update + * the title bar as well. + */ + public void loadIn(Tab t) { + if (mVoiceIntent != null) { + t.activateVoiceSearchMode(mVoiceIntent); + } else { + t.getWebView().loadUrl(mUrl, mHeaders); + } + } + } + +} diff --git a/src/com/android/browser/NetworkStateHandler.java b/src/com/android/browser/NetworkStateHandler.java new file mode 100644 index 0000000..3b2007e --- /dev/null +++ b/src/com/android/browser/NetworkStateHandler.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.webkit.WebView; + +/** + * Handle network state changes + */ +public class NetworkStateHandler { + + Activity mActivity; + Controller mController; + + // monitor platform changes + private IntentFilter mNetworkStateChangedFilter; + private BroadcastReceiver mNetworkStateIntentReceiver; + private boolean mIsNetworkUp; + + /* hold a ref so we can auto-cancel if necessary */ + private AlertDialog mAlertDialog; + + public NetworkStateHandler(Activity activity, Controller controller) { + mActivity = activity; + mController = controller; + // Find out if the network is currently up. + ConnectivityManager cm = (ConnectivityManager) mActivity + .getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo info = cm.getActiveNetworkInfo(); + if (info != null) { + mIsNetworkUp = info.isAvailable(); + } + + /* + * enables registration for changes in network status from http stack + */ + mNetworkStateChangedFilter = new IntentFilter(); + mNetworkStateChangedFilter.addAction( + ConnectivityManager.CONNECTIVITY_ACTION); + mNetworkStateIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals( + ConnectivityManager.CONNECTIVITY_ACTION)) { + + NetworkInfo info = intent.getParcelableExtra( + ConnectivityManager.EXTRA_NETWORK_INFO); + String typeName = info.getTypeName(); + String subtypeName = info.getSubtypeName(); + sendNetworkType(typeName.toLowerCase(), + (subtypeName != null ? subtypeName.toLowerCase() : "")); + + onNetworkToggle(info.isAvailable()); + } + } + }; + + } + + void onPause() { + // unregister network state listener + mActivity.unregisterReceiver(mNetworkStateIntentReceiver); + } + + void onResume() { + mActivity.registerReceiver(mNetworkStateIntentReceiver, + mNetworkStateChangedFilter); + } + + /** + * connectivity manager says net has come or gone... inform the user + * @param up true if net has come up, false if net has gone down + */ + void onNetworkToggle(boolean up) { + if (up == mIsNetworkUp) { + return; + } else if (up) { + mIsNetworkUp = true; + if (mAlertDialog != null) { + mAlertDialog.cancel(); + mAlertDialog = null; + } + } else { + mIsNetworkUp = false; + if (mController.isInLoad()) { + createAndShowNetworkDialog(); + } + } + WebView w = mController.getCurrentWebView(); + if (w != null) { + w.setNetworkAvailable(up); + } + } + + boolean isNetworkUp() { + return mIsNetworkUp; + } + + // This method shows the network dialog alerting the user that the net is + // down. It will only show the dialog if mAlertDialog is null. + void createAndShowNetworkDialog() { + if (mAlertDialog == null) { + mAlertDialog = new AlertDialog.Builder(mActivity) + .setTitle(R.string.loadSuspendedTitle) + .setMessage(R.string.loadSuspended) + .setPositiveButton(R.string.ok, null) + .show(); + } + } + + private void sendNetworkType(String type, String subtype) { + WebView w = mController.getCurrentWebView(); + if (w != null) { + w.setNetworkType(type, subtype); + } + } + +} diff --git a/src/com/android/browser/PageDialogsHandler.java b/src/com/android/browser/PageDialogsHandler.java new file mode 100644 index 0000000..6843a10 --- /dev/null +++ b/src/com/android/browser/PageDialogsHandler.java @@ -0,0 +1,465 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Configuration; +import android.net.http.SslCertificate; +import android.net.http.SslError; +import android.text.format.DateFormat; +import android.view.LayoutInflater; +import android.view.View; +import android.webkit.HttpAuthHandler; +import android.webkit.SslErrorHandler; +import android.webkit.WebView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.util.Date; + +/** + * Displays page info + * + */ +public class PageDialogsHandler { + + private Context mContext; + private Controller mController; + private boolean mPageInfoFromShowSSLCertificateOnError; + private Tab mPageInfoView; + private AlertDialog mPageInfoDialog; + + // as SSLCertificateOnError has different style for landscape / portrait, + // we have to re-open it when configuration changed + private AlertDialog mSSLCertificateOnErrorDialog; + private WebView mSSLCertificateOnErrorView; + private SslErrorHandler mSSLCertificateOnErrorHandler; + private SslError mSSLCertificateOnErrorError; + + // as SSLCertificate has different style for landscape / portrait, we + // have to re-open it when configuration changed + private AlertDialog mSSLCertificateDialog; + private Tab mSSLCertificateView; + private HttpAuthenticationDialog mHttpAuthenticationDialog; + + public PageDialogsHandler(Context context, Controller controller) { + mContext = context; + mController = controller; + } + + public void onConfigurationChanged(Configuration config) { + if (mPageInfoDialog != null) { + mPageInfoDialog.dismiss(); + showPageInfo(mPageInfoView, mPageInfoFromShowSSLCertificateOnError); + } + if (mSSLCertificateDialog != null) { + mSSLCertificateDialog.dismiss(); + showSSLCertificate(mSSLCertificateView); + } + if (mSSLCertificateOnErrorDialog != null) { + mSSLCertificateOnErrorDialog.dismiss(); + showSSLCertificateOnError(mSSLCertificateOnErrorView, mSSLCertificateOnErrorHandler, + mSSLCertificateOnErrorError); + } + if (mHttpAuthenticationDialog != null) { + mHttpAuthenticationDialog.reshow(); + } + } + + /** + * Displays an http-authentication dialog. + */ + void showHttpAuthentication(final Tab tab, final HttpAuthHandler handler, String host, String realm) { + mHttpAuthenticationDialog = new HttpAuthenticationDialog(mContext, host, realm); + mHttpAuthenticationDialog.setOkListener(new HttpAuthenticationDialog.OkListener() { + public void onOk(String host, String realm, String username, String password) { + setHttpAuthUsernamePassword(host, realm, username, password); + handler.proceed(username, password); + mHttpAuthenticationDialog = null; + } + }); + mHttpAuthenticationDialog.setCancelListener(new HttpAuthenticationDialog.CancelListener() { + public void onCancel() { + handler.cancel(); + mController.resetTitleAndRevertLockIcon(tab); + mHttpAuthenticationDialog = null; + } + }); + mHttpAuthenticationDialog.show(); + } + + /** + * Set HTTP authentication password. + * + * @param host The host for the password + * @param realm The realm for the password + * @param username The username for the password. If it is null, it means + * password can't be saved. + * @param password The password + */ + public void setHttpAuthUsernamePassword(String host, String realm, + String username, + String password) { + WebView w = mController.getCurrentTopWebView(); + if (w != null) { + w.setHttpAuthUsernamePassword(host, realm, username, password); + } + } + + /** + * Displays a page-info dialog. + * @param tab The tab to show info about + * @param fromShowSSLCertificateOnError The flag that indicates whether + * this dialog was opened from the SSL-certificate-on-error dialog or + * not. This is important, since we need to know whether to return to + * the parent dialog or simply dismiss. + */ + void showPageInfo(final Tab tab, + final boolean fromShowSSLCertificateOnError) { + final LayoutInflater factory = LayoutInflater.from(mContext); + + final View pageInfoView = factory.inflate(R.layout.page_info, null); + + final WebView view = tab.getWebView(); + + String url = null; + String title = null; + + if (view == null) { + url = tab.getUrl(); + title = tab.getTitle(); + } else if (view == mController.getCurrentWebView()) { + // Use the cached title and url if this is the current WebView + url = tab.getCurrentUrl(); + title = tab.getCurrentTitle(); + } else { + url = view.getUrl(); + title = view.getTitle(); + } + + if (url == null) { + url = ""; + } + if (title == null) { + title = ""; + } + + ((TextView) pageInfoView.findViewById(R.id.address)).setText(url); + ((TextView) pageInfoView.findViewById(R.id.title)).setText(title); + + mPageInfoView = tab; + mPageInfoFromShowSSLCertificateOnError = fromShowSSLCertificateOnError; + + AlertDialog.Builder alertDialogBuilder = + new AlertDialog.Builder(mContext) + .setTitle(R.string.page_info) + .setIcon(android.R.drawable.ic_dialog_info) + .setView(pageInfoView) + .setPositiveButton( + R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int whichButton) { + mPageInfoDialog = null; + mPageInfoView = null; + + // if we came here from the SSL error dialog + if (fromShowSSLCertificateOnError) { + // go back to the SSL error dialog + showSSLCertificateOnError( + mSSLCertificateOnErrorView, + mSSLCertificateOnErrorHandler, + mSSLCertificateOnErrorError); + } + } + }) + .setOnCancelListener( + new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + mPageInfoDialog = null; + mPageInfoView = null; + + // if we came here from the SSL error dialog + if (fromShowSSLCertificateOnError) { + // go back to the SSL error dialog + showSSLCertificateOnError( + mSSLCertificateOnErrorView, + mSSLCertificateOnErrorHandler, + mSSLCertificateOnErrorError); + } + } + }); + + // if we have a main top-level page SSL certificate set or a certificate + // error + if (fromShowSSLCertificateOnError || + (view != null && view.getCertificate() != null)) { + // add a 'View Certificate' button + alertDialogBuilder.setNeutralButton( + R.string.view_certificate, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int whichButton) { + mPageInfoDialog = null; + mPageInfoView = null; + + // if we came here from the SSL error dialog + if (fromShowSSLCertificateOnError) { + // go back to the SSL error dialog + showSSLCertificateOnError( + mSSLCertificateOnErrorView, + mSSLCertificateOnErrorHandler, + mSSLCertificateOnErrorError); + } else { + // otherwise, display the top-most certificate from + // the chain + if (view.getCertificate() != null) { + showSSLCertificate(tab); + } + } + } + }); + } + + mPageInfoDialog = alertDialogBuilder.show(); + } + + /** + * Displays the main top-level page SSL certificate dialog + * (accessible from the Page-Info dialog). + * @param tab The tab to show certificate for. + */ + private void showSSLCertificate(final Tab tab) { + final View certificateView = + inflateCertificateView(tab.getWebView().getCertificate()); + if (certificateView == null) { + return; + } + + LayoutInflater factory = LayoutInflater.from(mContext); + + final LinearLayout placeholder = + (LinearLayout)certificateView.findViewById(R.id.placeholder); + + LinearLayout ll = (LinearLayout) factory.inflate( + R.layout.ssl_success, placeholder); + ((TextView)ll.findViewById(R.id.success)) + .setText(R.string.ssl_certificate_is_valid); + + mSSLCertificateView = tab; + mSSLCertificateDialog = + new AlertDialog.Builder(mContext) + .setTitle(R.string.ssl_certificate).setIcon( + R.drawable.ic_dialog_browser_certificate_secure) + .setView(certificateView) + .setPositiveButton(R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int whichButton) { + mSSLCertificateDialog = null; + mSSLCertificateView = null; + + showPageInfo(tab, false); + } + }) + .setOnCancelListener( + new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + mSSLCertificateDialog = null; + mSSLCertificateView = null; + + showPageInfo(tab, false); + } + }) + .show(); + } + + /** + * Displays the SSL error certificate dialog. + * @param view The target web-view. + * @param handler The SSL error handler responsible for cancelling the + * connection that resulted in an SSL error or proceeding per user request. + * @param error The SSL error object. + */ + void showSSLCertificateOnError( + final WebView view, final SslErrorHandler handler, + final SslError error) { + + final View certificateView = + inflateCertificateView(error.getCertificate()); + if (certificateView == null) { + return; + } + + LayoutInflater factory = LayoutInflater.from(mContext); + + final LinearLayout placeholder = + (LinearLayout)certificateView.findViewById(R.id.placeholder); + + if (error.hasError(SslError.SSL_UNTRUSTED)) { + LinearLayout ll = (LinearLayout)factory + .inflate(R.layout.ssl_warning, placeholder); + ((TextView)ll.findViewById(R.id.warning)) + .setText(R.string.ssl_untrusted); + } + + if (error.hasError(SslError.SSL_IDMISMATCH)) { + LinearLayout ll = (LinearLayout)factory + .inflate(R.layout.ssl_warning, placeholder); + ((TextView)ll.findViewById(R.id.warning)) + .setText(R.string.ssl_mismatch); + } + + if (error.hasError(SslError.SSL_EXPIRED)) { + LinearLayout ll = (LinearLayout)factory + .inflate(R.layout.ssl_warning, placeholder); + ((TextView)ll.findViewById(R.id.warning)) + .setText(R.string.ssl_expired); + } + + if (error.hasError(SslError.SSL_NOTYETVALID)) { + LinearLayout ll = (LinearLayout)factory + .inflate(R.layout.ssl_warning, placeholder); + ((TextView)ll.findViewById(R.id.warning)) + .setText(R.string.ssl_not_yet_valid); + } + + mSSLCertificateOnErrorHandler = handler; + mSSLCertificateOnErrorView = view; + mSSLCertificateOnErrorError = error; + mSSLCertificateOnErrorDialog = + new AlertDialog.Builder(mContext) + .setTitle(R.string.ssl_certificate).setIcon( + R.drawable.ic_dialog_browser_certificate_partially_secure) + .setView(certificateView) + .setPositiveButton(R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int whichButton) { + mSSLCertificateOnErrorDialog = null; + mSSLCertificateOnErrorView = null; + mSSLCertificateOnErrorHandler = null; + mSSLCertificateOnErrorError = null; + + view.getWebViewClient().onReceivedSslError( + view, handler, error); + } + }) + .setNeutralButton(R.string.page_info_view, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int whichButton) { + mSSLCertificateOnErrorDialog = null; + + // do not clear the dialog state: we will + // need to show the dialog again once the + // user is done exploring the page-info details + + showPageInfo(mController.getTabControl() + .getTabFromView(view), + true); + } + }) + .setOnCancelListener( + new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + mSSLCertificateOnErrorDialog = null; + mSSLCertificateOnErrorView = null; + mSSLCertificateOnErrorHandler = null; + mSSLCertificateOnErrorError = null; + + view.getWebViewClient().onReceivedSslError( + view, handler, error); + } + }) + .show(); + } + + /** + * Inflates the SSL certificate view (helper method). + * @param certificate The SSL certificate. + * @return The resultant certificate view with issued-to, issued-by, + * issued-on, expires-on, and possibly other fields set. + * If the input certificate is null, returns null. + */ + private View inflateCertificateView(SslCertificate certificate) { + if (certificate == null) { + return null; + } + + LayoutInflater factory = LayoutInflater.from(mContext); + + View certificateView = factory.inflate( + R.layout.ssl_certificate, null); + + // issued to: + SslCertificate.DName issuedTo = certificate.getIssuedTo(); + if (issuedTo != null) { + ((TextView) certificateView.findViewById(R.id.to_common)) + .setText(issuedTo.getCName()); + ((TextView) certificateView.findViewById(R.id.to_org)) + .setText(issuedTo.getOName()); + ((TextView) certificateView.findViewById(R.id.to_org_unit)) + .setText(issuedTo.getUName()); + } + + // issued by: + SslCertificate.DName issuedBy = certificate.getIssuedBy(); + if (issuedBy != null) { + ((TextView) certificateView.findViewById(R.id.by_common)) + .setText(issuedBy.getCName()); + ((TextView) certificateView.findViewById(R.id.by_org)) + .setText(issuedBy.getOName()); + ((TextView) certificateView.findViewById(R.id.by_org_unit)) + .setText(issuedBy.getUName()); + } + + // issued on: + String issuedOn = formatCertificateDate( + certificate.getValidNotBeforeDate()); + ((TextView) certificateView.findViewById(R.id.issued_on)) + .setText(issuedOn); + + // expires on: + String expiresOn = formatCertificateDate( + certificate.getValidNotAfterDate()); + ((TextView) certificateView.findViewById(R.id.expires_on)) + .setText(expiresOn); + + return certificateView; + } + + /** + * Formats the certificate date to a properly localized date string. + * @return Properly localized version of the certificate date string and + * the "" if it fails to localize. + */ + private String formatCertificateDate(Date certificateDate) { + if (certificateDate == null) { + return ""; + } + String formattedDate = DateFormat.getDateFormat(mContext) + .format(certificateDate); + if (formattedDate == null) { + return ""; + } + return formattedDate; + } + +} diff --git a/src/com/android/browser/Performance.java b/src/com/android/browser/Performance.java new file mode 100644 index 0000000..e9ddfa2 --- /dev/null +++ b/src/com/android/browser/Performance.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.net.WebAddress; +import android.os.Debug; +import android.os.Process; +import android.os.SystemClock; +import android.util.Log; + +/** + * Performance analysis + */ +public class Performance { + + private static final String LOGTAG = "browser"; + + private final static boolean LOGD_ENABLED = + com.android.browser.Browser.LOGD_ENABLED; + + private static boolean mInTrace; + + // Performance probe + private static final int[] SYSTEM_CPU_FORMAT = new int[] { + Process.PROC_SPACE_TERM | Process.PROC_COMBINE, + Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 1: user time + Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 2: nice time + Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 3: sys time + Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 4: idle time + Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 5: iowait time + Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 6: irq time + Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG // 7: softirq time + }; + + private static long mStart; + private static long mProcessStart; + private static long mUserStart; + private static long mSystemStart; + private static long mIdleStart; + private static long mIrqStart; + + private static long mUiStart; + + static void tracePageStart(String url) { + if (BrowserSettings.getInstance().isTracing()) { + String host; + try { + WebAddress uri = new WebAddress(url); + host = uri.getHost(); + } catch (android.net.ParseException ex) { + host = "browser"; + } + host = host.replace('.', '_'); + host += ".trace"; + mInTrace = true; + Debug.startMethodTracing(host, 20 * 1024 * 1024); + } + } + + static void tracePageFinished() { + if (mInTrace) { + mInTrace = false; + Debug.stopMethodTracing(); + } + } + + static void onPageStarted() { + mStart = SystemClock.uptimeMillis(); + mProcessStart = Process.getElapsedCpuTime(); + long[] sysCpu = new long[7]; + if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null, sysCpu, null)) { + mUserStart = sysCpu[0] + sysCpu[1]; + mSystemStart = sysCpu[2]; + mIdleStart = sysCpu[3]; + mIrqStart = sysCpu[4] + sysCpu[5] + sysCpu[6]; + } + mUiStart = SystemClock.currentThreadTimeMillis(); + } + + static void onPageFinished(String url) { + long[] sysCpu = new long[7]; + if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null, sysCpu, null)) { + String uiInfo = + "UI thread used " + (SystemClock.currentThreadTimeMillis() - mUiStart) + " ms"; + if (LOGD_ENABLED) { + Log.d(LOGTAG, uiInfo); + } + // The string that gets written to the log + String performanceString = + "It took total " + (SystemClock.uptimeMillis() - mStart) + + " ms clock time to load the page." + "\nbrowser process used " + + (Process.getElapsedCpuTime() - mProcessStart) + + " ms, user processes used " + (sysCpu[0] + sysCpu[1] - mUserStart) + * 10 + " ms, kernel used " + (sysCpu[2] - mSystemStart) * 10 + + " ms, idle took " + (sysCpu[3] - mIdleStart) * 10 + + " ms and irq took " + (sysCpu[4] + sysCpu[5] + sysCpu[6] - mIrqStart) + * 10 + " ms, " + uiInfo; + if (LOGD_ENABLED) { + Log.d(LOGTAG, performanceString + "\nWebpage: " + url); + } + if (url != null) { + // strip the url to maintain consistency + String newUrl = new String(url); + if (newUrl.startsWith("http://www.")) { + newUrl = newUrl.substring(11); + } else if (newUrl.startsWith("http://")) { + newUrl = newUrl.substring(7); + } else if (newUrl.startsWith("https://www.")) { + newUrl = newUrl.substring(12); + } else if (newUrl.startsWith("https://")) { + newUrl = newUrl.substring(8); + } + if (LOGD_ENABLED) { + Log.d(LOGTAG, newUrl + " loaded"); + } + } + } + } +} diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java index 2436e84..ec42584 100644 --- a/src/com/android/browser/Tab.java +++ b/src/com/android/browser/Tab.java @@ -16,38 +16,29 @@ package com.android.browser; -import com.android.browser.TabControl.TabChangeListener; import com.android.common.speech.LoggingEvents; +import android.app.Activity; import android.app.AlertDialog; import android.app.SearchManager; import android.content.ContentResolver; -import android.content.ContentValues; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.content.Intent; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; import android.graphics.Bitmap; import android.net.Uri; import android.net.http.SslError; -import android.os.AsyncTask; import android.os.Bundle; import android.os.Message; import android.os.SystemClock; -import android.provider.Browser; -import android.provider.BrowserContract.History; import android.speech.RecognizerResultsIntent; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; -import android.view.ViewGroup; import android.view.ViewStub; import android.webkit.ConsoleMessage; -import android.webkit.CookieSyncManager; import android.webkit.DownloadListener; import android.webkit.GeolocationPermissions; import android.webkit.HttpAuthHandler; @@ -58,7 +49,6 @@ import android.webkit.WebBackForwardList; import android.webkit.WebBackForwardListClient; import android.webkit.WebChromeClient; import android.webkit.WebHistoryItem; -import android.webkit.WebIconDatabase; import android.webkit.WebStorage; import android.webkit.WebView; import android.webkit.WebViewClient; @@ -78,6 +68,7 @@ import java.util.Vector; * Class for maintaining Tabs with a main WebView and a subwindow. */ class Tab { + // Log Tag private static final String LOGTAG = "Tab"; // Special case the logtag for messages for the Console to make it easier to @@ -85,6 +76,13 @@ class Tab { // of the browser. private static final String CONSOLE_LOGTAG = "browser"; + final static int LOCK_ICON_UNSECURE = 0; + final static int LOCK_ICON_SECURE = 1; + final static int LOCK_ICON_MIXED = 2; + + Activity mActivity; + private WebViewController mWebViewController; + // The Geolocation permissions prompt private GeolocationPermissionsPrompt mGeolocationPermissionsPrompt; // Main WebView wrapper @@ -111,8 +109,9 @@ class Tab { private boolean mCloseOnExit; // If true, the tab is in the foreground of the current activity. private boolean mInForeground; - // If true, the tab is in loading state. - private boolean mInLoad; + // If true, the tab is in page loading state (after onPageStarted, + // before onPageFinsihed) + private boolean mInPageLoad; // The time the load started, used to find load page time private long mLoadStartTime; // Application identifier used to find tabs that another application wants @@ -121,6 +120,10 @@ class Tab { // Keep the original url around to avoid killing the old WebView if the url // has not changed. private String mOriginalUrl; + // Hold on to the currently loaded url + private String mCurrentUrl; + //The currently loaded title + private String mCurrentTitle; // Error console for the tab private ErrorConsoleView mErrorConsole; // the lock icon type and previous lock icon type for the tab @@ -128,8 +131,6 @@ class Tab { private int mPrevLockIconType; // Inflation service for making subwindows. private final LayoutInflater mInflateService; - // The BrowserActivity which owners the Tab - private final BrowserActivity mActivity; // The listener that gets invoked when a download is started from the // mMainView private final DownloadListener mDownloadListener; @@ -172,10 +173,11 @@ class Tab { if (mVoiceSearchData != null) { mVoiceSearchData = null; if (mInForeground) { - mActivity.revertVoiceTitleBar(); + mWebViewController.revertVoiceSearchMode(this); } } } + /** * Return whether the tab is in voice search mode. */ @@ -273,7 +275,7 @@ class Tab { mVoiceSearchData.mLastVoiceSearchTitle = mVoiceSearchData.mVoiceSearchResults.get(index); if (mInForeground) { - mActivity.showVoiceTitleBar(mVoiceSearchData.mLastVoiceSearchTitle); + mWebViewController.activateVoiceSearchMode(mVoiceSearchData.mLastVoiceSearchTitle); } if (mVoiceSearchData.mVoiceSearchHtmls != null) { // When index was found it was already ensured that it was valid @@ -300,7 +302,7 @@ class Tab { mVoiceSearchData.mLastVoiceSearchUrl = mVoiceSearchData.mVoiceSearchUrls.get(index); if (null == mVoiceSearchData.mLastVoiceSearchUrl) { - mVoiceSearchData.mLastVoiceSearchUrl = mActivity.smartUrlFilter( + mVoiceSearchData.mLastVoiceSearchUrl = UrlUtils.smartUrlFilter( mVoiceSearchData.mLastVoiceSearchTitle); } Map<String, String> headers = null; @@ -393,7 +395,7 @@ class Tab { mDescription = desc; mError = error; } - }; + } private void processNextError() { if (mQueuedErrors == null) { @@ -460,7 +462,7 @@ class Tab { private Message mResend; @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { - mInLoad = true; + mInPageLoad = true; mLoadStartTime = SystemClock.uptimeMillis(); if (mVoiceSearchData != null && !url.equals(mVoiceSearchData.mLastVoiceSearchUrl)) { @@ -472,12 +474,6 @@ class Tab { revertVoiceSearchMode(); } - // 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. - mActivity.removeMessages(BrowserActivity.UPDATE_BOOKMARK_THUMBNAIL, - view); // 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 @@ -490,54 +486,23 @@ class Tab { // reset the error console if (mErrorConsole != null) { mErrorConsole.clearErrorMessages(); - if (mActivity.shouldShowErrorConsole()) { + if (mWebViewController.shouldShowErrorConsole()) { mErrorConsole.showConsole(ErrorConsoleView.SHOW_NONE); } } - // update the bookmark database for favicon - maybeUpdateFavicon(null, url, favicon); - - // reset sync timer to avoid sync starts during loading a page - CookieSyncManager.getInstance().resetSync(); - - if (!mActivity.isNetworkUp()) { - view.setNetworkAvailable(false); - } // finally update the UI in the activity if it is in the foreground - if (mInForeground) { - mActivity.onPageStarted(view, url, favicon); - } - if (getTabChangeListener() != null) { - getTabChangeListener().onPageStarted(Tab.this, url, favicon); - } + mWebViewController.onPageStarted(Tab.this, view, url, favicon); } @Override public void onPageFinished(WebView view, String url) { LogTag.logPageFinishedLoading( url, SystemClock.uptimeMillis() - mLoadStartTime); - mInLoad = false; - - if (!isPrivateBrowsingEnabled()) { - if (mInForeground && !mActivity.didUserStopLoading() - || !mInForeground) { - // Only update the bookmark screenshot if the user did not - // cancel the load early. - mActivity.postMessage( - BrowserActivity.UPDATE_BOOKMARK_THUMBNAIL, 0, 0, view, - 500); - } - } + mInPageLoad = false; - // finally update the UI in the activity if it is in the foreground - if (mInForeground) { - mActivity.onPageFinished(view, url); - } - if (getTabChangeListener() != null) { - getTabChangeListener().onPageFinished(Tab.this); - } + mWebViewController.onPageFinished(Tab.this, url); } // return true if want to hijack the url to let another app to handle it @@ -556,7 +521,7 @@ class Tab { mActivity.sendBroadcast(logIntent); } if (mInForeground) { - return mActivity.shouldOverrideUrlLoading(view, url); + return mWebViewController.shouldOverrideUrlLoading(view, url); } else { return false; } @@ -573,11 +538,11 @@ class Tab { if (url != null && url.length() > 0) { // It is only if the page claims to be secure that we may have // to update the lock: - if (mLockIconType == BrowserActivity.LOCK_ICON_SECURE) { + if (mLockIconType == LOCK_ICON_SECURE) { // If NOT a 'safe' url, change the lock to mixed content! if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url) || URLUtil.isAboutUrl(url))) { - mLockIconType = BrowserActivity.LOCK_ICON_MIXED; + mLockIconType = LOCK_ICON_MIXED; } } } @@ -606,7 +571,7 @@ class Tab { // We need to reset the title after an error if it is in foreground. if (mInForeground) { - mActivity.resetTitleAndRevertLockIcon(); + mWebViewController.resetTitleAndRevertLockIcon(Tab.this); } } @@ -672,35 +637,7 @@ class Tab { @Override public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) { - // Don't save anything in private browsing mode - if (isPrivateBrowsingEnabled()) return; - - if (url.regionMatches(true, 0, "about:", 0, 6)) { - return; - } - // remove "client" before updating it to the history so that it wont - // show up in the auto-complete list. - int index = url.indexOf("client=ms-"); - if (index > 0 && url.contains(".google.")) { - int end = url.indexOf('&', index); - if (end > 0) { - url = url.substring(0, index) - .concat(url.substring(end + 1)); - } else { - // the url.charAt(index-1) should be either '?' or '&' - url = url.substring(0, index-1); - } - } - final ContentResolver cr = mActivity.getContentResolver(); - final String newUrl = url; - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... unused) { - Browser.updateVisitedHistory(cr, newUrl, true); - return null; - } - }.execute(); - WebIconDatabase.getInstance().retainIconForPageUrl(url); + mWebViewController.doUpdateVisitedHistory(Tab.this, url, isReload); } /** @@ -766,7 +703,7 @@ class Tab { new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { - mActivity.showSSLCertificateOnError(view, + mWebViewController.showSslCertificateOnError(view, handler, error); } }).setNegativeButton(R.string.cancel, @@ -774,13 +711,13 @@ class Tab { public void onClick(DialogInterface dialog, int whichButton) { handler.cancel(); - mActivity.resetTitleAndRevertLockIcon(); + mWebViewController.resetTitleAndRevertLockIcon(Tab.this); } }).setOnCancelListener( new DialogInterface.OnCancelListener() { public void onCancel(DialogInterface dialog) { handler.cancel(); - mActivity.resetTitleAndRevertLockIcon(); + mWebViewController.resetTitleAndRevertLockIcon(Tab.this); } }).show(); } else { @@ -799,30 +736,7 @@ class Tab { public void onReceivedHttpAuthRequest(WebView view, final HttpAuthHandler handler, final String host, final String realm) { - String username = null; - String password = null; - - boolean reuseHttpAuthUsernamePassword = handler - .useHttpAuthUsernamePassword(); - - if (reuseHttpAuthUsernamePassword && view != null) { - String[] credentials = view.getHttpAuthUsernamePassword( - host, realm); - if (credentials != null && credentials.length == 2) { - username = credentials[0]; - password = credentials[1]; - } - } - - if (username != null && password != null) { - handler.proceed(username, password); - } else { - if (mInForeground) { - mActivity.showHttpAuthentication(handler, host, realm); - } else { - handler.cancel(); - } - } + mWebViewController.onReceivedHttpAuthRequest(Tab.this, view, handler, host, realm); } @Override @@ -830,25 +744,15 @@ class Tab { if (!mInForeground) { return false; } - if (mActivity.isMenuDown()) { - // only check shortcut key when MENU is held - return mActivity.getWindow().isShortcutKey(event.getKeyCode(), - event); - } else { - return false; - } + return mWebViewController.shouldOverrideKeyEvent(event); } @Override public void onUnhandledKeyEvent(WebView view, KeyEvent event) { - if (!mInForeground || mActivity.mActivityInPause) { + if (!mInForeground) { return; } - if (event.getAction() == KeyEvent.ACTION_DOWN) { - mActivity.onKeyDown(event.getKeyCode(), event); - } else { - mActivity.onKeyUp(event.getKeyCode(), event); - } + mWebViewController.onUnhandledKeyEvent(event); } }; @@ -863,11 +767,11 @@ class Tab { (WebView.WebViewTransport) msg.obj; if (dialog) { createSubWindow(); - mActivity.attachSubWindow(Tab.this); + mWebViewController.attachSubWindow(Tab.this); transport.setWebView(mSubView); } else { - final Tab newTab = mActivity.openTabAndShow( - BrowserActivity.EMPTY_URL_DATA, false, null); + final Tab newTab = mWebViewController.openTabAndShow( + IntentHandler.EMPTY_URL_DATA, false, null); if (newTab != Tab.this) { Tab.this.addChildTab(newTab); } @@ -892,7 +796,7 @@ class Tab { .setPositiveButton(R.string.ok, null) .show(); return false; - } else if (!mActivity.getTabControl().canCreateNewTab()) { + } else if (!mWebViewController.getTabControl().canCreateNewTab()) { new AlertDialog.Builder(mActivity) .setTitle(R.string.too_many_windows_dialog_title) .setIcon(android.R.drawable.ic_dialog_alert) @@ -944,7 +848,7 @@ class Tab { @Override public void onRequestFocus(WebView view) { if (!mInForeground) { - mActivity.switchToTab(mActivity.getTabControl().getTabIndex( + mWebViewController.switchToTab(mWebViewController.getTabControl().getTabIndex( Tab.this)); } } @@ -954,92 +858,26 @@ class Tab { if (mParentTab != null) { // JavaScript can only close popup window. if (mInForeground) { - mActivity.switchToTab(mActivity.getTabControl() + mWebViewController.switchToTab(mWebViewController.getTabControl() .getTabIndex(mParentTab)); } - mActivity.closeTab(Tab.this); + mWebViewController.closeTab(Tab.this); } } @Override public void onProgressChanged(WebView view, int newProgress) { - if (newProgress == 100) { - // sync cookies and cache promptly here. - CookieSyncManager.getInstance().sync(); - } - if (mInForeground) { - mActivity.onProgressChanged(view, newProgress); - } - if (getTabChangeListener() != null) { - getTabChangeListener().onProgress(Tab.this, newProgress); - } + mWebViewController.onProgressChanged(Tab.this, newProgress); } @Override public void onReceivedTitle(WebView view, final String title) { - final String pageUrl = view.getUrl(); - if (mInForeground) { - // here, if url is null, we want to reset the title - mActivity.setUrlTitle(pageUrl, title); - } - TabChangeListener tcl = getTabChangeListener(); - if (tcl != null) { - tcl.onUrlAndTitle(Tab.this, pageUrl,title); - } - if (pageUrl == null || pageUrl.length() - >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) { - return; - } - - // Update the title in the history database if not in private browsing mode - if (!isPrivateBrowsingEnabled()) { - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... unused) { - // See if we can find the current url in our history - // database and add the new title to it. - String url = pageUrl; - if (url.startsWith("http://www.")) { - url = url.substring(11); - } else if (url.startsWith("http://")) { - url = url.substring(4); - } - // Escape wildcards for LIKE operator. - url = url.replace("\\", "\\\\").replace("%", "\\%") - .replace("_", "\\_"); - Cursor c = null; - try { - final ContentResolver cr = mActivity.getContentResolver(); - String selection = History.URL + " LIKE ? ESCAPE '\\'"; - String [] selectionArgs = new String[] { "%" + url }; - ContentValues values = new ContentValues(); - values.put(History.TITLE, title); - cr.update(History.CONTENT_URI, values, selection, selectionArgs); - } catch (IllegalStateException e) { - Log.e(LOGTAG, "Tab onReceived title", e); - } catch (SQLiteException ex) { - Log.e(LOGTAG, - "onReceivedTitle() caught SQLiteException: ", - ex); - } finally { - if (c != null) c.close(); - } - return null; - } - }.execute(); - } + mWebViewController.onReceivedTitle(Tab.this, title); } @Override public void onReceivedIcon(WebView view, Bitmap icon) { - maybeUpdateFavicon(view.getOriginalUrl(), view.getUrl(), icon); - - if (mInForeground) { - mActivity.setFavicon(icon); - } - if (getTabChangeListener() != null) { - getTabChangeListener().onFavicon(Tab.this, icon); - } + mWebViewController.onFavicon(Tab.this, view, icon); } @Override @@ -1054,7 +892,8 @@ class Tab { } // Have only one async task at a time. if (mTouchIconLoader == null) { - mTouchIconLoader = new DownloadTouchIcon(Tab.this, mActivity, cr, view); + mTouchIconLoader = new DownloadTouchIcon(Tab.this, + mActivity, cr, view); mTouchIconLoader.execute(url); } } @@ -1062,12 +901,13 @@ class Tab { @Override public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) { - if (mInForeground) mActivity.onShowCustomView(view, callback); + if (mInForeground) mWebViewController.showCustomView(Tab.this, view, + callback); } @Override public void onHideCustomView() { - if (mInForeground) mActivity.onHideCustomView(); + if (mInForeground) mWebViewController.hideCustomView(); } /** @@ -1146,8 +986,9 @@ class Tab { // call getErrorConsole(true) so it will create one if needed ErrorConsoleView errorConsole = getErrorConsole(true); errorConsole.addErrorMessage(consoleMessage); - if (mActivity.shouldShowErrorConsole() - && errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) { + if (mWebViewController.shouldShowErrorConsole() + && errorConsole.getShowState() != + ErrorConsoleView.SHOW_MAXIMIZED) { errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED); } } @@ -1188,7 +1029,7 @@ class Tab { @Override public Bitmap getDefaultVideoPoster() { if (mInForeground) { - return mActivity.getDefaultVideoPoster(); + return mWebViewController.getDefaultVideoPoster(); } return null; } @@ -1201,7 +1042,7 @@ class Tab { @Override public View getVideoLoadingProgressView() { if (mInForeground) { - return mActivity.getVideoLoadingProgressView(); + return mWebViewController.getVideoLoadingProgressView(); } return null; } @@ -1209,7 +1050,7 @@ class Tab { @Override public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) { if (mInForeground) { - mActivity.openFileChooser(uploadMsg, acceptType); + mWebViewController.openFileChooser(uploadMsg, acceptType); } else { uploadMsg.onReceiveValue(null); } @@ -1220,19 +1061,8 @@ class Tab { */ @Override public void getVisitedHistory(final ValueCallback<String[]> callback) { - AsyncTask<Void, Void, String[]> task = new AsyncTask<Void, Void, String[]>() { - @Override - public String[] doInBackground(Void... unused) { - return Browser.getVisitedHistory(mActivity - .getContentResolver()); - } - @Override - public void onPostExecute(String[] result) { - callback.onReceiveValue(result); - }; - }; - task.execute(); - }; + mWebViewController.getVisitedHistory(callback); + } }; // ------------------------------------------------------------------------- @@ -1244,18 +1074,18 @@ class Tab { private static class SubWindowClient extends WebViewClient { // The main WebViewClient. private final WebViewClient mClient; - private final BrowserActivity mBrowserActivity; + private final WebViewController mController; - SubWindowClient(WebViewClient client, BrowserActivity activity) { + SubWindowClient(WebViewClient client, WebViewController controller) { mClient = client; - mBrowserActivity = activity; + mController = controller; } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { // Unlike the others, do not call mClient's version, which would // change the progress bar. However, we do want to remove the // find or select dialog. - mBrowserActivity.endActionMode(); + mController.endActionMode(); } @Override public void doUpdateVisitedHistory(WebView view, String url, @@ -1323,25 +1153,29 @@ class Tab { if (window != mSubView) { Log.e(LOGTAG, "Can't close the window"); } - mActivity.dismissSubWindow(Tab.this); + mWebViewController.dismissSubWindow(Tab.this); } } // ------------------------------------------------------------------------- + // TODO temporarily use activity here + // remove later + // Construct a new tab - Tab(BrowserActivity activity, WebView w, boolean closeOnExit, String appId, + Tab(WebViewController wvcontroller, WebView w, boolean closeOnExit, String appId, String url) { - mActivity = activity; + mWebViewController = wvcontroller; + mActivity = mWebViewController.getActivity(); mCloseOnExit = closeOnExit; mAppId = appId; mOriginalUrl = url; - mLockIconType = BrowserActivity.LOCK_ICON_UNSECURE; - mPrevLockIconType = BrowserActivity.LOCK_ICON_UNSECURE; - mInLoad = false; + mLockIconType = LOCK_ICON_UNSECURE; + mPrevLockIconType = LOCK_ICON_UNSECURE; + mInPageLoad = false; mInForeground = false; - mInflateService = LayoutInflater.from(activity); + mInflateService = LayoutInflater.from(mActivity); // The tab consists of a container view, which contains the main // WebView, as well as any other UI elements associated with the tab. @@ -1351,20 +1185,8 @@ class Tab { public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) { - mActivity.onDownloadStart(url, userAgent, contentDisposition, + mWebViewController.onDownloadStart(Tab.this, url, userAgent, contentDisposition, mimetype, contentLength); - if (mMainView.copyBackForwardList().getSize() == 0) { - // This Tab was opened for the sole purpose of downloading a - // file. Remove it. - if (mActivity.getTabControl().getCurrentWebView() - == mMainView) { - // In this case, the Tab is still on top. - mActivity.goBackOnePageOrQuit(); - } else { - // In this case, it is not. - mActivity.closeTab(Tab.this); - } - } } }; mWebBackForwardListClient = new WebBackForwardListClient() { @@ -1456,7 +1278,7 @@ class Tab { */ boolean createSubWindow() { if (mSubView == null) { - mActivity.endActionMode(); + mWebViewController.endActionMode(); mSubViewContainer = mInflateService.inflate( R.layout.browser_subwindow, null); mSubView = (WebView) mSubViewContainer.findViewById(R.id.webview); @@ -1466,7 +1288,7 @@ class Tab { // Enable the built-in zoom mSubView.getSettings().setBuiltInZoomControls(true); mSubView.setWebViewClient(new SubWindowClient(mWebViewClient, - mActivity)); + mWebViewController)); mSubView.setWebChromeClient(new SubWindowChromeClient( mWebChromeClient)); // Set a different DownloadListener for the mSubView, since it will @@ -1475,12 +1297,12 @@ class Tab { public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) { - mActivity.onDownloadStart(url, userAgent, + mWebViewController.onDownloadStart(Tab.this, url, userAgent, contentDisposition, mimetype, contentLength); if (mSubView.copyBackForwardList().getSize() == 0) { // This subwindow was opened for the sole purpose of // downloading a file. Remove it. - mActivity.dismissSubWindow(Tab.this); + mWebViewController.dismissSubWindow(Tab.this); } } }); @@ -1504,7 +1326,7 @@ class Tab { */ void dismissSubWindow() { if (mSubView != null) { - mActivity.endActionMode(); + mWebViewController.endActionMode(); BrowserSettings.getInstance().deleteObserver( mSubView.getSettings()); mSubView.destroy(); @@ -1513,84 +1335,6 @@ class Tab { } } - /** - * Attach the sub window to the content view. - */ - void attachSubWindow(ViewGroup content) { - if (mSubView != null) { - content.addView(mSubViewContainer, - BrowserActivity.COVER_SCREEN_PARAMS); - } - } - - /** - * Remove the sub window from the content view. - */ - void removeSubWindow(ViewGroup content) { - if (mSubView != null) { - content.removeView(mSubViewContainer); - mActivity.endActionMode(); - } - } - - /** - * This method attaches both the WebView and any sub window to the - * given content view. - */ - void attachTabToContentView(ViewGroup content) { - if (mMainView == null) { - return; - } - - // Attach the WebView to the container and then attach the - // container to the content view. - FrameLayout wrapper = - (FrameLayout) mContainer.findViewById(R.id.webview_wrapper); - ViewGroup parent = (ViewGroup) mMainView.getParent(); - if (parent != wrapper) { - if (parent != null) { - Log.w(LOGTAG, "mMainView already has a parent in" - + " attachTabToContentView!"); - parent.removeView(mMainView); - } - wrapper.addView(mMainView); - } else { - Log.w(LOGTAG, "mMainView is already attached to wrapper in" - + " attachTabToContentView!"); - } - parent = (ViewGroup) mContainer.getParent(); - if (parent != content) { - if (parent != null) { - Log.w(LOGTAG, "mContainer already has a parent in" - + " attachTabToContentView!"); - parent.removeView(mContainer); - } - content.addView(mContainer, BrowserActivity.COVER_SCREEN_PARAMS); - } else { - Log.w(LOGTAG, "mContainer is already attached to content in" - + " attachTabToContentView!"); - } - attachSubWindow(content); - } - - /** - * Remove the WebView and any sub window from the given content view. - */ - void removeTabFromContentView(ViewGroup content) { - if (mMainView == null) { - return; - } - - // Remove the container from the content and then remove the - // WebView from the container. This will trigger a focus change - // needed by WebView. - FrameLayout wrapper = - (FrameLayout) mContainer.findViewById(R.id.webview_wrapper); - wrapper.removeView(mMainView); - content.removeView(mContainer); - mActivity.endActionMode(); - removeSubWindow(content); - } /** * Set the parent tab of this tab. @@ -1605,7 +1349,7 @@ class Tab { if (parent == null) { mSavedState.remove(PARENTTAB); } else { - mSavedState.putInt(PARENTTAB, mActivity.getTabControl() + mSavedState.putInt(PARENTTAB, mWebViewController.getTabControl() .getTabIndex(parent)); } } @@ -1668,6 +1412,10 @@ class Tab { } } + boolean inForeground() { + return mInForeground; + } + /** * Return the top window of this tab; either the subwindow if it is not * null or the main window. @@ -1690,12 +1438,16 @@ class Tab { return mMainView; } + View getViewContainer() { + return mContainer; + } + /** * Return whether private browsing is enabled for the main window of * this tab. * @return True if private browsing is enabled. */ - private boolean isPrivateBrowsingEnabled() { + boolean isPrivateBrowsingEnabled() { WebView webView = getWebView(); if (webView == null) { return false; @@ -1711,6 +1463,10 @@ class Tab { return mSubView; } + View getSubViewContainer() { + return mSubViewContainer; + } + /** * @return The geolocation permissions prompt for this tab. */ @@ -1755,6 +1511,28 @@ class Tab { } /** + * set the title for the tab + */ + void setCurrentTitle(String title) { + mCurrentTitle = title; + } + + /** + * set url for this tab + * @param url + */ + void setCurrentUrl(String url) { + mCurrentUrl = url; + } + + String getCurrentTitle() { + return mCurrentTitle; + } + + String getCurrentUrl() { + return mCurrentUrl; + } + /** * Get the url of this tab. Valid after calling populatePickerData, but * before calling wipePickerData, or if the webview has been destroyed. * @return The WebView's url or null. @@ -1791,19 +1569,6 @@ class Tab { return null; } - /* - * Update the favorites icon if the private browsing isn't enabled and the - * icon is valid. - */ - void maybeUpdateFavicon(final String originalUrl, final String url, Bitmap favicon) { - if (favicon == null) { - return; - } - if (!isPrivateBrowsingEnabled()) { - Bookmarks.updateFavicon(mActivity - .getContentResolver(), originalUrl, url, favicon); - } - } /** * Return the tab's error console. Creates the console if createIfNEcessary @@ -1845,9 +1610,9 @@ class Tab { */ void resetLockIcon(String url) { mPrevLockIconType = mLockIconType; - mLockIconType = BrowserActivity.LOCK_ICON_UNSECURE; + mLockIconType = LOCK_ICON_UNSECURE; if (URLUtil.isHttpsUrl(url)) { - mLockIconType = BrowserActivity.LOCK_ICON_SECURE; + mLockIconType = LOCK_ICON_SECURE; } } @@ -1870,14 +1635,14 @@ class Tab { * @return TRUE if onPageStarted is called while onPageFinished is not * called yet. */ - boolean inLoad() { - return mInLoad; + boolean inPageLoad() { + return mInPageLoad; } // force mInLoad to be false. This should only be called before closing the // tab to ensure BrowserActivity's pauseWebViewTimers() is called correctly. - void clearInLoad() { - mInLoad = false; + void clearInPageLoad() { + mInPageLoad = false; } void populatePickerData() { @@ -1973,7 +1738,7 @@ class Tab { } // Remember the parent tab so the relationship can be restored. if (mParentTab != null) { - mSavedState.putInt(PARENTTAB, mActivity.getTabControl().getTabIndex( + mSavedState.putInt(PARENTTAB, mWebViewController.getTabControl().getTabIndex( mParentTab)); } return true; @@ -2001,12 +1766,4 @@ class Tab { return true; } - /** - * always get the TabChangeListener form the tab control - * @return the TabControl change listener - */ - private TabChangeListener getTabChangeListener() { - return mActivity.getTabControl().getTabChangeListener(); - } - } diff --git a/src/com/android/browser/TabBar.java b/src/com/android/browser/TabBar.java index a939639..169f934 100644 --- a/src/com/android/browser/TabBar.java +++ b/src/com/android/browser/TabBar.java @@ -17,8 +17,8 @@ package com.android.browser; import com.android.browser.ScrollWebView.ScrollListener; -import com.android.browser.TabControl.TabChangeListener; +import android.app.Activity; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; @@ -46,19 +46,20 @@ import java.util.Map; * tabbed title bar for xlarge screen browser */ public class TabBar extends LinearLayout - implements TabChangeListener, ScrollListener, OnClickListener { + implements ScrollListener, OnClickListener { private static final int PROGRESS_MAX = 100; - private BrowserActivity mBrowserActivity; + private Activity mActivity; + private UiController mUiController; + private TabControl mTabControl; + private BaseUi mUi; private final int mTabWidthSelected; private final int mTabWidthUnselected; - private TitleBarXLarge mTitleBar; - private TabScrollView mTabs; - private TabControl mControl; + private ImageButton mNewTab; private int mButtonWidth; @@ -72,20 +73,19 @@ public class TabBar extends LinearLayout private Drawable mGenericFavicon; private String mLoadingText; - public TabBar(BrowserActivity context, TabControl tabcontrol, TitleBarXLarge titlebar) { - super(context); - Resources res = context.getResources(); + public TabBar(Activity activity, UiController controller, BaseUi ui) { + super(activity); + mActivity = activity; + mUiController = controller; + mTabControl = mUiController.getTabControl(); + mUi = ui; + Resources res = activity.getResources(); mTabWidthSelected = (int) res.getDimension(R.dimen.tab_width_selected); mTabWidthUnselected = (int) res.getDimension(R.dimen.tab_width_unselected); - mTitleBar = titlebar; - mTitleBar.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, - LayoutParams.WRAP_CONTENT)); mTabMap = new HashMap<Tab, TabViewData>(); - mBrowserActivity = context; - mControl = tabcontrol; - Resources resources = context.getResources(); - LayoutInflater factory = LayoutInflater.from(context); + Resources resources = activity.getResources(); + LayoutInflater factory = LayoutInflater.from(activity); factory.inflate(R.layout.tab_bar, this); mTabs = (TabScrollView) findViewById(R.id.tabs); mNewTab = (ImageButton) findViewById(R.id.newtab); @@ -97,16 +97,14 @@ public class TabBar extends LinearLayout // back/forward. Probably should be done inside onPageStarted. // build tabs - int tabcount = mControl.getTabCount(); + int tabcount = mTabControl.getTabCount(); for (int i = 0; i < tabcount; i++) { - Tab tab = mControl.getTab(i); + Tab tab = mTabControl.getTab(i); TabViewData data = buildTab(tab); TabView tv = buildView(data); } - mTabs.setSelectedTab(mControl.getCurrentIndex()); + mTabs.setSelectedTab(mTabControl.getCurrentIndex()); - // register the tab change listener - mControl.setOnTabChangeListener(this); mUserRequestedUrlbar = false; mTitleVisible = true; mButtonWidth = -1; @@ -127,12 +125,12 @@ public class TabBar extends LinearLayout } public void onClick(View view) { - mBrowserActivity.removeComboView(); + mUi.hideComboView(); if (mNewTab == view) { - mBrowserActivity.openTabToHomePage(); + mUiController.openTabToHomePage(); } else if (mTabs.getSelectedTab() == view) { - if (mBrowserActivity.isFakeTitleBarShowing() && !isLoading()) { - mBrowserActivity.hideFakeTitleBar(); + if (mUi.isFakeTitleBarShowing() && !isLoading()) { + mUi.hideFakeTitleBar(); } else { showUrlBar(); } @@ -140,14 +138,14 @@ public class TabBar extends LinearLayout int ix = mTabs.getChildIndex(view); if (ix >= 0) { mTabs.setSelectedTab(ix); - mBrowserActivity.switchToTab(ix); + mUiController.switchToTab(ix); } } } private void showUrlBar() { - mBrowserActivity.stopScrolling(); - mBrowserActivity.showFakeTitleBar(); + mUi.stopWebViewScrolling(); + mUi.showFakeTitleBar(); mUserRequestedUrlbar = true; } @@ -163,7 +161,7 @@ public class TabBar extends LinearLayout // callback after fake titlebar is hidden void onHideTitleBar() { setShowUrlMode(!mTitleVisible); - Tab tab = mControl.getCurrentTab(); + Tab tab = mTabControl.getCurrentTab(); tab.getWebView().requestFocus(); mUserRequestedUrlbar = false; } @@ -172,25 +170,28 @@ public class TabBar extends LinearLayout @Override public void onScroll(boolean titleVisible) { - mTitleVisible = titleVisible; - if (!mShowUrlMode && !mTitleVisible && !isLoading()) { - if (mUserRequestedUrlbar) { - mBrowserActivity.hideFakeTitleBar(); - } else { - setShowUrlMode(true); - } - } else if (mTitleVisible && !isLoading()) { - if (mShowUrlMode) { - setShowUrlMode(false); + // isLoading is using the current tab, which initially might not be set yet + if (mTabControl.getCurrentTab() != null) { + mTitleVisible = titleVisible; + if (!mShowUrlMode && !mTitleVisible && !isLoading()) { + if (mUserRequestedUrlbar) { + mUi.hideFakeTitleBar(); + } else { + setShowUrlMode(true); + } + } else if (mTitleVisible && !isLoading()) { + if (mShowUrlMode) { + setShowUrlMode(false); + } } } } @Override public void createContextMenu(ContextMenu menu) { - MenuInflater inflater = mBrowserActivity.getMenuInflater(); + MenuInflater inflater = mActivity.getMenuInflater(); inflater.inflate(R.menu.title_context, menu); - mBrowserActivity.onCreateContextMenu(menu, this, null); + mActivity.onCreateContextMenu(menu, this, null); } private TabViewData buildTab(Tab tab) { @@ -200,7 +201,7 @@ public class TabBar extends LinearLayout } private TabView buildView(final TabViewData data) { - TabView tv = new TabView(mBrowserActivity, data); + TabView tv = new TabView(mActivity, data); tv.setTag(data); tv.setOnClickListener(this); mTabs.addTab(tv); @@ -278,7 +279,7 @@ public class TabBar extends LinearLayout public void setActivated(boolean selected) { mSelected = selected; mClose.setVisibility(mSelected ? View.VISIBLE : View.GONE); - mTitle.setTextAppearance(mBrowserActivity, mSelected ? + mTitle.setTextAppearance(mActivity, mSelected ? R.style.TabTitleSelected : R.style.TabTitleUnselected); setHorizontalFadingEdgeEnabled(!mSelected); setFadingEdgeLength(50); @@ -305,11 +306,6 @@ public class TabBar extends LinearLayout } } - void setTitleCompoundDrawables(Drawable left, Drawable top, - Drawable right, Drawable bottom) { - mTitle.setCompoundDrawables(left, top, right, bottom); - } - void setProgress(int newProgress) { if (newProgress >= PROGRESS_MAX) { mInLoad = false; @@ -321,10 +317,10 @@ public class TabBar extends LinearLayout } private void closeTab() { - if (mTabData.mTab == mControl.getCurrentTab()) { - mBrowserActivity.closeCurrentWindow(); + if (mTabData.mTab == mTabControl.getCurrentTab()) { + mUiController.closeCurrentTab(); } else { - mBrowserActivity.closeTab(mTabData.mTab); + mUiController.closeTab(mTabData.mTab); } } @@ -392,9 +388,8 @@ public class TabBar extends LinearLayout // TabChangeListener implementation - @Override - public void onCurrentTab(Tab tab) { - mTabs.setSelectedTab(mControl.getCurrentIndex()); + public void onSetActiveTab(Tab tab) { + mTabs.setSelectedTab(mTabControl.getTabIndex(tab)); TabViewData tvd = mTabMap.get(tab); if (tvd != null) { tvd.setProgress(tvd.mProgress); @@ -404,7 +399,6 @@ public class TabBar extends LinearLayout } } - @Override public void onFavicon(Tab tab, Bitmap favicon) { TabViewData tvd = mTabMap.get(tab); if (tvd != null) { @@ -412,13 +406,11 @@ public class TabBar extends LinearLayout } } - @Override public void onNewTab(Tab tab) { TabViewData tvd = buildTab(tab); buildView(tvd); } - @Override public void onProgress(Tab tab, int progress) { TabViewData tvd = mTabMap.get(tab); if (tvd != null) { @@ -426,7 +418,6 @@ public class TabBar extends LinearLayout } } - @Override public void onRemoveTab(Tab tab) { TabViewData tvd = mTabMap.get(tab); if (tvd != null) { @@ -438,7 +429,6 @@ public class TabBar extends LinearLayout mTabMap.remove(tab); } - @Override public void onUrlAndTitle(Tab tab, String url, String title) { mHasReceivedTitle = true; TabViewData tvd = mTabMap.get(tab); @@ -447,7 +437,6 @@ public class TabBar extends LinearLayout } } - @Override public void onPageFinished(Tab tab) { if (!mHasReceivedTitle) { TabViewData tvd = mTabMap.get(tab); @@ -457,19 +446,23 @@ public class TabBar extends LinearLayout } } - @Override public void onPageStarted(Tab tab, String url, Bitmap favicon) { mHasReceivedTitle = false; TabViewData tvd = mTabMap.get(tab); if (tvd != null) { + tvd.setUrlAndTitle(url, null); tvd.setFavicon(favicon); tvd.setUrlAndTitle(url, mLoadingText); } } - private boolean isLoading() { - return mTabMap.get(mControl.getCurrentTab()).mTabView.mInLoad; + TabViewData tvd = mTabMap.get(mTabControl.getCurrentTab()); + if ((tvd != null) && (tvd.mTabView != null)) { + return tvd.mTabView.mInLoad; + } else { + return false; + } } } diff --git a/src/com/android/browser/TabControl.java b/src/com/android/browser/TabControl.java index fcccad1..aeffbc0 100644 --- a/src/com/android/browser/TabControl.java +++ b/src/com/android/browser/TabControl.java @@ -16,10 +16,10 @@ package com.android.browser; -import android.graphics.Bitmap; +import com.android.browser.IntentHandler.UrlData; + import android.os.Bundle; import android.util.Log; -import android.view.View; import android.webkit.WebBackForwardList; import android.webkit.WebView; @@ -39,25 +39,19 @@ class TabControl { private ArrayList<Tab> mTabQueue; // Current position in mTabs. private int mCurrentTab = -1; - // A private instance of BrowserActivity to interface with when adding and - // switching between tabs. - private final BrowserActivity mActivity; - // Directory to store thumbnails for each WebView. + // the main browser controller + private final Controller mController; + private final File mThumbnailDir; - // Use on screen zoom buttons - private boolean mDisplayZoomControls; /** - * Construct a new TabControl object that interfaces with the given - * BrowserActivity instance. - * @param activity A BrowserActivity instance that TabControl will interface - * with. + * Construct a new TabControl object */ - TabControl(BrowserActivity activity) { - mActivity = activity; - mThumbnailDir = activity.getDir("thumbnails", 0); - mDisplayZoomControls = true; - mMaxTabs = activity.getResources().getInteger(R.integer.max_tabs); + TabControl(Controller controller) { + mController = controller; + mThumbnailDir = mController.getActivity() + .getDir("thumbnails", 0); + mMaxTabs = mController.getMaxTabs(); mTabs = new ArrayList<Tab>(mMaxTabs); mTabQueue = new ArrayList<Tab>(mMaxTabs); } @@ -66,18 +60,6 @@ class TabControl { return mThumbnailDir; } - BrowserActivity getBrowserActivity() { - return mActivity; - } - - /** - * Set if the webview should use the on screen zoom controls - * @param enabled - */ - void setDisplayZoomControls(boolean enabled) { - mDisplayZoomControls = enabled; - } - /** * Return the current tab's main WebView. This will always return the main * WebView for a given tab and not a subwindow. @@ -188,13 +170,10 @@ class TabControl { final WebView w = createNewWebView(privateBrowsing); // Create a new tab and add it to the tab list - Tab t = new Tab(mActivity, w, closeOnExit, appId, url); + Tab t = new Tab(mController, w, closeOnExit, appId, url); mTabs.add(t); // Initially put the tab in the background. t.putInBackground(); - if (mTabChangeListener != null) { - mTabChangeListener.onNewTab(t); - } return t; } @@ -259,9 +238,6 @@ class TabControl { // Remove it from the queue of viewed tabs. mTabQueue.remove(t); - if (mTabChangeListener != null) { - mTabChangeListener.onRemoveTab(t); - } return true; } @@ -357,7 +333,7 @@ class TabControl { } else { // Create a new tab and don't restore the state yet, add it // to the tab list - Tab t = new Tab(mActivity, null, false, null, null); + Tab t = new Tab(mController, null, false, null, null); if (state != null) { t.setSavedState(state); t.populatePickerDataFromSavedState(); @@ -551,7 +527,7 @@ class TabControl { * requires a load, whether it was due to the fact that it was deleted, or * it is because it was a voice search. */ - boolean recreateWebView(Tab t, BrowserActivity.UrlData urlData) { + boolean recreateWebView(Tab t, UrlData urlData) { final String url = urlData.mUrl; final WebView w = t.getWebView(); if (w != null) { @@ -598,22 +574,7 @@ class TabControl { * WebView. */ private WebView createNewWebView(boolean privateBrowsing) { - // Create a new WebView - ScrollWebView w = new ScrollWebView(mActivity, null, - android.R.attr.webViewStyle, privateBrowsing); - w.setScrollListener(mActivity.getScrollListener()); - w.setScrollbarFadingEnabled(true); - w.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY); - w.setMapTrackballToArrowKeys(false); // use trackball directly - // Enable the built-in zoom - w.getSettings().setBuiltInZoomControls(true); - w.getSettings().setDisplayZoomControls(mDisplayZoomControls); - // Add this WebView to the settings observer list and update the - // settings - final BrowserSettings s = BrowserSettings.getInstance(); - s.addObserver(w.getSettings()).update(s, null); - - return w; + return mController.getWebViewFactory().createWebView(privateBrowsing); } /** @@ -681,41 +642,4 @@ class TabControl { return true; } - interface TabChangeListener { - - public void onNewTab(Tab tab); - - public void onRemoveTab(Tab tab); - - public void onCurrentTab(Tab tab); - - public void onProgress(Tab tab, int progress); - - public void onUrlAndTitle(Tab tab, String url, String title); - - public void onFavicon(Tab tab, Bitmap favicon); - - public void onPageStarted(Tab tab, String url, Bitmap favicon); - - public void onPageFinished(Tab tab); - - } - - private TabChangeListener mTabChangeListener; - - /** - * register the TabChangeListener with the tab control - * @param listener - */ - void setOnTabChangeListener(TabChangeListener listener) { - mTabChangeListener = listener; - } - - /** - * get the current TabChangeListener (used by the tabs) - */ - TabChangeListener getTabChangeListener() { - return mTabChangeListener; - } - } diff --git a/src/com/android/browser/TabScrollView.java b/src/com/android/browser/TabScrollView.java index 7268ddc..fbb40aa 100644 --- a/src/com/android/browser/TabScrollView.java +++ b/src/com/android/browser/TabScrollView.java @@ -35,7 +35,7 @@ import android.widget.LinearLayout; */ public class TabScrollView extends HorizontalScrollView { - private BrowserActivity mBrowserActivity; + private Context mContext; private LinearLayout mContentView; private int mSelected; private Drawable mArrowLeft; @@ -70,11 +70,11 @@ public class TabScrollView extends HorizontalScrollView { } private void init(Context ctx) { - mBrowserActivity = (BrowserActivity) ctx; + mContext = ctx; mAnimationDuration = ctx.getResources().getInteger( R.integer.tab_animation_duration); setHorizontalScrollBarEnabled(false); - mContentView = new LinearLayout(mBrowserActivity); + mContentView = new LinearLayout(mContext); mContentView.setOrientation(LinearLayout.HORIZONTAL); mContentView.setLayoutParams( new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT)); diff --git a/src/com/android/browser/TitleBar.java b/src/com/android/browser/TitleBar.java index 035dc34..6dabd76 100644 --- a/src/com/android/browser/TitleBar.java +++ b/src/com/android/browser/TitleBar.java @@ -16,7 +16,9 @@ package com.android.browser; -import android.content.Context; +import com.android.common.speech.LoggingEvents; + +import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -42,41 +44,42 @@ import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; -import com.android.common.speech.LoggingEvents; - /** * This class represents a title bar for a particular "tab" or "window" in the * browser. */ public class TitleBar extends TitleBarBase { - private TextView mTitle; - private ImageView mRtButton; - private Drawable mCircularProgress; - private ProgressBar mHorizontalProgress; - private ImageView mStopButton; - private Drawable mBookmarkDrawable; - private Drawable mVoiceDrawable; - private boolean mInLoad; - private BrowserActivity mBrowserActivity; - private View mTitleBg; - private MyHandler mHandler; - private Intent mVoiceSearchIntent; - private boolean mInVoiceMode; - private Drawable mVoiceModeBackground; - private Drawable mNormalBackground; - private Drawable mLoadingBackground; - private ImageSpan mArcsSpan; - private int mLeftMargin; - private int mRightMargin; + + private Activity mActivity; + private UiController mController; + private TextView mTitle; + private ImageView mRtButton; + private Drawable mCircularProgress; + private ProgressBar mHorizontalProgress; + private ImageView mStopButton; + private Drawable mBookmarkDrawable; + private Drawable mVoiceDrawable; + private boolean mInLoad; + private View mTitleBg; + private MyHandler mHandler; + private Intent mVoiceSearchIntent; + private boolean mInVoiceMode; + private Drawable mVoiceModeBackground; + private Drawable mNormalBackground; + private Drawable mLoadingBackground; + private ImageSpan mArcsSpan; + private int mLeftMargin; + private int mRightMargin; private static int LONG_PRESS = 1; - public TitleBar(BrowserActivity context) { - super(context); + public TitleBar(Activity activity, UiController controller) { + super(activity); mHandler = new MyHandler(); - LayoutInflater factory = LayoutInflater.from(context); + LayoutInflater factory = LayoutInflater.from(activity); factory.inflate(R.layout.title_bar, this); - mBrowserActivity = context; + mActivity = activity; + mController = controller; mTitle = (TextView) findViewById(R.id.title); mTitle.setCompoundDrawablePadding(5); @@ -87,8 +90,8 @@ public class TitleBar extends TitleBarBase { mStopButton = (ImageView) findViewById(R.id.stop); mRtButton = (ImageView) findViewById(R.id.rt_btn); - Resources resources = context.getResources(); - mCircularProgress = (Drawable) resources.getDrawable( + Resources resources = activity.getResources(); + mCircularProgress = resources.getDrawable( com.android.internal.R.drawable.search_spinner); DisplayMetrics metrics = resources.getDisplayMetrics(); mLeftMargin = (int) TypedValue.applyDimension( @@ -107,8 +110,9 @@ public class TitleBar extends TitleBarBase { // results intent - http://b/2546173 // // TODO: Make a constant for this extra. - mVoiceSearchIntent.putExtra("android.speech.extras.SEND_APPLICATION_ID_EXTRA", false); - PackageManager pm = context.getPackageManager(); + mVoiceSearchIntent.putExtra("android.speech.extras.SEND_APPLICATION_ID_EXTRA", + false); + PackageManager pm = activity.getPackageManager(); ResolveInfo ri = pm.resolveActivity(mVoiceSearchIntent, PackageManager.MATCH_DEFAULT_ONLY); if (ri == null) { @@ -122,11 +126,12 @@ public class TitleBar extends TitleBarBase { R.drawable.title_voice); mNormalBackground = mTitleBg.getBackground(); mLoadingBackground = resources.getDrawable(R.drawable.title_loading); - mArcsSpan = new ImageSpan(context, R.drawable.arcs, + mArcsSpan = new ImageSpan(activity, R.drawable.arcs, ImageSpan.ALIGN_BASELINE); } private class MyHandler extends Handler { + @Override public void handleMessage(Message msg) { if (msg.what == LONG_PRESS) { // Prevent the normal action from happening by setting the title @@ -135,16 +140,20 @@ public class TitleBar extends TitleBarBase { // Need to call a special method on BrowserActivity for when the // fake title bar is up, because its ViewGroup does not show a // context menu. - mBrowserActivity.showTitleBarContextMenu(); + // TODO: + // this test is not valid for all UIs; fix later + if (getParent() != null) { + mActivity.openContextMenu(TitleBar.this); + } } } }; @Override public void createContextMenu(ContextMenu menu) { - MenuInflater inflater = mBrowserActivity.getMenuInflater(); + MenuInflater inflater = mActivity.getMenuInflater(); inflater.inflate(R.menu.title_context, menu); - mBrowserActivity.onCreateContextMenu(menu, this, null); + mActivity.onCreateContextMenu(menu, this, null); } @Override @@ -165,7 +174,7 @@ public class TitleBar extends TitleBarBase { } break; case MotionEvent.ACTION_MOVE: - int slop = ViewConfiguration.get(mBrowserActivity) + int slop = ViewConfiguration.get(mActivity) .getScaledTouchSlop(); if ((int) event.getY() > getHeight() + slop) { // We only trigger the actions in ACTION_UP if one or the @@ -193,37 +202,37 @@ public class TitleBar extends TitleBarBase { case MotionEvent.ACTION_UP: if (button.isPressed()) { if (mInVoiceMode) { - if (mBrowserActivity.getTabControl().getCurrentTab() + if (mController.getTabControl().getCurrentTab() .voiceSearchSourceIsGoogle()) { Intent i = new Intent( LoggingEvents.ACTION_LOG_EVENT); i.putExtra(LoggingEvents.EXTRA_EVENT, LoggingEvents.VoiceSearch.RETRY); - mBrowserActivity.sendBroadcast(i); + mActivity.sendBroadcast(i); } - mBrowserActivity.startActivity(mVoiceSearchIntent); + mActivity.startActivity(mVoiceSearchIntent); } else if (mInLoad) { - mBrowserActivity.stopLoading(); + mController.stopLoading(); } else { - mBrowserActivity.bookmarkCurrentPage( + mController.bookmarkCurrentPage( AddBookmarkPage.DEFAULT_FOLDER_ID); } button.setPressed(false); } else if (mTitleBg.isPressed()) { mHandler.removeMessages(LONG_PRESS); if (mInVoiceMode) { - if (mBrowserActivity.getTabControl().getCurrentTab() + if (mController.getTabControl().getCurrentTab() .voiceSearchSourceIsGoogle()) { Intent i = new Intent( LoggingEvents.ACTION_LOG_EVENT); i.putExtra(LoggingEvents.EXTRA_EVENT, LoggingEvents.VoiceSearch.N_BEST_REVEAL); - mBrowserActivity.sendBroadcast(i); + mActivity.sendBroadcast(i); } - mBrowserActivity.showVoiceSearchResults( + mController.showVoiceSearchResults( mTitle.getText().toString().trim()); } else { - mBrowserActivity.editUrl(); + mController.editUrl(); } mTitleBg.setPressed(false); } @@ -238,7 +247,8 @@ public class TitleBar extends TitleBarBase { * Change the TitleBar to or from voice mode. If there is no package to * handle voice search, the TitleBar cannot be set to voice mode. */ - /* package */ void setInVoiceMode(boolean inVoiceMode) { + @Override + void setInVoiceMode(boolean inVoiceMode) { if (mInVoiceMode == inVoiceMode) return; mInVoiceMode = inVoiceMode && mVoiceSearchIntent != null; Drawable titleDrawable; @@ -272,7 +282,8 @@ public class TitleBar extends TitleBarBase { /** * Update the progress, from 0 to 100. */ - /* package */ void setProgress(int newProgress) { + @Override + void setProgress(int newProgress) { if (newProgress >= mHorizontalProgress.getMax()) { mTitle.setCompoundDrawables(null, null, null, null); ((Animatable) mCircularProgress).stop(); @@ -312,7 +323,8 @@ public class TitleBar extends TitleBarBase { * @param title String to display. If null, the loading string will be * shown. */ - /* package */ void setDisplayTitle(String title) { + @Override + void setDisplayTitle(String title) { if (title == null) { mTitle.setText(R.string.title_bar_loading); } else { diff --git a/src/com/android/browser/TitleBarXLarge.java b/src/com/android/browser/TitleBarXLarge.java index f39c769..7e54710 100644 --- a/src/com/android/browser/TitleBarXLarge.java +++ b/src/com/android/browser/TitleBarXLarge.java @@ -18,6 +18,7 @@ package com.android.browser; import com.android.browser.UrlInputView.UrlInputListener; +import android.app.Activity; import android.app.SearchManager; import android.content.Context; import android.content.Intent; @@ -42,11 +43,12 @@ public class TitleBarXLarge extends TitleBarBase private static final int PROGRESS_MAX = 100; - private BrowserActivity mBrowserActivity; + private Activity mActivity; + private UiController mUiController; + private Drawable mStopDrawable; private Drawable mReloadDrawable; - private View mContainer; private View mBackButton; private View mForwardButton; @@ -63,13 +65,14 @@ public class TitleBarXLarge extends TitleBarBase private TextView mUrlUnfocused; private boolean mInLoad; - public TitleBarXLarge(BrowserActivity context) { - super(context); - mBrowserActivity = context; - Resources resources = context.getResources(); + public TitleBarXLarge(Activity activity, UiController controller) { + super(activity); + mActivity = activity; + mUiController = controller; + Resources resources = activity.getResources(); mStopDrawable = resources.getDrawable(R.drawable.ic_stop_normal); mReloadDrawable = resources.getDrawable(R.drawable.ic_refresh_normal); - rebuildLayout(context, true); + rebuildLayout(activity, true); } private void rebuildLayout(Context context, boolean rebuildData) { @@ -123,14 +126,14 @@ public class TitleBarXLarge extends TitleBarBase if (mUnfocusContainer == v) { mUrlUnfocused.requestFocus(); } else if (mBackButton == v) { - mBrowserActivity.getTopWindow().goBack(); + mUiController.getCurrentTopWebView().goBack(); } else if (mForwardButton == v) { - mBrowserActivity.getTopWindow().goForward(); + mUiController.getCurrentTopWebView().goForward(); } else if (mStar == v) { - mBrowserActivity.bookmarkCurrentPage( + mUiController.bookmarkCurrentPage( AddBookmarkPage.DEFAULT_FOLDER_ID); } else if (mAllButton == v) { - mBrowserActivity.bookmarksOrHistoryPicker(false); + mUiController.bookmarksOrHistoryPicker(false); } else if (mSearchButton == v) { search(); } else if (mStopButton == v) { @@ -155,25 +158,25 @@ public class TitleBarXLarge extends TitleBarBase @Override public void onAction(String text, String extra) { - mBrowserActivity.getTabControl().getCurrentTopWebView().requestFocus(); - mBrowserActivity.hideFakeTitleBar(); + mUiController.getCurrentTopWebView().requestFocus(); + ((BaseUi) mUiController.getUi()).hideFakeTitleBar(); Intent i = new Intent(); i.setAction(Intent.ACTION_SEARCH); i.putExtra(SearchManager.QUERY, text); if (extra != null) { i.putExtra(SearchManager.EXTRA_DATA_KEY, extra); } - mBrowserActivity.onNewIntent(i); + mUiController.handleNewIntent(i); setUrlMode(false); setDisplayTitle(text); } @Override public void onDismiss() { - mBrowserActivity.getTabControl().getCurrentTopWebView().requestFocus(); - mBrowserActivity.hideFakeTitleBar(); + mUiController.getCurrentTopWebView().requestFocus(); + ((BaseUi) mUiController.getUi()).hideFakeTitleBar(); setUrlMode(false); - setDisplayTitle(mBrowserActivity.getTabControl().getCurrentWebView().getUrl()); + setDisplayTitle(mUiController.getCurrentWebView().getUrl()); } @Override @@ -202,9 +205,9 @@ public class TitleBarXLarge extends TitleBarBase @Override public void createContextMenu(ContextMenu menu) { - MenuInflater inflater = mBrowserActivity.getMenuInflater(); + MenuInflater inflater = mActivity.getMenuInflater(); inflater.inflate(R.menu.title_context, menu); - mBrowserActivity.onCreateContextMenu(menu, this, null); + mActivity.onCreateContextMenu(menu, this, null); } private void search() { @@ -214,9 +217,9 @@ public class TitleBarXLarge extends TitleBarBase private void stopOrRefresh() { if (mInLoad) { - mBrowserActivity.stopLoading(); + mUiController.stopLoading(); } else { - mBrowserActivity.getTopWindow().reload(); + mUiController.getCurrentTopWebView().reload(); } } diff --git a/src/com/android/browser/UI.java b/src/com/android/browser/UI.java new file mode 100644 index 0000000..b290891 --- /dev/null +++ b/src/com/android/browser/UI.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import com.android.browser.ScrollWebView.ScrollListener; + +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.view.ActionMode; +import android.view.Menu; +import android.view.View; +import android.webkit.WebChromeClient.CustomViewCallback; + +/** + * UI interface definitions + */ +public interface UI extends ScrollListener { + + public void onPause(); + + public void onResume(); + + public void onDestroy(); + + public void onConfigurationChanged(Configuration config); + + public boolean onBackKey(); + + public void addTab(Tab tab); + + public void removeTab(Tab tab); + + public void setActiveTab(Tab tab); + + public void detachTab(Tab tab); + + public void attachTab(Tab tab); + + public void attachSubWindow(View subContainer); + + public void removeSubWindow(View subContainer); + + // TODO: consolidate + public void setUrlTitle(Tab tab, String url, String title); + + // TODO: consolidate + public void setFavicon(Tab tab, Bitmap icon); + + public void resetTitleAndRevertLockIcon(Tab tab); + + public void resetTitleAndIcon(Tab tab); + + public void onPageStarted(Tab tab, String url, Bitmap favicon); + + public void onPageFinished(Tab tab, String url); + + public void onPageStopped(Tab tab); + + public void onProgressChanged(Tab tab, int progress); + + public void showActiveTabsPage(); + + public void removeActiveTabsPage(); + + public void showComboView(boolean startWithHistory, Bundle extra); + + public void hideComboView(); + + public void showCustomView(View view, CustomViewCallback callback); + + public void onHideCustomView(); + + public boolean isCustomViewShowing(); + + public void showVoiceTitleBar(String title); + + public void revertVoiceTitleBar(Tab tab); + + // allow the ui to update state + public void onPrepareOptionsMenu(Menu menu); + + public void onOptionsMenuOpened(); + + public void onExtendedMenuOpened(); + + public void onOptionsMenuClosed(boolean inLoad); + + public void onExtendedMenuClosed(boolean inLoad); + + public void onContextMenuCreated(Menu menu); + + public void onContextMenuClosed(Menu menu, boolean inLoad); + + public void onActionModeStarted(ActionMode mode); + + public void onActionModeFinished(boolean inLoad); + + public void setShouldShowErrorConsole(Tab tab, boolean show); + + // returns if the web page is clear of any overlays (not including sub windows) + public boolean showsWeb(); + + Bitmap getDefaultVideoPoster(); + + View getVideoLoadingProgressView(); + +} diff --git a/src/com/android/browser/UiController.java b/src/com/android/browser/UiController.java new file mode 100644 index 0000000..1426132 --- /dev/null +++ b/src/com/android/browser/UiController.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.content.Intent; +import android.webkit.WebView; + + +/** + * UI aspect of the controller + */ +public interface UiController extends BookmarksHistoryCallbacks { + + UI getUi(); + + WebView getCurrentWebView(); + + WebView getCurrentTopWebView(); + + TabControl getTabControl(); + + Tab openTabToHomePage(); + + Tab openIncognitoTab(); + + boolean switchToTab(int tabIndex); + + void closeCurrentTab(); + + void closeTab(Tab tab); + + void stopLoading(); + + void bookmarkCurrentPage(long folderId); + + void bookmarksOrHistoryPicker(boolean openHistory); + + void showVoiceSearchResults(String title); + + void editUrl(); + + void removeActiveTabsPage(boolean attach); + + void handleNewIntent(Intent intent); + + boolean shouldShowErrorConsole(); + + void removeComboView(); + + void hideCustomView(); + + void attachSubWindow(Tab tab); + + void removeSubWindow(Tab tab); + + boolean isInCustomActionMode(); + + void endActionMode(); + +} diff --git a/src/com/android/browser/UploadHandler.java b/src/com/android/browser/UploadHandler.java new file mode 100644 index 0000000..d9b387f --- /dev/null +++ b/src/com/android/browser/UploadHandler.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.os.Environment; +import android.provider.MediaStore; +import android.webkit.ValueCallback; + +import java.io.File; +import java.util.Vector; + +/** + * Handle the file upload callbacks from WebView here + */ +public class UploadHandler { + + /* + * The Object used to inform the WebView of the file to upload. + */ + private ValueCallback<Uri> mUploadMessage; + private String mCameraFilePath; + + private Controller mController; + + public UploadHandler(Controller controller) { + mController = controller; + } + + String getFilePath() { + return mCameraFilePath; + } + + void onResult(int resultCode, Intent intent) { + Uri result = intent == null || resultCode != Activity.RESULT_OK ? null + : intent.getData(); + + // As we ask the camera to save the result of the user taking + // a picture, the camera application does not return anything other + // than RESULT_OK. So we need to check whether the file we expected + // was written to disk in the in the case that we + // did not get an intent returned but did get a RESULT_OK. If it was, + // we assume that this result has came back from the camera. + if (result == null && intent == null && resultCode == Activity.RESULT_OK) { + File cameraFile = new File(mCameraFilePath); + if (cameraFile.exists()) { + result = Uri.fromFile(cameraFile); + // Broadcast to the media scanner that we have a new photo + // so it will be added into the gallery for the user. + mController.getActivity().sendBroadcast( + new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result)); + } + } + + mUploadMessage.onReceiveValue(result); + } + + void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) { + + final String imageMimeType = "image/*"; + final String videoMimeType = "video/*"; + final String audioMimeType = "audio/*"; + final String mediaSourceKey = "capture"; + final String mediaSourceValueCamera = "camera"; + final String mediaSourceValueFileSystem = "filesystem"; + final String mediaSourceValueCamcorder = "camcorder"; + final String mediaSourceValueMicrophone = "microphone"; + + // media source can be 'filesystem' or 'camera' or 'camcorder' or 'microphone'. + String mediaSource = ""; + + // We add the camera intent if there was no accept type (or '*/*' or 'image/*'). + boolean addCameraIntent = true; + // We add the camcorder intent if there was no accept type (or '*/*' or 'video/*'). + boolean addCamcorderIntent = true; + + if (mUploadMessage != null) { + // Already a file picker operation in progress. + return; + } + + mUploadMessage = uploadMsg; + + // Parse the accept type. + String params[] = acceptType.split(";"); + String mimeType = params[0]; + + for (String p : params) { + String[] keyValue = p.split("="); + if (keyValue.length == 2) { + // Process key=value parameters. + if (mediaSourceKey.equals(keyValue[0])) { + mediaSource = keyValue[1]; + } + } + } + + // This intent will display the standard OPENABLE file picker. + Intent i = new Intent(Intent.ACTION_GET_CONTENT); + i.addCategory(Intent.CATEGORY_OPENABLE); + + // Create an intent to add to the standard file picker that will + // capture an image from the camera. We'll combine this intent with + // the standard OPENABLE picker unless the web developer specifically + // requested the camera or gallery be opened by passing a parameter + // in the accept type. + Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + File externalDataDir = Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DCIM); + File cameraDataDir = new File(externalDataDir.getAbsolutePath() + + File.separator + "browser-photos"); + cameraDataDir.mkdirs(); + mCameraFilePath = cameraDataDir.getAbsolutePath() + File.separator + + System.currentTimeMillis() + ".jpg"; + cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(mCameraFilePath))); + + Intent camcorderIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); + + Intent soundRecIntent = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION); + + if (mimeType.equals(imageMimeType)) { + i.setType(imageMimeType); + addCamcorderIntent = false; + if (mediaSource.equals(mediaSourceValueCamera)) { + // Specified 'image/*' and requested the camera, so go ahead and launch the camera + // directly. + startActivity(cameraIntent); + return; + } else if (mediaSource.equals(mediaSourceValueFileSystem)) { + // Specified filesytem as the source, so don't want to consider the camera. + addCameraIntent = false; + } + } else if (mimeType.equals(videoMimeType)) { + i.setType(videoMimeType); + addCameraIntent = false; + // The camcorder saves it's own file and returns it to us in the intent, so + // we don't need to generate one here. + mCameraFilePath = null; + + if (mediaSource.equals(mediaSourceValueCamcorder)) { + // Specified 'video/*' and requested the camcorder, so go ahead and launch the + // camcorder directly. + startActivity(camcorderIntent); + return; + } else if (mediaSource.equals(mediaSourceValueFileSystem)) { + // Specified filesystem as the source, so don't want to consider the camcorder. + addCamcorderIntent = false; + } + } else if (mimeType.equals(audioMimeType)) { + i.setType(audioMimeType); + addCameraIntent = false; + addCamcorderIntent = false; + if (mediaSource.equals(mediaSourceValueMicrophone)) { + // Specified 'audio/*' and requested microphone, so go ahead and launch the sound + // recorder. + startActivity(soundRecIntent); + return; + } + // On a default system, there is no single option to open an audio "gallery". Both the + // sound recorder and music browser respond to the OPENABLE/audio/* intent unlike the + // image/* and video/* OPENABLE intents where the image / video gallery are the only + // respondants (and so the user is not prompted by default). + } else { + i.setType("*/*"); + } + + // Combine the chooser and the extra choices (like camera or camcorder) + Intent chooser = new Intent(Intent.ACTION_CHOOSER); + chooser.putExtra(Intent.EXTRA_INTENT, i); + + Vector<Intent> extraInitialIntents = new Vector<Intent>(0); + + if (addCameraIntent) { + extraInitialIntents.add(cameraIntent); + } + + if (addCamcorderIntent) { + extraInitialIntents.add(camcorderIntent); + } + + if (extraInitialIntents.size() > 0) { + Intent[] extraIntents = new Intent[extraInitialIntents.size()]; + chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, + extraInitialIntents.toArray(extraIntents)); + } + + chooser.putExtra(Intent.EXTRA_TITLE, + mController.getActivity().getResources() + .getString(R.string.choose_upload)); + startActivity(chooser); + } + + private void startActivity(Intent intent) { + mController.getActivity().startActivityForResult(intent, + Controller.FILE_SELECTED); + } + +} diff --git a/src/com/android/browser/UrlHandler.java b/src/com/android/browser/UrlHandler.java new file mode 100644 index 0000000..72704e0 --- /dev/null +++ b/src/com/android/browser/UrlHandler.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.net.Uri; +import android.util.Log; +import android.webkit.WebView; + +import java.net.URISyntaxException; + +/** + * + */ +public class UrlHandler { + + // Use in overrideUrlLoading + /* package */ final static String SCHEME_WTAI = "wtai://wp/"; + /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;"; + /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;"; + /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;"; + + Controller mController; + Activity mActivity; + + private Boolean mIsProviderPresent = null; + private Uri mRlzUri = null; + + public UrlHandler(Controller controller) { + mController = controller; + mActivity = mController.getActivity(); + } + + boolean shouldOverrideUrlLoading(WebView view, String url) { + if (view.isPrivateBrowsingEnabled()) { + // Don't allow urls to leave the browser app when in + // private browsing mode + mController.loadUrl(view, url); + return true; + } + + if (url.startsWith(SCHEME_WTAI)) { + // wtai://wp/mc;number + // number=string(phone-number) + if (url.startsWith(SCHEME_WTAI_MC)) { + Intent intent = new Intent(Intent.ACTION_VIEW, + Uri.parse(WebView.SCHEME_TEL + + url.substring(SCHEME_WTAI_MC.length()))); + mActivity.startActivity(intent); + // before leaving BrowserActivity, close the empty child tab. + // If a new tab is created through JavaScript open to load this + // url, we would like to close it as we will load this url in a + // different Activity. + mController.closeEmptyChildTab(); + return true; + } + // wtai://wp/sd;dtmf + // dtmf=string(dialstring) + if (url.startsWith(SCHEME_WTAI_SD)) { + // TODO: only send when there is active voice connection + return false; + } + // wtai://wp/ap;number;name + // number=string(phone-number) + // name=string + if (url.startsWith(SCHEME_WTAI_AP)) { + // TODO + return false; + } + } + + // The "about:" schemes are internal to the browser; don't want these to + // be dispatched to other apps. + if (url.startsWith("about:")) { + return false; + } + + // If this is a Google search, attempt to add an RLZ string + // (if one isn't already present). + if (rlzProviderPresent()) { + Uri siteUri = Uri.parse(url); + if (needsRlzString(siteUri)) { + String rlz = null; + Cursor cur = null; + try { + cur = mActivity.getContentResolver() + .query(getRlzUri(), null, null, null, null); + if (cur != null && cur.moveToFirst() && !cur.isNull(0)) { + url = siteUri.buildUpon() + .appendQueryParameter("rlz", cur.getString(0)) + .build().toString(); + } + } finally { + if (cur != null) { + cur.close(); + } + } + mController.loadUrl(view, url); + return true; + } + } + + Intent intent; + // perform generic parsing of the URI to turn it into an Intent. + try { + intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); + } catch (URISyntaxException ex) { + Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage()); + return false; + } + + // check whether the intent can be resolved. If not, we will see + // whether we can download it from the Market. + if (mActivity.getPackageManager().resolveActivity(intent, 0) == null) { + String packagename = intent.getPackage(); + if (packagename != null) { + intent = new Intent(Intent.ACTION_VIEW, Uri + .parse("market://search?q=pname:" + packagename)); + intent.addCategory(Intent.CATEGORY_BROWSABLE); + mActivity.startActivity(intent); + // before leaving BrowserActivity, close the empty child tab. + // If a new tab is created through JavaScript open to load this + // url, we would like to close it as we will load this url in a + // different Activity. + mController.closeEmptyChildTab(); + return true; + } else { + return false; + } + } + + // sanitize the Intent, ensuring web pages can not bypass browser + // security (only access to BROWSABLE activities). + intent.addCategory(Intent.CATEGORY_BROWSABLE); + intent.setComponent(null); + try { + if (mActivity.startActivityIfNeeded(intent, -1)) { + // before leaving BrowserActivity, close the empty child tab. + // If a new tab is created through JavaScript open to load this + // url, we would like to close it as we will load this url in a + // different Activity. + mController.closeEmptyChildTab(); + return true; + } + } catch (ActivityNotFoundException ex) { + // ignore the error. If no application can handle the URL, + // eg about:blank, assume the browser can handle it. + } + + if (mController.isMenuDown()) { + mController.openTab(url, false); + mActivity.closeOptionsMenu(); + return true; + } + return false; + } + + // Determine whether the RLZ provider is present on the system. + private boolean rlzProviderPresent() { + if (mIsProviderPresent == null) { + PackageManager pm = mActivity.getPackageManager(); + mIsProviderPresent = pm.resolveContentProvider( + BrowserSettings.RLZ_PROVIDER, 0) != null; + } + return mIsProviderPresent; + } + + // Retrieve the RLZ access point string and cache the URI used to + // retrieve RLZ values. + private Uri getRlzUri() { + if (mRlzUri == null) { + String ap = mActivity.getResources() + .getString(R.string.rlz_access_point); + mRlzUri = Uri.withAppendedPath(BrowserSettings.RLZ_PROVIDER_URI, ap); + } + return mRlzUri; + } + + // Determine if this URI appears to be for a Google search + // and does not have an RLZ parameter. + // Taken largely from Chrome source, src/chrome/browser/google_url_tracker.cc + private static boolean needsRlzString(Uri uri) { + String scheme = uri.getScheme(); + if (("http".equals(scheme) || "https".equals(scheme)) && + (uri.getQueryParameter("q") != null) && + (uri.getQueryParameter("rlz") == null)) { + String host = uri.getHost(); + if (host == null) { + return false; + } + String[] hostComponents = host.split("\\."); + + if (hostComponents.length < 2) { + return false; + } + int googleComponent = hostComponents.length - 2; + String component = hostComponents[googleComponent]; + if (!"google".equals(component)) { + if (hostComponents.length < 3 || + (!"co".equals(component) && !"com".equals(component))) { + return false; + } + googleComponent = hostComponents.length - 3; + if (!"google".equals(hostComponents[googleComponent])) { + return false; + } + } + + // Google corp network handling. + if (googleComponent > 0 && "corp".equals( + hostComponents[googleComponent - 1])) { + return false; + } + + return true; + } + return false; + } + +} diff --git a/src/com/android/browser/UrlUtils.java b/src/com/android/browser/UrlUtils.java index cb6377a..2df0a61 100644 --- a/src/com/android/browser/UrlUtils.java +++ b/src/com/android/browser/UrlUtils.java @@ -13,13 +13,33 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.android.browser; +import android.net.Uri; +import android.util.Patterns; +import android.webkit.URLUtil; + import java.util.regex.Matcher; import java.util.regex.Pattern; +/** + * Utility methods for Url manipulation + */ public class UrlUtils { + static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile( + "(?i)" + // switch on case insensitive matching + "(" + // begin group for schema + "(?:http|https|file):\\/\\/" + + "|(?:inline|data|about|content|javascript):" + + ")" + + "(.*)" ); + + // Google search + private final static String QUICKSEARCH_G = "http://www.google.com/m?q=%s"; + private final static String QUERY_PLACE_HOLDER = "%s"; + // Regular expression which matches http://, followed by some stuff, followed by // optionally a trailing slash, all matched as separate groups. private static final Pattern STRIP_URL_PATTERN = Pattern.compile("^(http://)(.*?)(/$)?"); @@ -46,4 +66,83 @@ public class UrlUtils { return url; } } + + protected static String smartUrlFilter(Uri inUri) { + if (inUri != null) { + return smartUrlFilter(inUri.toString()); + } + return null; + } + + /** + * Attempts to determine whether user input is a URL or search + * terms. Anything with a space is passed to search. + * + * Converts to lowercase any mistakenly uppercased schema (i.e., + * "Http://" converts to "http://" + * + * @return Original or modified URL + * + */ + protected static String smartUrlFilter(String url) { + + String inUrl = url.trim(); + boolean hasSpace = inUrl.indexOf(' ') != -1; + + Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl); + if (matcher.matches()) { + // force scheme to lowercase + String scheme = matcher.group(1); + String lcScheme = scheme.toLowerCase(); + if (!lcScheme.equals(scheme)) { + inUrl = lcScheme + matcher.group(2); + } + if (hasSpace) { + inUrl = inUrl.replace(" ", "%20"); + } + return inUrl; + } + if (!hasSpace) { + if (Patterns.WEB_URL.matcher(inUrl).matches()) { + return URLUtil.guessUrl(inUrl); + } + } + + // FIXME: Is this the correct place to add to searches? + // what if someone else calls this function? + +// Browser.addSearchUrl(mBrowser.getContentResolver(), inUrl); + return URLUtil.composeSearchUrl(inUrl, QUICKSEARCH_G, QUERY_PLACE_HOLDER); + } + + /* package */ static String fixUrl(String inUrl) { + // FIXME: Converting the url to lower case + // duplicates functionality in smartUrlFilter(). + // However, changing all current callers of fixUrl to + // call smartUrlFilter in addition may have unwanted + // consequences, and is deferred for now. + int colon = inUrl.indexOf(':'); + boolean allLower = true; + for (int index = 0; index < colon; index++) { + char ch = inUrl.charAt(index); + if (!Character.isLetter(ch)) { + break; + } + allLower &= Character.isLowerCase(ch); + if (index == colon - 1 && !allLower) { + inUrl = inUrl.substring(0, colon).toLowerCase() + + inUrl.substring(colon); + } + } + if (inUrl.startsWith("http://") || inUrl.startsWith("https://")) + return inUrl; + if (inUrl.startsWith("http:") || + inUrl.startsWith("https:")) { + if (inUrl.startsWith("http:/") || inUrl.startsWith("https:/")) { + inUrl = inUrl.replaceFirst("/", "//"); + } else inUrl = inUrl.replaceFirst(":", "://"); + } + return inUrl; + } + } diff --git a/src/com/android/browser/WallpaperHandler.java b/src/com/android/browser/WallpaperHandler.java new file mode 100644 index 0000000..0c88a50 --- /dev/null +++ b/src/com/android/browser/WallpaperHandler.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.app.ProgressDialog; +import android.app.WallpaperManager; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.util.Log; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Handle setWallpaper requests + * + */ +public class WallpaperHandler extends Thread + implements OnMenuItemClickListener, DialogInterface.OnCancelListener { + + + private static final String LOGTAG = "WallpaperHandler"; + + private Context mContext; + private URL mUrl; + private ProgressDialog mWallpaperProgress; + private boolean mCanceled = false; + + public WallpaperHandler(Context context, String url) { + mContext = context; + try { + mUrl = new URL(url); + } catch (MalformedURLException e) { + mUrl = null; + } + } + + @Override + public void onCancel(DialogInterface dialog) { + mCanceled = true; + } + + @Override + public boolean onMenuItemClick(MenuItem item) { + if (mUrl != null) { + // The user may have tried to set a image with a large file size as + // their background so it may take a few moments to perform the + // operation. + // Display a progress spinner while it is working. + mWallpaperProgress = new ProgressDialog(mContext); + mWallpaperProgress.setIndeterminate(true); + mWallpaperProgress.setMessage(mContext.getResources() + .getText(R.string.progress_dialog_setting_wallpaper)); + mWallpaperProgress.setCancelable(true); + mWallpaperProgress.setOnCancelListener(this); + mWallpaperProgress.show(); + start(); + } + return true; + } + + @Override + public void run() { + Drawable oldWallpaper = + WallpaperManager.getInstance(mContext).getDrawable(); + try { + // TODO: This will cause the resource to be downloaded again, when + // we should in most cases be able to grab it from the cache. To fix + // this we should query WebCore to see if we can access a cached + // version and instead open an input stream on that. This pattern + // could also be used in the download manager where the same problem + // exists. + InputStream inputstream = mUrl.openStream(); + if (inputstream != null) { + WallpaperManager.getInstance(mContext).setStream(inputstream); + } + } catch (IOException e) { + Log.e(LOGTAG, "Unable to set new wallpaper"); + // Act as though the user canceled the operation so we try to + // restore the old wallpaper. + mCanceled = true; + } + + if (mCanceled) { + // Restore the old wallpaper if the user cancelled whilst we were + // setting + // the new wallpaper. + int width = oldWallpaper.getIntrinsicWidth(); + int height = oldWallpaper.getIntrinsicHeight(); + Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); + Canvas canvas = new Canvas(bm); + oldWallpaper.setBounds(0, 0, width, height); + oldWallpaper.draw(canvas); + try { + WallpaperManager.getInstance(mContext).setBitmap(bm); + } catch (IOException e) { + Log.e(LOGTAG, "Unable to restore old wallpaper."); + } + mCanceled = false; + } + + if (mWallpaperProgress.isShowing()) { + mWallpaperProgress.dismiss(); + } + } +} diff --git a/src/com/android/browser/WebViewController.java b/src/com/android/browser/WebViewController.java new file mode 100644 index 0000000..fdd8ab1 --- /dev/null +++ b/src/com/android/browser/WebViewController.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import com.android.browser.IntentHandler.UrlData; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.net.Uri; +import android.net.http.SslError; +import android.view.KeyEvent; +import android.view.View; +import android.webkit.HttpAuthHandler; +import android.webkit.SslErrorHandler; +import android.webkit.ValueCallback; +import android.webkit.WebChromeClient; +import android.webkit.WebView; + +/** + * WebView aspect of the controller + */ +public interface WebViewController { + + Activity getActivity(); + + TabControl getTabControl(); + + void onPageStarted(Tab tab, WebView view, String url, Bitmap favicon); + + void onPageFinished(Tab tab, String url); + + void onProgressChanged(Tab tab, int newProgress); + + void onReceivedTitle(Tab tab, final String title); + + void onFavicon(Tab tab, WebView view, Bitmap icon); + + boolean shouldOverrideUrlLoading(WebView view, String url); + + boolean shouldOverrideKeyEvent(KeyEvent event); + + void onUnhandledKeyEvent(KeyEvent event); + + void doUpdateVisitedHistory(Tab tab, String url, boolean isReload); + + void getVisitedHistory(final ValueCallback<String[]> callback); + + void onReceivedHttpAuthRequest(Tab tab, WebView view, final HttpAuthHandler handler, + final String host, final String realm); + + void onDownloadStart(Tab tab, String url, String useragent, String contentDisposition, + String mimeType, long contentLength); + + void showCustomView(Tab tab, View view, WebChromeClient.CustomViewCallback callback); + + void hideCustomView(); + + Bitmap getDefaultVideoPoster(); + + View getVideoLoadingProgressView(); + + void showSslCertificateOnError(WebView view, SslErrorHandler handler, + SslError error); + + void activateVoiceSearchMode(String title); + + void revertVoiceSearchMode(Tab tab); + + boolean shouldShowErrorConsole(); + + void resetTitleAndRevertLockIcon(Tab tab); + + void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType); + + void endActionMode(); + + void attachSubWindow(Tab tab); + + void dismissSubWindow(Tab tab); + + Tab openTabAndShow(UrlData urlData, boolean closeOnExit, String appId); + + boolean switchToTab(int tabindex); + + void closeTab(Tab tab); + +} diff --git a/src/com/android/browser/WebViewFactory.java b/src/com/android/browser/WebViewFactory.java new file mode 100644 index 0000000..6047d1c --- /dev/null +++ b/src/com/android/browser/WebViewFactory.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.webkit.WebView; + +/** + * Factory for WebViews + */ +public interface WebViewFactory { + + public WebView createWebView(boolean privateBrowsing); + +} |