You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1118 lines
32 KiB
1118 lines
32 KiB
/* |
|
* Copyright 2014 A.C.R. Development |
|
*/ |
|
|
|
package acr.browser.lightning; |
|
|
|
import java.io.ByteArrayInputStream; |
|
import java.io.File; |
|
import java.io.FileNotFoundException; |
|
import java.io.FileOutputStream; |
|
import java.io.FileWriter; |
|
import java.io.IOException; |
|
import java.net.URISyntaxException; |
|
|
|
import android.annotation.SuppressLint; |
|
import android.app.Activity; |
|
import android.app.AlertDialog; |
|
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.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.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; |
|
|
|
public class LightningView { |
|
|
|
private Title mTitle; |
|
private WebView mWebView; |
|
private BrowserController mBrowserController; |
|
private GestureDetector mGestureDetector; |
|
private Activity mActivity; |
|
private WebSettings mSettings; |
|
private static String mHomepage; |
|
private static String mDefaultUserAgent; |
|
private static Bitmap mWebpageBitmap; |
|
private static PreferenceManager mPreferences; |
|
private AdBlock mAdBlock; |
|
private IntentUtils mIntentUtils; |
|
private final Paint mPaint = new Paint(); |
|
private boolean isForegroundTab; |
|
private boolean mTextReflow = false; |
|
private boolean mInvertPage = false; |
|
private static final int API = android.os.Build.VERSION.SDK_INT; |
|
private static final int SCROLL_UP_THRESHOLD = Utils.convertDpToPixels(10); |
|
private static final int SCROLL_DOWN_THRESHOLD = Utils.convertDpToPixels(100); |
|
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 |
|
}; |
|
|
|
@SuppressWarnings("deprecation") |
|
@SuppressLint("NewApi") |
|
public LightningView(Activity activity, String url, boolean darkTheme) { |
|
|
|
mActivity = activity; |
|
mWebView = new WebView(activity); |
|
mTitle = new Title(activity, darkTheme); |
|
mAdBlock = AdBlock.getInstance(activity.getApplicationContext()); |
|
|
|
mWebpageBitmap = Utils.getWebpageBitmap(activity.getResources(), darkTheme); |
|
|
|
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(activity.getResources().getColor(android.R.color.white)); |
|
|
|
if (API > 15) { |
|
mWebView.setBackground(null); |
|
mWebView.getRootView().setBackground(null); |
|
} else if (mWebView.getRootView() != null) { |
|
mWebView.getRootView().setBackgroundDrawable(null); |
|
} |
|
mWebView.setScrollbarFadingEnabled(true); |
|
mWebView.setSaveEnabled(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(); |
|
mSettings = mWebView.getSettings(); |
|
initializeSettings(mWebView.getSettings(), activity); |
|
initializePreferences(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 { |
|
if (mHomepage.startsWith("about:home")) { |
|
mWebView.loadUrl(getHomepage()); |
|
} else if (mHomepage.startsWith("about:bookmarks")) { |
|
mBrowserController.openBookmarkPage(mWebView); |
|
} else { |
|
mWebView.loadUrl(mHomepage); |
|
} |
|
} |
|
} |
|
|
|
public String getHomepage() { |
|
StringBuilder homepageBuilder = new StringBuilder(); |
|
homepageBuilder.append(HomepageVariables.HEAD); |
|
String icon; |
|
String searchUrl; |
|
switch (mPreferences.getSearchChoice()) { |
|
case 0: |
|
// CUSTOM SEARCH |
|
icon = "file:///android_asset/lightning.png"; |
|
searchUrl = mPreferences.getSearchUrl(); |
|
break; |
|
case 1: |
|
// GOOGLE_SEARCH; |
|
icon = "file:///android_asset/google.png"; |
|
// "https://www.google.com/images/srpr/logo11w.png"; |
|
searchUrl = Constants.GOOGLE_SEARCH; |
|
break; |
|
case 2: |
|
// ANDROID SEARCH; |
|
icon = "file:///android_asset/ask.png"; |
|
searchUrl = Constants.ASK_SEARCH; |
|
break; |
|
case 3: |
|
// BING_SEARCH; |
|
icon = "file:///android_asset/bing.png"; |
|
// "http://upload.wikimedia.org/wikipedia/commons/thumb/b/b1/Bing_logo_%282013%29.svg/500px-Bing_logo_%282013%29.svg.png"; |
|
searchUrl = Constants.BING_SEARCH; |
|
break; |
|
case 4: |
|
// YAHOO_SEARCH; |
|
icon = "file:///android_asset/yahoo.png"; |
|
// "http://upload.wikimedia.org/wikipedia/commons/thumb/2/24/Yahoo%21_logo.svg/799px-Yahoo%21_logo.svg.png"; |
|
searchUrl = Constants.YAHOO_SEARCH; |
|
break; |
|
case 5: |
|
// STARTPAGE_SEARCH; |
|
icon = "file:///android_asset/startpage.png"; |
|
// "https://startpage.com/graphics/startp_logo.gif"; |
|
searchUrl = Constants.STARTPAGE_SEARCH; |
|
break; |
|
case 6: |
|
// STARTPAGE_MOBILE |
|
icon = "file:///android_asset/startpage.png"; |
|
// "https://startpage.com/graphics/startp_logo.gif"; |
|
searchUrl = Constants.STARTPAGE_MOBILE_SEARCH; |
|
case 7: |
|
// DUCK_SEARCH; |
|
icon = "file:///android_asset/duckduckgo.png"; |
|
// "https://duckduckgo.com/assets/logo_homepage.normal.v101.png"; |
|
searchUrl = Constants.DUCK_SEARCH; |
|
break; |
|
case 8: |
|
// DUCK_LITE_SEARCH; |
|
icon = "file:///android_asset/duckduckgo.png"; |
|
// "https://duckduckgo.com/assets/logo_homepage.normal.v101.png"; |
|
searchUrl = Constants.DUCK_LITE_SEARCH; |
|
break; |
|
case 9: |
|
// BAIDU_SEARCH; |
|
icon = "file:///android_asset/baidu.png"; |
|
// "http://www.baidu.com/img/bdlogo.gif"; |
|
searchUrl = Constants.BAIDU_SEARCH; |
|
break; |
|
case 10: |
|
// YANDEX_SEARCH; |
|
icon = "file:///android_asset/yandex.png"; |
|
// "http://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Yandex.svg/600px-Yandex.svg.png"; |
|
searchUrl = Constants.YANDEX_SEARCH; |
|
break; |
|
default: |
|
// DEFAULT GOOGLE_SEARCH; |
|
icon = "file:///android_asset/google.png"; |
|
searchUrl = Constants.GOOGLE_SEARCH; |
|
break; |
|
|
|
} |
|
|
|
homepageBuilder.append(icon); |
|
homepageBuilder.append(HomepageVariables.MIDDLE); |
|
homepageBuilder.append(searchUrl); |
|
homepageBuilder.append(HomepageVariables.END); |
|
|
|
File homepage = new File(mActivity.getFilesDir(), "homepage.html"); |
|
try { |
|
FileWriter hWriter = new FileWriter(homepage, false); |
|
hWriter.write(homepageBuilder.toString()); |
|
hWriter.close(); |
|
} catch (IOException e) { |
|
e.printStackTrace(); |
|
} |
|
|
|
return Constants.FILE + homepage; |
|
} |
|
|
|
@SuppressWarnings("deprecation") |
|
@SuppressLint({ "NewApi", "SetJavaScriptEnabled" }) |
|
public synchronized void initializePreferences(Context context) { |
|
mPreferences = PreferenceManager.getInstance(); |
|
mHomepage = mPreferences.getHomepage(); |
|
mAdBlock.updatePreference(); |
|
if (mSettings == null && mWebView != null) { |
|
mSettings = mWebView.getSettings(); |
|
} else if (mSettings == null) { |
|
return; |
|
} |
|
|
|
setColorMode(mPreferences.getRenderingMode()); |
|
|
|
if (!mBrowserController.isIncognito()) { |
|
mSettings.setGeolocationEnabled(mPreferences.getLocationEnabled()); |
|
} else { |
|
mSettings.setGeolocationEnabled(false); |
|
} |
|
if (API < 19) { |
|
switch (mPreferences.getFlashSupport()) { |
|
case 0: |
|
mSettings.setPluginState(PluginState.OFF); |
|
break; |
|
case 1: |
|
mSettings.setPluginState(PluginState.ON_DEMAND); |
|
break; |
|
case 2: |
|
mSettings.setPluginState(PluginState.ON); |
|
break; |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
switch (mPreferences.getUserAgentChoice()) { |
|
case 1: |
|
if (API > 16) { |
|
mSettings.setUserAgentString(WebSettings.getDefaultUserAgent(context)); |
|
} else { |
|
mSettings.setUserAgentString(mDefaultUserAgent); |
|
} |
|
break; |
|
case 2: |
|
mSettings.setUserAgentString(Constants.DESKTOP_USER_AGENT); |
|
break; |
|
case 3: |
|
mSettings.setUserAgentString(Constants.MOBILE_USER_AGENT); |
|
break; |
|
case 4: |
|
mSettings.setUserAgentString(mPreferences.getUserAgentString(mDefaultUserAgent)); |
|
break; |
|
} |
|
|
|
if (mPreferences.getSavePasswordsEnabled() && !mBrowserController.isIncognito()) { |
|
if (API < 18) { |
|
mSettings.setSavePassword(true); |
|
} |
|
mSettings.setSaveFormData(true); |
|
} else { |
|
if (API < 18) { |
|
mSettings.setSavePassword(false); |
|
} |
|
mSettings.setSaveFormData(false); |
|
} |
|
|
|
if (mPreferences.getJavaScriptEnabled()) { |
|
mSettings.setJavaScriptEnabled(true); |
|
mSettings.setJavaScriptCanOpenWindowsAutomatically(true); |
|
} |
|
|
|
if (mPreferences.getTextReflowEnabled()) { |
|
mTextReflow = true; |
|
mSettings.setLayoutAlgorithm(LayoutAlgorithm.NARROW_COLUMNS); |
|
if (API >= android.os.Build.VERSION_CODES.KITKAT) { |
|
try { |
|
mSettings.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; |
|
mSettings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL); |
|
} |
|
|
|
mSettings.setBlockNetworkImage(mPreferences.getBlockImagesEnabled()); |
|
mSettings.setSupportMultipleWindows(mPreferences.getPopupsEnabled()); |
|
mSettings.setUseWideViewPort(mPreferences.getUseWideViewportEnabled()); |
|
mSettings.setLoadWithOverviewMode(mPreferences.getOverviewModeEnabled()); |
|
switch (mPreferences.getTextSize()) { |
|
case 1: |
|
mSettings.setTextZoom(200); |
|
break; |
|
case 2: |
|
mSettings.setTextZoom(150); |
|
break; |
|
case 3: |
|
mSettings.setTextZoom(100); |
|
break; |
|
case 4: |
|
mSettings.setTextZoom(75); |
|
break; |
|
case 5: |
|
mSettings.setTextZoom(50); |
|
break; |
|
} |
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
|
CookieManager.getInstance().setAcceptThirdPartyCookies(mWebView, |
|
!mPreferences.getBlockThirdPartyCookiesEnabled()); |
|
} |
|
} |
|
|
|
@SuppressWarnings("deprecation") |
|
@SuppressLint({ "SetJavaScriptEnabled", "NewApi" }) |
|
public void initializeSettings(WebSettings settings, Context context) { |
|
if (API < 18) { |
|
settings.setAppCacheMaxSize(Long.MAX_VALUE); |
|
} |
|
if (API < 17) { |
|
settings.setEnableSmoothTransition(true); |
|
} |
|
if (API > 16) { |
|
settings.setMediaPlaybackRequiresUserGesture(true); |
|
} |
|
if (API >= Build.VERSION_CODES.LOLLIPOP && !mBrowserController.isIncognito()) { |
|
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); |
|
} |
|
settings.setDomStorageEnabled(true); |
|
settings.setAppCacheEnabled(true); |
|
settings.setCacheMode(WebSettings.LOAD_DEFAULT); |
|
settings.setDatabaseEnabled(true); |
|
settings.setSupportZoom(true); |
|
settings.setBuiltInZoomControls(true); |
|
settings.setDisplayZoomControls(false); |
|
settings.setAllowContentAccess(true); |
|
settings.setAllowFileAccess(true); |
|
settings.setDefaultTextEncodingName("utf-8"); |
|
if (API > 16) { |
|
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 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 void setForegroundTab(boolean isForeground) { |
|
isForegroundTab = isForeground; |
|
mBrowserController.update(); |
|
} |
|
|
|
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(); |
|
} |
|
} |
|
|
|
public void setHardwareRendering() { |
|
mWebView.setLayerType(View.LAYER_TYPE_HARDWARE, mPaint); |
|
} |
|
|
|
public void setNormalRendering() { |
|
mWebView.setLayerType(View.LAYER_TYPE_NONE, null); |
|
} |
|
|
|
public void setSoftwareRendering() { |
|
mWebView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); |
|
} |
|
|
|
public 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 void clearCache(boolean disk) { |
|
if (mWebView != null) { |
|
mWebView.clearCache(disk); |
|
} |
|
} |
|
|
|
public synchronized void reload() { |
|
if (mWebView != null) { |
|
mWebView.reload(); |
|
} |
|
} |
|
|
|
private void cacheFavicon(Bitmap icon) { |
|
String hash = String.valueOf(Utils.getDomainName(getUrl()).hashCode()); |
|
Log.d(Constants.TAG, "Caching icon for " + Utils.getDomainName(getUrl())); |
|
File image = new File(mActivity.getCacheDir(), hash + ".png"); |
|
try { |
|
FileOutputStream fos = new FileOutputStream(image); |
|
icon.compress(Bitmap.CompressFormat.PNG, 100, fos); |
|
fos.flush(); |
|
fos.close(); |
|
} catch (FileNotFoundException e) { |
|
e.printStackTrace(); |
|
} catch (IOException e) { |
|
e.printStackTrace(); |
|
} |
|
} |
|
|
|
@SuppressWarnings("deprecation") |
|
@SuppressLint("NewApi") |
|
public synchronized void find(String text) { |
|
if (mWebView != null) { |
|
if (API > 16) { |
|
mWebView.findAllAsync(text); |
|
} else { |
|
mWebView.findAll(text); |
|
} |
|
} |
|
} |
|
|
|
public Activity getActivity() { |
|
return mActivity; |
|
} |
|
|
|
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(); |
|
} |
|
|
|
public WebView getWebView() { |
|
return mWebView; |
|
} |
|
|
|
public Bitmap getFavicon() { |
|
return mTitle.getFavicon(); |
|
} |
|
|
|
public synchronized void loadUrl(String url) { |
|
if (mWebView != null) { |
|
mWebView.loadUrl(url); |
|
} |
|
} |
|
|
|
public synchronized void invalidate() { |
|
if (mWebView != null) { |
|
mWebView.invalidate(); |
|
} |
|
} |
|
|
|
public String getTitle() { |
|
return mTitle.getTitle(); |
|
} |
|
|
|
public String getUrl() { |
|
if (mWebView != null) { |
|
return mWebView.getUrl(); |
|
} else { |
|
return ""; |
|
} |
|
} |
|
|
|
public class LightningWebClient extends WebViewClient { |
|
|
|
Context mActivity; |
|
|
|
LightningWebClient(Context context) { |
|
mActivity = context; |
|
} |
|
|
|
@Override |
|
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { |
|
if (mAdBlock.isAd(request.getUrl().getHost())) { |
|
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); |
|
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.update(); |
|
} |
|
|
|
@Override |
|
public void onPageStarted(WebView view, String url, Bitmap favicon) { |
|
if (isShown()) { |
|
mBrowserController.updateUrl(url, false); |
|
mBrowserController.showActionBar(); |
|
} |
|
mTitle.setFavicon(mWebpageBitmap); |
|
mBrowserController.update(); |
|
} |
|
|
|
@Override |
|
public void onReceivedHttpAuthRequest(final WebView view, 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, 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, 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) { |
|
if (mBrowserController.isIncognito()) { |
|
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(mActivity, mailTo.getTo(), mailTo.getSubject(), |
|
mailTo.getBody(), mailTo.getCc()); |
|
mActivity.startActivity(i); |
|
view.reload(); |
|
return true; |
|
} else if (url.startsWith("intent://")) { |
|
Intent intent = null; |
|
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 { |
|
|
|
Context mActivity; |
|
|
|
LightningChromeClient(Context context) { |
|
mActivity = context; |
|
} |
|
|
|
@Override |
|
public void onProgressChanged(WebView view, int newProgress) { |
|
if (isShown()) { |
|
mBrowserController.updateProgress(newProgress); |
|
} |
|
} |
|
|
|
@Override |
|
public void onReceivedIcon(WebView view, Bitmap icon) { |
|
mTitle.setFavicon(icon); |
|
mBrowserController.update(); |
|
cacheFavicon(icon); |
|
} |
|
|
|
@Override |
|
public void onReceivedTitle(WebView view, String title) { |
|
if (!title.isEmpty()) { |
|
mTitle.setTitle(title); |
|
} else { |
|
mTitle.setTitle(mActivity.getString(R.string.untitled)); |
|
} |
|
mBrowserController.update(); |
|
mBrowserController.updateHistory(title, view.getUrl()); |
|
} |
|
|
|
@Override |
|
public void onGeolocationPermissionsShowPrompt(final String origin, |
|
final GeolocationPermissions.Callback callback) { |
|
final boolean remember = true; |
|
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); |
|
builder.setTitle(mActivity.getString(R.string.location)); |
|
String org = null; |
|
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(isUserGesture, resultMsg); |
|
return true; |
|
} |
|
|
|
@Override |
|
public void onCloseWindow(WebView window) { |
|
// TODO Auto-generated method stub |
|
super.onCloseWindow(window); |
|
} |
|
|
|
public void openFileChooser(ValueCallback<Uri> uploadMsg) { |
|
mBrowserController.openFileChooser(uploadMsg); |
|
} |
|
|
|
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) { |
|
mBrowserController.openFileChooser(uploadMsg); |
|
} |
|
|
|
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) { |
|
mBrowserController.openFileChooser(uploadMsg); |
|
} |
|
|
|
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> 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 { |
|
Activity activity = mBrowserController.getActivity(); |
|
mBrowserController.onShowCustomView(view, activity.getRequestedOrientation(), 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, requestedOrientation, callback); |
|
|
|
// } |
|
|
|
super.onShowCustomView(view, requestedOrientation, callback); |
|
} |
|
} |
|
|
|
public class Title { |
|
|
|
private Bitmap mFavicon; |
|
private String mTitle; |
|
private Bitmap mDefaultIcon; |
|
|
|
public Title(Context context, boolean darkTheme) { |
|
mDefaultIcon = Utils.getWebpageBitmap(context.getResources(), 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 && !view.hasFocus()) { |
|
view.requestFocus(); |
|
} |
|
mAction = arg1.getAction(); |
|
mY = arg1.getY(); |
|
if (mAction == MotionEvent.ACTION_DOWN) { |
|
mLocation = mY; |
|
} else if (mAction == MotionEvent.ACTION_UP) { |
|
if ((mY - mLocation) > SCROLL_DOWN_THRESHOLD) { |
|
if (mWebView.getScrollY() != 0) { |
|
mBrowserController.showActionBar(); |
|
} else { |
|
mBrowserController.toggleActionBar(); |
|
} |
|
} else if ((mY - mLocation) < -SCROLL_UP_THRESHOLD) { |
|
mBrowserController.hideActionBar(); |
|
} |
|
mLocation = 0; |
|
} |
|
mGestureDetector.onTouchEvent(arg1); |
|
return false; |
|
} |
|
} |
|
|
|
private class CustomGestureListener extends SimpleOnGestureListener { |
|
|
|
/** |
|
* 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; |
|
} |
|
} |
|
}
|
|
|