/* * Copyright (C) 2006 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.google.android.googleapps.IGoogleLoginService; import com.google.android.googlelogin.GoogleLoginServiceConstants; import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; import android.app.SearchManager; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.DialogInterface.OnCancelListener; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.AssetManager; import android.content.res.Configuration; import android.content.res.Resources; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.DrawFilter; import android.graphics.Paint; import android.graphics.PaintFlagsDrawFilter; import android.graphics.Picture; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.hardware.SensorListener; import android.hardware.SensorManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.net.WebAddress; import android.net.http.EventHandler; 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.IBinder; import android.os.Message; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.provider.Browser; import android.provider.ContactsContract; import android.provider.ContactsContract.Intents.Insert; import android.provider.Downloads; import android.provider.MediaStore; import android.text.IClipboard; import android.text.TextUtils; import android.text.format.DateFormat; import android.text.util.Regex; import android.util.AttributeSet; import android.util.Log; import android.view.ContextMenu; import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.view.ContextMenu.ContextMenuInfo; import android.view.MenuItem.OnMenuItemClickListener; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.DecelerateInterpolator; import android.view.animation.ScaleAnimation; import android.view.animation.TranslateAnimation; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import android.webkit.DownloadListener; import android.webkit.GeolocationPermissions; import android.webkit.HttpAuthHandler; import android.webkit.PluginManager; import android.webkit.SslErrorHandler; import android.webkit.URLUtil; import android.webkit.WebChromeClient; import android.webkit.WebChromeClient.CustomViewCallback; import android.webkit.WebHistoryItem; import android.webkit.WebIconDatabase; import android.webkit.WebStorage; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLEncoder; import java.text.ParseException; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.LinkedList; import java.util.Vector; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; public class BrowserActivity extends Activity implements View.OnCreateContextMenuListener, DownloadListener { /* 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 IGoogleLoginService mGls = null; private ServiceConnection mGlsConnection = null; private SensorManager mSensorManager = null; // These are single-character shortcuts for searching popular sources. private static final int SHORTCUT_INVALID = 0; private static final int SHORTCUT_GOOGLE_SEARCH = 1; private static final int SHORTCUT_WIKIPEDIA_SEARCH = 2; private static final int SHORTCUT_DICTIONARY_SEARCH = 3; private static final int SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH = 4; /* Whitelisted webpages private static HashSet sWhiteList; static { sWhiteList = new HashSet(); sWhiteList.add("cnn.com/"); sWhiteList.add("espn.go.com/"); sWhiteList.add("nytimes.com/"); sWhiteList.add("engadget.com/"); sWhiteList.add("yahoo.com/"); sWhiteList.add("msn.com/"); sWhiteList.add("amazon.com/"); sWhiteList.add("consumerist.com/"); sWhiteList.add("google.com/m/news"); } */ private void setupHomePage() { final Runnable getAccount = new Runnable() { public void run() { // Lower priority Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); // get the default home page String homepage = mSettings.getHomePage(); try { if (mGls == null) return; if (!homepage.startsWith("http://www.google.")) return; if (homepage.indexOf('?') == -1) return; String hostedUser = mGls.getAccount(GoogleLoginServiceConstants.PREFER_HOSTED); String googleUser = mGls.getAccount(GoogleLoginServiceConstants.REQUIRE_GOOGLE); // three cases: // // hostedUser == googleUser // The device has only a google account // // hostedUser != googleUser // The device has a hosted account and a google account // // hostedUser != null, googleUser == null // The device has only a hosted account (so far) // developers might have no accounts at all if (hostedUser == null) return; if (googleUser == null || !hostedUser.equals(googleUser)) { String domain = hostedUser.substring(hostedUser.lastIndexOf('@')+1); homepage = homepage.replace("?", "/a/" + domain + "?"); } } catch (RemoteException ignore) { // Login service died; carry on } catch (RuntimeException ignore) { // Login service died; carry on } finally { finish(homepage); } } private void finish(final String homepage) { mHandler.post(new Runnable() { public void run() { mSettings.setHomePage(BrowserActivity.this, homepage); resumeAfterCredentials(); // as this is running in a separate thread, // BrowserActivity's onDestroy() may have been called, // which also calls unbindService(). if (mGlsConnection != null) { // we no longer need to keep GLS open unbindService(mGlsConnection); mGlsConnection = null; } } }); } }; final boolean[] done = { false }; // Open a connection to the Google Login Service. The first // time the connection is established, set up the homepage depending on // the account in a background thread. mGlsConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { mGls = IGoogleLoginService.Stub.asInterface(service); if (done[0] == false) { done[0] = true; Thread account = new Thread(getAccount); account.setName("GLSAccount"); account.start(); } } public void onServiceDisconnected(ComponentName className) { mGls = null; } }; bindService(GoogleLoginServiceConstants.SERVICE_INTENT, mGlsConnection, Context.BIND_AUTO_CREATE); } private static class ClearThumbnails extends AsyncTask { @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; } } /** * This layout holds everything you see below the status bar, including the * error console, the custom view container, and the webviews. */ private FrameLayout mBrowserFrameLayout; @Override public void onCreate(Bundle icicle) { if (LOGV_ENABLED) { Log.v(LOGTAG, this + " onStart"); } super.onCreate(icicle); // test the browser in OpenGL // requestWindowFeature(Window.FEATURE_OPENGL); 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())) { finish(); return; } // // start MASF proxy service // //Intent proxyServiceIntent = new Intent(); //proxyServiceIntent.setComponent // (new ComponentName( // "com.android.masfproxyservice", // "com.android.masfproxyservice.MasfProxyService")); //startService(proxyServiceIntent, null); mSecLockIcon = Resources.getSystem().getDrawable( android.R.drawable.ic_secure); mMixLockIcon = Resources.getSystem().getDrawable( android.R.drawable.ic_partial_secure); FrameLayout frameLayout = (FrameLayout) getWindow().getDecorView() .findViewById(com.android.internal.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); mTitleBar = new TitleBar(this); // Create the tab control and our initial tab mTabControl = new TabControl(this); // Open the icon database and retain all the bookmark urls for favicons retainIconsOnStartup(); // Keep a settings instance handy. mSettings = BrowserSettings.getInstance(); mSettings.setTabControl(mTabControl); mSettings.loadFromDb(this); PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Browser"); /* 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 = (NetworkInfo) intent.getParcelableExtra( ConnectivityManager.EXTRA_NETWORK_INFO); onNetworkToggle( (info != null) ? info.isConnected() : false); } } }; IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addDataScheme("package"); mPackageInstallationReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); final String packageName = intent.getData() .getSchemeSpecificPart(); final boolean replacing = intent.getBooleanExtra( Intent.EXTRA_REPLACING, false); if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) { // if it is replacing, refreshPlugins() when adding return; } PackageManager pm = BrowserActivity.this.getPackageManager(); PackageInfo pkgInfo = null; try { pkgInfo = pm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS); } catch (PackageManager.NameNotFoundException e) { return; } if (pkgInfo != null) { String permissions[] = pkgInfo.requestedPermissions; if (permissions == null) { return; } boolean permissionOk = false; for (String permit : permissions) { if (PluginManager.PLUGIN_PERMISSION.equals(permit)) { permissionOk = true; break; } } if (permissionOk) { PluginManager.getInstance(BrowserActivity.this) .refreshPlugins( Intent.ACTION_PACKAGE_ADDED .equals(action)); } } } }; registerReceiver(mPackageInstallationReceiver, filter); if (!mTabControl.restoreState(icicle)) { // clear up the thumbnail directory if we can't restore the state as // none of the files in the directory are referenced any more. 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(); 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); final TabControl.Tab t = mTabControl.createNewTab( Intent.ACTION_VIEW.equals(intent.getAction()) && intent.getData() != null, intent.getStringExtra(Browser.EXTRA_APPLICATION_ID), urlData.mUrl); 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 we are not restoring from an icicle, then there is a high // likely hood this is the first run. So, check to see if the // homepage needs to be configured and copy any plugins from our // asset directory to the data partition. if ((extra == null || !extra.getBoolean("testing")) && !mSettings.isLoginInitialized()) { setupHomePage(); } if (urlData.isEmpty()) { if (mSettings.isLoginInitialized()) { webView.loadUrl(mSettings.getHomePage()); } else { waitForCredentials(); } } else { if (extra != null) { urlData.setPostData(extra .getByteArray(Browser.EXTRA_POST_DATA)); } urlData.loadIn(webView); } } else { // TabControl.restoreState() will create a new tab even if // restoring the state fails. attachTabToContentView(mTabControl.getCurrentTab()); } // Read JavaScript flags if it exists. String jsFlags = mSettings.getJsFlags(); if (jsFlags.trim().length() != 0) { mTabControl.getCurrentWebView().setJsFlags(jsFlags); } } @Override protected void onNewIntent(Intent intent) { TabControl.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; } if (Intent.ACTION_VIEW.equals(action) || Intent.ACTION_SEARCH.equals(action) || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action) || Intent.ACTION_WEB_SEARCH.equals(action)) { // 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()); } urlData.setPostData(intent .getByteArrayExtra(Browser.EXTRA_POST_DATA)); final String appId = intent .getStringExtra(Browser.EXTRA_APPLICATION_ID); if (Intent.ACTION_VIEW.equals(action) && !getPackageName().equals(appId) && (flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) { TabControl.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.mUrl); if (current != appTab) { switchToTab(mTabControl.getTabIndex(appTab)); if (needsLoad) { urlData.loadIn(appTab.getWebView()); } } else { // If the tab was the current tab, we have to attach // it to the view system again. attachTabToContentView(appTab); if (needsLoad) { urlData.loadIn(appTab.getWebView()); } } 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 ("about:debug".equals(urlData.mUrl)) { mSettings.toggleDebugSettings(); return; } // Get rid of the subwindow if it exists dismissSubWindow(current); urlData.loadIn(current.getWebView()); } } } private int parseUrlShortcut(String url) { if (url == null) return SHORTCUT_INVALID; // FIXME: quick search, need to be customized by setting if (url.length() > 2 && url.charAt(1) == ' ') { switch (url.charAt(0)) { case 'g': return SHORTCUT_GOOGLE_SEARCH; case 'w': return SHORTCUT_WIKIPEDIA_SEARCH; case 'd': return SHORTCUT_DICTIONARY_SEARCH; case 'l': return SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH; } } return SHORTCUT_INVALID; } /** * 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 (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 and site specific search shortcuts are handled by the regular flow of control, so // return early. if (Regex.WEB_URL_PATTERN.matcher(url).matches() || ACCEPTED_URI_SCHEMA.matcher(url).matches() || parseUrlShortcut(url) != SHORTCUT_INVALID) { return false; } Browser.updateVisitedHistory(mResolver, url, false); Browser.addSearchUrl(mResolver, url); Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.putExtra(SearchManager.QUERY, url); if (appData != null) { intent.putExtra(SearchManager.APP_DATA, appData); } if (extraData != null) { intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData); } intent.putExtra(Browser.EXTRA_APPLICATION_ID, getPackageName()); startActivity(intent); return true; } private UrlData getUrlDataFromIntent(Intent intent) { String url = 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 ("inline:".equals(url)) { return new InlinedUrlData( intent.getStringExtra(Browser.EXTRA_INLINE_CONTENT), intent.getType(), intent.getStringExtra(Browser.EXTRA_INLINE_ENCODING), intent.getStringExtra(Browser.EXTRA_INLINE_FAILURL)); } } 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; // Don't add Urls, just search terms. // Urls will get added when the page is loaded. if (!Regex.WEB_URL_PATTERN.matcher(url).matches()) { Browser.updateVisitedHistory(mResolver, url, false); } // 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); 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(SearchManager.SOURCE); } if (TextUtils.isEmpty(source)) { source = GOOGLE_SEARCH_SOURCE_UNKNOWN; } url = url.replace(searchSource, "&source=android-"+source+"&"); } } } } return new UrlData(url); } /* 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; } /** * Looking for the pattern like this * * * * * * * *** * ******* * * * * * * * * */ private final SensorListener mSensorListener = new SensorListener() { private long mLastGestureTime; private float[] mPrev = new float[3]; private float[] mPrevDiff = new float[3]; private float[] mDiff = new float[3]; private float[] mRevertDiff = new float[3]; public void onSensorChanged(int sensor, float[] values) { boolean show = false; float[] diff = new float[3]; for (int i = 0; i < 3; i++) { diff[i] = values[i] - mPrev[i]; if (Math.abs(diff[i]) > 1) { show = true; } if ((diff[i] > 1.0 && mDiff[i] < 0.2) || (diff[i] < -1.0 && mDiff[i] > -0.2)) { // start track when there is a big move, or revert mRevertDiff[i] = mDiff[i]; mDiff[i] = 0; } else if (diff[i] > -0.2 && diff[i] < 0.2) { // reset when it is flat mDiff[i] = mRevertDiff[i] = 0; } mDiff[i] += diff[i]; mPrevDiff[i] = diff[i]; mPrev[i] = values[i]; } if (false) { // only shows if we think the delta is big enough, in an attempt // to detect "serious" moves left/right or up/down Log.d("BrowserSensorHack", "sensorChanged " + sensor + " (" + values[0] + ", " + values[1] + ", " + values[2] + ")" + " diff(" + diff[0] + " " + diff[1] + " " + diff[2] + ")"); Log.d("BrowserSensorHack", " mDiff(" + mDiff[0] + " " + mDiff[1] + " " + mDiff[2] + ")" + " mRevertDiff(" + mRevertDiff[0] + " " + mRevertDiff[1] + " " + mRevertDiff[2] + ")"); } long now = android.os.SystemClock.uptimeMillis(); if (now - mLastGestureTime > 1000) { mLastGestureTime = 0; float y = mDiff[1]; float z = mDiff[2]; float ay = Math.abs(y); float az = Math.abs(z); float ry = mRevertDiff[1]; float rz = mRevertDiff[2]; float ary = Math.abs(ry); float arz = Math.abs(rz); boolean gestY = ay > 2.5f && ary > 1.0f && ay > ary; boolean gestZ = az > 3.5f && arz > 1.0f && az > arz; if ((gestY || gestZ) && !(gestY && gestZ)) { WebView view = mTabControl.getCurrentWebView(); if (view != null) { if (gestZ) { if (z < 0) { view.zoomOut(); } else { view.zoomIn(); } } else { view.flingScroll(0, Math.round(y * 100)); } } mLastGestureTime = now; } } } public void onAccuracyChanged(int sensor, int accuracy) { // TODO Auto-generated method stub } }; @Override protected void onResume() { super.onResume(); 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(); } if (mCredsDlg != null) { if (!mHandler.hasMessages(CANCEL_CREDS_REQUEST)) { // In case credential request never comes back mHandler.sendEmptyMessageDelayed(CANCEL_CREDS_REQUEST, 6000); } } registerReceiver(mNetworkStateIntentReceiver, mNetworkStateChangedFilter); WebView.enablePlatformNotifications(); if (mSettings.doFlick()) { if (mSensorManager == null) { mSensorManager = (SensorManager) getSystemService( Context.SENSOR_SERVICE); } mSensorManager.registerListener(mSensorListener, SensorManager.SENSOR_ACCELEROMETER, SensorManager.SENSOR_DELAY_FASTEST); } else { mSensorManager = null; } } /** * Since the actual title bar is embedded in the WebView, and removing it * would change its appearance, create a temporary title bar to go at * the top of the screen while the menu is open. */ private TitleBar mFakeTitleBar; /** * Holder for the fake title bar. It will have a foreground shadow, as well * as a white background, so the fake title bar looks like the real one. */ private ViewGroup mFakeTitleBarHolder; /** * Layout parameters for the fake title bar within mFakeTitleBarHolder */ private FrameLayout.LayoutParams mFakeTitleBarParams = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); /** * 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; } } return true; } /** * Special class used exclusively for the shadow drawn underneath the fake * title bar. The shadow does not need to be drawn if the WebView * underneath is scrolled to the top, because it will draw directly on top * of the embedded shadow. */ private static class Shadow extends View { private WebView mWebView; public Shadow(Context context, AttributeSet attrs) { super(context, attrs); } public void setWebView(WebView view) { mWebView = view; } @Override public void draw(Canvas canvas) { // In general onDraw is the method to override, but we care about // whether or not the background gets drawn, which happens in draw() if (mWebView == null || mWebView.getScrollY() > getHeight()) { super.draw(canvas); } // Need to invalidate so that if the scroll position changes, we // still draw as appropriate. invalidate(); } } private void showFakeTitleBar() { final View decor = getWindow().peekDecorView(); if (mFakeTitleBar == null && mActiveTabsPage == null && !mActivityInPause && decor != null && decor.getWindowToken() != null) { Rect visRect = new Rect(); if (!mBrowserFrameLayout.getGlobalVisibleRect(visRect)) { if (LOGD_ENABLED) { Log.d(LOGTAG, "showFakeTitleBar visRect failed"); } return; } final WebView webView = getTopWindow(); mFakeTitleBar = new TitleBar(this); mFakeTitleBar.setTitleAndUrl(null, webView.getUrl()); mFakeTitleBar.setProgress(webView.getProgress()); mFakeTitleBar.setFavicon(webView.getFavicon()); updateLockIconToLatest(); 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.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); params.gravity = Gravity.TOP; WebView mainView = mTabControl.getCurrentWebView(); boolean atTop = mainView != null && mainView.getScrollY() == 0; params.windowAnimations = atTop ? 0 : com.android.internal.R.style.Animation_DropDownDown; // XXX : Without providing an offset, the fake title bar will be // placed underneath the status bar. Use the global visible rect // of mBrowserFrameLayout to determine the bottom of the status bar params.y = visRect.top; // Add a holder for the title bar. It also holds a shadow to show // below the title bar. if (mFakeTitleBarHolder == null) { mFakeTitleBarHolder = (ViewGroup) LayoutInflater.from(this) .inflate(R.layout.title_bar_bg, null); } Shadow shadow = (Shadow) mFakeTitleBarHolder.findViewById( R.id.shadow); shadow.setWebView(mainView); mFakeTitleBarHolder.addView(mFakeTitleBar, 0, mFakeTitleBarParams); manager.addView(mFakeTitleBarHolder, params); } } @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(); } } private void hideFakeTitleBar() { if (mFakeTitleBar == null) return; WindowManager.LayoutParams params = (WindowManager.LayoutParams) mFakeTitleBarHolder.getLayoutParams(); WebView mainView = mTabControl.getCurrentWebView(); // Although we decided whether or not to animate based on the current // scroll position, the scroll position may have changed since the // fake title bar was displayed. Make sure it has the appropriate // animation/lack thereof before removing. params.windowAnimations = mainView != null && mainView.getScrollY() == 0 ? 0 : com.android.internal.R.style.Animation_DropDownDown; WindowManager manager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); manager.updateViewLayout(mFakeTitleBarHolder, params); mFakeTitleBarHolder.removeView(mFakeTitleBar); manager.removeView(mFakeTitleBarHolder); mFakeTitleBar = null; } /** * Special method for the fake title bar to call when displaying its context * menu, since it is in its own Window, and its parent does not show a * context menu. */ /* package */ void showTitleBarContextMenu() { if (null == mTitleBar.getParent()) { return; } openContextMenu(mTitleBar); } /** * onSaveInstanceState(Bundle map) * onSaveInstanceState is called right before onStop(). The map contains * the saved state. */ @Override protected void onSaveInstanceState(Bundle outState) { 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); } @Override protected void 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); } // Clear the credentials toast if it is up if (mCredsDlg != null && mCredsDlg.isShowing()) { mCredsDlg.dismiss(); } mCredsDlg = null; // 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(); if (mSensorManager != null) { mSensorManager.unregisterListener(mSensorListener); } } @Override protected void onDestroy() { if (LOGV_ENABLED) { Log.v(LOGTAG, "BrowserActivity.onDestroy: this=" + this); } super.onDestroy(); if (mTabControl == null) return; // Remove the current tab and sub window TabControl.Tab t = mTabControl.getCurrentTab(); if (t != null) { dismissSubWindow(t); removeTabFromContentView(t); } // Destroy all the tabs mTabControl.destroy(); WebIconDatabase.getInstance().close(); if (mGlsConnection != null) { unbindService(mGlsConnection); mGlsConnection = null; } // // stop MASF proxy service // //Intent proxyServiceIntent = new Intent(); //proxyServiceIntent.setComponent // (new ComponentName( // "com.android.masfproxyservice", // "com.android.masfproxyservice.MasfProxyService")); //stopService(proxyServiceIntent); unregisterReceiver(mPackageInstallationReceiver); } @Override public void onConfigurationChanged(Configuration newConfig) { mConfigChanged = true; super.onConfigurationChanged(newConfig); if (mPageInfoDialog != null) { mPageInfoDialog.dismiss(); showPageInfo( mPageInfoView, mPageInfoFromShowSSLCertificateOnError.booleanValue()); } if (mSSLCertificateDialog != null) { mSSLCertificateDialog.dismiss(); showSSLCertificate( mSSLCertificateView); } if (mSSLCertificateOnErrorDialog != null) { mSSLCertificateOnErrorDialog.dismiss(); showSSLCertificateOnError( mSSLCertificateOnErrorView, mSSLCertificateOnErrorHandler, mSSLCertificateOnErrorError); } if (mHttpAuthenticationDialog != null) { String title = ((TextView) mHttpAuthenticationDialog .findViewById(com.android.internal.R.id.alertTitle)).getText() .toString(); String name = ((TextView) mHttpAuthenticationDialog .findViewById(R.id.username_edit)).getText().toString(); String password = ((TextView) mHttpAuthenticationDialog .findViewById(R.id.password_edit)).getText().toString(); int focusId = mHttpAuthenticationDialog.getCurrentFocus() .getId(); mHttpAuthenticationDialog.dismiss(); showHttpAuthentication(mHttpAuthHandler, null, null, title, name, password, focusId); } if (mFindDialog != null && mFindDialog.isShowing()) { mFindDialog.onConfigurationChanged(newConfig); } } @Override public void onLowMemory() { super.onLowMemory(); mTabControl.freeMemory(); } private boolean resumeWebViewTimers() { if ((!mActivityInPause && !mPageStarted) || (mActivityInPause && mPageStarted)) { CookieSyncManager.getInstance().startSync(); WebView w = mTabControl.getCurrentWebView(); if (w != null) { w.resumeTimers(); } return true; } else { return false; } } private boolean pauseWebViewTimers() { if (mActivityInPause && !mPageStarted) { CookieSyncManager.getInstance().stopSync(); WebView w = mTabControl.getCurrentWebView(); if (w != null) { w.pauseTimers(); } return true; } else { return false; } } // FIXME: Do we want to call this when loading google for the first time? /* * This function is called when we are launching for the first time. We * are waiting for the login credentials before loading Google home * pages. This way the user will be logged in straight away. */ private void waitForCredentials() { // Show a toast mCredsDlg = new ProgressDialog(this); mCredsDlg.setIndeterminate(true); mCredsDlg.setMessage(getText(R.string.retrieving_creds_dlg_msg)); // If the user cancels the operation, then cancel the Google // Credentials request. mCredsDlg.setCancelMessage(mHandler.obtainMessage(CANCEL_CREDS_REQUEST)); mCredsDlg.show(); // We set a timeout for the retrieval of credentials in onResume() // as that is when we have freed up some CPU time to get // the login credentials. } /* * If we have received the credentials or we have timed out and we are * showing the credentials dialog, then it is time to move on. */ private void resumeAfterCredentials() { if (mCredsDlg == null) { return; } // Clear the toast if (mCredsDlg.isShowing()) { mCredsDlg.dismiss(); } mCredsDlg = null; // Clear any pending timeout mHandler.removeMessages(CANCEL_CREDS_REQUEST); // Load the page WebView w = mTabControl.getCurrentWebView(); if (w != null) { w.loadUrl(mSettings.getHomePage()); } // Update the settings, need to do this last as it can take a moment // to persist the settings. In the mean time we could be loading // content. mSettings.setLoginInitialized(this); } // 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()); try { Cursor c = Browser.getAllBookmarks(mResolver); if (!c.moveToFirst()) { c.deactivate(); return; } int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL); do { String url = c.getString(urlIndex); db.retainIconForPageUrl(url); } while (c.moveToNext()); c.deactivate(); } catch (IllegalStateException e) { Log.e(LOGTAG, "retainIconsOnStartup", e); } } // Helper method for getting the top window. WebView getTopWindow() { return mTabControl.getCurrentTopWebView(); } @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 src = mInLoad ? mMenu.findItem(R.id.stop_menu_id): mMenu.findItem(R.id.reload_menu_id); MenuItem dest = mMenu.findItem(R.id.stop_reload_menu_id); 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(); switch (id) { // For the context menu from the title bar case R.id.title_bar_share_page_url: case R.id.title_bar_copy_page_url: WebView mainView = mTabControl.getCurrentWebView(); if (null == mainView) { return false; } if (id == R.id.title_bar_share_page_url) { Browser.sendString(this, mainView.getUrl()); } else { copy(mainView.getUrl()); } break; // -- Browser context menu case R.id.open_context_menu_id: case R.id.open_newtab_context_menu_id: case R.id.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) { return false; } final HashMap hrefMap = new HashMap(); hrefMap.put("webview", webView); final Message msg = mHandler.obtainMessage( FOCUS_NODE_HREF, id, 0, hrefMap); webView.requestFocusNodeHref(msg); break; default: // For other context menus return onOptionsItemSelected(item); } mCanChord = false; return true; } private Bundle createGoogleSearchSourceBundle(String source) { Bundle bundle = new Bundle(); bundle.putString(SearchManager.SOURCE, source); return bundle; } /** * Overriding this to insert a local information bundle */ @Override public boolean onSearchRequested() { if (mOptionsMenuOpen) closeOptionsMenu(); String url = (getTopWindow() == null) ? null : getTopWindow().getUrl(); startSearch(mSettings.getHomePage().equals(url) ? null : url, true, createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_SEARCHKEY), false); return true; } @Override public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, boolean globalSearch) { if (appSearchData == null) { appSearchData = createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_TYPE); } 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) { TabControl.Tab tab = mTabControl.getTab(index); TabControl.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 */ TabControl.Tab openTabToHomePage() { return openTabAndShow(mSettings.getHomePage(), false, null); } /* package */ void closeCurrentWindow() { final TabControl.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. TabControl.Tab newTab = openTabToHomePage(); closeTab(current); return; } final TabControl.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); mActiveTabsPage = null; mMenuState = R.id.MAIN_MENU; if (needToAttach) { attachTabToContentView(mTabControl.getCurrentTab()); } getTopWindow().requestFocus(); } @Override public boolean onOptionsItemSelected(MenuItem item) { 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.goto_menu_id: onSearchRequested(); 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()); hideFakeTitleBar(); mContentView.addView(mActiveTabsPage, COVER_SCREEN_PARAMS); mActiveTabsPage.requestFocus(); mMenuState = EMPTY_MENU; break; case R.id.add_bookmark_menu_id: Intent i = new Intent(BrowserActivity.this, AddBookmarkPage.class); WebView w = getTopWindow(); i.putExtra("url", w.getUrl()); i.putExtra("title", w.getTitle()); i.putExtra("touch_icon_url", w.getTouchIconUrl()); i.putExtra("thumbnail", createScreenshot(w)); startActivity(i); break; 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: TabControl.Tab current = mTabControl.getCurrentTab(); if (current != null) { dismissSubWindow(current); current.getWebView().loadUrl(mSettings.getHomePage()); } break; case R.id.preferences_menu_id: Intent intent = new Intent(this, BrowserPreferencesPage.class); startActivityForResult(intent, PREFERENCES_PAGE); break; case R.id.find_menu_id: if (null == mFindDialog) { mFindDialog = new FindDialog(this); } mFindDialog.setWebView(getTopWindow()); mFindDialog.show(); mMenuState = EMPTY_MENU; break; case R.id.select_text_id: getTopWindow().emulateShiftHeld(); 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.share_page_menu_id: Browser.sendString(this, getTopWindow().getUrl(), getText(R.string.choosertitle_sharevia).toString()); break; case R.id.dump_nav_menu_id: getTopWindow().debugDump(); 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(null); 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) { TabControl.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. } mCanChord = false; return true; } public void closeFind() { mMenuState = R.id.MAIN_MENU; } @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); menu.findItem(R.id.forward_menu_id) .setEnabled(canGoForward); menu.findItem(R.id.new_tab_menu_id).setEnabled( mTabControl.getTabCount() < TabControl.MAX_TABS); // 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); break; } mCurrentMenuState = mMenuState; return true; } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { 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 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 menu.findItem(R.id.open_newtab_context_menu_id).setVisible( mTabControl.getTabCount() < TabControl.MAX_TABS); 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)); break; default: Log.w(LOGTAG, "We should not get here."); break; } } // Attach the given tab to the content view. // this should only be called for the current tab. private void attachTabToContentView(TabControl.Tab t) { // Attach the container that contains the main WebView and any other UI // associated with the tab. t.attachTabToContentView(mContentView); if (mShouldShowErrorConsole) { ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true); if (errorConsole.numberOfErrors() == 0) { errorConsole.showConsole(ErrorConsoleView.SHOW_NONE); } else { errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED); } mErrorConsoleContainer.addView(errorConsole, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); } setLockIconType(t.getLockIconType()); setPrevLockType(t.getPrevLockIconType()); // this is to match the code in removeTabFromContentView() if (!mPageStarted && t.getTopWindow().getProgress() < 100) { mPageStarted = true; } WebView view = t.getWebView(); view.setEmbeddedTitleBar(mTitleBar); // Request focus on the top window. t.getTopWindow().requestFocus(); } // Attach a sub window to the main WebView of the given tab. private void attachSubWindow(TabControl.Tab t) { t.attachSubWindow(mContentView); getTopWindow().requestFocus(); } // Remove the given tab from the content view. private void removeTabFromContentView(TabControl.Tab t) { // Remove the container that contains the main WebView. t.removeTabFromContentView(mContentView); if (mTabControl.getCurrentErrorConsole(false) != null) { mErrorConsoleContainer.removeView(mTabControl.getCurrentErrorConsole(false)); } WebView view = t.getWebView(); if (view != null) { view.setEmbeddedTitleBar(null); } // unlike attachTabToContentView(), removeTabFromContentView() can be // called for the non-current tab. Need to add the check. if (t == mTabControl.getCurrentTab()) { t.setLockIconType(getLockIconType()); t.setPrevLockIconType(getPrevLockType()); // this is not a perfect solution. But currently there is one // WebViewClient for all the WebView. if user switches from an // in-load window to an already loaded window, mPageStarted will not // be set to false. If user leaves the Browser, pauseWebViewTimers() // won't do anything and leaves the timer running even Browser is in // the background. if (mPageStarted) { mPageStarted = false; } } } // 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(TabControl.Tab t) { t.removeSubWindow(mContentView); // Tell the TabControl to dismiss the subwindow. This will destroy // the WebView. mTabControl.dismissSubWindow(t); getTopWindow().requestFocus(); } // A wrapper function of {@link #openTabAndShow(UrlData, boolean, String)} // that accepts url as string. private TabControl.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 */ TabControl.Tab openTabAndShow(UrlData urlData, boolean closeOnExit, String appId) { final boolean newTab = mTabControl.getTabCount() != TabControl.MAX_TABS; final TabControl.Tab currentTab = mTabControl.getCurrentTab(); if (newTab) { final TabControl.Tab tab = mTabControl.createNewTab( closeOnExit, appId, urlData.mUrl); 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()) { urlData.loadIn(webview); } return tab; } else { // Get rid of the subwindow if it exists dismissSubWindow(currentTab); if (!urlData.isEmpty()) { // Load the given url. urlData.loadIn(currentTab.getWebView()); } } return currentTab; } private TabControl.Tab openTab(String url) { if (mSettings.openInBackground()) { TabControl.Tab t = mTabControl.createNewTab(); if (t != null) { WebView view = t.getWebView(); view.loadUrl(url); } return t; } else { return openTabAndShow(url, false, 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 void copy(CharSequence text) { try { IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard")); if (clip != null) { clip.setClipboardText(text); } } catch (android.os.RemoteException e) { Log.e(LOGTAG, "Copy failed", e); } } /** * 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() { revertLockIcon(); resetTitleIconAndProgress(); } /** * Reset the title, favicon, and progress. */ private void resetTitleIconAndProgress() { WebView current = mTabControl.getCurrentWebView(); if (current == null) { return; } resetTitleAndIcon(current); int progress = current.getProgress(); mWebChromeClient.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. */ private void setUrlTitle(String url, String title) { mUrl = url; mTitle = title; mTitleBar.setTitleAndUrl(title, url); if (mFakeTitleBar != null) { mFakeTitleBar.setTitleAndUrl(title, 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. private void setFavicon(Bitmap icon) { mTitleBar.setFavicon(icon); if (mFakeTitleBar != null) { mFakeTitleBar.setFavicon(icon); } } /** * Saves the current lock-icon state before resetting * the lock icon. If we have an error, we may need to * roll back to the previous state. */ private void saveLockIcon() { mPrevLockType = mLockIconType; } /** * Reverts the lock-icon state to the last saved state, * for example, if we had an error, and need to cancel * the load. */ private void revertLockIcon() { mLockIconType = mPrevLockType; if (LOGV_ENABLED) { Log.v(LOGTAG, "BrowserActivity.revertLockIcon:" + " revert lock icon to " + mLockIconType); } updateLockIconToLatest(); } /** * Close the tab, remove its associated title bar, and adjust mTabControl's * current tab to a valid value. */ /* package */ void closeTab(TabControl.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(); } private void goBackOnePageOrQuit() { TabControl.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. TabControl.Tab parent = current.getParentTab(); if (parent != null) { switchToTab(mTabControl.getTabIndex(parent)); // Now we close the other tab closeTab(current); } else { if (current.closeOnExit()) { // force mPageStarted to be false as we are going to either // finish the activity or remove the tab. This will ensure // pauseWebView() taking action. mPageStarted = false; 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); } } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is // still down, we don't want to trigger the search. Pretend to consume // the key and do nothing. if (mMenuIsDown) return true; switch(keyCode) { case KeyEvent.KEYCODE_MENU: mMenuIsDown = true; break; case KeyEvent.KEYCODE_SPACE: // WebView/WebTextView handle the keys in the KeyDown. As // the Activity's shortcut keys are only handled when WebView // doesn't, have to do it in onKeyDown instead of onKeyUp. if (event.isShiftPressed()) { getTopWindow().pageUp(false); } else { getTopWindow().pageDown(false); } return true; case KeyEvent.KEYCODE_BACK: if (event.getRepeatCount() == 0) { event.startTracking(); return true; } else if (mCustomView == null && mActiveTabsPage == null && event.isLongPress()) { bookmarksOrHistoryPicker(true); return true; } break; } return 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 mWebChromeClient.onHideCustomView(); } else if (mActiveTabsPage != null) { // if tab page is showing, hide it removeActiveTabPage(true); } 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(); mWebViewClient.onPageFinished(w, w.getUrl()); cancelStopToast(); mStopToast = Toast .makeText(this, R.string.stopping, Toast.LENGTH_SHORT); mStopToast.show(); } private void cancelStopToast() { if (mStopToast != null) { mStopToast.cancel(); mStopToast = null; } } // called by a non-UI thread to post the message public void postMessage(int what, int arg1, int arg2, Object obj) { mHandler.sendMessage(mHandler.obtainMessage(what, arg1, arg2, obj)); } // 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 CANCEL_CREDS_REQUEST = 103; private static final int RELEASE_WAKELOCK = 107; private static final int UPDATE_BOOKMARK_THUMBNAIL = 108; // Private handler for handling javascript and saving passwords private Handler mHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case FOCUS_NODE_HREF: { String url = (String) msg.getData().get("url"); if (url == null || url.length() == 0) { break; } 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: loadURL(getTopWindow(), url); break; case R.id.open_newtab_context_menu_id: final TabControl.Tab parent = mTabControl .getCurrentTab(); final TabControl.Tab newTab = openTab(url); if (newTab != parent) { parent.addChildTab(newTab); } break; case R.id.bookmark_context_menu_id: Intent intent = new Intent(BrowserActivity.this, AddBookmarkPage.class); intent.putExtra("url", url); startActivity(intent); break; case R.id.share_link_context_menu_id: Browser.sendString(BrowserActivity.this, url, getText(R.string.choosertitle_sharevia).toString()); 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: loadURL(getTopWindow(), (String) msg.obj); break; case STOP_LOAD: stopLoading(); break; case CANCEL_CREDS_REQUEST: resumeAfterCredentials(); break; case RELEASE_WAKELOCK: if (mWakeLock.isHeld()) { mWakeLock.release(); } break; case UPDATE_BOOKMARK_THUMBNAIL: WebView view = (WebView) msg.obj; if (view != null) { updateScreenshot(view); } break; } } }; 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. ContentResolver cr = getContentResolver(); final Cursor c = BrowserBookmarksAdapter.queryBookmarksForUrl( cr, view.getOriginalUrl(), view.getUrl(), true); if (c != null) { boolean succeed = c.moveToFirst(); ContentValues values = null; while (succeed) { if (values == null) { final ByteArrayOutputStream os = new ByteArrayOutputStream(); Bitmap bm = createScreenshot(view); if (bm == null) { c.close(); return; } bm.compress(Bitmap.CompressFormat.PNG, 100, os); values = new ContentValues(); values.put(Browser.BookmarkColumns.THUMBNAIL, os.toByteArray()); } cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI, c.getInt(0)), values, null, null); succeed = c.moveToNext(); } c.close(); } } /** * Values for the size of the thumbnail created when taking a screenshot. * Lazily initialized. Instead of using these directly, use * getDesiredThumbnailWidth() or getDesiredThumbnailHeight(). */ private static int THUMBNAIL_WIDTH = 0; private static int THUMBNAIL_HEIGHT = 0; /** * Return the desired width for thumbnail screenshots, which are stored in * the database, and used on the bookmarks screen. * @param context Context for finding out the density of the screen. * @return int desired width for thumbnail screenshot. */ /* package */ static int getDesiredThumbnailWidth(Context context) { if (THUMBNAIL_WIDTH == 0) { float density = context.getResources().getDisplayMetrics().density; THUMBNAIL_WIDTH = (int) (90 * density); THUMBNAIL_HEIGHT = (int) (80 * density); } return THUMBNAIL_WIDTH; } /** * Return the desired height for thumbnail screenshots, which are stored in * the database, and used on the bookmarks screen. * @param context Context for finding out the density of the screen. * @return int desired height for thumbnail screenshot. */ /* package */ static int getDesiredThumbnailHeight(Context context) { // To ensure that they are both initialized. getDesiredThumbnailWidth(context); return THUMBNAIL_HEIGHT; } private Bitmap createScreenshot(WebView view) { Picture thumbnail = view.capturePicture(); if (thumbnail == null) { return null; } Bitmap bm = Bitmap.createBitmap(getDesiredThumbnailWidth(this), getDesiredThumbnailHeight(this), Bitmap.Config.ARGB_4444); Canvas canvas = new Canvas(bm); // May need to tweak these values to determine what is the // best scale factor int thumbnailWidth = thumbnail.getWidth(); if (thumbnailWidth > 0) { float scaleFactor = (float) getDesiredThumbnailWidth(this) / (float)thumbnailWidth; canvas.scale(scaleFactor, scaleFactor); } thumbnail.draw(canvas); return bm; } // ------------------------------------------------------------------------- // WebViewClient implementation. //------------------------------------------------------------------------- // 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;"; /* package */ WebViewClient getWebViewClient() { return mWebViewClient; } private void updateIcon(WebView view, Bitmap icon) { if (icon != null) { BrowserBookmarksAdapter.updateBookmarkFavicon(mResolver, view.getOriginalUrl(), view.getUrl(), icon); } setFavicon(icon); } private void updateIcon(String url, Bitmap icon) { if (icon != null) { BrowserBookmarksAdapter.updateBookmarkFavicon(mResolver, null, url, icon); } setFavicon(icon); } private final WebViewClient mWebViewClient = new WebViewClient() { @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { resetLockIcon(url); setUrlTitle(url, null); // We've started to load a new page. If there was a pending message // to save a screenshot then we will now take the new page and // save an incorrect screenshot. Therefore, remove any pending // thumbnail messages from the queue. mHandler.removeMessages(UPDATE_BOOKMARK_THUMBNAIL); // If we start a touch icon load and then load a new page, we don't // want to cancel the current touch icon loader. But, we do want to // create a new one when the touch icon url is known. if (mTouchIconLoader != null) { mTouchIconLoader.mActivity = null; mTouchIconLoader = null; } ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(false); if (errorConsole != null) { errorConsole.clearErrorMessages(); if (mShouldShowErrorConsole) { errorConsole.showConsole(ErrorConsoleView.SHOW_NONE); } } // Call updateIcon instead of setFavicon so the bookmark // database can be updated. updateIcon(url, favicon); if (mSettings.isTracing()) { String host; try { WebAddress uri = new WebAddress(url); host = uri.mHost; } 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(); } if (!mPageStarted) { mPageStarted = true; // if onResume() has been called, resumeWebViewTimers() does // nothing. resumeWebViewTimers(); } // reset sync timer to avoid sync starts during loading a page CookieSyncManager.getInstance().resetSync(); mInLoad = true; mDidStopLoad = false; showFakeTitleBar(); updateInLoadMenuItems(); if (!mIsNetworkUp) { createAndShowNetworkDialog(); if (view != null) { view.setNetworkAvailable(false); } } } @Override public void onPageFinished(WebView view, String url) { // Reset the title and icon in case we stopped a provisional // load. resetTitleAndIcon(view); if (!mDidStopLoad) { // Only update the bookmark screenshot if the user did not // cancel the load early. Message updateScreenshot = Message.obtain(mHandler, UPDATE_BOOKMARK_THUMBNAIL, view); mHandler.sendMessageDelayed(updateScreenshot, 500); } // Update the lock icon image only once we are done loading updateLockIconToLatest(); // 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 (sWhiteList.contains(newUrl)) { // The string that gets pushed to the statistcs // service performanceString = performanceString + "\nWebpage: " + newUrl + "\nCarrier: " + android.os.SystemProperties .get("gsm.sim.operator.alpha"); if (mWebView != null && mWebView.getContext() != null && mWebView.getContext().getSystemService( Context.CONNECTIVITY_SERVICE) != null) { ConnectivityManager cManager = (ConnectivityManager) mWebView .getContext().getSystemService( Context.CONNECTIVITY_SERVICE); NetworkInfo nInfo = cManager .getActiveNetworkInfo(); if (nInfo != null) { performanceString = performanceString + "\nNetwork Type: " + nInfo.getType().toString(); } } Checkin.logEvent(mResolver, Checkin.Events.Tag.WEBPAGE_LOAD, performanceString); Log.w(LOGTAG, "pushed to the statistics service"); } */ } } } if (mInTrace) { mInTrace = false; Debug.stopMethodTracing(); } if (mPageStarted) { mPageStarted = false; // pauseWebViewTimers() will do nothing and return false if // onPause() is not called yet. if (pauseWebViewTimers()) { if (mWakeLock.isHeld()) { mHandler.removeMessages(RELEASE_WAKELOCK); mWakeLock.release(); } } } } // return true if want to hijack the url to let another app to handle it @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { 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); 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; } 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); 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)) { 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); closeOptionsMenu(); return true; } return false; } /** * Updates the lock icon. This method is called when we discover another * resource to be loaded for this page (for example, javascript). While * we update the icon type, we do not update the lock icon itself until * we are done loading, it is slightly more secure this way. */ @Override public void onLoadResource(WebView view, String url) { 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 == 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 = LOCK_ICON_MIXED; if (LOGV_ENABLED) { Log.v(LOGTAG, "BrowserActivity.updateLockIcon:" + " updated lock icon to " + mLockIconType + " due to " + url); } } } } } /** * Show the dialog, asking the user if they would like to continue after * an excessive number of HTTP redirects. */ @Override public void onTooManyRedirects(WebView view, final Message cancelMsg, final Message continueMsg) { new AlertDialog.Builder(BrowserActivity.this) .setTitle(R.string.browserFrameRedirect) .setMessage(R.string.browserFrame307Post) .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { continueMsg.sendToTarget(); }}) .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { cancelMsg.sendToTarget(); }}) .setOnCancelListener(new OnCancelListener() { public void onCancel(DialogInterface dialog) { cancelMsg.sendToTarget(); }}) .show(); } // Container class for the next error dialog that needs to be // displayed. class ErrorDialog { public final int mTitle; public final String mDescription; public final int mError; ErrorDialog(int title, String desc, int error) { mTitle = title; mDescription = desc; mError = error; } }; private void processNextError() { if (mQueuedErrors == null) { return; } // The first one is currently displayed so just remove it. mQueuedErrors.removeFirst(); if (mQueuedErrors.size() == 0) { mQueuedErrors = null; return; } showError(mQueuedErrors.getFirst()); } private DialogInterface.OnDismissListener mDialogListener = new DialogInterface.OnDismissListener() { public void onDismiss(DialogInterface d) { processNextError(); } }; private LinkedList mQueuedErrors; private void queueError(int err, String desc) { if (mQueuedErrors == null) { mQueuedErrors = new LinkedList(); } for (ErrorDialog d : mQueuedErrors) { if (d.mError == err) { // Already saw a similar error, ignore the new one. return; } } ErrorDialog errDialog = new ErrorDialog( err == WebViewClient.ERROR_FILE_NOT_FOUND ? R.string.browserFrameFileErrorLabel : R.string.browserFrameNetworkErrorLabel, desc, err); mQueuedErrors.addLast(errDialog); // Show the dialog now if the queue was empty. if (mQueuedErrors.size() == 1) { showError(errDialog); } } private void showError(ErrorDialog errDialog) { AlertDialog d = new AlertDialog.Builder(BrowserActivity.this) .setTitle(errDialog.mTitle) .setMessage(errDialog.mDescription) .setPositiveButton(R.string.ok, null) .create(); d.setOnDismissListener(mDialogListener); d.show(); } /** * Show a dialog informing the user of the network error reported by * WebCore. */ @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { if (errorCode != WebViewClient.ERROR_HOST_LOOKUP && errorCode != WebViewClient.ERROR_CONNECT && errorCode != WebViewClient.ERROR_BAD_URL && errorCode != WebViewClient.ERROR_UNSUPPORTED_SCHEME && errorCode != WebViewClient.ERROR_FILE) { queueError(errorCode, description); } Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl + " " + description); // We need to reset the title after an error. resetTitleAndRevertLockIcon(); } /** * Check with the user if it is ok to resend POST data as the page they * are trying to navigate to is the result of a POST. */ @Override public void onFormResubmission(WebView view, final Message dontResend, final Message resend) { new AlertDialog.Builder(BrowserActivity.this) .setTitle(R.string.browserFrameFormResubmitLabel) .setMessage(R.string.browserFrameFormResubmitMessage) .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { resend.sendToTarget(); }}) .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dontResend.sendToTarget(); }}) .setOnCancelListener(new OnCancelListener() { public void onCancel(DialogInterface dialog) { dontResend.sendToTarget(); }}) .show(); } /** * Insert the url into the visited history database. * @param url The url to be inserted. * @param isReload True if this url is being reloaded. * FIXME: Not sure what to do when reloading the page. */ @Override public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) { 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-1).concat(url.substring(end)); } else { url = url.substring(0, index-1); } } Browser.updateVisitedHistory(mResolver, url, true); WebIconDatabase.getInstance().retainIconForPageUrl(url); } /** * Displays SSL error(s) dialog to the user. */ @Override public void onReceivedSslError( final WebView view, final SslErrorHandler handler, final SslError error) { if (mSettings.showSecurityWarnings()) { final LayoutInflater factory = LayoutInflater.from(BrowserActivity.this); final View warningsView = factory.inflate(R.layout.ssl_warnings, null); final LinearLayout placeholder = (LinearLayout)warningsView.findViewById(R.id.placeholder); if (error.hasError(SslError.SSL_UNTRUSTED)) { LinearLayout ll = (LinearLayout)factory .inflate(R.layout.ssl_warning, null); ((TextView)ll.findViewById(R.id.warning)) .setText(R.string.ssl_untrusted); placeholder.addView(ll); } if (error.hasError(SslError.SSL_IDMISMATCH)) { LinearLayout ll = (LinearLayout)factory .inflate(R.layout.ssl_warning, null); ((TextView)ll.findViewById(R.id.warning)) .setText(R.string.ssl_mismatch); placeholder.addView(ll); } if (error.hasError(SslError.SSL_EXPIRED)) { LinearLayout ll = (LinearLayout)factory .inflate(R.layout.ssl_warning, null); ((TextView)ll.findViewById(R.id.warning)) .setText(R.string.ssl_expired); placeholder.addView(ll); } if (error.hasError(SslError.SSL_NOTYETVALID)) { LinearLayout ll = (LinearLayout)factory .inflate(R.layout.ssl_warning, null); ((TextView)ll.findViewById(R.id.warning)) .setText(R.string.ssl_not_yet_valid); placeholder.addView(ll); } new AlertDialog.Builder(BrowserActivity.this) .setTitle(R.string.security_warning) .setIcon(android.R.drawable.ic_dialog_alert) .setView(warningsView) .setPositiveButton(R.string.ssl_continue, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { handler.proceed(); } }) .setNeutralButton(R.string.view_certificate, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { showSSLCertificateOnError(view, handler, error); } }) .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { handler.cancel(); BrowserActivity.this.resetTitleAndRevertLockIcon(); } }) .setOnCancelListener( new DialogInterface.OnCancelListener() { public void onCancel(DialogInterface dialog) { handler.cancel(); BrowserActivity.this.resetTitleAndRevertLockIcon(); } }) .show(); } else { handler.proceed(); } } /** * Handles an HTTP authentication request. * * @param handler The authentication handler * @param host The host * @param realm The realm */ @Override 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 && (mTabControl.getCurrentWebView() != null)) { String[] credentials = mTabControl.getCurrentWebView() .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 { showHttpAuthentication(handler, host, realm, null, null, null, 0); } } @Override public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) { if (mMenuIsDown) { // only check shortcut key when MENU is held return getWindow().isShortcutKey(event.getKeyCode(), event); } else { return false; } } @Override public void onUnhandledKeyEvent(WebView view, KeyEvent event) { if (view != mTabControl.getCurrentTopWebView()) { return; } if (event.isDown()) { BrowserActivity.this.onKeyDown(event.getKeyCode(), event); } else { BrowserActivity.this.onKeyUp(event.getKeyCode(), event); } } }; //-------------------------------------------------------------------------- // WebChromeClient implementation //-------------------------------------------------------------------------- /* package */ WebChromeClient getWebChromeClient() { return mWebChromeClient; } private final WebChromeClient mWebChromeClient = new WebChromeClient() { // Helper method to create a new tab or sub window. private void createWindow(final boolean dialog, final Message msg) { if (dialog) { mTabControl.createSubWindow(); final TabControl.Tab t = mTabControl.getCurrentTab(); attachSubWindow(t); WebView.WebViewTransport transport = (WebView.WebViewTransport) msg.obj; transport.setWebView(t.getSubWebView()); msg.sendToTarget(); } else { final TabControl.Tab parent = mTabControl.getCurrentTab(); final TabControl.Tab newTab = openTabAndShow(EMPTY_URL_DATA, false, null); if (newTab != parent) { parent.addChildTab(newTab); } WebView.WebViewTransport transport = (WebView.WebViewTransport) msg.obj; transport.setWebView(mTabControl.getCurrentWebView()); msg.sendToTarget(); } } @Override public boolean onCreateWindow(WebView view, final boolean dialog, final boolean userGesture, final Message resultMsg) { // Short-circuit if we can't create any more tabs or sub windows. if (dialog && mTabControl.getCurrentSubWindow() != null) { new AlertDialog.Builder(BrowserActivity.this) .setTitle(R.string.too_many_subwindows_dialog_title) .setIcon(android.R.drawable.ic_dialog_alert) .setMessage(R.string.too_many_subwindows_dialog_message) .setPositiveButton(R.string.ok, null) .show(); return false; } else if (mTabControl.getTabCount() >= TabControl.MAX_TABS) { new AlertDialog.Builder(BrowserActivity.this) .setTitle(R.string.too_many_windows_dialog_title) .setIcon(android.R.drawable.ic_dialog_alert) .setMessage(R.string.too_many_windows_dialog_message) .setPositiveButton(R.string.ok, null) .show(); return false; } // Short-circuit if this was a user gesture. if (userGesture) { createWindow(dialog, resultMsg); return true; } // Allow the popup and create the appropriate window. final AlertDialog.OnClickListener allowListener = new AlertDialog.OnClickListener() { public void onClick(DialogInterface d, int which) { createWindow(dialog, resultMsg); } }; // Block the popup by returning a null WebView. final AlertDialog.OnClickListener blockListener = new AlertDialog.OnClickListener() { public void onClick(DialogInterface d, int which) { resultMsg.sendToTarget(); } }; // Build a confirmation dialog to display to the user. final AlertDialog d = new AlertDialog.Builder(BrowserActivity.this) .setTitle(R.string.attention) .setIcon(android.R.drawable.ic_dialog_alert) .setMessage(R.string.popup_window_attempt) .setPositiveButton(R.string.allow, allowListener) .setNegativeButton(R.string.block, blockListener) .setCancelable(false) .create(); // Show the confirmation dialog. d.show(); return true; } @Override public void onCloseWindow(WebView window) { final TabControl.Tab current = mTabControl.getCurrentTab(); final TabControl.Tab parent = current.getParentTab(); if (parent != null) { // JavaScript can only close popup window. switchToTab(mTabControl.getTabIndex(parent)); // Now we need to close the window closeTab(current); } } @Override public void onProgressChanged(WebView view, int newProgress) { mTitleBar.setProgress(newProgress); if (mFakeTitleBar != null) { 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) // sync cookies and cache promptly here. CookieSyncManager.getInstance().sync(); 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(); if (!mOptionsMenuOpen || mIconView) { // This page has begun to load, so show the title bar showFakeTitleBar(); } } } @Override public void onReceivedTitle(WebView view, String title) { String url = view.getUrl(); // here, if url is null, we want to reset the title setUrlTitle(url, title); if (url == null || url.length() >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) { return; } // See if we can find the current url in our history database and // add the new title to it. if (url.startsWith("http://www.")) { url = url.substring(11); } else if (url.startsWith("http://")) { url = url.substring(4); } try { url = "%" + url; String [] selArgs = new String[] { url }; String where = Browser.BookmarkColumns.URL + " LIKE ? AND " + Browser.BookmarkColumns.BOOKMARK + " = 0"; Cursor c = mResolver.query(Browser.BOOKMARKS_URI, Browser.HISTORY_PROJECTION, where, selArgs, null); if (c.moveToFirst()) { // Current implementation of database only has one entry per // url. ContentValues map = new ContentValues(); map.put(Browser.BookmarkColumns.TITLE, title); mResolver.update(Browser.BOOKMARKS_URI, map, "_id = " + c.getInt(0), null); } c.close(); } catch (IllegalStateException e) { Log.e(LOGTAG, "BrowserActivity onReceived title", e); } catch (SQLiteException ex) { Log.e(LOGTAG, "onReceivedTitle() caught SQLiteException: ", ex); } } @Override public void onReceivedIcon(WebView view, Bitmap icon) { updateIcon(view, icon); } @Override public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) { final ContentResolver cr = getContentResolver(); final Cursor c = BrowserBookmarksAdapter.queryBookmarksForUrl(cr, view.getOriginalUrl(), view.getUrl(), true); if (c != null) { if (c.getCount() > 0) { // Let precomposed icons take precedence over non-composed // icons. if (precomposed && mTouchIconLoader != null) { mTouchIconLoader.cancel(false); mTouchIconLoader = null; } // Have only one async task at a time. if (mTouchIconLoader == null) { mTouchIconLoader = new DownloadTouchIcon( BrowserActivity.this, cr, c, view); mTouchIconLoader.execute(url); } } else { c.close(); } } } @Override public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) { if (mCustomView != null) 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. 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; // Reset the old menu state. mMenuState = mOldMenuState; mOldMenuState = EMPTY_MENU; mCustomViewContainer.setVisibility(View.GONE); mCustomViewCallback.onCustomViewHidden(); // Show the content view. mContentView.setVisibility(View.VISIBLE); } /** * The origin has exceeded its database quota. * @param url the URL that exceeded the quota * @param databaseIdentifier the identifier of the database on * which the transaction that caused the quota overflow was run * @param currentQuota the current quota for the origin. * @param estimatedSize the estimated size of the database. * @param totalUsedQuota is the sum of all origins' quota. * @param quotaUpdater The callback to run when a decision to allow or * deny quota has been made. Don't forget to call this! */ @Override public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize, long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) { mSettings.getWebStorageSizeManager().onExceededDatabaseQuota( url, databaseIdentifier, currentQuota, estimatedSize, totalUsedQuota, quotaUpdater); } /** * The Application Cache has exceeded its max size. * @param spaceNeeded is the amount of disk space that would be needed * in order for the last appcache operation to succeed. * @param totalUsedQuota is the sum of all origins' quota. * @param quotaUpdater A callback to inform the WebCore thread that a new * app cache size is available. This callback must always be executed at * some point to ensure that the sleeping WebCore thread is woken up. */ @Override public void onReachedMaxAppCacheSize(long spaceNeeded, long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) { mSettings.getWebStorageSizeManager().onReachedMaxAppCacheSize( spaceNeeded, totalUsedQuota, quotaUpdater); } /** * Instructs the browser to show a prompt to ask the user to set the * Geolocation permission state for the specified origin. * @param origin The origin for which Geolocation permissions are * requested. * @param callback The callback to call once the user has set the * Geolocation permission state. */ @Override public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) { mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().show( origin, callback); } /** * Instructs the browser to hide the Geolocation permissions prompt. */ @Override public void onGeolocationPermissionsHidePrompt() { mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().hide(); } /* Adds a JavaScript error message to the system log. * @param message The error message to report. * @param lineNumber The line number of the error. * @param sourceID The name of the source file that caused the error. */ @Override public void addMessageToConsole(String message, int lineNumber, String sourceID) { ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true); errorConsole.addErrorMessage(message, sourceID, lineNumber); if (mShouldShowErrorConsole && errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) { errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED); } Log.w(LOGTAG, "Console: " + message + " " + sourceID + ":" + lineNumber); } /** * Ask the browser for an icon to represent a