/* * Copyright 2014 A.C.R. Development */ package acr.browser.lightning.view; import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; import android.net.MailTo; import android.net.Uri; import android.net.http.SslError; import android.os.Build; import android.os.Message; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.app.AlertDialog; import android.text.InputType; import android.text.method.PasswordTransformationMethod; import android.util.Log; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewConfiguration; import android.webkit.CookieManager; import android.webkit.GeolocationPermissions; import android.webkit.HttpAuthHandler; import android.webkit.SslErrorHandler; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebResourceRequest; import android.webkit.WebResourceResponse; import android.webkit.WebSettings; import android.webkit.WebSettings.LayoutAlgorithm; import android.webkit.WebSettings.PluginState; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.EditText; import android.widget.LinearLayout; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.net.URISyntaxException; import acr.browser.lightning.R; import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.StartPage; import acr.browser.lightning.controller.BrowserController; import acr.browser.lightning.download.LightningDownloadListener; import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.utils.AdBlock; import acr.browser.lightning.utils.IntentUtils; import acr.browser.lightning.utils.PermissionsManager; import acr.browser.lightning.utils.ThemeUtils; import acr.browser.lightning.utils.Utils; public class LightningView { private final Title mTitle; private WebView mWebView; private final boolean mIsIncognitoTab; private BrowserController mBrowserController; private GestureDetector mGestureDetector; private final Activity mActivity; private static String mHomepage; private static String mDefaultUserAgent; // TODO fix so that mWebpageBitmap can be static - static changes the icon when switching from light to dark and then back to light private final Bitmap mWebpageBitmap; private static PreferenceManager mPreferences; private final AdBlock mAdBlock; private IntentUtils mIntentUtils; private final Paint mPaint = new Paint(); private boolean isForegroundTab; private boolean mTextReflow = false; private boolean mInvertPage = false; private boolean mToggleDesktop = false; private static float mMaxFling; private static final int API = android.os.Build.VERSION.SDK_INT; private static final int SCROLL_UP_THRESHOLD = Utils.dpToPx(10); private static final float[] mNegativeColorArray = {-1.0f, 0, 0, 0, 255, // red 0, -1.0f, 0, 0, 255, // green 0, 0, -1.0f, 0, 255, // blue 0, 0, 0, 1.0f, 0 // alpha }; private PermissionsManager mPermissionsManager; private static final String[] PERMISSIONS = new String[]{Manifest.permission.ACCESS_FINE_LOCATION}; public LightningView(Activity activity, String url, boolean darkTheme, boolean isIncognito) { mActivity = activity; mWebView = new WebView(activity); mIsIncognitoTab = isIncognito; mTitle = new Title(activity, darkTheme); mAdBlock = AdBlock.getInstance(activity.getApplicationContext()); mPermissionsManager = PermissionsManager.getInstance(); mWebpageBitmap = mTitle.mDefaultIcon; mMaxFling = ViewConfiguration.get(activity).getScaledMaximumFlingVelocity(); try { mBrowserController = (BrowserController) activity; } catch (ClassCastException e) { throw new ClassCastException(activity + " must implement BrowserController"); } mIntentUtils = new IntentUtils(mBrowserController); mWebView.setDrawingCacheBackgroundColor(0x00000000); mWebView.setFocusableInTouchMode(true); mWebView.setFocusable(true); mWebView.setAnimationCacheEnabled(false); mWebView.setDrawingCacheEnabled(false); mWebView.setWillNotCacheDrawing(true); mWebView.setAlwaysDrawnWithCacheEnabled(false); mWebView.setBackgroundColor(0); if (API >= Build.VERSION_CODES.JELLY_BEAN) { mWebView.setBackground(null); mWebView.getRootView().setBackground(null); } else if (mWebView.getRootView() != null) { mWebView.getRootView().setBackgroundDrawable(null); } mWebView.setScrollbarFadingEnabled(true); mWebView.setSaveEnabled(true); mWebView.setNetworkAvailable(true); mWebView.setWebChromeClient(new LightningChromeClient(activity)); mWebView.setWebViewClient(new LightningWebClient(activity)); mWebView.setDownloadListener(new LightningDownloadListener(activity)); mGestureDetector = new GestureDetector(activity, new CustomGestureListener()); mWebView.setOnTouchListener(new TouchListener()); mDefaultUserAgent = mWebView.getSettings().getUserAgentString(); initializeSettings(mWebView.getSettings(), activity); initializePreferences(mWebView.getSettings(), activity); if (url != null) { if (!url.trim().isEmpty()) { mWebView.loadUrl(url); } else { // don't load anything, the user is looking for a blank tab } } else { loadHomepage(); } } public void loadHomepage() { if (mWebView == null) { return; } if (mHomepage.startsWith("about:home")) { mWebView.loadUrl(StartPage.getHomepage(mActivity)); } else if (mHomepage.startsWith("about:bookmarks")) { mBrowserController.openBookmarkPage(mWebView); } else { mWebView.loadUrl(mHomepage); } } /** * Initialize the preference driven settings of the WebView * * @param settings the WebSettings object to use, you can pass in null * if you don't have a reference to them * @param context the context in which the WebView was created */ public synchronized void initializePreferences(@Nullable WebSettings settings, Context context) { if (settings == null && mWebView == null) { return; } else if (settings == null) { settings = mWebView.getSettings(); } mPreferences = PreferenceManager.getInstance(); settings.setDefaultTextEncodingName(mPreferences.getTextEncoding()); mHomepage = mPreferences.getHomepage(); mAdBlock.updatePreference(); setColorMode(mPreferences.getRenderingMode()); if (!mIsIncognitoTab) { settings.setGeolocationEnabled(mPreferences.getLocationEnabled()); if (mPreferences.getLocationEnabled() && !PermissionsManager.checkPermissions(mActivity, PERMISSIONS)) { mPermissionsManager.requestPermissionsIfNecessary(mActivity, PERMISSIONS); } } else { settings.setGeolocationEnabled(false); } if (API < Build.VERSION_CODES.KITKAT) { switch (mPreferences.getFlashSupport()) { case 0: settings.setPluginState(PluginState.OFF); break; case 1: settings.setPluginState(PluginState.ON_DEMAND); break; case 2: settings.setPluginState(PluginState.ON); break; default: break; } } setUserAgent(context, mPreferences.getUserAgentChoice()); if (mPreferences.getSavePasswordsEnabled() && !mIsIncognitoTab) { if (API < 18) { settings.setSavePassword(true); } settings.setSaveFormData(true); } else { if (API < 18) { settings.setSavePassword(false); } settings.setSaveFormData(false); } if (mPreferences.getJavaScriptEnabled()) { settings.setJavaScriptEnabled(true); settings.setJavaScriptCanOpenWindowsAutomatically(true); } else { settings.setJavaScriptEnabled(false); settings.setJavaScriptCanOpenWindowsAutomatically(false); } if (mPreferences.getTextReflowEnabled()) { mTextReflow = true; settings.setLayoutAlgorithm(LayoutAlgorithm.NARROW_COLUMNS); if (API >= android.os.Build.VERSION_CODES.KITKAT) { try { settings.setLayoutAlgorithm(LayoutAlgorithm.TEXT_AUTOSIZING); } catch (Exception e) { // This shouldn't be necessary, but there are a number // of KitKat devices that crash trying to set this Log.e(Constants.TAG, "Problem setting LayoutAlgorithm to TEXT_AUTOSIZING"); } } } else { mTextReflow = false; settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL); } settings.setBlockNetworkImage(mPreferences.getBlockImagesEnabled()); if (!mIsIncognitoTab) { settings.setSupportMultipleWindows(mPreferences.getPopupsEnabled()); } else { settings.setSupportMultipleWindows(false); } settings.setUseWideViewPort(mPreferences.getUseWideViewportEnabled()); settings.setLoadWithOverviewMode(mPreferences.getOverviewModeEnabled()); switch (mPreferences.getTextSize()) { case 0: settings.setTextZoom(200); break; case 1: settings.setTextZoom(150); break; case 2: settings.setTextZoom(125); break; case 3: settings.setTextZoom(100); break; case 4: settings.setTextZoom(75); break; case 5: settings.setTextZoom(50); break; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { CookieManager.getInstance().setAcceptThirdPartyCookies(mWebView, !mPreferences.getBlockThirdPartyCookiesEnabled()); } } /** * Initialize the settings of the WebView that are intrinsic to Lightning and cannot * be altered by the user. Distinguish between Incognito and Regular tabs here. * * @param settings the WebSettings object to use. * @param context the Context which was used to construct the WebView. */ private void initializeSettings(WebSettings settings, Context context) { if (API < Build.VERSION_CODES.JELLY_BEAN_MR2) { settings.setAppCacheMaxSize(Long.MAX_VALUE); } if (API < Build.VERSION_CODES.JELLY_BEAN_MR1) { settings.setEnableSmoothTransition(true); } if (API > Build.VERSION_CODES.JELLY_BEAN) { settings.setMediaPlaybackRequiresUserGesture(true); } if (API >= Build.VERSION_CODES.LOLLIPOP && !mIsIncognitoTab) { settings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE); } else if (API >= Build.VERSION_CODES.LOLLIPOP) { // We're in Incognito mode, reject settings.setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW); } if (!mIsIncognitoTab) { settings.setDomStorageEnabled(true); settings.setAppCacheEnabled(true); settings.setCacheMode(WebSettings.LOAD_DEFAULT); settings.setDatabaseEnabled(true); } else { settings.setDomStorageEnabled(false); settings.setAppCacheEnabled(false); settings.setDatabaseEnabled(false); settings.setCacheMode(WebSettings.LOAD_NO_CACHE); } settings.setSupportZoom(true); settings.setBuiltInZoomControls(true); settings.setDisplayZoomControls(false); settings.setAllowContentAccess(true); settings.setAllowFileAccess(true); if (API >= Build.VERSION_CODES.JELLY_BEAN) { settings.setAllowFileAccessFromFileURLs(false); settings.setAllowUniversalAccessFromFileURLs(false); } settings.setAppCachePath(context.getDir("appcache", 0).getPath()); settings.setGeolocationDatabasePath(context.getDir("geolocation", 0).getPath()); if (API < Build.VERSION_CODES.KITKAT) { settings.setDatabasePath(context.getDir("databases", 0).getPath()); } } public void toggleDesktopUA(@NonNull Context context) { if (mWebView == null) return; if (!mToggleDesktop) mWebView.getSettings().setUserAgentString(Constants.DESKTOP_USER_AGENT); else setUserAgent(context, mPreferences.getUserAgentChoice()); mToggleDesktop = !mToggleDesktop; } public void setUserAgent(Context context, int choice) { if (mWebView == null) return; WebSettings settings = mWebView.getSettings(); switch (choice) { case 1: if (API >= Build.VERSION_CODES.JELLY_BEAN_MR1) { settings.setUserAgentString(WebSettings.getDefaultUserAgent(context)); } else { settings.setUserAgentString(mDefaultUserAgent); } break; case 2: settings.setUserAgentString(Constants.DESKTOP_USER_AGENT); break; case 3: settings.setUserAgentString(Constants.MOBILE_USER_AGENT); break; case 4: settings.setUserAgentString(mPreferences.getUserAgentString(mDefaultUserAgent)); break; } } public boolean isShown() { return mWebView != null && mWebView.isShown(); } public synchronized void onPause() { if (mWebView != null) mWebView.onPause(); } public synchronized void onResume() { if (mWebView != null) mWebView.onResume(); } public synchronized void freeMemory() { if (mWebView != null && Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) mWebView.freeMemory(); } public void setForegroundTab(boolean isForeground) { isForegroundTab = isForeground; mBrowserController.updateTabs(); } public boolean isForegroundTab() { return isForegroundTab; } public int getProgress() { if (mWebView != null) { return mWebView.getProgress(); } else { return 100; } } public synchronized void stopLoading() { if (mWebView != null) { mWebView.stopLoading(); } } private void setHardwareRendering() { mWebView.setLayerType(View.LAYER_TYPE_HARDWARE, mPaint); } private void setNormalRendering() { mWebView.setLayerType(View.LAYER_TYPE_NONE, null); } public void setSoftwareRendering() { mWebView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); } private void setColorMode(int mode) { mInvertPage = false; switch (mode) { case 0: mPaint.setColorFilter(null); // setSoftwareRendering(); // Some devices get segfaults // in the WebView with Hardware Acceleration enabled, // the only fix is to disable hardware rendering setNormalRendering(); mInvertPage = false; break; case 1: ColorMatrixColorFilter filterInvert = new ColorMatrixColorFilter( mNegativeColorArray); mPaint.setColorFilter(filterInvert); setHardwareRendering(); mInvertPage = true; break; case 2: ColorMatrix cm = new ColorMatrix(); cm.setSaturation(0); ColorMatrixColorFilter filterGray = new ColorMatrixColorFilter(cm); mPaint.setColorFilter(filterGray); setHardwareRendering(); break; case 3: ColorMatrix matrix = new ColorMatrix(); matrix.set(mNegativeColorArray); ColorMatrix matrixGray = new ColorMatrix(); matrixGray.setSaturation(0); ColorMatrix concat = new ColorMatrix(); concat.setConcat(matrix, matrixGray); ColorMatrixColorFilter filterInvertGray = new ColorMatrixColorFilter(concat); mPaint.setColorFilter(filterInvertGray); setHardwareRendering(); mInvertPage = true; break; } } public synchronized void pauseTimers() { if (mWebView != null) { mWebView.pauseTimers(); } } public synchronized void resumeTimers() { if (mWebView != null) { mWebView.resumeTimers(); } } public void requestFocus() { if (mWebView != null && !mWebView.hasFocus()) { mWebView.requestFocus(); } } public void setVisibility(int visible) { if (mWebView != null) { mWebView.setVisibility(visible); } } public synchronized void reload() { // Check if configured proxy is available if (mBrowserController.proxyIsNotReady()) { // User has been notified return; } if (mWebView != null) { mWebView.reload(); } } private void cacheFavicon(Bitmap icon) { if (icon == null) return; String hash = String.valueOf(Utils.getDomainName(getUrl()).hashCode()); Log.d(Constants.TAG, "Caching icon for " + Utils.getDomainName(getUrl())); FileOutputStream fos = null; try { File image = new File(mActivity.getCacheDir(), hash + ".png"); fos = new FileOutputStream(image); icon.compress(Bitmap.CompressFormat.PNG, 100, fos); fos.flush(); } catch (IOException e) { e.printStackTrace(); } finally { Utils.close(fos); } } public synchronized void find(String text) { if (mWebView != null) { if (API >= Build.VERSION_CODES.JELLY_BEAN_MR1) { mWebView.findAllAsync(text); } else { mWebView.findAll(text); } } } public synchronized void onDestroy() { if (mWebView != null) { mWebView.stopLoading(); mWebView.onPause(); mWebView.clearHistory(); mWebView.setVisibility(View.GONE); mWebView.removeAllViews(); mWebView.destroyDrawingCache(); // mWebView.destroy(); //this is causing the segfault mWebView = null; } } public synchronized void goBack() { if (mWebView != null) { mWebView.goBack(); } } public String getUserAgent() { if (mWebView != null) { return mWebView.getSettings().getUserAgentString(); } else { return ""; } } public synchronized void goForward() { if (mWebView != null) { mWebView.goForward(); } } public boolean canGoBack() { return mWebView != null && mWebView.canGoBack(); } public boolean canGoForward() { return mWebView != null && mWebView.canGoForward(); } @Nullable public WebView getWebView() { return mWebView; } public Bitmap getFavicon() { return mTitle.getFavicon(); } public synchronized void loadUrl(String url) { // Check if configured proxy is available if (mBrowserController.proxyIsNotReady()) { // User has been notified return; } if (mWebView != null) { mWebView.loadUrl(url); } } public synchronized void invalidate() { if (mWebView != null) { mWebView.invalidate(); } } public String getTitle() { return mTitle.getTitle(); } @NonNull public String getUrl() { if (mWebView != null && mWebView.getUrl() != null) { return mWebView.getUrl(); } else { return ""; } } public class LightningWebClient extends WebViewClient { final Activity mActivity; LightningWebClient(Activity activity) { mActivity = activity; } @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (mAdBlock.isAd(request.getUrl().toString())) { ByteArrayInputStream EMPTY = new ByteArrayInputStream("".getBytes()); return new WebResourceResponse("text/plain", "utf-8", EMPTY); } } return super.shouldInterceptRequest(view, request); } @Override public WebResourceResponse shouldInterceptRequest(WebView view, String url) { if (mAdBlock.isAd(url)) { ByteArrayInputStream EMPTY = new ByteArrayInputStream("".getBytes()); return new WebResourceResponse("text/plain", "utf-8", EMPTY); } return null; } @Override public void onPageFinished(WebView view, String url) { if (view.isShown()) { mBrowserController.updateUrl(url, true); mBrowserController.updateBookmarkIndicator(url); view.postInvalidate(); } if (view.getTitle() == null || view.getTitle().isEmpty()) { mTitle.setTitle(mActivity.getString(R.string.untitled)); } else { mTitle.setTitle(view.getTitle()); } if (API >= android.os.Build.VERSION_CODES.KITKAT && mInvertPage) { view.evaluateJavascript(Constants.JAVASCRIPT_INVERT_PAGE, null); } mBrowserController.updateTabs(); } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { if (isShown()) { mBrowserController.updateUrl(url, false); mBrowserController.updateBookmarkIndicator(url); mBrowserController.showActionBar(); } mTitle.setFavicon(mWebpageBitmap); mBrowserController.updateTabs(); } @Override public void onReceivedHttpAuthRequest(final WebView view, @NonNull final HttpAuthHandler handler, final String host, final String realm) { AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); final EditText name = new EditText(mActivity); final EditText password = new EditText(mActivity); LinearLayout passLayout = new LinearLayout(mActivity); passLayout.setOrientation(LinearLayout.VERTICAL); passLayout.addView(name); passLayout.addView(password); name.setHint(mActivity.getString(R.string.hint_username)); name.setSingleLine(); password.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD); password.setSingleLine(); password.setTransformationMethod(new PasswordTransformationMethod()); password.setHint(mActivity.getString(R.string.hint_password)); builder.setTitle(mActivity.getString(R.string.title_sign_in)); builder.setView(passLayout); builder.setCancelable(true) .setPositiveButton(mActivity.getString(R.string.title_sign_in), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { String user = name.getText().toString(); String pass = password.getText().toString(); handler.proceed(user.trim(), pass.trim()); Log.d(Constants.TAG, "Request Login"); } }) .setNegativeButton(mActivity.getString(R.string.action_cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { handler.cancel(); } }); AlertDialog alert = builder.create(); alert.show(); } private boolean mIsRunning = false; private float mZoomScale = 0.0f; @Override public void onScaleChanged(final WebView view, final float oldScale, final float newScale) { if (view.isShown() && mTextReflow && API >= android.os.Build.VERSION_CODES.KITKAT) { if (mIsRunning) return; if (Math.abs(mZoomScale - newScale) > 0.01f) { mIsRunning = view.postDelayed(new Runnable() { @Override public void run() { mZoomScale = newScale; view.evaluateJavascript(Constants.JAVASCRIPT_TEXT_REFLOW, null); mIsRunning = false; } }, 100); } } } @Override public void onReceivedSslError(WebView view, @NonNull final SslErrorHandler handler, SslError error) { AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); builder.setTitle(mActivity.getString(R.string.title_warning)); builder.setMessage(mActivity.getString(R.string.message_untrusted_certificate)) .setCancelable(true) .setPositiveButton(mActivity.getString(R.string.action_yes), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { handler.proceed(); } }) .setNegativeButton(mActivity.getString(R.string.action_no), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { handler.cancel(); } }); AlertDialog alert = builder.create(); if (error.getPrimaryError() == SslError.SSL_UNTRUSTED) { alert.show(); } else { handler.proceed(); } } @Override public void onFormResubmission(WebView view, @NonNull final Message dontResend, final Message resend) { AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); builder.setTitle(mActivity.getString(R.string.title_form_resubmission)); builder.setMessage(mActivity.getString(R.string.message_form_resubmission)) .setCancelable(true) .setPositiveButton(mActivity.getString(R.string.action_yes), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { resend.sendToTarget(); } }) .setNegativeButton(mActivity.getString(R.string.action_no), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { dontResend.sendToTarget(); } }); AlertDialog alert = builder.create(); alert.show(); } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // Check if configured proxy is available if (mBrowserController.proxyIsNotReady()) { // User has been notified return true; } if (mIsIncognitoTab) { return super.shouldOverrideUrlLoading(view, url); } if (url.startsWith("about:")) { return super.shouldOverrideUrlLoading(view, url); } if (url.contains("mailto:")) { MailTo mailTo = MailTo.parse(url); Intent i = Utils.newEmailIntent(mailTo.getTo(), mailTo.getSubject(), mailTo.getBody(), mailTo.getCc()); mActivity.startActivity(i); view.reload(); return true; } else if (url.startsWith("intent://")) { Intent intent; try { intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); } catch (URISyntaxException ex) { return false; } if (intent != null) { try { mActivity.startActivity(intent); } catch (ActivityNotFoundException e) { Log.e(Constants.TAG, "ActivityNotFoundException"); } return true; } } return mIntentUtils.startActivityForUrl(mWebView, url); } } public class LightningChromeClient extends WebChromeClient { final Activity mActivity; LightningChromeClient(Activity activity) { mActivity = activity; } @Override public void onProgressChanged(WebView view, int newProgress) { if (isShown()) { mBrowserController.updateProgress(newProgress); } } @Override public void onReceivedIcon(WebView view, Bitmap icon) { if (icon == null) return; mTitle.setFavicon(icon); mBrowserController.updateTabs(); cacheFavicon(icon); } @Override public void onReceivedTitle(WebView view, String title) { if (title != null && !title.isEmpty()) { mTitle.setTitle(title); } else { mTitle.setTitle(mActivity.getString(R.string.untitled)); } mBrowserController.updateTabs(); if (view != null) mBrowserController.updateHistory(title, view.getUrl()); } @Override public void onGeolocationPermissionsShowPrompt(final String origin, final GeolocationPermissions.Callback callback) { mPermissionsManager.requestPermissionsIfNecessary(mActivity, PERMISSIONS); final boolean remember = true; AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); builder.setTitle(mActivity.getString(R.string.location)); String org; if (origin.length() > 50) { org = origin.subSequence(0, 50) + "..."; } else { org = origin; } builder.setMessage(org + mActivity.getString(R.string.message_location)) .setCancelable(true) .setPositiveButton(mActivity.getString(R.string.action_allow), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { callback.invoke(origin, true, remember); } }) .setNegativeButton(mActivity.getString(R.string.action_dont_allow), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { callback.invoke(origin, false, remember); } }); AlertDialog alert = builder.create(); alert.show(); } @Override public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { mBrowserController.onCreateWindow(resultMsg); return true; } @Override public void onCloseWindow(WebView window) { mBrowserController.onCloseWindow(LightningView.this); } public void openFileChooser(ValueCallback uploadMsg) { mBrowserController.openFileChooser(uploadMsg); } public void openFileChooser(ValueCallback uploadMsg, String acceptType) { mBrowserController.openFileChooser(uploadMsg); } public void openFileChooser(ValueCallback uploadMsg, String acceptType, String capture) { mBrowserController.openFileChooser(uploadMsg); } public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) { mBrowserController.showFileChooser(filePathCallback); return true; } @Override public Bitmap getDefaultVideoPoster() { return mBrowserController.getDefaultVideoPoster(); } @Override public View getVideoLoadingProgressView() { return mBrowserController.getVideoLoadingProgressView(); } @Override public void onHideCustomView() { mBrowserController.onHideCustomView(); super.onHideCustomView(); } @Override public void onShowCustomView(View view, CustomViewCallback callback) { // While these lines might look like they work, in practice, // Full-screen videos won't work correctly. I may test this out some // more // if (view instanceof FrameLayout) { // FrameLayout frame = (FrameLayout) view; // if (frame.getFocusedChild() instanceof VideoView) { // VideoView video = (VideoView) frame.getFocusedChild(); // video.stopPlayback(); // frame.removeView(video); // video.setVisibility(View.GONE); // } // } else { mBrowserController.onShowCustomView(view, callback); // } super.onShowCustomView(view, callback); } @Override @Deprecated public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) { // While these lines might look like they work, in practice, // Full-screen videos won't work correctly. I may test this out some // more // if (view instanceof FrameLayout) { // FrameLayout frame = (FrameLayout) view; // if (frame.getFocusedChild() instanceof VideoView) { // VideoView video = (VideoView) frame.getFocusedChild(); // video.stopPlayback(); // frame.removeView(video); // video.setVisibility(View.GONE); // } // } else { mBrowserController.onShowCustomView(view, callback); // } super.onShowCustomView(view, requestedOrientation, callback); } } public class Title { private Bitmap mFavicon; private String mTitle; private final Bitmap mDefaultIcon; public Title(Context context, boolean darkTheme) { mDefaultIcon = ThemeUtils.getThemedBitmap(context, R.drawable.ic_webpage, darkTheme); mFavicon = mDefaultIcon; mTitle = mActivity.getString(R.string.action_new_tab); } public void setFavicon(Bitmap favicon) { if (favicon == null) { mFavicon = mDefaultIcon; } else { mFavicon = Utils.padFavicon(favicon); } } public void setTitle(String title) { if (title == null) { mTitle = ""; } else { mTitle = title; } } public void setTitleAndFavicon(String title, Bitmap favicon) { mTitle = title; if (favicon == null) { mFavicon = mDefaultIcon; } else { mFavicon = Utils.padFavicon(favicon); } } public String getTitle() { return mTitle; } public Bitmap getFavicon() { return mFavicon; } } private class TouchListener implements OnTouchListener { float mLocation; float mY; int mAction; @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouch(View view, MotionEvent arg1) { if (view == null) return false; if (!view.hasFocus()) { view.requestFocus(); } mAction = arg1.getAction(); mY = arg1.getY(); if (mAction == MotionEvent.ACTION_DOWN) { mLocation = mY; } else if (mAction == MotionEvent.ACTION_UP) { final float distance = (mY - mLocation); if (distance > SCROLL_UP_THRESHOLD && view.getScrollY() < SCROLL_UP_THRESHOLD) { mBrowserController.showActionBar(); } else if (distance < -SCROLL_UP_THRESHOLD) { mBrowserController.hideActionBar(); } mLocation = 0; } mGestureDetector.onTouchEvent(arg1); return false; } } private class CustomGestureListener extends SimpleOnGestureListener { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { int power = (int) (velocityY * 100 / mMaxFling); if (power < -10) { mBrowserController.hideActionBar(); } else if (power > 15) { mBrowserController.showActionBar(); } return super.onFling(e1, e2, velocityX, velocityY); } /** * Without this, onLongPress is not called when user is zooming using * two fingers, but is when using only one. *

* The required behaviour is to not trigger this when the user is * zooming, it shouldn't matter how much fingers the user's using. */ private boolean mCanTriggerLongPress = true; @Override public void onLongPress(MotionEvent e) { if (mCanTriggerLongPress) mBrowserController.onLongPress(); } /** * Is called when the user is swiping after the doubletap, which in our * case means that he is zooming. */ @Override public boolean onDoubleTapEvent(MotionEvent e) { mCanTriggerLongPress = false; return false; } /** * Is called when something is starting being pressed, always before * onLongPress. */ @Override public void onShowPress(MotionEvent e) { mCanTriggerLongPress = true; } } }