| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| */ |
| package org.apache.cordova.inappbrowser; |
| |
| import android.annotation.SuppressLint; |
| import android.annotation.TargetApi; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ResolveInfo; |
| import android.os.Parcelable; |
| import android.provider.Browser; |
| import android.content.res.Resources; |
| import android.graphics.Bitmap; |
| import android.graphics.drawable.Drawable; |
| import android.graphics.PorterDuff; |
| import android.graphics.PorterDuffColorFilter; |
| import android.graphics.Color; |
| import android.net.http.SslError; |
| import android.net.Uri; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.text.InputType; |
| import android.util.TypedValue; |
| import android.view.Gravity; |
| import android.view.KeyEvent; |
| import android.view.View; |
| import android.view.Window; |
| import android.view.WindowManager; |
| import android.view.WindowManager.LayoutParams; |
| import android.view.inputmethod.EditorInfo; |
| import android.view.inputmethod.InputMethodManager; |
| import android.webkit.CookieManager; |
| import android.webkit.CookieSyncManager; |
| import android.webkit.HttpAuthHandler; |
| import android.webkit.JavascriptInterface; |
| 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.WebView; |
| import android.webkit.WebViewClient; |
| import android.widget.EditText; |
| import android.widget.ImageButton; |
| import android.widget.ImageView; |
| import android.widget.LinearLayout; |
| import android.widget.RelativeLayout; |
| import android.widget.TextView; |
| |
| import org.apache.cordova.CallbackContext; |
| import org.apache.cordova.Config; |
| import org.apache.cordova.CordovaArgs; |
| import org.apache.cordova.CordovaHttpAuthHandler; |
| import org.apache.cordova.CordovaPlugin; |
| import org.apache.cordova.CordovaWebView; |
| import org.apache.cordova.LOG; |
| import org.apache.cordova.PluginManager; |
| import org.apache.cordova.PluginResult; |
| import org.json.JSONException; |
| import org.json.JSONObject; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.HashMap; |
| import java.util.StringTokenizer; |
| |
| @SuppressLint("SetJavaScriptEnabled") |
| public class InAppBrowser extends CordovaPlugin { |
| |
| private static final String NULL = "null"; |
| protected static final String LOG_TAG = "InAppBrowser"; |
| private static final String SELF = "_self"; |
| private static final String SYSTEM = "_system"; |
| private static final String EXIT_EVENT = "exit"; |
| private static final String LOCATION = "location"; |
| private static final String ZOOM = "zoom"; |
| private static final String HIDDEN = "hidden"; |
| private static final String LOAD_START_EVENT = "loadstart"; |
| private static final String LOAD_STOP_EVENT = "loadstop"; |
| private static final String LOAD_ERROR_EVENT = "loaderror"; |
| private static final String MESSAGE_EVENT = "message"; |
| private static final String CLEAR_ALL_CACHE = "clearcache"; |
| private static final String CLEAR_SESSION_CACHE = "clearsessioncache"; |
| private static final String HARDWARE_BACK_BUTTON = "hardwareback"; |
| private static final String MEDIA_PLAYBACK_REQUIRES_USER_ACTION = "mediaPlaybackRequiresUserAction"; |
| private static final String SHOULD_PAUSE = "shouldPauseOnSuspend"; |
| private static final Boolean DEFAULT_HARDWARE_BACK = true; |
| private static final String USER_WIDE_VIEW_PORT = "useWideViewPort"; |
| private static final String TOOLBAR_COLOR = "toolbarcolor"; |
| private static final String CLOSE_BUTTON_CAPTION = "closebuttoncaption"; |
| private static final String CLOSE_BUTTON_COLOR = "closebuttoncolor"; |
| private static final String LEFT_TO_RIGHT = "lefttoright"; |
| private static final String HIDE_NAVIGATION = "hidenavigationbuttons"; |
| private static final String NAVIGATION_COLOR = "navigationbuttoncolor"; |
| private static final String HIDE_URL = "hideurlbar"; |
| private static final String FOOTER = "footer"; |
| private static final String FOOTER_COLOR = "footercolor"; |
| private static final String BEFORELOAD = "beforeload"; |
| private static final String FULLSCREEN = "fullscreen"; |
| |
| private static final List customizableOptions = Arrays.asList(CLOSE_BUTTON_CAPTION, TOOLBAR_COLOR, NAVIGATION_COLOR, CLOSE_BUTTON_COLOR, FOOTER_COLOR); |
| |
| private InAppBrowserDialog dialog; |
| private WebView inAppWebView; |
| private EditText edittext; |
| private CallbackContext callbackContext; |
| private boolean showLocationBar = true; |
| private boolean showZoomControls = true; |
| private boolean openWindowHidden = false; |
| private boolean clearAllCache = false; |
| private boolean clearSessionCache = false; |
| private boolean hadwareBackButton = true; |
| private boolean mediaPlaybackRequiresUserGesture = false; |
| private boolean shouldPauseInAppBrowser = false; |
| private boolean useWideViewPort = true; |
| private ValueCallback<Uri> mUploadCallback; |
| private ValueCallback<Uri[]> mUploadCallbackLollipop; |
| private final static int FILECHOOSER_REQUESTCODE = 1; |
| private final static int FILECHOOSER_REQUESTCODE_LOLLIPOP = 2; |
| private String closeButtonCaption = ""; |
| private String closeButtonColor = ""; |
| private boolean leftToRight = false; |
| private int toolbarColor = android.graphics.Color.LTGRAY; |
| private boolean hideNavigationButtons = false; |
| private String navigationButtonColor = ""; |
| private boolean hideUrlBar = false; |
| private boolean showFooter = false; |
| private String footerColor = ""; |
| private String beforeload = ""; |
| private boolean fullscreen = true; |
| private String[] allowedSchemes; |
| private InAppBrowserClient currentClient; |
| |
| /** |
| * Executes the request and returns PluginResult. |
| * |
| * @param action the action to execute. |
| * @param args JSONArry of arguments for the plugin. |
| * @param callbackContext the callbackContext used when calling back into JavaScript. |
| * @return A PluginResult object with a status and message. |
| */ |
| public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException { |
| if (action.equals("open")) { |
| this.callbackContext = callbackContext; |
| final String url = args.getString(0); |
| String t = args.optString(1); |
| if (t == null || t.equals("") || t.equals(NULL)) { |
| t = SELF; |
| } |
| final String target = t; |
| final HashMap<String, String> features = parseFeature(args.optString(2)); |
| |
| LOG.d(LOG_TAG, "target = " + target); |
| |
| this.cordova.getActivity().runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| String result = ""; |
| // SELF |
| if (SELF.equals(target)) { |
| LOG.d(LOG_TAG, "in self"); |
| /* This code exists for compatibility between 3.x and 4.x versions of Cordova. |
| * Previously the Config class had a static method, isUrlWhitelisted(). That |
| * responsibility has been moved to the plugins, with an aggregating method in |
| * PluginManager. |
| */ |
| Boolean shouldAllowNavigation = null; |
| if (url.startsWith("javascript:")) { |
| shouldAllowNavigation = true; |
| } |
| if (shouldAllowNavigation == null) { |
| try { |
| Method iuw = Config.class.getMethod("isUrlWhiteListed", String.class); |
| shouldAllowNavigation = (Boolean)iuw.invoke(null, url); |
| } catch (NoSuchMethodException e) { |
| LOG.d(LOG_TAG, e.getLocalizedMessage()); |
| } catch (IllegalAccessException e) { |
| LOG.d(LOG_TAG, e.getLocalizedMessage()); |
| } catch (InvocationTargetException e) { |
| LOG.d(LOG_TAG, e.getLocalizedMessage()); |
| } |
| } |
| if (shouldAllowNavigation == null) { |
| try { |
| Method gpm = webView.getClass().getMethod("getPluginManager"); |
| PluginManager pm = (PluginManager)gpm.invoke(webView); |
| Method san = pm.getClass().getMethod("shouldAllowNavigation", String.class); |
| shouldAllowNavigation = (Boolean)san.invoke(pm, url); |
| } catch (NoSuchMethodException e) { |
| LOG.d(LOG_TAG, e.getLocalizedMessage()); |
| } catch (IllegalAccessException e) { |
| LOG.d(LOG_TAG, e.getLocalizedMessage()); |
| } catch (InvocationTargetException e) { |
| LOG.d(LOG_TAG, e.getLocalizedMessage()); |
| } |
| } |
| // load in webview |
| if (Boolean.TRUE.equals(shouldAllowNavigation)) { |
| LOG.d(LOG_TAG, "loading in webview"); |
| webView.loadUrl(url); |
| } |
| //Load the dialer |
| else if (url.startsWith(WebView.SCHEME_TEL)) |
| { |
| try { |
| LOG.d(LOG_TAG, "loading in dialer"); |
| Intent intent = new Intent(Intent.ACTION_DIAL); |
| intent.setData(Uri.parse(url)); |
| cordova.getActivity().startActivity(intent); |
| } catch (android.content.ActivityNotFoundException e) { |
| LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString()); |
| } |
| } |
| // load in InAppBrowser |
| else { |
| LOG.d(LOG_TAG, "loading in InAppBrowser"); |
| result = showWebPage(url, features); |
| } |
| } |
| // SYSTEM |
| else if (SYSTEM.equals(target)) { |
| LOG.d(LOG_TAG, "in system"); |
| result = openExternal(url); |
| } |
| // BLANK - or anything else |
| else { |
| LOG.d(LOG_TAG, "in blank"); |
| result = showWebPage(url, features); |
| } |
| |
| PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, result); |
| pluginResult.setKeepCallback(true); |
| callbackContext.sendPluginResult(pluginResult); |
| } |
| }); |
| } |
| else if (action.equals("close")) { |
| closeDialog(); |
| } |
| else if (action.equals("loadAfterBeforeload")) { |
| if (beforeload == null) { |
| LOG.e(LOG_TAG, "unexpected loadAfterBeforeload called without feature beforeload=yes"); |
| } |
| final String url = args.getString(0); |
| this.cordova.getActivity().runOnUiThread(new Runnable() { |
| @SuppressLint("NewApi") |
| @Override |
| public void run() { |
| if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O) { |
| currentClient.waitForBeforeload = false; |
| inAppWebView.setWebViewClient(currentClient); |
| } else { |
| ((InAppBrowserClient)inAppWebView.getWebViewClient()).waitForBeforeload = false; |
| } |
| inAppWebView.loadUrl(url); |
| } |
| }); |
| } |
| else if (action.equals("injectScriptCode")) { |
| String jsWrapper = null; |
| if (args.getBoolean(1)) { |
| jsWrapper = String.format("(function(){prompt(JSON.stringify([eval(%%s)]), 'gap-iab://%s')})()", callbackContext.getCallbackId()); |
| } |
| injectDeferredObject(args.getString(0), jsWrapper); |
| } |
| else if (action.equals("injectScriptFile")) { |
| String jsWrapper; |
| if (args.getBoolean(1)) { |
| jsWrapper = String.format("(function(d) { var c = d.createElement('script'); c.src = %%s; c.onload = function() { prompt('', 'gap-iab://%s'); }; d.body.appendChild(c); })(document)", callbackContext.getCallbackId()); |
| } else { |
| jsWrapper = "(function(d) { var c = d.createElement('script'); c.src = %s; d.body.appendChild(c); })(document)"; |
| } |
| injectDeferredObject(args.getString(0), jsWrapper); |
| } |
| else if (action.equals("injectStyleCode")) { |
| String jsWrapper; |
| if (args.getBoolean(1)) { |
| jsWrapper = String.format("(function(d) { var c = d.createElement('style'); c.innerHTML = %%s; d.body.appendChild(c); prompt('', 'gap-iab://%s');})(document)", callbackContext.getCallbackId()); |
| } else { |
| jsWrapper = "(function(d) { var c = d.createElement('style'); c.innerHTML = %s; d.body.appendChild(c); })(document)"; |
| } |
| injectDeferredObject(args.getString(0), jsWrapper); |
| } |
| else if (action.equals("injectStyleFile")) { |
| String jsWrapper; |
| if (args.getBoolean(1)) { |
| jsWrapper = String.format("(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %%s; d.head.appendChild(c); prompt('', 'gap-iab://%s');})(document)", callbackContext.getCallbackId()); |
| } else { |
| jsWrapper = "(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %s; d.head.appendChild(c); })(document)"; |
| } |
| injectDeferredObject(args.getString(0), jsWrapper); |
| } |
| else if (action.equals("show")) { |
| this.cordova.getActivity().runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| if (dialog != null && !cordova.getActivity().isFinishing()) { |
| dialog.show(); |
| } |
| } |
| }); |
| PluginResult pluginResult = new PluginResult(PluginResult.Status.OK); |
| pluginResult.setKeepCallback(true); |
| this.callbackContext.sendPluginResult(pluginResult); |
| } |
| else if (action.equals("hide")) { |
| this.cordova.getActivity().runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| if (dialog != null && !cordova.getActivity().isFinishing()) { |
| dialog.hide(); |
| } |
| } |
| }); |
| PluginResult pluginResult = new PluginResult(PluginResult.Status.OK); |
| pluginResult.setKeepCallback(true); |
| this.callbackContext.sendPluginResult(pluginResult); |
| } |
| else { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Called when the view navigates. |
| */ |
| @Override |
| public void onReset() { |
| closeDialog(); |
| } |
| |
| /** |
| * Called when the system is about to start resuming a previous activity. |
| */ |
| @Override |
| public void onPause(boolean multitasking) { |
| if (shouldPauseInAppBrowser) { |
| inAppWebView.onPause(); |
| } |
| } |
| |
| /** |
| * Called when the activity will start interacting with the user. |
| */ |
| @Override |
| public void onResume(boolean multitasking) { |
| if (shouldPauseInAppBrowser) { |
| inAppWebView.onResume(); |
| } |
| } |
| |
| /** |
| * Called by AccelBroker when listener is to be shut down. |
| * Stop listener. |
| */ |
| public void onDestroy() { |
| closeDialog(); |
| } |
| |
| /** |
| * Inject an object (script or style) into the InAppBrowser WebView. |
| * |
| * This is a helper method for the inject{Script|Style}{Code|File} API calls, which |
| * provides a consistent method for injecting JavaScript code into the document. |
| * |
| * If a wrapper string is supplied, then the source string will be JSON-encoded (adding |
| * quotes) and wrapped using string formatting. (The wrapper string should have a single |
| * '%s' marker) |
| * |
| * @param source The source object (filename or script/style text) to inject into |
| * the document. |
| * @param jsWrapper A JavaScript string to wrap the source string in, so that the object |
| * is properly injected, or null if the source string is JavaScript text |
| * which should be executed directly. |
| */ |
| private void injectDeferredObject(String source, String jsWrapper) { |
| if (inAppWebView!=null) { |
| String scriptToInject; |
| if (jsWrapper != null) { |
| org.json.JSONArray jsonEsc = new org.json.JSONArray(); |
| jsonEsc.put(source); |
| String jsonRepr = jsonEsc.toString(); |
| String jsonSourceString = jsonRepr.substring(1, jsonRepr.length()-1); |
| scriptToInject = String.format(jsWrapper, jsonSourceString); |
| } else { |
| scriptToInject = source; |
| } |
| final String finalScriptToInject = scriptToInject; |
| this.cordova.getActivity().runOnUiThread(new Runnable() { |
| @SuppressLint("NewApi") |
| @Override |
| public void run() { |
| if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { |
| // This action will have the side-effect of blurring the currently focused element |
| inAppWebView.loadUrl("javascript:" + finalScriptToInject); |
| } else { |
| inAppWebView.evaluateJavascript(finalScriptToInject, null); |
| } |
| } |
| }); |
| } else { |
| LOG.d(LOG_TAG, "Can't inject code into the system browser"); |
| } |
| } |
| |
| /** |
| * Put the list of features into a hash map |
| * |
| * @param optString |
| * @return |
| */ |
| private HashMap<String, String> parseFeature(String optString) { |
| if (optString.equals(NULL)) { |
| return null; |
| } else { |
| HashMap<String, String> map = new HashMap<String, String>(); |
| StringTokenizer features = new StringTokenizer(optString, ","); |
| StringTokenizer option; |
| while(features.hasMoreElements()) { |
| option = new StringTokenizer(features.nextToken(), "="); |
| if (option.hasMoreElements()) { |
| String key = option.nextToken(); |
| String value = option.nextToken(); |
| if (!customizableOptions.contains(key)) { |
| value = value.equals("yes") || value.equals("no") ? value : "yes"; |
| } |
| map.put(key, value); |
| } |
| } |
| return map; |
| } |
| } |
| |
| /** |
| * Display a new browser with the specified URL. |
| * |
| * @param url the url to load. |
| * @return "" if ok, or error message. |
| */ |
| public String openExternal(String url) { |
| try { |
| Intent intent = null; |
| intent = new Intent(Intent.ACTION_VIEW); |
| // Omitting the MIME type for file: URLs causes "No Activity found to handle Intent". |
| // Adding the MIME type to http: URLs causes them to not be handled by the downloader. |
| Uri uri = Uri.parse(url); |
| if ("file".equals(uri.getScheme())) { |
| intent.setDataAndType(uri, webView.getResourceApi().getMimeType(uri)); |
| } else { |
| intent.setData(uri); |
| } |
| intent.putExtra(Browser.EXTRA_APPLICATION_ID, cordova.getActivity().getPackageName()); |
| // CB-10795: Avoid circular loops by preventing it from opening in the current app |
| this.openExternalExcludeCurrentApp(intent); |
| return ""; |
| // not catching FileUriExposedException explicitly because buildtools<24 doesn't know about it |
| } catch (java.lang.RuntimeException e) { |
| LOG.d(LOG_TAG, "InAppBrowser: Error loading url "+url+":"+ e.toString()); |
| return e.toString(); |
| } |
| } |
| |
| /** |
| * Opens the intent, providing a chooser that excludes the current app to avoid |
| * circular loops. |
| */ |
| private void openExternalExcludeCurrentApp(Intent intent) { |
| String currentPackage = cordova.getActivity().getPackageName(); |
| boolean hasCurrentPackage = false; |
| |
| PackageManager pm = cordova.getActivity().getPackageManager(); |
| List<ResolveInfo> activities = pm.queryIntentActivities(intent, 0); |
| ArrayList<Intent> targetIntents = new ArrayList<Intent>(); |
| |
| for (ResolveInfo ri : activities) { |
| if (!currentPackage.equals(ri.activityInfo.packageName)) { |
| Intent targetIntent = (Intent)intent.clone(); |
| targetIntent.setPackage(ri.activityInfo.packageName); |
| targetIntents.add(targetIntent); |
| } |
| else { |
| hasCurrentPackage = true; |
| } |
| } |
| |
| // If the current app package isn't a target for this URL, then use |
| // the normal launch behavior |
| if (hasCurrentPackage == false || targetIntents.size() == 0) { |
| this.cordova.getActivity().startActivity(intent); |
| } |
| // If there's only one possible intent, launch it directly |
| else if (targetIntents.size() == 1) { |
| this.cordova.getActivity().startActivity(targetIntents.get(0)); |
| } |
| // Otherwise, show a custom chooser without the current app listed |
| else if (targetIntents.size() > 0) { |
| Intent chooser = Intent.createChooser(targetIntents.remove(targetIntents.size()-1), null); |
| chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetIntents.toArray(new Parcelable[] {})); |
| this.cordova.getActivity().startActivity(chooser); |
| } |
| } |
| |
| /** |
| * Closes the dialog |
| */ |
| public void closeDialog() { |
| this.cordova.getActivity().runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| final WebView childView = inAppWebView; |
| // The JS protects against multiple calls, so this should happen only when |
| // closeDialog() is called by other native code. |
| if (childView == null) { |
| return; |
| } |
| |
| childView.setWebViewClient(new WebViewClient() { |
| // NB: wait for about:blank before dismissing |
| public void onPageFinished(WebView view, String url) { |
| if (dialog != null && !cordova.getActivity().isFinishing()) { |
| dialog.dismiss(); |
| dialog = null; |
| } |
| } |
| }); |
| // NB: From SDK 19: "If you call methods on WebView from any thread |
| // other than your app's UI thread, it can cause unexpected results." |
| // http://developer.android.com/guide/webapps/migrating.html#Threads |
| childView.loadUrl("about:blank"); |
| |
| try { |
| JSONObject obj = new JSONObject(); |
| obj.put("type", EXIT_EVENT); |
| sendUpdate(obj, false); |
| } catch (JSONException ex) { |
| LOG.d(LOG_TAG, "Should never happen"); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Checks to see if it is possible to go back one page in history, then does so. |
| */ |
| public void goBack() { |
| if (this.inAppWebView.canGoBack()) { |
| this.inAppWebView.goBack(); |
| } |
| } |
| |
| /** |
| * Can the web browser go back? |
| * @return boolean |
| */ |
| public boolean canGoBack() { |
| return this.inAppWebView.canGoBack(); |
| } |
| |
| /** |
| * Has the user set the hardware back button to go back |
| * @return boolean |
| */ |
| public boolean hardwareBack() { |
| return hadwareBackButton; |
| } |
| |
| /** |
| * Checks to see if it is possible to go forward one page in history, then does so. |
| */ |
| private void goForward() { |
| if (this.inAppWebView.canGoForward()) { |
| this.inAppWebView.goForward(); |
| } |
| } |
| |
| /** |
| * Navigate to the new page |
| * |
| * @param url to load |
| */ |
| private void navigate(String url) { |
| InputMethodManager imm = (InputMethodManager)this.cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); |
| imm.hideSoftInputFromWindow(edittext.getWindowToken(), 0); |
| |
| if (!url.startsWith("http") && !url.startsWith("file:")) { |
| this.inAppWebView.loadUrl("http://" + url); |
| } else { |
| this.inAppWebView.loadUrl(url); |
| } |
| this.inAppWebView.requestFocus(); |
| } |
| |
| |
| /** |
| * Should we show the location bar? |
| * |
| * @return boolean |
| */ |
| private boolean getShowLocationBar() { |
| return this.showLocationBar; |
| } |
| |
| private InAppBrowser getInAppBrowser() { |
| return this; |
| } |
| |
| /** |
| * Display a new browser with the specified URL. |
| * |
| * @param url the url to load. |
| * @param features jsonObject |
| */ |
| public String showWebPage(final String url, HashMap<String, String> features) { |
| // Determine if we should hide the location bar. |
| showLocationBar = true; |
| showZoomControls = true; |
| openWindowHidden = false; |
| mediaPlaybackRequiresUserGesture = false; |
| |
| if (features != null) { |
| String show = features.get(LOCATION); |
| if (show != null) { |
| showLocationBar = show.equals("yes") ? true : false; |
| } |
| if(showLocationBar) { |
| String hideNavigation = features.get(HIDE_NAVIGATION); |
| String hideUrl = features.get(HIDE_URL); |
| if(hideNavigation != null) hideNavigationButtons = hideNavigation.equals("yes") ? true : false; |
| if(hideUrl != null) hideUrlBar = hideUrl.equals("yes") ? true : false; |
| } |
| String zoom = features.get(ZOOM); |
| if (zoom != null) { |
| showZoomControls = zoom.equals("yes") ? true : false; |
| } |
| String hidden = features.get(HIDDEN); |
| if (hidden != null) { |
| openWindowHidden = hidden.equals("yes") ? true : false; |
| } |
| String hardwareBack = features.get(HARDWARE_BACK_BUTTON); |
| if (hardwareBack != null) { |
| hadwareBackButton = hardwareBack.equals("yes") ? true : false; |
| } else { |
| hadwareBackButton = DEFAULT_HARDWARE_BACK; |
| } |
| String mediaPlayback = features.get(MEDIA_PLAYBACK_REQUIRES_USER_ACTION); |
| if (mediaPlayback != null) { |
| mediaPlaybackRequiresUserGesture = mediaPlayback.equals("yes") ? true : false; |
| } |
| String cache = features.get(CLEAR_ALL_CACHE); |
| if (cache != null) { |
| clearAllCache = cache.equals("yes") ? true : false; |
| } else { |
| cache = features.get(CLEAR_SESSION_CACHE); |
| if (cache != null) { |
| clearSessionCache = cache.equals("yes") ? true : false; |
| } |
| } |
| String shouldPause = features.get(SHOULD_PAUSE); |
| if (shouldPause != null) { |
| shouldPauseInAppBrowser = shouldPause.equals("yes") ? true : false; |
| } |
| String wideViewPort = features.get(USER_WIDE_VIEW_PORT); |
| if (wideViewPort != null ) { |
| useWideViewPort = wideViewPort.equals("yes") ? true : false; |
| } |
| String closeButtonCaptionSet = features.get(CLOSE_BUTTON_CAPTION); |
| if (closeButtonCaptionSet != null) { |
| closeButtonCaption = closeButtonCaptionSet; |
| } |
| String closeButtonColorSet = features.get(CLOSE_BUTTON_COLOR); |
| if (closeButtonColorSet != null) { |
| closeButtonColor = closeButtonColorSet; |
| } |
| String leftToRightSet = features.get(LEFT_TO_RIGHT); |
| leftToRight = leftToRightSet != null && leftToRightSet.equals("yes"); |
| |
| String toolbarColorSet = features.get(TOOLBAR_COLOR); |
| if (toolbarColorSet != null) { |
| toolbarColor = android.graphics.Color.parseColor(toolbarColorSet); |
| } |
| String navigationButtonColorSet = features.get(NAVIGATION_COLOR); |
| if (navigationButtonColorSet != null) { |
| navigationButtonColor = navigationButtonColorSet; |
| } |
| String showFooterSet = features.get(FOOTER); |
| if (showFooterSet != null) { |
| showFooter = showFooterSet.equals("yes") ? true : false; |
| } |
| String footerColorSet = features.get(FOOTER_COLOR); |
| if (footerColorSet != null) { |
| footerColor = footerColorSet; |
| } |
| if (features.get(BEFORELOAD) != null) { |
| beforeload = features.get(BEFORELOAD); |
| } |
| String fullscreenSet = features.get(FULLSCREEN); |
| if (fullscreenSet != null) { |
| fullscreen = fullscreenSet.equals("yes") ? true : false; |
| } |
| } |
| |
| final CordovaWebView thatWebView = this.webView; |
| |
| // Create dialog in new thread |
| Runnable runnable = new Runnable() { |
| /** |
| * Convert our DIP units to Pixels |
| * |
| * @return int |
| */ |
| private int dpToPixels(int dipValue) { |
| int value = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, |
| (float) dipValue, |
| cordova.getActivity().getResources().getDisplayMetrics() |
| ); |
| |
| return value; |
| } |
| |
| private View createCloseButton(int id) { |
| View _close; |
| Resources activityRes = cordova.getActivity().getResources(); |
| |
| if (closeButtonCaption != "") { |
| // Use TextView for text |
| TextView close = new TextView(cordova.getActivity()); |
| close.setText(closeButtonCaption); |
| close.setTextSize(20); |
| if (closeButtonColor != "") close.setTextColor(android.graphics.Color.parseColor(closeButtonColor)); |
| close.setGravity(android.view.Gravity.CENTER_VERTICAL); |
| close.setPadding(this.dpToPixels(10), 0, this.dpToPixels(10), 0); |
| _close = close; |
| } |
| else { |
| ImageButton close = new ImageButton(cordova.getActivity()); |
| int closeResId = activityRes.getIdentifier("ic_action_remove", "drawable", cordova.getActivity().getPackageName()); |
| Drawable closeIcon = activityRes.getDrawable(closeResId); |
| if (closeButtonColor != "") close.setColorFilter(android.graphics.Color.parseColor(closeButtonColor)); |
| close.setImageDrawable(closeIcon); |
| close.setScaleType(ImageView.ScaleType.FIT_CENTER); |
| if (Build.VERSION.SDK_INT >= 16) |
| close.getAdjustViewBounds(); |
| |
| _close = close; |
| } |
| |
| RelativeLayout.LayoutParams closeLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); |
| if (leftToRight) closeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT); |
| else closeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); |
| _close.setLayoutParams(closeLayoutParams); |
| |
| if (Build.VERSION.SDK_INT >= 16) |
| _close.setBackground(null); |
| else |
| _close.setBackgroundDrawable(null); |
| |
| _close.setContentDescription("Close Button"); |
| _close.setId(Integer.valueOf(id)); |
| _close.setOnClickListener(new View.OnClickListener() { |
| public void onClick(View v) { |
| closeDialog(); |
| } |
| }); |
| |
| return _close; |
| } |
| |
| @SuppressLint("NewApi") |
| public void run() { |
| |
| // CB-6702 InAppBrowser hangs when opening more than one instance |
| if (dialog != null) { |
| dialog.dismiss(); |
| }; |
| |
| // Let's create the main dialog |
| dialog = new InAppBrowserDialog(cordova.getActivity(), android.R.style.Theme_NoTitleBar); |
| dialog.getWindow().getAttributes().windowAnimations = android.R.style.Animation_Dialog; |
| dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); |
| if (fullscreen) { |
| dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); |
| } |
| dialog.setCancelable(true); |
| dialog.setInAppBroswer(getInAppBrowser()); |
| |
| // Main container layout |
| LinearLayout main = new LinearLayout(cordova.getActivity()); |
| main.setOrientation(LinearLayout.VERTICAL); |
| |
| // Toolbar layout |
| RelativeLayout toolbar = new RelativeLayout(cordova.getActivity()); |
| //Please, no more black! |
| toolbar.setBackgroundColor(toolbarColor); |
| toolbar.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, this.dpToPixels(44))); |
| toolbar.setPadding(this.dpToPixels(2), this.dpToPixels(2), this.dpToPixels(2), this.dpToPixels(2)); |
| if (leftToRight) { |
| toolbar.setHorizontalGravity(Gravity.LEFT); |
| } else { |
| toolbar.setHorizontalGravity(Gravity.RIGHT); |
| } |
| toolbar.setVerticalGravity(Gravity.TOP); |
| |
| // Action Button Container layout |
| RelativeLayout actionButtonContainer = new RelativeLayout(cordova.getActivity()); |
| RelativeLayout.LayoutParams actionButtonLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); |
| if (leftToRight) actionButtonLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); |
| else actionButtonLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT); |
| actionButtonContainer.setLayoutParams(actionButtonLayoutParams); |
| actionButtonContainer.setHorizontalGravity(Gravity.LEFT); |
| actionButtonContainer.setVerticalGravity(Gravity.CENTER_VERTICAL); |
| actionButtonContainer.setId(leftToRight ? Integer.valueOf(5) : Integer.valueOf(1)); |
| |
| // Back button |
| ImageButton back = new ImageButton(cordova.getActivity()); |
| RelativeLayout.LayoutParams backLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); |
| backLayoutParams.addRule(RelativeLayout.ALIGN_LEFT); |
| back.setLayoutParams(backLayoutParams); |
| back.setContentDescription("Back Button"); |
| back.setId(Integer.valueOf(2)); |
| Resources activityRes = cordova.getActivity().getResources(); |
| int backResId = activityRes.getIdentifier("ic_action_previous_item", "drawable", cordova.getActivity().getPackageName()); |
| Drawable backIcon = activityRes.getDrawable(backResId); |
| if (navigationButtonColor != "") back.setColorFilter(android.graphics.Color.parseColor(navigationButtonColor)); |
| if (Build.VERSION.SDK_INT >= 16) |
| back.setBackground(null); |
| else |
| back.setBackgroundDrawable(null); |
| back.setImageDrawable(backIcon); |
| back.setScaleType(ImageView.ScaleType.FIT_CENTER); |
| back.setPadding(0, this.dpToPixels(10), 0, this.dpToPixels(10)); |
| if (Build.VERSION.SDK_INT >= 16) |
| back.getAdjustViewBounds(); |
| |
| back.setOnClickListener(new View.OnClickListener() { |
| public void onClick(View v) { |
| goBack(); |
| } |
| }); |
| |
| // Forward button |
| ImageButton forward = new ImageButton(cordova.getActivity()); |
| RelativeLayout.LayoutParams forwardLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); |
| forwardLayoutParams.addRule(RelativeLayout.RIGHT_OF, 2); |
| forward.setLayoutParams(forwardLayoutParams); |
| forward.setContentDescription("Forward Button"); |
| forward.setId(Integer.valueOf(3)); |
| int fwdResId = activityRes.getIdentifier("ic_action_next_item", "drawable", cordova.getActivity().getPackageName()); |
| Drawable fwdIcon = activityRes.getDrawable(fwdResId); |
| if (navigationButtonColor != "") forward.setColorFilter(android.graphics.Color.parseColor(navigationButtonColor)); |
| if (Build.VERSION.SDK_INT >= 16) |
| forward.setBackground(null); |
| else |
| forward.setBackgroundDrawable(null); |
| forward.setImageDrawable(fwdIcon); |
| forward.setScaleType(ImageView.ScaleType.FIT_CENTER); |
| forward.setPadding(0, this.dpToPixels(10), 0, this.dpToPixels(10)); |
| if (Build.VERSION.SDK_INT >= 16) |
| forward.getAdjustViewBounds(); |
| |
| forward.setOnClickListener(new View.OnClickListener() { |
| public void onClick(View v) { |
| goForward(); |
| } |
| }); |
| |
| // Edit Text Box |
| edittext = new EditText(cordova.getActivity()); |
| RelativeLayout.LayoutParams textLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); |
| textLayoutParams.addRule(RelativeLayout.RIGHT_OF, 1); |
| textLayoutParams.addRule(RelativeLayout.LEFT_OF, 5); |
| edittext.setLayoutParams(textLayoutParams); |
| edittext.setId(Integer.valueOf(4)); |
| edittext.setSingleLine(true); |
| edittext.setText(url); |
| edittext.setInputType(InputType.TYPE_TEXT_VARIATION_URI); |
| edittext.setImeOptions(EditorInfo.IME_ACTION_GO); |
| edittext.setInputType(InputType.TYPE_NULL); // Will not except input... Makes the text NON-EDITABLE |
| edittext.setOnKeyListener(new View.OnKeyListener() { |
| public boolean onKey(View v, int keyCode, KeyEvent event) { |
| // If the event is a key-down event on the "enter" button |
| if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { |
| navigate(edittext.getText().toString()); |
| return true; |
| } |
| return false; |
| } |
| }); |
| |
| |
| // Header Close/Done button |
| int closeButtonId = leftToRight ? 1 : 5; |
| View close = createCloseButton(closeButtonId); |
| toolbar.addView(close); |
| |
| // Footer |
| RelativeLayout footer = new RelativeLayout(cordova.getActivity()); |
| int _footerColor; |
| if(footerColor != "") { |
| _footerColor = Color.parseColor(footerColor); |
| } else { |
| _footerColor = android.graphics.Color.LTGRAY; |
| } |
| footer.setBackgroundColor(_footerColor); |
| RelativeLayout.LayoutParams footerLayout = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, this.dpToPixels(44)); |
| footerLayout.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE); |
| footer.setLayoutParams(footerLayout); |
| if (closeButtonCaption != "") footer.setPadding(this.dpToPixels(8), this.dpToPixels(8), this.dpToPixels(8), this.dpToPixels(8)); |
| footer.setHorizontalGravity(Gravity.LEFT); |
| footer.setVerticalGravity(Gravity.BOTTOM); |
| |
| View footerClose = createCloseButton(7); |
| footer.addView(footerClose); |
| |
| |
| // WebView |
| inAppWebView = new WebView(cordova.getActivity()); |
| inAppWebView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); |
| inAppWebView.setId(Integer.valueOf(6)); |
| // File Chooser Implemented ChromeClient |
| inAppWebView.setWebChromeClient(new InAppChromeClient(thatWebView) { |
| // For Android 5.0+ |
| public boolean onShowFileChooser (WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) |
| { |
| LOG.d(LOG_TAG, "File Chooser 5.0+"); |
| // If callback exists, finish it. |
| if(mUploadCallbackLollipop != null) { |
| mUploadCallbackLollipop.onReceiveValue(null); |
| } |
| mUploadCallbackLollipop = filePathCallback; |
| |
| // Create File Chooser Intent |
| Intent content = new Intent(Intent.ACTION_GET_CONTENT); |
| content.addCategory(Intent.CATEGORY_OPENABLE); |
| content.setType("*/*"); |
| |
| // Run cordova startActivityForResult |
| cordova.startActivityForResult(InAppBrowser.this, Intent.createChooser(content, "Select File"), FILECHOOSER_REQUESTCODE_LOLLIPOP); |
| return true; |
| } |
| |
| // For Android 4.1+ |
| public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) |
| { |
| LOG.d(LOG_TAG, "File Chooser 4.1+"); |
| // Call file chooser for Android 3.0+ |
| openFileChooser(uploadMsg, acceptType); |
| } |
| |
| // For Android 3.0+ |
| public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) |
| { |
| LOG.d(LOG_TAG, "File Chooser 3.0+"); |
| mUploadCallback = uploadMsg; |
| Intent content = new Intent(Intent.ACTION_GET_CONTENT); |
| content.addCategory(Intent.CATEGORY_OPENABLE); |
| |
| // run startActivityForResult |
| cordova.startActivityForResult(InAppBrowser.this, Intent.createChooser(content, "Select File"), FILECHOOSER_REQUESTCODE); |
| } |
| |
| }); |
| currentClient = new InAppBrowserClient(thatWebView, edittext, beforeload); |
| inAppWebView.setWebViewClient(currentClient); |
| WebSettings settings = inAppWebView.getSettings(); |
| settings.setJavaScriptEnabled(true); |
| settings.setJavaScriptCanOpenWindowsAutomatically(true); |
| settings.setBuiltInZoomControls(showZoomControls); |
| settings.setPluginState(android.webkit.WebSettings.PluginState.ON); |
| |
| // Add postMessage interface |
| class JsObject { |
| @JavascriptInterface |
| public void postMessage(String data) { |
| try { |
| JSONObject obj = new JSONObject(); |
| obj.put("type", MESSAGE_EVENT); |
| obj.put("data", new JSONObject(data)); |
| sendUpdate(obj, true); |
| } catch (JSONException ex) { |
| LOG.e(LOG_TAG, "data object passed to postMessage has caused a JSON error."); |
| } |
| } |
| } |
| |
| if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { |
| settings.setMediaPlaybackRequiresUserGesture(mediaPlaybackRequiresUserGesture); |
| inAppWebView.addJavascriptInterface(new JsObject(), "cordova_iab"); |
| } |
| |
| String overrideUserAgent = preferences.getString("OverrideUserAgent", null); |
| String appendUserAgent = preferences.getString("AppendUserAgent", null); |
| |
| if (overrideUserAgent != null) { |
| settings.setUserAgentString(overrideUserAgent); |
| } |
| if (appendUserAgent != null) { |
| settings.setUserAgentString(settings.getUserAgentString() + appendUserAgent); |
| } |
| |
| //Toggle whether this is enabled or not! |
| Bundle appSettings = cordova.getActivity().getIntent().getExtras(); |
| boolean enableDatabase = appSettings == null ? true : appSettings.getBoolean("InAppBrowserStorageEnabled", true); |
| if (enableDatabase) { |
| String databasePath = cordova.getActivity().getApplicationContext().getDir("inAppBrowserDB", Context.MODE_PRIVATE).getPath(); |
| settings.setDatabasePath(databasePath); |
| settings.setDatabaseEnabled(true); |
| } |
| settings.setDomStorageEnabled(true); |
| |
| if (clearAllCache) { |
| CookieManager.getInstance().removeAllCookie(); |
| } else if (clearSessionCache) { |
| CookieManager.getInstance().removeSessionCookie(); |
| } |
| |
| // Enable Thirdparty Cookies on >=Android 5.0 device |
| if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { |
| CookieManager.getInstance().setAcceptThirdPartyCookies(inAppWebView,true); |
| } |
| |
| inAppWebView.loadUrl(url); |
| inAppWebView.setId(Integer.valueOf(6)); |
| inAppWebView.getSettings().setLoadWithOverviewMode(true); |
| inAppWebView.getSettings().setUseWideViewPort(useWideViewPort); |
| inAppWebView.requestFocus(); |
| inAppWebView.requestFocusFromTouch(); |
| |
| // Add the back and forward buttons to our action button container layout |
| actionButtonContainer.addView(back); |
| actionButtonContainer.addView(forward); |
| |
| // Add the views to our toolbar if they haven't been disabled |
| if (!hideNavigationButtons) toolbar.addView(actionButtonContainer); |
| if (!hideUrlBar) toolbar.addView(edittext); |
| |
| // Don't add the toolbar if its been disabled |
| if (getShowLocationBar()) { |
| // Add our toolbar to our main view/layout |
| main.addView(toolbar); |
| } |
| |
| // Add our webview to our main view/layout |
| RelativeLayout webViewLayout = new RelativeLayout(cordova.getActivity()); |
| webViewLayout.addView(inAppWebView); |
| main.addView(webViewLayout); |
| |
| // Don't add the footer unless it's been enabled |
| if (showFooter) { |
| webViewLayout.addView(footer); |
| } |
| |
| WindowManager.LayoutParams lp = new WindowManager.LayoutParams(); |
| lp.copyFrom(dialog.getWindow().getAttributes()); |
| lp.width = WindowManager.LayoutParams.MATCH_PARENT; |
| lp.height = WindowManager.LayoutParams.MATCH_PARENT; |
| |
| if (dialog != null) { |
| dialog.setContentView(main); |
| dialog.show(); |
| dialog.getWindow().setAttributes(lp); |
| } |
| // the goal of openhidden is to load the url and not display it |
| // Show() needs to be called to cause the URL to be loaded |
| if (openWindowHidden && dialog != null) { |
| dialog.hide(); |
| } |
| } |
| }; |
| this.cordova.getActivity().runOnUiThread(runnable); |
| return ""; |
| } |
| |
| /** |
| * Create a new plugin success result and send it back to JavaScript |
| * |
| * @param obj a JSONObject contain event payload information |
| */ |
| private void sendUpdate(JSONObject obj, boolean keepCallback) { |
| sendUpdate(obj, keepCallback, PluginResult.Status.OK); |
| } |
| |
| /** |
| * Create a new plugin result and send it back to JavaScript |
| * |
| * @param obj a JSONObject contain event payload information |
| * @param status the status code to return to the JavaScript environment |
| */ |
| private void sendUpdate(JSONObject obj, boolean keepCallback, PluginResult.Status status) { |
| if (callbackContext != null) { |
| PluginResult result = new PluginResult(status, obj); |
| result.setKeepCallback(keepCallback); |
| callbackContext.sendPluginResult(result); |
| if (!keepCallback) { |
| callbackContext = null; |
| } |
| } |
| } |
| |
| /** |
| * Receive File Data from File Chooser |
| * |
| * @param requestCode the requested code from chromeclient |
| * @param resultCode the result code returned from android system |
| * @param intent the data from android file chooser |
| */ |
| public void onActivityResult(int requestCode, int resultCode, Intent intent) { |
| // For Android >= 5.0 |
| if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
| LOG.d(LOG_TAG, "onActivityResult (For Android >= 5.0)"); |
| // If RequestCode or Callback is Invalid |
| if(requestCode != FILECHOOSER_REQUESTCODE_LOLLIPOP || mUploadCallbackLollipop == null) { |
| super.onActivityResult(requestCode, resultCode, intent); |
| return; |
| } |
| mUploadCallbackLollipop.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, intent)); |
| mUploadCallbackLollipop = null; |
| } |
| // For Android < 5.0 |
| else { |
| LOG.d(LOG_TAG, "onActivityResult (For Android < 5.0)"); |
| // If RequestCode or Callback is Invalid |
| if(requestCode != FILECHOOSER_REQUESTCODE || mUploadCallback == null) { |
| super.onActivityResult(requestCode, resultCode, intent); |
| return; |
| } |
| |
| if (null == mUploadCallback) return; |
| Uri result = intent == null || resultCode != cordova.getActivity().RESULT_OK ? null : intent.getData(); |
| |
| mUploadCallback.onReceiveValue(result); |
| mUploadCallback = null; |
| } |
| } |
| |
| /** |
| * The webview client receives notifications about appView |
| */ |
| public class InAppBrowserClient extends WebViewClient { |
| EditText edittext; |
| CordovaWebView webView; |
| String beforeload; |
| boolean waitForBeforeload; |
| |
| /** |
| * Constructor. |
| * |
| * @param webView |
| * @param mEditText |
| */ |
| public InAppBrowserClient(CordovaWebView webView, EditText mEditText, String beforeload) { |
| this.webView = webView; |
| this.edittext = mEditText; |
| this.beforeload = beforeload; |
| this.waitForBeforeload = beforeload != null; |
| } |
| |
| /** |
| * Override the URL that should be loaded |
| * |
| * Legacy (deprecated in API 24) |
| * For Android 6 and below. |
| * |
| * @param webView |
| * @param url |
| */ |
| @SuppressWarnings("deprecation") |
| @Override |
| public boolean shouldOverrideUrlLoading(WebView webView, String url) { |
| return shouldOverrideUrlLoading(url, null); |
| } |
| |
| /** |
| * Override the URL that should be loaded |
| * |
| * New (added in API 24) |
| * For Android 7 and above. |
| * |
| * @param webView |
| * @param request |
| */ |
| @TargetApi(Build.VERSION_CODES.N) |
| @Override |
| public boolean shouldOverrideUrlLoading(WebView webView, WebResourceRequest request) { |
| return shouldOverrideUrlLoading(request.getUrl().toString(), request.getMethod()); |
| } |
| |
| /** |
| * Override the URL that should be loaded |
| * |
| * This handles a small subset of all the URIs that would be encountered. |
| * |
| * @param url |
| * @param method |
| */ |
| public boolean shouldOverrideUrlLoading(String url, String method) { |
| boolean override = false; |
| boolean useBeforeload = false; |
| String errorMessage = null; |
| |
| if (beforeload.equals("yes") && method == null) { |
| useBeforeload = true; |
| } else if(beforeload.equals("yes") |
| //TODO handle POST requests then this condition can be removed: |
| && !method.equals("POST")) |
| { |
| useBeforeload = true; |
| } else if(beforeload.equals("get") && (method == null || method.equals("GET"))) { |
| useBeforeload = true; |
| } else if(beforeload.equals("post") && (method == null || method.equals("POST"))) { |
| //TODO handle POST requests |
| errorMessage = "beforeload doesn't yet support POST requests"; |
| } |
| |
| // On first URL change, initiate JS callback. Only after the beforeload event, continue. |
| if (useBeforeload && this.waitForBeforeload) { |
| if(sendBeforeLoad(url, method)) { |
| return true; |
| } |
| } |
| |
| if(errorMessage != null) { |
| try { |
| LOG.e(LOG_TAG, errorMessage); |
| JSONObject obj = new JSONObject(); |
| obj.put("type", LOAD_ERROR_EVENT); |
| obj.put("url", url); |
| obj.put("code", -1); |
| obj.put("message", errorMessage); |
| sendUpdate(obj, true, PluginResult.Status.ERROR); |
| } catch(Exception e) { |
| LOG.e(LOG_TAG, "Error sending loaderror for " + url + ": " + e.toString()); |
| } |
| } |
| |
| if (url.startsWith(WebView.SCHEME_TEL)) { |
| try { |
| Intent intent = new Intent(Intent.ACTION_DIAL); |
| intent.setData(Uri.parse(url)); |
| cordova.getActivity().startActivity(intent); |
| override = true; |
| } catch (android.content.ActivityNotFoundException e) { |
| LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString()); |
| } |
| } else if (url.startsWith("geo:") || url.startsWith(WebView.SCHEME_MAILTO) || url.startsWith("market:") || url.startsWith("intent:")) { |
| try { |
| Intent intent = new Intent(Intent.ACTION_VIEW); |
| intent.setData(Uri.parse(url)); |
| cordova.getActivity().startActivity(intent); |
| override = true; |
| } catch (android.content.ActivityNotFoundException e) { |
| LOG.e(LOG_TAG, "Error with " + url + ": " + e.toString()); |
| } |
| } |
| // If sms:5551212?body=This is the message |
| else if (url.startsWith("sms:")) { |
| try { |
| Intent intent = new Intent(Intent.ACTION_VIEW); |
| |
| // Get address |
| String address = null; |
| int parmIndex = url.indexOf('?'); |
| if (parmIndex == -1) { |
| address = url.substring(4); |
| } else { |
| address = url.substring(4, parmIndex); |
| |
| // If body, then set sms body |
| Uri uri = Uri.parse(url); |
| String query = uri.getQuery(); |
| if (query != null) { |
| if (query.startsWith("body=")) { |
| intent.putExtra("sms_body", query.substring(5)); |
| } |
| } |
| } |
| intent.setData(Uri.parse("sms:" + address)); |
| intent.putExtra("address", address); |
| intent.setType("vnd.android-dir/mms-sms"); |
| cordova.getActivity().startActivity(intent); |
| override = true; |
| } catch (android.content.ActivityNotFoundException e) { |
| LOG.e(LOG_TAG, "Error sending sms " + url + ":" + e.toString()); |
| } |
| } |
| // Test for whitelisted custom scheme names like mycoolapp:// or twitteroauthresponse:// (Twitter Oauth Response) |
| else if (!url.startsWith("http:") && !url.startsWith("https:") && url.matches("^[A-Za-z0-9+.-]*://.*?$")) { |
| if (allowedSchemes == null) { |
| String allowed = preferences.getString("AllowedSchemes", null); |
| if(allowed != null) { |
| allowedSchemes = allowed.split(","); |
| } |
| } |
| if (allowedSchemes != null) { |
| for (String scheme : allowedSchemes) { |
| if (url.startsWith(scheme)) { |
| try { |
| JSONObject obj = new JSONObject(); |
| obj.put("type", "customscheme"); |
| obj.put("url", url); |
| sendUpdate(obj, true); |
| override = true; |
| } catch (JSONException ex) { |
| LOG.e(LOG_TAG, "Custom Scheme URI passed in has caused a JSON error."); |
| } |
| } |
| } |
| } |
| } |
| |
| if (useBeforeload) { |
| this.waitForBeforeload = true; |
| } |
| return override; |
| } |
| |
| private boolean sendBeforeLoad(String url, String method) { |
| try { |
| JSONObject obj = new JSONObject(); |
| obj.put("type", BEFORELOAD); |
| obj.put("url", url); |
| if(method != null) { |
| obj.put("method", method); |
| } |
| sendUpdate(obj, true); |
| return true; |
| } catch (JSONException ex) { |
| LOG.e(LOG_TAG, "URI passed in has caused a JSON error."); |
| } |
| return false; |
| } |
| |
| |
| /** |
| * Legacy (deprecated in API 21) |
| * For Android 4.4 and below. |
| * @param view |
| * @param url |
| * @return |
| */ |
| @SuppressWarnings("deprecation") |
| @Override |
| public WebResourceResponse shouldInterceptRequest (final WebView view, String url) { |
| return shouldInterceptRequest(url, super.shouldInterceptRequest(view, url), null); |
| } |
| |
| /** |
| * New (added in API 21) |
| * For Android 5.0 and above. |
| * |
| * @param webView |
| * @param request |
| */ |
| @TargetApi(Build.VERSION_CODES.LOLLIPOP) |
| @Override |
| public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { |
| return shouldInterceptRequest(request.getUrl().toString(), super.shouldInterceptRequest(view, request), request.getMethod()); |
| } |
| |
| public WebResourceResponse shouldInterceptRequest(String url, WebResourceResponse response, String method) { |
| return response; |
| } |
| |
| /* |
| * onPageStarted fires the LOAD_START_EVENT |
| * |
| * @param view |
| * @param url |
| * @param favicon |
| */ |
| @Override |
| public void onPageStarted(WebView view, String url, Bitmap favicon) { |
| super.onPageStarted(view, url, favicon); |
| String newloc = ""; |
| if (url.startsWith("http:") || url.startsWith("https:") || url.startsWith("file:")) { |
| newloc = url; |
| } |
| else |
| { |
| // Assume that everything is HTTP at this point, because if we don't specify, |
| // it really should be. Complain loudly about this!!! |
| LOG.e(LOG_TAG, "Possible Uncaught/Unknown URI"); |
| newloc = "http://" + url; |
| } |
| |
| // Update the UI if we haven't already |
| if (!newloc.equals(edittext.getText().toString())) { |
| edittext.setText(newloc); |
| } |
| |
| try { |
| JSONObject obj = new JSONObject(); |
| obj.put("type", LOAD_START_EVENT); |
| obj.put("url", newloc); |
| sendUpdate(obj, true); |
| } catch (JSONException ex) { |
| LOG.e(LOG_TAG, "URI passed in has caused a JSON error."); |
| } |
| } |
| |
| public void onPageFinished(WebView view, String url) { |
| super.onPageFinished(view, url); |
| |
| // Set the namespace for postMessage() |
| if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { |
| injectDeferredObject("window.webkit={messageHandlers:{cordova_iab:cordova_iab}}", null); |
| } |
| |
| // CB-10395 InAppBrowser's WebView not storing cookies reliable to local device storage |
| if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { |
| CookieManager.getInstance().flush(); |
| } else { |
| CookieSyncManager.getInstance().sync(); |
| } |
| |
| // https://issues.apache.org/jira/browse/CB-11248 |
| view.clearFocus(); |
| view.requestFocus(); |
| |
| try { |
| JSONObject obj = new JSONObject(); |
| obj.put("type", LOAD_STOP_EVENT); |
| obj.put("url", url); |
| |
| sendUpdate(obj, true); |
| } catch (JSONException ex) { |
| LOG.d(LOG_TAG, "Should never happen"); |
| } |
| } |
| |
| public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { |
| super.onReceivedError(view, errorCode, description, failingUrl); |
| |
| try { |
| JSONObject obj = new JSONObject(); |
| obj.put("type", LOAD_ERROR_EVENT); |
| obj.put("url", failingUrl); |
| obj.put("code", errorCode); |
| obj.put("message", description); |
| |
| sendUpdate(obj, true, PluginResult.Status.ERROR); |
| } catch (JSONException ex) { |
| LOG.d(LOG_TAG, "Should never happen"); |
| } |
| } |
| |
| @Override |
| public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { |
| super.onReceivedSslError(view, handler, error); |
| try { |
| JSONObject obj = new JSONObject(); |
| obj.put("type", LOAD_ERROR_EVENT); |
| obj.put("url", error.getUrl()); |
| obj.put("code", 0); |
| obj.put("sslerror", error.getPrimaryError()); |
| String message; |
| switch (error.getPrimaryError()) { |
| case SslError.SSL_DATE_INVALID: |
| message = "The date of the certificate is invalid"; |
| break; |
| case SslError.SSL_EXPIRED: |
| message = "The certificate has expired"; |
| break; |
| case SslError.SSL_IDMISMATCH: |
| message = "Hostname mismatch"; |
| break; |
| default: |
| case SslError.SSL_INVALID: |
| message = "A generic error occurred"; |
| break; |
| case SslError.SSL_NOTYETVALID: |
| message = "The certificate is not yet valid"; |
| break; |
| case SslError.SSL_UNTRUSTED: |
| message = "The certificate authority is not trusted"; |
| break; |
| } |
| obj.put("message", message); |
| |
| sendUpdate(obj, true, PluginResult.Status.ERROR); |
| } catch (JSONException ex) { |
| LOG.d(LOG_TAG, "Should never happen"); |
| } |
| handler.cancel(); |
| } |
| |
| /** |
| * On received http auth request. |
| */ |
| @Override |
| public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { |
| |
| // Check if there is some plugin which can resolve this auth challenge |
| PluginManager pluginManager = null; |
| try { |
| Method gpm = webView.getClass().getMethod("getPluginManager"); |
| pluginManager = (PluginManager)gpm.invoke(webView); |
| } catch (NoSuchMethodException e) { |
| LOG.d(LOG_TAG, e.getLocalizedMessage()); |
| } catch (IllegalAccessException e) { |
| LOG.d(LOG_TAG, e.getLocalizedMessage()); |
| } catch (InvocationTargetException e) { |
| LOG.d(LOG_TAG, e.getLocalizedMessage()); |
| } |
| |
| if (pluginManager == null) { |
| try { |
| Field pmf = webView.getClass().getField("pluginManager"); |
| pluginManager = (PluginManager)pmf.get(webView); |
| } catch (NoSuchFieldException e) { |
| LOG.d(LOG_TAG, e.getLocalizedMessage()); |
| } catch (IllegalAccessException e) { |
| LOG.d(LOG_TAG, e.getLocalizedMessage()); |
| } |
| } |
| |
| if (pluginManager != null && pluginManager.onReceivedHttpAuthRequest(webView, new CordovaHttpAuthHandler(handler), host, realm)) { |
| return; |
| } |
| |
| // By default handle 401 like we'd normally do! |
| super.onReceivedHttpAuthRequest(view, handler, host, realm); |
| } |
| } |
| } |