diff options
Diffstat (limited to 'StoneIsland/plugins/cordova-plugin-inappbrowser/src/android/InAppBrowser.java')
| -rw-r--r-- | StoneIsland/plugins/cordova-plugin-inappbrowser/src/android/InAppBrowser.java | 739 |
1 files changed, 641 insertions, 98 deletions
diff --git a/StoneIsland/plugins/cordova-plugin-inappbrowser/src/android/InAppBrowser.java b/StoneIsland/plugins/cordova-plugin-inappbrowser/src/android/InAppBrowser.java index 8f4f3d97..b3e0e612 100644 --- a/StoneIsland/plugins/cordova-plugin-inappbrowser/src/android/InAppBrowser.java +++ b/StoneIsland/plugins/cordova-plugin-inappbrowser/src/android/InAppBrowser.java @@ -19,12 +19,21 @@ 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; @@ -41,6 +50,12 @@ 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; @@ -49,6 +64,7 @@ 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; @@ -65,6 +81,9 @@ 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; @@ -82,11 +101,27 @@ public class InAppBrowser extends CordovaPlugin { 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; @@ -100,6 +135,24 @@ public class InAppBrowser extends CordovaPlugin { 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. @@ -118,7 +171,7 @@ public class InAppBrowser extends CordovaPlugin { t = SELF; } final String target = t; - final HashMap<String, Boolean> features = parseFeature(args.optString(2)); + final HashMap<String, String> features = parseFeature(args.optString(2)); LOG.d(LOG_TAG, "target = " + target); @@ -207,6 +260,25 @@ public class InAppBrowser extends CordovaPlugin { 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)) { @@ -245,7 +317,22 @@ public class InAppBrowser extends CordovaPlugin { this.cordova.getActivity().runOnUiThread(new Runnable() { @Override public void run() { - dialog.show(); + 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); @@ -311,29 +398,33 @@ public class InAppBrowser extends CordovaPlugin { * which should be executed directly. */ private void injectDeferredObject(String source, String jsWrapper) { - 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); + 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 { - scriptToInject = source; + LOG.d(LOG_TAG, "Can't inject code into the system browser"); } - 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); - } - } - }); } /** @@ -342,18 +433,21 @@ public class InAppBrowser extends CordovaPlugin { * @param optString * @return */ - private HashMap<String, Boolean> parseFeature(String optString) { + private HashMap<String, String> parseFeature(String optString) { if (optString.equals(NULL)) { return null; } else { - HashMap<String, Boolean> map = new HashMap<String, Boolean>(); + 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(); - Boolean value = option.nextToken().equals("no") ? Boolean.FALSE : Boolean.TRUE; + String value = option.nextToken(); + if (!customizableOptions.contains(key)) { + value = value.equals("yes") || value.equals("no") ? value : "yes"; + } map.put(key, value); } } @@ -380,15 +474,57 @@ public class InAppBrowser extends CordovaPlugin { intent.setData(uri); } intent.putExtra(Browser.EXTRA_APPLICATION_ID, cordova.getActivity().getPackageName()); - this.cordova.getActivity().startActivity(intent); + // CB-10795: Avoid circular loops by preventing it from opening in the current app + this.openExternalExcludeCurrentApp(intent); return ""; - } catch (android.content.ActivityNotFoundException e) { + // 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() { @@ -405,7 +541,7 @@ public class InAppBrowser extends CordovaPlugin { childView.setWebViewClient(new WebViewClient() { // NB: wait for about:blank before dismissing public void onPageFinished(WebView view, String url) { - if (dialog != null) { + if (dialog != null && !cordova.getActivity().isFinishing()) { dialog.dismiss(); dialog = null; } @@ -488,7 +624,7 @@ public class InAppBrowser extends CordovaPlugin { return this.showLocationBar; } - private InAppBrowser getInAppBrowser(){ + private InAppBrowser getInAppBrowser() { return this; } @@ -498,7 +634,7 @@ public class InAppBrowser extends CordovaPlugin { * @param url the url to load. * @param features jsonObject */ - public String showWebPage(final String url, HashMap<String, Boolean> features) { + public String showWebPage(final String url, HashMap<String, String> features) { // Determine if we should hide the location bar. showLocationBar = true; showZoomControls = true; @@ -506,38 +642,84 @@ public class InAppBrowser extends CordovaPlugin { mediaPlaybackRequiresUserGesture = false; if (features != null) { - Boolean show = features.get(LOCATION); + String show = features.get(LOCATION); if (show != null) { - showLocationBar = show.booleanValue(); + 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; } - Boolean zoom = features.get(ZOOM); + String zoom = features.get(ZOOM); if (zoom != null) { - showZoomControls = zoom.booleanValue(); + showZoomControls = zoom.equals("yes") ? true : false; } - Boolean hidden = features.get(HIDDEN); + String hidden = features.get(HIDDEN); if (hidden != null) { - openWindowHidden = hidden.booleanValue(); + openWindowHidden = hidden.equals("yes") ? true : false; } - Boolean hardwareBack = features.get(HARDWARE_BACK_BUTTON); + String hardwareBack = features.get(HARDWARE_BACK_BUTTON); if (hardwareBack != null) { - hadwareBackButton = hardwareBack.booleanValue(); + hadwareBackButton = hardwareBack.equals("yes") ? true : false; + } else { + hadwareBackButton = DEFAULT_HARDWARE_BACK; } - Boolean mediaPlayback = features.get(MEDIA_PLAYBACK_REQUIRES_USER_ACTION); + String mediaPlayback = features.get(MEDIA_PLAYBACK_REQUIRES_USER_ACTION); if (mediaPlayback != null) { - mediaPlaybackRequiresUserGesture = mediaPlayback.booleanValue(); + mediaPlaybackRequiresUserGesture = mediaPlayback.equals("yes") ? true : false; } - Boolean cache = features.get(CLEAR_ALL_CACHE); + String cache = features.get(CLEAR_ALL_CACHE); if (cache != null) { - clearAllCache = cache.booleanValue(); + clearAllCache = cache.equals("yes") ? true : false; } else { cache = features.get(CLEAR_SESSION_CACHE); if (cache != null) { - clearSessionCache = cache.booleanValue(); + clearSessionCache = cache.equals("yes") ? true : false; } } - Boolean shouldPause = features.get(SHOULD_PAUSE); + String shouldPause = features.get(SHOULD_PAUSE); if (shouldPause != null) { - shouldPauseInAppBrowser = shouldPause.booleanValue(); + 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; } } @@ -552,13 +734,61 @@ public class InAppBrowser extends CordovaPlugin { */ private int dpToPixels(int dipValue) { int value = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, - (float) dipValue, - cordova.getActivity().getResources().getDisplayMetrics() + (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() { @@ -571,6 +801,9 @@ public class InAppBrowser extends CordovaPlugin { 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()); @@ -581,18 +814,25 @@ public class InAppBrowser extends CordovaPlugin { // Toolbar layout RelativeLayout toolbar = new RelativeLayout(cordova.getActivity()); //Please, no more black! - toolbar.setBackgroundColor(android.graphics.Color.LTGRAY); + 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)); - toolbar.setHorizontalGravity(Gravity.LEFT); + 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()); - actionButtonContainer.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); + 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(Integer.valueOf(1)); + actionButtonContainer.setId(leftToRight ? Integer.valueOf(5) : Integer.valueOf(1)); // Back button ImageButton back = new ImageButton(cordova.getActivity()); @@ -604,6 +844,7 @@ public class InAppBrowser extends CordovaPlugin { 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 @@ -629,6 +870,7 @@ public class InAppBrowser extends CordovaPlugin { 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 @@ -661,53 +903,112 @@ public class InAppBrowser extends CordovaPlugin { 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; + navigate(edittext.getText().toString()); + return true; } return false; } }); - // Close/Done button - ImageButton close = new ImageButton(cordova.getActivity()); - RelativeLayout.LayoutParams closeLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); - closeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); - close.setLayoutParams(closeLayoutParams); - forward.setContentDescription("Close Button"); - close.setId(Integer.valueOf(5)); - int closeResId = activityRes.getIdentifier("ic_action_remove", "drawable", cordova.getActivity().getPackageName()); - Drawable closeIcon = activityRes.getDrawable(closeResId); - if (Build.VERSION.SDK_INT >= 16) - close.setBackground(null); - else - close.setBackgroundDrawable(null); - close.setImageDrawable(closeIcon); - close.setScaleType(ImageView.ScaleType.FIT_CENTER); - back.setPadding(0, this.dpToPixels(10), 0, this.dpToPixels(10)); - if (Build.VERSION.SDK_INT >= 16) - close.getAdjustViewBounds(); - close.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - closeDialog(); - } - }); + // 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)); - inAppWebView.setWebChromeClient(new InAppChromeClient(thatWebView)); - WebViewClient client = new InAppBrowserClient(thatWebView, edittext); - inAppWebView.setWebViewClient(client); + // 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); @@ -736,10 +1037,15 @@ public class InAppBrowser extends CordovaPlugin { 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(true); + inAppWebView.getSettings().setUseWideViewPort(useWideViewPort); inAppWebView.requestFocus(); inAppWebView.requestFocusFromTouch(); @@ -747,10 +1053,9 @@ public class InAppBrowser extends CordovaPlugin { actionButtonContainer.addView(back); actionButtonContainer.addView(forward); - // Add the views to our toolbar - toolbar.addView(actionButtonContainer); - toolbar.addView(edittext); - toolbar.addView(close); + // 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()) { @@ -759,19 +1064,28 @@ public class InAppBrowser extends CordovaPlugin { } // Add our webview to our main view/layout - main.addView(inAppWebView); + 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; - dialog.setContentView(main); - dialog.show(); - dialog.getWindow().setAttributes(lp); + 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) { + if (openWindowHidden && dialog != null) { dialog.hide(); } } @@ -807,11 +1121,49 @@ public class InAppBrowser extends CordovaPlugin { } /** + * 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. @@ -819,27 +1171,97 @@ public class InAppBrowser extends CordovaPlugin { * @param webView * @param mEditText */ - public InAppBrowserClient(CordovaWebView webView, EditText 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 * - * This handles a small subset of all the URIs that would be encountered. + * 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); - return true; + override = true; } catch (android.content.ActivityNotFoundException e) { LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString()); } @@ -848,7 +1270,7 @@ public class InAppBrowser extends CordovaPlugin { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(url)); cordova.getActivity().startActivity(intent); - return true; + override = true; } catch (android.content.ActivityNotFoundException e) { LOG.e(LOG_TAG, "Error with " + url + ": " + e.toString()); } @@ -879,15 +1301,89 @@ public class InAppBrowser extends CordovaPlugin { intent.putExtra("address", address); intent.setType("vnd.android-dir/mms-sms"); cordova.getActivity().startActivity(intent); - return true; + 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 * @@ -913,7 +1409,7 @@ public class InAppBrowser extends CordovaPlugin { // Update the UI if we haven't already if (!newloc.equals(edittext.getText().toString())) { edittext.setText(newloc); - } + } try { JSONObject obj = new JSONObject(); @@ -925,11 +1421,14 @@ public class InAppBrowser extends CordovaPlugin { } } - - 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(); @@ -937,6 +1436,10 @@ public class InAppBrowser extends CordovaPlugin { 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); @@ -964,6 +1467,46 @@ public class InAppBrowser extends CordovaPlugin { } } + @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. */ |
