/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package com.linkbubble.webrender; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.IntentFilter; import android.graphics.Bitmap; import android.net.ConnectivityManager; import android.net.http.SslError; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.webkit.ConsoleMessage; import android.webkit.CookieManager; import android.webkit.DownloadListener; import android.webkit.GeolocationPermissions; import android.webkit.JsPromptResult; import android.webkit.JsResult; import android.webkit.SslErrorHandler; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebResourceRequest; import android.webkit.WebResourceResponse; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.EditText; import android.widget.TextView; import com.linkbubble.Constant; import com.linkbubble.MainApplication; import com.linkbubble.MainController; import com.linkbubble.R; import com.linkbubble.Settings; import com.linkbubble.articlerender.ArticleContent; import com.linkbubble.ui.TabView; import com.linkbubble.util.Analytics; import com.linkbubble.util.CrashTracking; import com.linkbubble.util.NetworkConnectivity; import com.linkbubble.util.NetworkReceiver; import com.linkbubble.util.PageInspector; import com.linkbubble.util.Util; import com.linkbubble.util.YouTubeEmbedHelper; import com.squareup.otto.Subscribe; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.Map; class WebViewRenderer extends WebRenderer { protected String TAG; private Handler mHandler; protected CustomWebView mWebView; private View mTouchInterceptorView; private long mLastWebViewTouchUpTime = -1; private String mLastWebViewTouchDownUrl; private String mHost; private AlertDialog mJsAlertDialog; private AlertDialog mJsConfirmDialog; private AlertDialog mJsPromptDialog; private PageInspector mPageInspector; private int mCheckForEmbedsCount; private int mRunPageScripts = 0; private int mCurrentProgress; private boolean mPauseOnComplete; private Boolean mIsDestroyed = false; private boolean mRegisteredForBus; private boolean mTrackingProtectionEnabled = false; private boolean mAdblockEnabled = false; private boolean mHttpsEverywhereEnabled = false; private ArticleContent.BuildContentTask mBuildArticleContentTask; private ArticleContent mArticleContent; private static NetworkReceiver mLastNetworkReceiver = null; public WebViewRenderer(Context context, Controller controller, View webRendererPlaceholder, String tag) { super(context, controller, webRendererPlaceholder); mHandler = new Handler(); TAG = tag; mWebView = new CustomWebView(mContext); mWebView.setLayoutParams(webRendererPlaceholder.getLayoutParams()); Util.replaceViewAtPosition(webRendererPlaceholder, mWebView); mTouchInterceptorView = new View(mContext); mTouchInterceptorView.setLayoutParams(webRendererPlaceholder.getLayoutParams()); mTouchInterceptorView.setWillNotDraw(true); mTouchInterceptorView.setOnTouchListener(mWebViewOnTouchListener); ViewGroup parent = (ViewGroup)mWebView.getParent(); int index = parent.indexOfChild(mWebView); parent.addView(mTouchInterceptorView, index + 1); mWebView.setLongClickable(true); mWebView.setWebChromeClient(mWebChromeClient); mWebView.setWebViewClient(mWebViewClient); mWebView.setDownloadListener(mDownloadListener); mWebView.setOnLongClickListener(mOnWebViewLongClickListener); mWebView.setOnKeyListener(mOnKeyListener); mWebView.setOnScrollChangedCallback(mOnScrollChangedCallback); WebSettings webSettings = mWebView.getSettings(); webSettings.setJavaScriptEnabled(true); webSettings.setDomStorageEnabled(true); webSettings.setGeolocationEnabled(true); webSettings.setSupportZoom(true); webSettings.setTextZoom(Settings.get().getWebViewTextZoom()); webSettings.setBuiltInZoomControls(true); webSettings.setDisplayZoomControls(false); webSettings.setLoadWithOverviewMode(true); webSettings.setUseWideViewPort(true); webSettings.setSupportMultipleWindows(true); webSettings.setGeolocationDatabasePath(Constant.WEBVIEW_DATABASE_LOCATION); webSettings.setSavePassword(false); String userAgentString = Settings.get().getUserAgentString(); if (userAgentString != null) { webSettings.setUserAgentString(userAgentString); } mPageInspector = new PageInspector(mContext, mWebView, mOnPageInspectorItemFoundListener); MainApplication.registerForBus(context, this); mRegisteredForBus = true; } @Override public void destroy() { if (mRegisteredForBus) { MainApplication.unregisterForBus(mContext, this); mRegisteredForBus = false; } cancelBuildArticleContentTask(); mIsDestroyed = true; try { // The exception sometimes here is possible related to how we create our WebView. We use an application context, // but seems like should use an activity. That is the possible fix for the crash. It should gone when we have WebView // inside an Activity mWebView.stopLoading(); mWebView.removeAllViews(); mWebView.clearCache(true); mWebView.destroyDrawingCache(); mWebView.destroy(); } catch (IllegalArgumentException exc) { exc.printStackTrace(); } catch (Exception exc) { exc.printStackTrace(); } Log.d("Article", "WebViewRenderer.destroy()"); } @Override public View getView() { return mWebView; } @Override public void updateIncognitoMode(boolean incognito) { if (incognito) { mWebView.getSettings().setCacheMode(mWebView.getSettings().LOAD_NO_CACHE); mWebView.getSettings().setAppCacheEnabled(false); mWebView.clearHistory(); mWebView.clearCache(true); mWebView.clearFormData(); mWebView.getSettings().setSaveFormData(false); } else { mWebView.getSettings().setCacheMode(mWebView.getSettings().LOAD_DEFAULT); mWebView.getSettings().setAppCacheEnabled(true); mWebView.getSettings().setSaveFormData(true); } } private void cancelBuildArticleContentTask() { if (mBuildArticleContentTask != null) { mBuildArticleContentTask.cancel(true); Log.d("Article", "BuildContentTask().cancel()"); mBuildArticleContentTask = null; } } @Override public String getUserAgentString(Context context) { if (mWebView.getSettings() == null) { return Util.getDefaultUserAgentString(context); } return mWebView.getSettings().getUserAgentString(); } @Override public void setUserAgentString(String userAgentString) { WebSettings webSettings = mWebView.getSettings(); if (null != webSettings) { webSettings.setUserAgentString(userAgentString); } } @Override public void loadUrl(URL url, Mode mode) { mHost = url.getHost(); if (mHost.startsWith("www.")) { mHost = mHost.substring(4); } mTrackingProtectionEnabled = Settings.get().isTrackingProtectionEnabled(); mAdblockEnabled = Settings.get().isAdBlockEnabled(); mHttpsEverywhereEnabled = Settings.get().isHttpsEverywhereEnabled(); refresh3PCookieSetting(); String urlAsString = url.toString(); Log.d(TAG, "loadUrl() - " + urlAsString); cancelBuildArticleContentTask(); mArticleContent = null; mMode = mode; switch (mMode) { case Article: //mGetArticleContentTask = new GetArticleContentTask(); //mGetArticleContentTask.execute(urlAsString); // This is only called by Snacktory renderer so that the loading animations start at the point the page HTML commences. // Not needed for other Renderers given onPageStarted() will be called. mController.onLoadUrl(urlAsString); break; case Web: mWebView.loadUrl(url.toString()); break; } } private void refresh3PCookieSetting() { if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { CookieManager.getInstance().setAcceptThirdPartyCookies(mWebView, !Settings.get().isBlock3PCookiesEnabled()); } } @Override public void reload() { switch (mMode) { case Article: loadUrl(getUrl(), mMode); break; case Web: // In case the user changes adblock / TP settings and reloads the current bubble mTrackingProtectionEnabled = Settings.get().isTrackingProtectionEnabled(); mAdblockEnabled = Settings.get().isAdBlockEnabled(); mHttpsEverywhereEnabled = Settings.get().isHttpsEverywhereEnabled(); refresh3PCookieSetting(); mWebView.reload(); break; } } @Override public void stopLoading() { cancelBuildArticleContentTask(); mArticleContent = null; try { if (null != mWebView) { mWebView.stopLoading(); // Ensure the loading indicators cease when stop is pressed. mWebChromeClient.onProgressChanged(mWebView, 100); } } catch (NullPointerException exc) { CrashTracking.logHandledException(exc); } } @Override public void hidePopups() { if (mJsAlertDialog != null) { mJsAlertDialog.dismiss(); mJsAlertDialog = null; } if (mJsConfirmDialog != null) { mJsConfirmDialog.dismiss(); mJsConfirmDialog = null; } if (mJsPromptDialog != null) { mJsPromptDialog.dismiss(); mJsPromptDialog = null; } } @Override public void onPageLoadComplete() { super.onPageLoadComplete(); } @Override public void resetPageInspector() { mPageInspector.reset(); } @Override public void runPageInspector(String adInsert) { mPageInspector.run(mWebView, adInsert); } @Override public YouTubeEmbedHelper getPageInspectorYouTubeEmbedHelper() { return mPageInspector.getYouTubeEmbedHelper(); } PageInspector.OnItemFoundListener mOnPageInspectorItemFoundListener = new PageInspector.OnItemFoundListener() { @Override public void onYouTubeEmbeds() { mController.onPageInspectorYouTubeEmbedFound(); } @Override public void onTouchIconLoaded(Bitmap bitmap, String pageUrl) { mController.onPageInspectorTouchIconLoaded(bitmap, pageUrl); } @Override public void onFetchHtml(String html) { if (html != null && html.isEmpty() == false) { if (mBuildArticleContentTask == null) { mBuildArticleContentTask = ArticleContent.fetchArticleContent(getUrl().toString(), html, new ArticleContent.OnFinishedListener() { @Override public void onFinished(ArticleContent articleContent) { mArticleContent = articleContent; mController.onArticleContentReady(mArticleContent); mBuildArticleContentTask = null; } } ); } } } @Override public void onThemeColor(int color) { mController.onPagedInspectorThemeColorFound(color); } }; View.OnKeyListener mOnKeyListener = new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_DOWN && mIsDestroyed == false) { WebView webView = (WebView) v; switch (keyCode) { case KeyEvent.KEYCODE_BACK: { return mController.onBackPressed(); } } } return false; } }; View.OnLongClickListener mOnWebViewLongClickListener = new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { WebView.HitTestResult hitTestResult = mWebView.getHitTestResult(); Log.d(TAG, "onLongClick type: " + hitTestResult.getType()); switch (hitTestResult.getType()) { case WebView.HitTestResult.IMAGE_TYPE: case WebView.HitTestResult.SRC_ANCHOR_TYPE: case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE: { final String url = hitTestResult.getExtra(); if (url == null) { return false; } mController.onUrlLongClick(mWebView, url, hitTestResult.getType()); return true; } case WebView.HitTestResult.UNKNOWN_TYPE: default: if (Constant.ACTIVITY_WEBVIEW_RENDERING == false) { Message msg = new Message(); msg.setTarget(new Handler() { @Override public void handleMessage(Message msg) { Bundle b = msg.getData(); if (b != null && b.getString("url") != null) { mController.onShowBrowserPrompt(); } } }); mWebView.requestFocusNodeHref(msg); } return true; } } }; CustomWebView.OnScrollChangedCallback mOnScrollChangedCallback = new CustomWebView.OnScrollChangedCallback() { @Override public void onScroll(int newY, int oldY) { if (!mWebView.mInterceptScrollChangeCalls && 0 == newY) { mController.resetBubblePanelAdjustment(); } else { mController.adjustBubblesPanel(newY, oldY, false); } } }; private View.OnTouchListener mWebViewOnTouchListener = new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { final int action = event.getAction() & MotionEvent.ACTION_MASK; switch (action) { case MotionEvent.ACTION_DOWN: mLastWebViewTouchDownUrl = mUrl.toString(); mWebView.mInterceptScrollChangeCalls = true; //Log.d(TAG, "[urlstack] WebView - MotionEvent.ACTION_DOWN"); break; case MotionEvent.ACTION_UP: mLastWebViewTouchUpTime = System.currentTimeMillis(); mWebView.mInterceptScrollChangeCalls = false; //Log.d(TAG, "[urlstack] WebView - MotionEvent.ACTION_UP"); mController.adjustBubblesPanel(0, 0, true); break; } // Forcibly pass along to the WebView. This ensures we receive the ACTION_UP event above. mWebView.onTouchEvent(event); return true; } }; WebViewClient mWebViewClient = new WebViewClient() { @Override public void doUpdateVisitedHistory (WebView view, String url, boolean isReload) { WebView.HitTestResult hitResult = null; if (null != mWebView) { hitResult = mWebView.getHitTestResult(); } String extraRes = null; if (null != mWebView && null != hitResult) { extraRes = hitResult.getExtra(); if (null != extraRes) { try { URL extraURL = new URL(extraRes); extraRes = extraRes.substring(extraURL.getProtocol().length() + ("://").length()); } catch (MalformedURLException exc) { exc.printStackTrace(); } } } int webViewHitResultType = WebView.HitTestResult.UNKNOWN_TYPE; if (null != hitResult) { webViewHitResultType = hitResult.getType(); } if (null == extraRes || WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE == webViewHitResultType || (null != extraRes && url.endsWith(extraRes))) { mController.doUpdateVisitedHistory(url, isReload, WebView.HitTestResult.UNKNOWN_TYPE == webViewHitResultType || WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE == webViewHitResultType); } } @Override public boolean shouldOverrideUrlLoading(WebView wView, final String urlAsString) { boolean viaInput = false; if (mLastWebViewTouchUpTime > -1) { long touchUpTimeDelta = System.currentTimeMillis() - mLastWebViewTouchUpTime; // this value needs to be largish if (touchUpTimeDelta < 1500) { // If the url has changed since the use pressed their finger down, a redirect has likely occurred, // in which case we don't update the Url Stack if (mLastWebViewTouchDownUrl.equals(mUrl.toString())) { viaInput = true; } mLastWebViewTouchUpTime = -1; } } return mController.shouldOverrideUrlLoading(urlAsString, viaInput); } private WebResourceResponse interceptTheCall(WebView view, String urlStr, String filterOption, boolean apiLevelAbove21) { // null signifies allowing the request WebResourceResponse allowRequest = null; // Quickly check to see if no checks are needed because ad blocking and tracking // protection are not enabled. if (!mTrackingProtectionEnabled && !mAdblockEnabled && !mHttpsEverywhereEnabled) { return allowRequest; } String host; try { host = new URL(urlStr).getHost(); } catch (Exception e) { return allowRequest; } if (mTrackingProtectionEnabled && mController.shouldTrackingProtectionBlockUrl(mHost, host) || mAdblockEnabled && mController.shouldAdBlockUrl(mHost, urlStr, filterOption)) { // Unfortunately the deprecated API that we're targetting doesn't have a better // way to block this. Once we upgrade our target then we can use a better override // which allows us to set a response code. if (!apiLevelAbove21) { return new WebResourceResponse("text/html", "UTF-8", null); } else { return new WebResourceResponse("text/html", "UTF-8", 450, "Blocked", null, null); } } return HttpsEverywhereResponse(urlStr); } private WebResourceResponse HttpsEverywhereResponse(String urlStr) { if (!mHttpsEverywhereEnabled) { return null; } String newUrl = mController.getHTTPSUrl(urlStr); if (!newUrl.equals(urlStr)) { try { URL url = new URL(newUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); String mimeType = conn.getContentType(); String encoding = conn.getContentEncoding(); InputStream is = conn.getInputStream(); return new WebResourceResponse(mimeType, encoding, is); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } return null; } @Override public WebResourceResponse shouldInterceptRequest (WebView view, String urlStr) { // That call is for the API level is lower then 21 // We do not change or block the top URL if (mUrl.toString().equals(urlStr)) { return null; } // Just return as is for now, we will have a solution for older devices later. // The blocking by file extension not being reliable enough for now. return HttpsEverywhereResponse(urlStr); // Quickly check to see if no checks are needed because ad blocking and tracking // protection are not enabled. /*if (!mTrackingProtectionEnabled && !mAdblockEnabled) { return null; } String filterOption = "none";*/ /*HttpURLConnection connection; try { URL url = new URL(urlStr); connection = (HttpURLConnection)url.openConnection(); connection.setConnectTimeout(3000); connection.connect(); // get the size of the file which is in the header of the request int size = connection.getContentLength(); if (-1 != size) { InputStream inputStream = connection.getInputStream(); byte[] buffer = new byte[size]; inputStream.read(buffer); String str = new String(buffer); if (null != str) { if (str.contains("Accept")) { } } } } catch (Exception e) { // Do nothing here }*/ /*try { URL url = new URL(urlStr); String urlPath = url.getPath(); if (urlPath.endsWith("css") || urlStr.endsWith(".css") || urlStr.endsWith(".woff")) { filterOption = "/css"; } else if (urlStr.endsWith(".png") || urlStr.endsWith(".ico") || urlStr.endsWith(".gif") || urlStr.endsWith(".svg") || urlStr.endsWith(".icns") || urlStr.endsWith(".bmp") || urlStr.endsWith(".pdf") || urlStr.endsWith(".pcd") || urlStr.endsWith(".fpx") || urlStr.endsWith(".jp2") || urlStr.endsWith(".jpx") || urlStr.endsWith(".j2k") || urlStr.endsWith(".j2c") || urlStr.endsWith(".jpeg") || urlStr.endsWith(".jpg") || urlStr.endsWith(".jif") || urlStr.endsWith(".jfif") || urlStr.endsWith(".bmp") || urlStr.endsWith(".tif") || urlStr.endsWith(".tiff")) { filterOption = "image/"; } else if (urlStr.endsWith(".js") || urlPath.endsWith("js")) { filterOption = "javascript"; } } catch (Exception e) { // Do nothing here } return interceptTheCall(view, urlStr, filterOption, false);*/ } @Override public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest resourceRequest) { // That call is for the API level is higher or equal to 21 String currentUrl = resourceRequest.getUrl().toString(); // Quickly check to see if no checks are needed because ad blocking and tracking // protection are not enabled. if (!mTrackingProtectionEnabled && !mAdblockEnabled && !mHttpsEverywhereEnabled || mUrl.toString().equals(currentUrl)) { return null; } String filterOption = "none"; Map<String, String> requestHeaders = resourceRequest.getRequestHeaders(); for (Map.Entry<String, String> entry : requestHeaders.entrySet()) { if (entry.getKey().equals("Accept")) { if (entry.getValue().contains("/css")) { filterOption = "/css"; break; } else if (entry.getValue().contains("image/")) { filterOption = "image/"; break; } else if (entry.getValue().contains("javascript")) { filterOption = "javascript"; break; } } } return interceptTheCall(view, currentUrl, filterOption, true); } @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { Log.d(TAG, "WebViewRenderer - onReceivedError() - " + description + " - " + failingUrl); // Reload webviews once we have a connection. if (NetworkConnectivity.isConnected(mContext) == false) { Log.d(TAG, "Not connected, will retry on connection."); // We only reload a single webview at a time, so if there is a previous receiver, we unregister it. if (mLastNetworkReceiver != null) { try { mContext.unregisterReceiver(mLastNetworkReceiver); } catch(Exception e) { Log.d(TAG, "Could not unregister existing network receiver."); } mLastNetworkReceiver = null; } IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); NetworkReceiver receiver = new NetworkReceiver(WebViewRenderer.this); mContext.registerReceiver(receiver, filter); mLastNetworkReceiver = receiver; } mController.onReceivedError(); } @Override public void onReceivedSslError(WebView webView, final SslErrorHandler handler, SslError error) { handler.cancel(); /* AlertDialog.Builder builder = new AlertDialog.Builder(mContext); builder.setTitle(mContext.getString(R.string.warning)); String s = error.toString(); Log.d("blerg", s); URL url; try { url = new URL(error.getUrl()); } catch (MalformedURLException e) { e.printStackTrace(); url = mUrl; } builder.setMessage(String.format(mContext.getString(R.string.untrusted_certificate), url.getHost())) .setCancelable(true) .setPositiveButton(mContext.getString(R.string.yes), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { handler.proceed(); } }) .setNegativeButton(mContext.getString(R.string.action_no_recommended), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { handler.cancel(); } }); if (error.getPrimaryError() == SslError.SSL_UNTRUSTED) { AlertDialog alert = builder.create(); alert.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); Util.showThemedDialog(alert); } else { handler.proceed(); }*/ } @Override public void onPageStarted(WebView view, String urlAsString, Bitmap favIcon) { mController.onPageStarted(urlAsString, favIcon); } @Override public void onPageFinished(WebView webView, String urlAsString) { mController.onPageFinished(urlAsString); } }; DownloadListener mDownloadListener = new DownloadListener() { @Override public void onDownloadStart(String urlAsString, String userAgent, String contentDisposition, String mimetype, long contentLength) { mController.onDownloadStart(urlAsString); } }; WebChromeClient mWebChromeClient = new WebChromeClient() { @Override public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) { MainController.get().startFileBrowser(fileChooserParams.getAcceptTypes(), filePathCallback); return true; } @Override public void onReceivedTitle(WebView webView, String title) { mController.onReceivedTitle(webView.getUrl(), title); } @Override public void onReceivedIcon(WebView webView, Bitmap bitmap) { mController.onReceivedIcon(bitmap); } @Override public void onProgressChanged(WebView webView, int progress) { mCurrentProgress = progress; mController.onProgressChanged(progress, webView.getUrl()); // Inject page scripts after there has been some progress, otherwise they get injected into an empty page. if (mCurrentProgress >= 60 && mRunPageScripts == 0) { mRunPageScripts = 1; try { URL currentUrl = new URL(webView.getUrl()); mPageInspector.run(webView, mController.adInsertionList(currentUrl.getHost().replace("www.", "").replace("m.", ""))); } catch (MalformedURLException exc) { exc.printStackTrace(); } } if (mCurrentProgress == 100 && mPauseOnComplete) { mHandler.postDelayed(mCheckForPauseRunnable, 3000); } } @Override public void onCloseWindow(WebView window) { mController.onCloseWindow(); } @Override public boolean onJsAlert(WebView view, String url, String message, final JsResult result) { mJsAlertDialog = new AlertDialog.Builder(mContext).create(); mJsAlertDialog.setMessage(message); mJsAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); mJsAlertDialog.setButton(AlertDialog.BUTTON_POSITIVE, mContext.getResources().getString(R.string.action_ok), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { result.confirm(); } }); mJsAlertDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { result.cancel(); } }); mJsAlertDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { mJsAlertDialog = null; } }); Util.showThemedDialog(mJsAlertDialog); return true; } @Override public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) { mJsConfirmDialog = new AlertDialog.Builder(mContext).create(); mJsConfirmDialog.setTitle(R.string.confirm_title); mJsConfirmDialog.setMessage(message); mJsConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); mJsConfirmDialog.setButton(AlertDialog.BUTTON_POSITIVE, mContext.getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { result.confirm(); } }); mJsConfirmDialog.setButton(AlertDialog.BUTTON_NEGATIVE, mContext.getResources().getString(android.R.string.cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { result.cancel(); } }); mJsConfirmDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { mJsConfirmDialog = null; } }); Util.showThemedDialog(mJsConfirmDialog); return true; } @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, final JsPromptResult result) { final View v = LayoutInflater.from(mContext).inflate(R.layout.view_javascript_prompt, null); ((TextView)v.findViewById(R.id.prompt_message_text)).setText(message); ((EditText)v.findViewById(R.id.prompt_input_field)).setText(defaultValue); mJsPromptDialog = new AlertDialog.Builder(mContext).create(); mJsPromptDialog.setView(v); mJsPromptDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); mJsPromptDialog.setButton(AlertDialog.BUTTON_POSITIVE, mContext.getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String value = ((EditText)v.findViewById(R.id.prompt_input_field)).getText().toString(); result.confirm(value); } }); mJsPromptDialog.setButton(AlertDialog.BUTTON_NEGATIVE, mContext.getResources().getString(android.R.string.cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { result.cancel(); } }); mJsPromptDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { public void onCancel(DialogInterface dialog) { result.cancel(); } }); mJsPromptDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { mJsPromptDialog = null; } }); Util.showThemedDialog(mJsPromptDialog); return true; } @Override public boolean onConsoleMessage(ConsoleMessage consoleMessage) { // Call the old version of this function for backwards compatability. //onConsoleMessage(consoleMessage.message(), consoleMessage.lineNumber(), // consoleMessage.sourceId()); String message = consoleMessage.message(); if (message == null) { message = "(null)"; } Log.e("Console", message); return false; } @Override public boolean onCreateWindow(WebView view, boolean dialog, boolean userGesture, Message resultMsg) { TabView tabView = MainController.get().openUrl(Constant.NEW_TAB_URL, System.currentTimeMillis(), false, Analytics.OPENED_URL_FROM_NEW_WINDOW); if (tabView != null) { WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; transport.setWebView((WebView) tabView.getContentView().getWebRenderer().getView()); resultMsg.sendToTarget(); return true; } return false; } @Override public void onGeolocationPermissionsShowPrompt(final String origin, final GeolocationPermissions.Callback callback) { mController.onGeolocationPermissionsShowPrompt(origin, new GetGeolocationCallback() { @Override public void onAllow() { callback.invoke(origin, true, false); } }); } }; @Override public ArticleContent getArticleContent() { return mArticleContent; } private static final String BATTERY_SAVE_TAG = "BatterySaveWebView"; private Runnable mCheckForPauseRunnable = new Runnable() { @Override public void run() { switch (Settings.get().getWebViewBatterySaveMode()) { case Default: case Aggressive: if (mPauseOnComplete) { mPauseOnComplete = false; webviewPause("runnable"); } break; } } }; private void webviewPause(String via) { String msg = "PAUSE (" + via + ") "; if (mWebView != null && mIsDestroyed == false) { if (mCurrentProgress == 100) { mWebView.onPause(); mPauseOnComplete = false; } else { msg += " **IGNORE** (" + mCurrentProgress + ")"; mPauseOnComplete = true; } } Log.d(BATTERY_SAVE_TAG, msg + ", url:" + getUrl().getHost()); } private void webviewResume(String via) { mPauseOnComplete = false; String msg = "RESUME (" + via + ") "; if (mWebView != null && mIsDestroyed == false) { mWebView.onResume(); } Log.d(BATTERY_SAVE_TAG, msg + ", url:" + getUrl().getHost()); } @Override public void resumeOnSetActive() { // Nothing happens if we call resume on resumed WebView but // we should resume if we had Aggressive mode before and paused it and set it to Default or Off in Settings webviewResume("setActive"); } @Override public void pauseOnSetInactive() { switch (Settings.get().getWebViewBatterySaveMode()) { case Aggressive: webviewPause("setInactive"); break; } } @SuppressWarnings("unused") @Subscribe public void onUserPresentEvent(MainController.UserPresentEvent event) { switch (Settings.get().getWebViewBatterySaveMode()) { case Default: webviewResume("userPresent"); break; } } @SuppressWarnings("unused") @Subscribe public void onScreenOffEvent(MainController.ScreenOffEvent event) { switch (Settings.get().getWebViewBatterySaveMode()) { case Aggressive: case Default: webviewPause("screenOff"); break; } } @SuppressWarnings("unused") @Subscribe public void onBeginCollapseTransitionEvent(MainController.BeginCollapseTransitionEvent event) { switch (Settings.get().getWebViewBatterySaveMode()) { case Aggressive: webviewPause("beginCollapse"); break; } } @SuppressWarnings("unused") @Subscribe public void onBeginExpandTransitionEvent(MainController.BeginExpandTransitionEvent event) { // Do nothing here for now as we Resume current active Tab on resumeOnSetActive } @SuppressWarnings("unused") @Subscribe public void onHideContentEvent(MainController.HideContentEvent event) { switch (Settings.get().getWebViewBatterySaveMode()) { case Aggressive: case Default: webviewPause("hide event"); break; } } @SuppressWarnings("unused") @Subscribe public void onUnhideContentEvent(MainController.UnhideContentEvent event) { switch (Settings.get().getWebViewBatterySaveMode()) { case Default: webviewResume("unhide event"); break; } } }