/* * Tint Browser for Android * * Copyright (C) 2012 - to infinity and beyond J. Devauchelle and contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 3 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ package org.tint.ui.components; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; import java.util.UUID; import org.apache.http.HeaderElement; import org.apache.http.NameValuePair; import org.apache.http.message.BasicHeader; import org.tint.R; import org.tint.addons.AddonMenuItem; import org.tint.controllers.Controller; import org.tint.model.DownloadItem; import org.tint.ui.activities.TintBrowserActivity; import org.tint.ui.dialogs.DownloadConfirmDialog; import org.tint.ui.fragments.BaseWebViewFragment; import org.tint.ui.managers.UIManager; import org.tint.utils.ApplicationUtils; import org.tint.utils.Constants; import org.tint.utils.UrlUtils; import android.annotation.SuppressLint; import android.app.DownloadManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import android.preference.PreferenceManager; import android.util.AttributeSet; import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.MenuItem; import android.view.View; import android.webkit.CookieManager; import android.webkit.DownloadListener; import android.webkit.WebSettings; import android.webkit.WebSettings.PluginState; import android.webkit.WebView; import android.widget.Toast; public class CustomWebView extends WebView implements DownloadListener, DownloadConfirmDialog.IUserActionListener { private UIManager mUIManager; private Context mContext; private BaseWebViewFragment mParentFragment; private boolean mIsLoading = false; private boolean mPrivateBrowsing = false; private static boolean sMethodsLoaded = false; private static Method sWebSettingsSetProperty = null; public CustomWebView(UIManager uiManager, boolean privateBrowsing) { this(uiManager.getMainActivity(), null, privateBrowsing); mUIManager = uiManager; } // Used only by edit mode (UI designer) public CustomWebView(Context context, AttributeSet attrs) { super(context, attrs, android.R.attr.webViewStyle); mContext = context; } public CustomWebView(Context context, AttributeSet attrs, boolean privateBrowsing) { super(context, attrs, android.R.attr.webViewStyle); mPrivateBrowsing = privateBrowsing; mContext = context; if (!isInEditMode()) { if (!sMethodsLoaded) { loadMethods(); } loadSettings(); setupContextMenu(); } } public void setParentFragment(BaseWebViewFragment parentFragment) { mParentFragment = parentFragment; } public BaseWebViewFragment getParentFragment() { return mParentFragment; } public UUID getParentFragmentUUID() { return mParentFragment.getUUID(); } public boolean isLoading() { return mIsLoading; } public boolean isPrivateBrowsingEnabled() { return mPrivateBrowsing; } @Override public void loadUrl(String url) { if ((url != null) && (url.length() > 0)) { if (UrlUtils.isUrl(url)) { url = UrlUtils.checkUrl(url); } else { url = UrlUtils.getSearchUrl(mContext, url); } if (Constants.URL_ABOUT_TUTORIAL.equals(url)) { loadDataWithBaseURL( "file:///android_asset/", ApplicationUtils.getStringFromRawResource(mContext, R.raw.phone_tutorial_html), "text/html", "UTF-8", Constants.URL_ABOUT_TUTORIAL); } else { super.loadUrl(url); } } } public void loadRawUrl(String url) { super.loadUrl(url); } public void onClientPageStarted(String url) { mIsLoading = true; if (!isPrivateBrowsingEnabled()) { Controller.getInstance().getAddonManager().onPageStarted(mContext, this, url); } } public void onClientPageFinished(String url) { mIsLoading = false; if (!isPrivateBrowsingEnabled()) { Controller.getInstance().getAddonManager().onPageFinished(mContext, this, url); } mUIManager.onClientPageFinished(this, url); } @SuppressLint("SetJavaScriptEnabled") public void loadSettings() { WebSettings settings = getSettings(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); settings.setJavaScriptEnabled(prefs.getBoolean(Constants.PREFERENCE_ENABLE_JAVASCRIPT, true)); settings.setLoadsImagesAutomatically(prefs.getBoolean(Constants.PREFERENCE_ENABLE_IMAGES, true)); settings.setUseWideViewPort(prefs.getBoolean(Constants.PREFERENCE_USE_WIDE_VIEWPORT, true)); settings.setLoadWithOverviewMode(prefs.getBoolean(Constants.PREFERENCE_LOAD_WITH_OVERVIEW, false)); settings.setGeolocationEnabled(prefs.getBoolean(Constants.PREFERENCE_ENABLE_GEOLOCATION, true)); settings.setSaveFormData(prefs.getBoolean(Constants.PREFERENCE_REMEMBER_FORM_DATA, true)); settings.setSavePassword(prefs.getBoolean(Constants.PREFERENCE_REMEMBER_PASSWORDS, true)); settings.setTextZoom(prefs.getInt(Constants.PREFERENCE_TEXT_SCALING, 100)); int minimumFontSize = prefs.getInt(Constants.PREFERENCE_MINIMUM_FONT_SIZE, 1); settings.setMinimumFontSize(minimumFontSize); settings.setMinimumLogicalFontSize(minimumFontSize); boolean useInvertedDisplay = prefs.getBoolean(Constants.PREFERENCE_INVERTED_DISPLAY, false); setWebSettingsProperty(settings, "inverted", useInvertedDisplay ? "true" : "false"); if (useInvertedDisplay) { setWebSettingsProperty(settings, "inverted_contrast", Float.toString(prefs.getInt(Constants.PREFERENCE_INVERTED_DISPLAY_CONTRAST, 100) / 100f)); } settings.setUserAgentString(prefs.getString(Constants.PREFERENCE_USER_AGENT, Constants.USER_AGENT_ANDROID)); settings.setPluginState(PluginState.valueOf(prefs.getString(Constants.PREFERENCE_PLUGINS, PluginState.ON_DEMAND.toString()))); CookieManager.getInstance().setAcceptCookie(prefs.getBoolean(Constants.PREFERENCE_ACCEPT_COOKIES, true)); settings.setSupportZoom(true); settings.setDisplayZoomControls(false); settings.setBuiltInZoomControls(true); settings.setSupportMultipleWindows(true); settings.setEnableSmoothTransition(true); if (mPrivateBrowsing) { settings.setGeolocationEnabled(false); settings.setSaveFormData(false); settings.setSavePassword(false); settings.setAppCacheEnabled(false); settings.setDatabaseEnabled(false); settings.setDomStorageEnabled(false); } else { // HTML5 API flags settings.setAppCacheEnabled(true); settings.setDatabaseEnabled(true); settings.setDomStorageEnabled(true); // HTML5 configuration settings. settings.setAppCacheMaxSize(3 * 1024 * 1024); settings.setAppCachePath(mContext.getDir("appcache", 0).getPath()); settings.setDatabasePath(mContext.getDir("databases", 0).getPath()); settings.setGeolocationDatabasePath(mContext.getDir("geolocation", 0).getPath()); } setLongClickable(true); setDownloadListener(this); } @Override public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) { DownloadItem item = new DownloadItem(url); item.addRequestHeader("Cookie", CookieManager.getInstance().getCookie(url)); String fileName = item.getFileName(); BasicHeader header = new BasicHeader("Content-Disposition", contentDisposition); HeaderElement[] helelms = header.getElements(); if (helelms.length > 0) { HeaderElement helem = helelms[0]; if (helem.getName().equalsIgnoreCase("attachment")) { NameValuePair nmv = helem.getParameterByName("filename"); if (nmv != null) { fileName = nmv.getValue(); } } } item.setFilename(fileName); item.setIncognito(isPrivateBrowsingEnabled()); DownloadConfirmDialog dialog = new DownloadConfirmDialog(getContext()) .setDownloadItem(item) .setCallbackListener(this); dialog.show(); } @Override public void onAcceptDownload(DownloadItem item) { long id = ((DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE)).enqueue(item); item.setId(id); Controller.getInstance().getDownloadsList().add(item); Toast.makeText(mContext, String.format(mContext.getString(R.string.DownloadStart), item.getFileName()), Toast.LENGTH_SHORT).show(); } @Override public void onDenyDownload() { } private Intent createIntent(String action, int actionId, int hitTestResult, String url) { Intent result = new Intent(getContext(), TintBrowserActivity.class); result.setAction(action); result.putExtra(Constants.EXTRA_ACTION_ID, actionId); result.putExtra(Constants.EXTRA_HIT_TEST_RESULT, hitTestResult); result.putExtra(Constants.EXTRA_URL, url); result.putExtra(Constants.EXTRA_INCOGNITO, isPrivateBrowsingEnabled()); return result; } private void createContributedContextMenu(ContextMenu menu, int hitTestResult, String url) { if (!isPrivateBrowsingEnabled()) { MenuItem item; List<AddonMenuItem> contributedItems = Controller.getInstance().getAddonManager().getContributedLinkContextMenuItems(this, hitTestResult, url); for (AddonMenuItem contribution : contributedItems) { item = menu.add(0, contribution.getAddon().getMenuId(), 0, contribution.getMenuItem()); item.setIntent(createIntent(Constants.ACTION_BROWSER_CONTEXT_MENU, contribution.getAddon().getMenuId(), hitTestResult, url)); } } } private void setupContextMenu() { setOnCreateContextMenuListener(new OnCreateContextMenuListener() { @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { HitTestResult result = ((WebView) v).getHitTestResult(); int resultType = result.getType(); if ((resultType == HitTestResult.ANCHOR_TYPE) || (resultType == HitTestResult.IMAGE_ANCHOR_TYPE) || (resultType == HitTestResult.SRC_ANCHOR_TYPE) || (resultType == HitTestResult.SRC_IMAGE_ANCHOR_TYPE)) { MenuItem item = menu.add(0, TintBrowserActivity.CONTEXT_MENU_OPEN, 0, R.string.ContextMenuOpen); item.setIntent(createIntent(Constants.ACTION_BROWSER_CONTEXT_MENU, TintBrowserActivity.CONTEXT_MENU_OPEN, resultType, result.getExtra())); item = menu.add(0, TintBrowserActivity.CONTEXT_MENU_OPEN_IN_NEW_TAB, 0, R.string.ContextMenuOpenNewTab); item.setIntent(createIntent(Constants.ACTION_BROWSER_CONTEXT_MENU, TintBrowserActivity.CONTEXT_MENU_OPEN_IN_NEW_TAB, resultType, result.getExtra())); item = menu.add(0, TintBrowserActivity.CONTEXT_MENU_OPEN_IN_BACKGROUND, 0, R.string.ContextMenuOpenInBackground); item.setIntent(createIntent(Constants.ACTION_BROWSER_CONTEXT_MENU, TintBrowserActivity.CONTEXT_MENU_OPEN_IN_BACKGROUND, resultType, result.getExtra())); item = menu.add(0, TintBrowserActivity.CONTEXT_MENU_COPY, 0, R.string.ContextMenuCopyLinkUrl); item.setIntent(createIntent(Constants.ACTION_BROWSER_CONTEXT_MENU, TintBrowserActivity.CONTEXT_MENU_COPY, resultType, result.getExtra())); item = menu.add(0, TintBrowserActivity.CONTEXT_MENU_DOWNLOAD, 0, R.string.ContextMenuDownload); item.setIntent(createIntent(Constants.ACTION_BROWSER_CONTEXT_MENU, TintBrowserActivity.CONTEXT_MENU_DOWNLOAD, resultType, result.getExtra())); item = menu.add(0, TintBrowserActivity.CONTEXT_MENU_SHARE, 0, R.string.ContextMenuShareLinkUrl); item.setIntent(createIntent(Constants.ACTION_BROWSER_CONTEXT_MENU, TintBrowserActivity.CONTEXT_MENU_SHARE, resultType, result.getExtra())); createContributedContextMenu(menu, resultType, result.getExtra()); menu.setHeaderTitle(result.getExtra()); } else if (resultType == HitTestResult.IMAGE_TYPE) { MenuItem item = menu.add(0, TintBrowserActivity.CONTEXT_MENU_OPEN, 0, R.string.ContextMenuViewImage); item.setIntent(createIntent(Constants.ACTION_BROWSER_CONTEXT_MENU, TintBrowserActivity.CONTEXT_MENU_OPEN, resultType, result.getExtra())); item = menu.add(0, TintBrowserActivity.CONTEXT_MENU_OPEN_IN_NEW_TAB, 0, R.string.ContextMenuViewImageInNewTab); item.setIntent(createIntent(Constants.ACTION_BROWSER_CONTEXT_MENU, TintBrowserActivity.CONTEXT_MENU_OPEN_IN_NEW_TAB, resultType, result.getExtra())); item = menu.add(0, TintBrowserActivity.CONTEXT_MENU_COPY, 0, R.string.ContextMenuCopyImageUrl); item.setIntent(createIntent(Constants.ACTION_BROWSER_CONTEXT_MENU, TintBrowserActivity.CONTEXT_MENU_COPY, resultType, result.getExtra())); item = menu.add(0, TintBrowserActivity.CONTEXT_MENU_DOWNLOAD, 0, R.string.ContextMenuDownloadImage); item.setIntent(createIntent(Constants.ACTION_BROWSER_CONTEXT_MENU, TintBrowserActivity.CONTEXT_MENU_DOWNLOAD, resultType, result.getExtra())); item = menu.add(0, TintBrowserActivity.CONTEXT_MENU_SHARE, 0, R.string.ContextMenuShareImageUrl); item.setIntent(createIntent(Constants.ACTION_BROWSER_CONTEXT_MENU, TintBrowserActivity.CONTEXT_MENU_SHARE, resultType, result.getExtra())); createContributedContextMenu(menu, resultType, result.getExtra()); menu.setHeaderTitle(result.getExtra()); } else if (resultType == HitTestResult.EMAIL_TYPE) { Intent sendMail = new Intent(Intent.ACTION_VIEW, Uri.parse(WebView.SCHEME_MAILTO + result.getExtra())); MenuItem item = menu.add(0, TintBrowserActivity.CONTEXT_MENU_SEND_MAIL, 0, R.string.ContextMenuSendEmail); item.setIntent(sendMail); item = menu.add(0, TintBrowserActivity.CONTEXT_MENU_COPY, 0, R.string.ContextMenuCopyEmailUrl); item.setIntent(createIntent(Constants.ACTION_BROWSER_CONTEXT_MENU, TintBrowserActivity.CONTEXT_MENU_COPY, resultType, result.getExtra())); item = menu.add(0, TintBrowserActivity.CONTEXT_MENU_SHARE, 0, R.string.ContextMenuShareEmailUrl); item.setIntent(createIntent(Constants.ACTION_BROWSER_CONTEXT_MENU, TintBrowserActivity.CONTEXT_MENU_SHARE, resultType, result.getExtra())); createContributedContextMenu(menu, resultType, result.getExtra()); menu.setHeaderTitle(result.getExtra()); } } }); } private static void loadMethods() { try { // 15 is ICS 2nd release. if (android.os.Build.VERSION.SDK_INT > 15) { // WebSettings became abstract in JB, and "setProperty" moved to the concrete class, WebSettingsClassic, // not present in the SDK. So we must look for the class first, then for the methods. ClassLoader classLoader = CustomWebView.class.getClassLoader(); Class<?> webSettingsClassicClass = classLoader.loadClass("android.webkit.WebSettingsClassic"); sWebSettingsSetProperty = webSettingsClassicClass.getMethod("setProperty", new Class[] { String.class, String.class }); } else { sWebSettingsSetProperty = WebSettings.class.getMethod("setProperty", new Class[] { String.class, String.class }); } } catch (NoSuchMethodException e) { Log.e("CustomWebView", "loadMethods(): " + e.getMessage()); sWebSettingsSetProperty = null; } catch (ClassNotFoundException e) { Log.e("CustomWebView", "loadMethods(): " + e.getMessage()); sWebSettingsSetProperty = null; } sMethodsLoaded = true; } private static void setWebSettingsProperty(WebSettings settings, String key, String value) { if (sWebSettingsSetProperty != null) { try { sWebSettingsSetProperty.invoke(settings, key, value); } catch (IllegalArgumentException e) { Log.e("CustomWebView", "setWebSettingsProperty(): " + e.getMessage()); } catch (IllegalAccessException e) { Log.e("CustomWebView", "setWebSettingsProperty(): " + e.getMessage()); } catch (InvocationTargetException e) { Log.e("CustomWebView", "setWebSettingsProperty(): " + e.getMessage()); } } } }