· 4 years ago · Jun 08, 2021, 02:48 PM
1/*
2 Licensed to the Apache Software Foundation (ASF) under one
3 or more contributor license agreements. See the NOTICE file
4 distributed with this work for additional information
5 regarding copyright ownership. The ASF licenses this file
6 to you under the Apache License, Version 2.0 (the
7 "License"); you may not use this file except in compliance
8 with the License. You may obtain a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12 Unless required by applicable law or agreed to in writing,
13 software distributed under the License is distributed on an
14 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 KIND, either express or implied. See the License for the
16 specific language governing permissions and limitations
17 under the License.
18*/
19package org.apache.cordova.inappbrowser;
20
21import android.annotation.SuppressLint;
22import android.annotation.TargetApi;
23import android.content.Context;
24import android.content.Intent;
25import android.content.pm.PackageManager;
26import android.content.pm.ResolveInfo;
27import android.content.res.Resources;
28import android.graphics.Bitmap;
29import android.graphics.Color;
30import android.graphics.drawable.Drawable;
31import android.net.Uri;
32import android.net.http.SslError;
33import android.os.Build;
34import android.os.Bundle;
35import android.os.Handler;
36import android.os.Looper;
37import android.os.Parcelable;
38import android.provider.Browser;
39import android.text.InputType;
40import android.util.TypedValue;
41import android.view.Gravity;
42import android.view.KeyEvent;
43import android.view.View;
44import android.view.Window;
45import android.view.WindowManager;
46import android.view.WindowManager.LayoutParams;
47import android.view.inputmethod.EditorInfo;
48import android.view.inputmethod.InputMethodManager;
49import android.webkit.CookieManager;
50import android.webkit.CookieSyncManager;
51import android.webkit.HttpAuthHandler;
52import android.webkit.JavascriptInterface;
53import android.webkit.SslErrorHandler;
54import android.webkit.ValueCallback;
55import android.webkit.WebChromeClient;
56import android.webkit.WebResourceRequest;
57import android.webkit.WebResourceResponse;
58import android.webkit.WebSettings;
59import android.webkit.WebView;
60import android.webkit.WebViewClient;
61import android.widget.EditText;
62import android.widget.ImageButton;
63import android.widget.ImageView;
64import android.widget.LinearLayout;
65import android.widget.RelativeLayout;
66import android.widget.TextView;
67
68import org.apache.cordova.CallbackContext;
69import org.apache.cordova.Config;
70import org.apache.cordova.CordovaArgs;
71import org.apache.cordova.CordovaHttpAuthHandler;
72import org.apache.cordova.CordovaPlugin;
73import org.apache.cordova.CordovaWebView;
74import org.apache.cordova.LOG;
75import org.apache.cordova.PluginManager;
76import org.apache.cordova.PluginResult;
77import org.json.JSONException;
78import org.json.JSONObject;
79
80import java.lang.reflect.Field;
81import java.lang.reflect.InvocationTargetException;
82import java.lang.reflect.Method;
83import java.util.ArrayList;
84import java.util.Arrays;
85import java.util.HashMap;
86import java.util.List;
87import java.util.Map;
88import java.util.StringTokenizer;
89
90@SuppressLint("SetJavaScriptEnabled")
91public class InAppBrowser extends CordovaPlugin {
92
93 private static final String NULL = "null";
94 protected static final String LOG_TAG = "InAppBrowser";
95 private static final String SELF = "_self";
96 private static final String SYSTEM = "_system";
97 private static final String EXIT_EVENT = "exit";
98 private static final String LOCATION = "location";
99 private static final String ZOOM = "zoom";
100 private static final String HIDDEN = "hidden";
101 private static final String LOAD_START_EVENT = "loadstart";
102 private static final String LOAD_STOP_EVENT = "loadstop";
103 private static final String LOAD_ERROR_EVENT = "loaderror";
104 private static final String MESSAGE_EVENT = "message";
105 private static final String CLEAR_ALL_CACHE = "clearcache";
106 private static final String CLEAR_SESSION_CACHE = "clearsessioncache";
107 private static final String HARDWARE_BACK_BUTTON = "hardwareback";
108 private static final String MEDIA_PLAYBACK_REQUIRES_USER_ACTION = "mediaPlaybackRequiresUserAction";
109 private static final String SHOULD_PAUSE = "shouldPauseOnSuspend";
110 private static final Boolean DEFAULT_HARDWARE_BACK = true;
111 private static final String USER_WIDE_VIEW_PORT = "useWideViewPort";
112 private static final String TOOLBAR_COLOR = "toolbarcolor";
113 private static final String CLOSE_BUTTON_CAPTION = "closebuttoncaption";
114 private static final String CLOSE_BUTTON_COLOR = "closebuttoncolor";
115 private static final String LEFT_TO_RIGHT = "lefttoright";
116 private static final String HIDE_NAVIGATION = "hidenavigationbuttons";
117 private static final String NAVIGATION_COLOR = "navigationbuttoncolor";
118 private static final String HIDE_URL = "hideurlbar";
119 private static final String FOOTER = "footer";
120 private static final String FOOTER_COLOR = "footercolor";
121 private static final String BEFORELOAD = "beforeload";
122 private static final String FULLSCREEN = "fullscreen";
123 private static final String ACCESS_TOKEN = "accessToken";
124
125 private static final List customizableOptions = Arrays.asList(
126 CLOSE_BUTTON_CAPTION, TOOLBAR_COLOR, NAVIGATION_COLOR,
127 CLOSE_BUTTON_COLOR, FOOTER_COLOR, ACCESS_TOKEN);
128
129 private InAppBrowserDialog dialog;
130 private WebView inAppWebView;
131 private EditText edittext;
132 private CallbackContext callbackContext;
133 private boolean showLocationBar = true;
134 private boolean showZoomControls = true;
135 private boolean openWindowHidden = false;
136 private boolean clearAllCache = false;
137 private boolean clearSessionCache = false;
138 private boolean hadwareBackButton = true;
139 private boolean mediaPlaybackRequiresUserGesture = false;
140 private boolean shouldPauseInAppBrowser = false;
141 private boolean useWideViewPort = true;
142 private ValueCallback<Uri> mUploadCallback;
143 private ValueCallback<Uri[]> mUploadCallbackLollipop;
144 private final static int FILECHOOSER_REQUESTCODE = 1;
145 private final static int FILECHOOSER_REQUESTCODE_LOLLIPOP = 2;
146 private String closeButtonCaption = "";
147 private String closeButtonColor = "";
148 private boolean leftToRight = false;
149 private int toolbarColor = android.graphics.Color.LTGRAY;
150 private boolean hideNavigationButtons = false;
151 private String navigationButtonColor = "";
152 private boolean hideUrlBar = false;
153 private boolean showFooter = false;
154 private String footerColor = "";
155 private String beforeload = "";
156 private String accessToken = "";
157 private boolean fullscreen = true;
158 private String[] allowedSchemes;
159 private InAppBrowserClient currentClient;
160 private HashMap<String, String> features;
161
162 /**
163 * Executes the request and returns PluginResult.
164 *
165 * @param action the action to execute.
166 * @param args JSONArry of arguments for the plugin.
167 * @param callbackContext the callbackContext used when calling back into JavaScript.
168 * @return A PluginResult object with a status and message.
169 */
170 public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException {
171 if (action.equals("open")) {
172 this.callbackContext = callbackContext;
173 final String url = args.getString(0);
174 String t = args.optString(1);
175 if (t == null || t.equals("") || t.equals(NULL)) {
176 t = SELF;
177 }
178 final String target = t;
179 this.features = parseFeature(args.optString(2));
180 final HashMap<String, String> features = parseFeature(args.optString(2));
181
182 LOG.d(LOG_TAG, "target = " + target);
183
184 this.cordova.getActivity().runOnUiThread(new Runnable() {
185 @Override
186 public void run() {
187 String result = "";
188 // SELF
189 if (SELF.equals(target)) {
190 LOG.d(LOG_TAG, "in self");
191 /* This code exists for compatibility between 3.x and 4.x versions of Cordova.
192 * Previously the Config class had a static method, isUrlWhitelisted(). That
193 * responsibility has been moved to the plugins, with an aggregating method in
194 * PluginManager.
195 */
196 Boolean shouldAllowNavigation = null;
197 if (url.startsWith("javascript:")) {
198 shouldAllowNavigation = true;
199 }
200 if (shouldAllowNavigation == null) {
201 try {
202 Method iuw = Config.class.getMethod("isUrlWhiteListed", String.class);
203 shouldAllowNavigation = (Boolean)iuw.invoke(null, url);
204 } catch (NoSuchMethodException e) {
205 LOG.d(LOG_TAG, e.getLocalizedMessage());
206 } catch (IllegalAccessException e) {
207 LOG.d(LOG_TAG, e.getLocalizedMessage());
208 } catch (InvocationTargetException e) {
209 LOG.d(LOG_TAG, e.getLocalizedMessage());
210 }
211 }
212 if (shouldAllowNavigation == null) {
213 try {
214 Method gpm = webView.getClass().getMethod("getPluginManager");
215 PluginManager pm = (PluginManager)gpm.invoke(webView);
216 Method san = pm.getClass().getMethod("shouldAllowNavigation", String.class);
217 shouldAllowNavigation = (Boolean)san.invoke(pm, url);
218 } catch (NoSuchMethodException e) {
219 LOG.d(LOG_TAG, e.getLocalizedMessage());
220 } catch (IllegalAccessException e) {
221 LOG.d(LOG_TAG, e.getLocalizedMessage());
222 } catch (InvocationTargetException e) {
223 LOG.d(LOG_TAG, e.getLocalizedMessage());
224 }
225 }
226 // load in webview
227 if (Boolean.TRUE.equals(shouldAllowNavigation)) {
228 LOG.d(LOG_TAG, "loading in webview");
229 webView.loadUrl(url);
230 }
231 //Load the dialer
232 else if (url.startsWith(WebView.SCHEME_TEL))
233 {
234 try {
235 LOG.d(LOG_TAG, "loading in dialer");
236 Intent intent = new Intent(Intent.ACTION_DIAL);
237 intent.setData(Uri.parse(url));
238 cordova.getActivity().startActivity(intent);
239 } catch (android.content.ActivityNotFoundException e) {
240 LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString());
241 }
242 }
243 // load in InAppBrowser
244 else {
245 LOG.d(LOG_TAG, "loading in InAppBrowser");
246 result = showWebPage(url, features, new HashMap<>());
247 }
248 }
249 // SYSTEM
250 else if (SYSTEM.equals(target)) {
251 LOG.d(LOG_TAG, "in system");
252 result = openExternal(url);
253 }
254 // BLANK - or anything else
255 else {
256 LOG.d(LOG_TAG, "in blank");
257 result = showWebPage(url, features, new HashMap<>());
258 }
259
260 PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, result);
261 pluginResult.setKeepCallback(true);
262 callbackContext.sendPluginResult(pluginResult);
263 }
264 });
265 }
266 else if (action.equals("close")) {
267 closeDialog();
268 }
269 else if (action.equals("loadAfterBeforeload")) {
270 if (beforeload == null) {
271 LOG.e(LOG_TAG, "unexpected loadAfterBeforeload called without feature beforeload=yes");
272 }
273 final String url = args.getString(0);
274 this.cordova.getActivity().runOnUiThread(new Runnable() {
275 @SuppressLint("NewApi")
276 @Override
277 public void run() {
278 if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O) {
279 currentClient.waitForBeforeload = false;
280 inAppWebView.setWebViewClient(currentClient);
281 } else {
282 ((InAppBrowserClient)inAppWebView.getWebViewClient()).waitForBeforeload = false;
283 }
284 inAppWebView.loadUrl(url);
285 }
286 });
287 }
288 else if (action.equals("injectScriptCode")) {
289 String jsWrapper = null;
290 if (args.getBoolean(1)) {
291 jsWrapper = String.format("(function(){prompt(JSON.stringify([eval(%%s)]), 'gap-iab://%s')})()", callbackContext.getCallbackId());
292 }
293 injectDeferredObject(args.getString(0), jsWrapper);
294 }
295 else if (action.equals("injectScriptFile")) {
296 String jsWrapper;
297 if (args.getBoolean(1)) {
298 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());
299 } else {
300 jsWrapper = "(function(d) { var c = d.createElement('script'); c.src = %s; d.body.appendChild(c); })(document)";
301 }
302 injectDeferredObject(args.getString(0), jsWrapper);
303 }
304 else if (action.equals("injectStyleCode")) {
305 String jsWrapper;
306 if (args.getBoolean(1)) {
307 jsWrapper = String.format("(function(d) { var c = d.createElement('style'); c.innerHTML = %%s; d.body.appendChild(c); prompt('', 'gap-iab://%s');})(document)", callbackContext.getCallbackId());
308 } else {
309 jsWrapper = "(function(d) { var c = d.createElement('style'); c.innerHTML = %s; d.body.appendChild(c); })(document)";
310 }
311 injectDeferredObject(args.getString(0), jsWrapper);
312 }
313 else if (action.equals("injectStyleFile")) {
314 String jsWrapper;
315 if (args.getBoolean(1)) {
316 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());
317 } else {
318 jsWrapper = "(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %s; d.head.appendChild(c); })(document)";
319 }
320 injectDeferredObject(args.getString(0), jsWrapper);
321 }
322 else if (action.equals("show")) {
323 this.cordova.getActivity().runOnUiThread(new Runnable() {
324 @Override
325 public void run() {
326 if (dialog != null && !cordova.getActivity().isFinishing()) {
327 dialog.show();
328 }
329 }
330 });
331 PluginResult pluginResult = new PluginResult(PluginResult.Status.OK);
332 pluginResult.setKeepCallback(true);
333 this.callbackContext.sendPluginResult(pluginResult);
334 }
335 else if (action.equals("hide")) {
336 this.cordova.getActivity().runOnUiThread(new Runnable() {
337 @Override
338 public void run() {
339 if (dialog != null && !cordova.getActivity().isFinishing()) {
340 dialog.hide();
341 }
342 }
343 });
344 PluginResult pluginResult = new PluginResult(PluginResult.Status.OK);
345 pluginResult.setKeepCallback(true);
346 this.callbackContext.sendPluginResult(pluginResult);
347 }
348 else {
349 return false;
350 }
351 return true;
352 }
353
354 /**
355 * Called when the view navigates.
356 */
357 @Override
358 public void onReset() {
359 closeDialog();
360 }
361
362 /**
363 * Called when the system is about to start resuming a previous activity.
364 */
365 @Override
366 public void onPause(boolean multitasking) {
367 if (shouldPauseInAppBrowser) {
368 inAppWebView.onPause();
369 }
370 }
371
372 /**
373 * Called when the activity will start interacting with the user.
374 */
375 @Override
376 public void onResume(boolean multitasking) {
377 if (shouldPauseInAppBrowser) {
378 inAppWebView.onResume();
379 }
380 }
381
382 /**
383 * Called by AccelBroker when listener is to be shut down.
384 * Stop listener.
385 */
386 public void onDestroy() {
387 closeDialog();
388 }
389
390 /**
391 * Inject an object (script or style) into the InAppBrowser WebView.
392 *
393 * This is a helper method for the inject{Script|Style}{Code|File} API calls, which
394 * provides a consistent method for injecting JavaScript code into the document.
395 *
396 * If a wrapper string is supplied, then the source string will be JSON-encoded (adding
397 * quotes) and wrapped using string formatting. (The wrapper string should have a single
398 * '%s' marker)
399 *
400 * @param source The source object (filename or script/style text) to inject into
401 * the document.
402 * @param jsWrapper A JavaScript string to wrap the source string in, so that the object
403 * is properly injected, or null if the source string is JavaScript text
404 * which should be executed directly.
405 */
406 private void injectDeferredObject(String source, String jsWrapper) {
407 if (inAppWebView!=null) {
408 String scriptToInject;
409 if (jsWrapper != null) {
410 org.json.JSONArray jsonEsc = new org.json.JSONArray();
411 jsonEsc.put(source);
412 String jsonRepr = jsonEsc.toString();
413 String jsonSourceString = jsonRepr.substring(1, jsonRepr.length()-1);
414 scriptToInject = String.format(jsWrapper, jsonSourceString);
415 } else {
416 scriptToInject = source;
417 }
418 final String finalScriptToInject = scriptToInject;
419 this.cordova.getActivity().runOnUiThread(new Runnable() {
420 @SuppressLint("NewApi")
421 @Override
422 public void run() {
423 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
424 // This action will have the side-effect of blurring the currently focused element
425 inAppWebView.loadUrl("javascript:" + finalScriptToInject);
426 } else {
427 inAppWebView.evaluateJavascript(finalScriptToInject, null);
428 }
429 }
430 });
431 } else {
432 LOG.d(LOG_TAG, "Can't inject code into the system browser");
433 }
434 }
435
436 /**
437 * Put the list of features into a hash map
438 *
439 * @param optString
440 * @return
441 */
442 private HashMap<String, String> parseFeature(String optString) {
443 if (optString.equals(NULL)) {
444 return null;
445 } else {
446 HashMap<String, String> map = new HashMap<String, String>();
447 StringTokenizer features = new StringTokenizer(optString, ",");
448 StringTokenizer option;
449 while(features.hasMoreElements()) {
450 option = new StringTokenizer(features.nextToken(), "=");
451 if (option.hasMoreElements()) {
452 String key = option.nextToken();
453 String value = option.nextToken();
454 if (!customizableOptions.contains(key)) {
455 value = value.equals("yes") || value.equals("no") ? value : "yes";
456 }
457 map.put(key, value);
458 }
459 }
460 return map;
461 }
462 }
463
464 /**
465 * Display a new browser with the specified URL.
466 *
467 * @param url the url to load.
468 * @return "" if ok, or error message.
469 */
470 public String openExternal(String url) {
471 try {
472 Intent intent = null;
473 intent = new Intent(Intent.ACTION_VIEW);
474 // Omitting the MIME type for file: URLs causes "No Activity found to handle Intent".
475 // Adding the MIME type to http: URLs causes them to not be handled by the downloader.
476 Uri uri = Uri.parse(url);
477 if ("file".equals(uri.getScheme())) {
478 intent.setDataAndType(uri, webView.getResourceApi().getMimeType(uri));
479 } else {
480 intent.setData(uri);
481 }
482 intent.putExtra(Browser.EXTRA_APPLICATION_ID, cordova.getActivity().getPackageName());
483 // CB-10795: Avoid circular loops by preventing it from opening in the current app
484 this.openExternalExcludeCurrentApp(intent);
485 return "";
486 // not catching FileUriExposedException explicitly because buildtools<24 doesn't know about it
487 } catch (java.lang.RuntimeException e) {
488 LOG.d(LOG_TAG, "InAppBrowser: Error loading url "+url+":"+ e.toString());
489 return e.toString();
490 }
491 }
492
493 /**
494 * Opens the intent, providing a chooser that excludes the current app to avoid
495 * circular loops.
496 */
497 private void openExternalExcludeCurrentApp(Intent intent) {
498 String currentPackage = cordova.getActivity().getPackageName();
499 boolean hasCurrentPackage = false;
500
501 PackageManager pm = cordova.getActivity().getPackageManager();
502 List<ResolveInfo> activities = pm.queryIntentActivities(intent, 0);
503 ArrayList<Intent> targetIntents = new ArrayList<Intent>();
504
505 for (ResolveInfo ri : activities) {
506 if (!currentPackage.equals(ri.activityInfo.packageName)) {
507 Intent targetIntent = (Intent)intent.clone();
508 targetIntent.setPackage(ri.activityInfo.packageName);
509 targetIntents.add(targetIntent);
510 }
511 else {
512 hasCurrentPackage = true;
513 }
514 }
515
516 // If the current app package isn't a target for this URL, then use
517 // the normal launch behavior
518 if (hasCurrentPackage == false || targetIntents.size() == 0) {
519 this.cordova.getActivity().startActivity(intent);
520 }
521 // If there's only one possible intent, launch it directly
522 else if (targetIntents.size() == 1) {
523 this.cordova.getActivity().startActivity(targetIntents.get(0));
524 }
525 // Otherwise, show a custom chooser without the current app listed
526 else if (targetIntents.size() > 0) {
527 Intent chooser = Intent.createChooser(targetIntents.remove(targetIntents.size()-1), null);
528 chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetIntents.toArray(new Parcelable[] {}));
529 this.cordova.getActivity().startActivity(chooser);
530 }
531 }
532
533 /**
534 * Closes the dialog
535 */
536 public void closeDialog() {
537 this.cordova.getActivity().runOnUiThread(new Runnable() {
538 @Override
539 public void run() {
540 final WebView childView = inAppWebView;
541 // The JS protects against multiple calls, so this should happen only when
542 // closeDialog() is called by other native code.
543 if (childView == null) {
544 return;
545 }
546
547 childView.setWebViewClient(new WebViewClient() {
548 // NB: wait for about:blank before dismissing
549 public void onPageFinished(WebView view, String url) {
550 if (dialog != null && !cordova.getActivity().isFinishing()) {
551 dialog.dismiss();
552 dialog = null;
553 }
554 }
555 });
556 // NB: From SDK 19: "If you call methods on WebView from any thread
557 // other than your app's UI thread, it can cause unexpected results."
558 // http://developer.android.com/guide/webapps/migrating.html#Threads
559 childView.loadUrl("about:blank");
560
561 try {
562 JSONObject obj = new JSONObject();
563 obj.put("type", EXIT_EVENT);
564 sendUpdate(obj, false);
565 } catch (JSONException ex) {
566 LOG.d(LOG_TAG, "Should never happen");
567 }
568 }
569 });
570 }
571
572 /**
573 * Checks to see if it is possible to go back one page in history, then does so.
574 */
575 public void goBack() {
576 if (this.inAppWebView.canGoBack()) {
577 this.inAppWebView.goBack();
578 }
579 }
580
581 /**
582 * Can the web browser go back?
583 * @return boolean
584 */
585 public boolean canGoBack() {
586 return this.inAppWebView.canGoBack();
587 }
588
589 /**
590 * Has the user set the hardware back button to go back
591 * @return boolean
592 */
593 public boolean hardwareBack() {
594 return hadwareBackButton;
595 }
596
597 /**
598 * Checks to see if it is possible to go forward one page in history, then does so.
599 */
600 private void goForward() {
601 if (this.inAppWebView.canGoForward()) {
602 this.inAppWebView.goForward();
603 }
604 }
605
606 /**
607 * Navigate to the new page
608 *
609 * @param url to load
610 */
611 private void navigate(String url) {
612 InputMethodManager imm = (InputMethodManager)this.cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
613 imm.hideSoftInputFromWindow(edittext.getWindowToken(), 0);
614
615 if (!url.startsWith("http") && !url.startsWith("file:")) {
616 this.inAppWebView.loadUrl("http://" + url);
617 } else {
618 this.inAppWebView.loadUrl(url);
619 }
620 this.inAppWebView.requestFocus();
621 }
622
623
624 /**
625 * Should we show the location bar?
626 *
627 * @return boolean
628 */
629 private boolean getShowLocationBar() {
630 return this.showLocationBar;
631 }
632
633 private InAppBrowser getInAppBrowser() {
634 return this;
635 }
636
637 /**
638 * Display a new browser with the specified URL.
639 *
640 * @param url the url to load.
641 * @param features jsonObject
642 */
643 public String showWebPage(final String url, HashMap<String, String> features, Map<String, String> headers) {
644 // Determine if we should hide the location bar.
645 showLocationBar = true;
646 showZoomControls = true;
647 openWindowHidden = false;
648 mediaPlaybackRequiresUserGesture = false;
649
650 if (features != null) {
651 String show = features.get(LOCATION);
652 if (show != null) {
653 showLocationBar = show.equals("yes") ? true : false;
654 }
655 if(showLocationBar) {
656 String hideNavigation = features.get(HIDE_NAVIGATION);
657 String hideUrl = features.get(HIDE_URL);
658 if(hideNavigation != null) hideNavigationButtons = hideNavigation.equals("yes") ? true : false;
659 if(hideUrl != null) hideUrlBar = hideUrl.equals("yes") ? true : false;
660 }
661 String zoom = features.get(ZOOM);
662 if (zoom != null) {
663 showZoomControls = zoom.equals("yes") ? true : false;
664 }
665 String hidden = features.get(HIDDEN);
666 if (hidden != null) {
667 openWindowHidden = hidden.equals("yes") ? true : false;
668 }
669 String hardwareBack = features.get(HARDWARE_BACK_BUTTON);
670 if (hardwareBack != null) {
671 hadwareBackButton = hardwareBack.equals("yes") ? true : false;
672 } else {
673 hadwareBackButton = DEFAULT_HARDWARE_BACK;
674 }
675 String mediaPlayback = features.get(MEDIA_PLAYBACK_REQUIRES_USER_ACTION);
676 if (mediaPlayback != null) {
677 mediaPlaybackRequiresUserGesture = mediaPlayback.equals("yes") ? true : false;
678 }
679 String cache = features.get(CLEAR_ALL_CACHE);
680 if (cache != null) {
681 clearAllCache = cache.equals("yes") ? true : false;
682 } else {
683 cache = features.get(CLEAR_SESSION_CACHE);
684 if (cache != null) {
685 clearSessionCache = cache.equals("yes") ? true : false;
686 }
687 }
688 String shouldPause = features.get(SHOULD_PAUSE);
689 if (shouldPause != null) {
690 shouldPauseInAppBrowser = shouldPause.equals("yes") ? true : false;
691 }
692 String wideViewPort = features.get(USER_WIDE_VIEW_PORT);
693 if (wideViewPort != null ) {
694 useWideViewPort = wideViewPort.equals("yes") ? true : false;
695 }
696 String closeButtonCaptionSet = features.get(CLOSE_BUTTON_CAPTION);
697 if (closeButtonCaptionSet != null) {
698 closeButtonCaption = closeButtonCaptionSet;
699 }
700 String closeButtonColorSet = features.get(CLOSE_BUTTON_COLOR);
701 if (closeButtonColorSet != null) {
702 closeButtonColor = closeButtonColorSet;
703 }
704 String leftToRightSet = features.get(LEFT_TO_RIGHT);
705 leftToRight = leftToRightSet != null && leftToRightSet.equals("yes");
706
707 String toolbarColorSet = features.get(TOOLBAR_COLOR);
708 if (toolbarColorSet != null) {
709 toolbarColor = android.graphics.Color.parseColor(toolbarColorSet);
710 }
711 String navigationButtonColorSet = features.get(NAVIGATION_COLOR);
712 if (navigationButtonColorSet != null) {
713 navigationButtonColor = navigationButtonColorSet;
714 }
715 String showFooterSet = features.get(FOOTER);
716 if (showFooterSet != null) {
717 showFooter = showFooterSet.equals("yes") ? true : false;
718 }
719 String footerColorSet = features.get(FOOTER_COLOR);
720 if (footerColorSet != null) {
721 footerColor = footerColorSet;
722 }
723 if (features.get(BEFORELOAD) != null) {
724 beforeload = features.get(BEFORELOAD);
725 }
726 if (features.get(ACCESS_TOKEN) != null) {
727 accessToken = features.get(ACCESS_TOKEN);
728 }
729 String fullscreenSet = features.get(FULLSCREEN);
730 if (fullscreenSet != null) {
731 fullscreen = fullscreenSet.equals("yes") ? true : false;
732 }
733 }
734
735 final CordovaWebView thatWebView = this.webView;
736 // Create dialog in new thread
737 Runnable runnable = new Runnable() {
738 /**
739 * Convert our DIP units to Pixels
740 *
741 * @return int
742 */
743 private int dpToPixels(int dipValue) {
744 int value = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP,
745 (float) dipValue,
746 cordova.getActivity().getResources().getDisplayMetrics()
747 );
748
749 return value;
750 }
751
752 private View createCloseButton(int id) {
753 View _close;
754 Resources activityRes = cordova.getActivity().getResources();
755
756 if (closeButtonCaption != "") {
757 // Use TextView for text
758 TextView close = new TextView(cordova.getActivity());
759 close.setText(closeButtonCaption);
760 close.setTextSize(20);
761 if (closeButtonColor != "") close.setTextColor(android.graphics.Color.parseColor(closeButtonColor));
762 close.setGravity(android.view.Gravity.CENTER_VERTICAL);
763 close.setPadding(this.dpToPixels(10), 0, this.dpToPixels(10), 0);
764 _close = close;
765 }
766 else {
767 ImageButton close = new ImageButton(cordova.getActivity());
768 int closeResId = activityRes.getIdentifier("ic_action_remove", "drawable", cordova.getActivity().getPackageName());
769 Drawable closeIcon = activityRes.getDrawable(closeResId);
770 if (closeButtonColor != "") close.setColorFilter(android.graphics.Color.parseColor(closeButtonColor));
771 close.setImageDrawable(closeIcon);
772 close.setScaleType(ImageView.ScaleType.FIT_CENTER);
773 if (Build.VERSION.SDK_INT >= 16)
774 close.getAdjustViewBounds();
775
776 _close = close;
777 }
778
779 RelativeLayout.LayoutParams closeLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
780 if (leftToRight) closeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
781 else closeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
782 _close.setLayoutParams(closeLayoutParams);
783
784 if (Build.VERSION.SDK_INT >= 16)
785 _close.setBackground(null);
786 else
787 _close.setBackgroundDrawable(null);
788
789 _close.setContentDescription("Close Button");
790 _close.setId(Integer.valueOf(id));
791 _close.setOnClickListener(new View.OnClickListener() {
792 public void onClick(View v) {
793 closeDialog();
794 }
795 });
796
797 return _close;
798 }
799
800 @SuppressLint("NewApi")
801 public void run() {
802
803 // CB-6702 InAppBrowser hangs when opening more than one instance
804 if (dialog != null) {
805 dialog.dismiss();
806 };
807
808 // Let's create the main dialog
809 dialog = new InAppBrowserDialog(cordova.getActivity(), android.R.style.Theme_NoTitleBar);
810 dialog.getWindow().getAttributes().windowAnimations = android.R.style.Animation_Dialog;
811 dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
812 if (fullscreen) {
813 dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
814 }
815 dialog.setCancelable(true);
816 dialog.setInAppBroswer(getInAppBrowser());
817
818 // Main container layout
819 LinearLayout main = new LinearLayout(cordova.getActivity());
820 main.setOrientation(LinearLayout.VERTICAL);
821
822 // Toolbar layout
823 RelativeLayout toolbar = new RelativeLayout(cordova.getActivity());
824 //Please, no more black!
825 toolbar.setBackgroundColor(toolbarColor);
826 toolbar.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, this.dpToPixels(44)));
827 toolbar.setPadding(this.dpToPixels(2), this.dpToPixels(2), this.dpToPixels(2), this.dpToPixels(2));
828 if (leftToRight) {
829 toolbar.setHorizontalGravity(Gravity.LEFT);
830 } else {
831 toolbar.setHorizontalGravity(Gravity.RIGHT);
832 }
833 toolbar.setVerticalGravity(Gravity.TOP);
834
835 // Action Button Container layout
836 RelativeLayout actionButtonContainer = new RelativeLayout(cordova.getActivity());
837 RelativeLayout.LayoutParams actionButtonLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
838 if (leftToRight) actionButtonLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
839 else actionButtonLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
840 actionButtonContainer.setLayoutParams(actionButtonLayoutParams);
841 actionButtonContainer.setHorizontalGravity(Gravity.LEFT);
842 actionButtonContainer.setVerticalGravity(Gravity.CENTER_VERTICAL);
843 actionButtonContainer.setId(leftToRight ? Integer.valueOf(5) : Integer.valueOf(1));
844
845 // Back button
846 ImageButton back = new ImageButton(cordova.getActivity());
847 RelativeLayout.LayoutParams backLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
848 backLayoutParams.addRule(RelativeLayout.ALIGN_LEFT);
849 back.setLayoutParams(backLayoutParams);
850 back.setContentDescription("Back Button");
851 back.setId(Integer.valueOf(2));
852 Resources activityRes = cordova.getActivity().getResources();
853 int backResId = activityRes.getIdentifier("ic_action_previous_item", "drawable", cordova.getActivity().getPackageName());
854 Drawable backIcon = activityRes.getDrawable(backResId);
855 if (navigationButtonColor != "") back.setColorFilter(android.graphics.Color.parseColor(navigationButtonColor));
856 if (Build.VERSION.SDK_INT >= 16)
857 back.setBackground(null);
858 else
859 back.setBackgroundDrawable(null);
860 back.setImageDrawable(backIcon);
861 back.setScaleType(ImageView.ScaleType.FIT_CENTER);
862 back.setPadding(0, this.dpToPixels(10), 0, this.dpToPixels(10));
863 if (Build.VERSION.SDK_INT >= 16)
864 back.getAdjustViewBounds();
865
866 back.setOnClickListener(new View.OnClickListener() {
867 public void onClick(View v) {
868 goBack();
869 }
870 });
871
872 // Forward button
873 ImageButton forward = new ImageButton(cordova.getActivity());
874 RelativeLayout.LayoutParams forwardLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
875 forwardLayoutParams.addRule(RelativeLayout.RIGHT_OF, 2);
876 forward.setLayoutParams(forwardLayoutParams);
877 forward.setContentDescription("Forward Button");
878 forward.setId(Integer.valueOf(3));
879 int fwdResId = activityRes.getIdentifier("ic_action_next_item", "drawable", cordova.getActivity().getPackageName());
880 Drawable fwdIcon = activityRes.getDrawable(fwdResId);
881 if (navigationButtonColor != "") forward.setColorFilter(android.graphics.Color.parseColor(navigationButtonColor));
882 if (Build.VERSION.SDK_INT >= 16)
883 forward.setBackground(null);
884 else
885 forward.setBackgroundDrawable(null);
886 forward.setImageDrawable(fwdIcon);
887 forward.setScaleType(ImageView.ScaleType.FIT_CENTER);
888 forward.setPadding(0, this.dpToPixels(10), 0, this.dpToPixels(10));
889 if (Build.VERSION.SDK_INT >= 16)
890 forward.getAdjustViewBounds();
891
892 forward.setOnClickListener(new View.OnClickListener() {
893 public void onClick(View v) {
894 goForward();
895 }
896 });
897
898 // Edit Text Box
899 edittext = new EditText(cordova.getActivity());
900 RelativeLayout.LayoutParams textLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
901 textLayoutParams.addRule(RelativeLayout.RIGHT_OF, 1);
902 textLayoutParams.addRule(RelativeLayout.LEFT_OF, 5);
903 edittext.setLayoutParams(textLayoutParams);
904 edittext.setId(Integer.valueOf(4));
905 edittext.setSingleLine(true);
906 edittext.setText(url);
907 edittext.setInputType(InputType.TYPE_TEXT_VARIATION_URI);
908 edittext.setImeOptions(EditorInfo.IME_ACTION_GO);
909 edittext.setInputType(InputType.TYPE_NULL); // Will not except input... Makes the text NON-EDITABLE
910 edittext.setOnKeyListener(new View.OnKeyListener() {
911 public boolean onKey(View v, int keyCode, KeyEvent event) {
912 // If the event is a key-down event on the "enter" button
913 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
914 navigate(edittext.getText().toString());
915 return true;
916 }
917 return false;
918 }
919 });
920
921
922 // Header Close/Done button
923 int closeButtonId = leftToRight ? 1 : 5;
924 View close = createCloseButton(closeButtonId);
925 toolbar.addView(close);
926
927 // Footer
928 RelativeLayout footer = new RelativeLayout(cordova.getActivity());
929 int _footerColor;
930 if(footerColor != "") {
931 _footerColor = Color.parseColor(footerColor);
932 } else {
933 _footerColor = android.graphics.Color.LTGRAY;
934 }
935 footer.setBackgroundColor(_footerColor);
936 RelativeLayout.LayoutParams footerLayout = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, this.dpToPixels(44));
937 footerLayout.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE);
938 footer.setLayoutParams(footerLayout);
939 if (closeButtonCaption != "") footer.setPadding(this.dpToPixels(8), this.dpToPixels(8), this.dpToPixels(8), this.dpToPixels(8));
940 footer.setHorizontalGravity(Gravity.LEFT);
941 footer.setVerticalGravity(Gravity.BOTTOM);
942
943 View footerClose = createCloseButton(7);
944 footer.addView(footerClose);
945
946
947 // WebView
948 inAppWebView = new WebView(cordova.getActivity());
949 inAppWebView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
950 inAppWebView.setId(Integer.valueOf(6));
951 // File Chooser Implemented ChromeClient
952 inAppWebView.setWebChromeClient(new InAppChromeClient(thatWebView) {
953 // For Android 5.0+
954 public boolean onShowFileChooser (WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams)
955 {
956 LOG.d(LOG_TAG, "File Chooser 5.0+");
957 // If callback exists, finish it.
958 if(mUploadCallbackLollipop != null) {
959 mUploadCallbackLollipop.onReceiveValue(null);
960 }
961 mUploadCallbackLollipop = filePathCallback;
962
963 // Create File Chooser Intent
964 Intent content = new Intent(Intent.ACTION_GET_CONTENT);
965 content.addCategory(Intent.CATEGORY_OPENABLE);
966 content.setType("*/*");
967
968 // Run cordova startActivityForResult
969 cordova.startActivityForResult(InAppBrowser.this, Intent.createChooser(content, "Select File"), FILECHOOSER_REQUESTCODE_LOLLIPOP);
970 return true;
971 }
972
973 // For Android 4.1+
974 public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture)
975 {
976 LOG.d(LOG_TAG, "File Chooser 4.1+");
977 // Call file chooser for Android 3.0+
978 openFileChooser(uploadMsg, acceptType);
979 }
980
981 // For Android 3.0+
982 public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType)
983 {
984 LOG.d(LOG_TAG, "File Chooser 3.0+");
985 mUploadCallback = uploadMsg;
986 Intent content = new Intent(Intent.ACTION_GET_CONTENT);
987 content.addCategory(Intent.CATEGORY_OPENABLE);
988
989 // run startActivityForResult
990 cordova.startActivityForResult(InAppBrowser.this, Intent.createChooser(content, "Select File"), FILECHOOSER_REQUESTCODE);
991 }
992
993 });
994 currentClient = new InAppBrowserClient(thatWebView, edittext, beforeload);
995 inAppWebView.setWebViewClient(currentClient);
996 WebSettings settings = inAppWebView.getSettings();
997 settings.setJavaScriptEnabled(true);
998 settings.setDomStorageEnabled(true);
999 settings.setAllowContentAccess(true);
1000 settings.setAllowFileAccess(true);
1001 settings.setDatabaseEnabled(true);
1002 settings.setGeolocationEnabled(true);
1003 settings.setJavaScriptCanOpenWindowsAutomatically(true);
1004 settings.setBuiltInZoomControls(showZoomControls);
1005 settings.setPluginState(android.webkit.WebSettings.PluginState.ON);
1006
1007 // Add postMessage interface
1008 class JsObject {
1009 @JavascriptInterface
1010 public void postMessage(String data) {
1011 try {
1012 JSONObject obj = new JSONObject();
1013 obj.put("type", MESSAGE_EVENT);
1014 obj.put("data", new JSONObject(data));
1015 sendUpdate(obj, true);
1016 } catch (JSONException ex) {
1017 LOG.e(LOG_TAG, "data object passed to postMessage has caused a JSON error.");
1018 }
1019 }
1020 }
1021
1022 if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
1023 settings.setMediaPlaybackRequiresUserGesture(mediaPlaybackRequiresUserGesture);
1024 inAppWebView.addJavascriptInterface(new JsObject(), "cordova_iab");
1025 }
1026
1027 String overrideUserAgent = preferences.getString("OverrideUserAgent", null);
1028 String appendUserAgent = preferences.getString("AppendUserAgent", null);
1029
1030 if (overrideUserAgent != null) {
1031 settings.setUserAgentString(overrideUserAgent);
1032 }
1033 if (appendUserAgent != null) {
1034 settings.setUserAgentString(settings.getUserAgentString() + appendUserAgent);
1035 }
1036
1037 //Toggle whether this is enabled or not!
1038 Bundle appSettings = cordova.getActivity().getIntent().getExtras();
1039 boolean enableDatabase = appSettings == null ? true : appSettings.getBoolean("InAppBrowserStorageEnabled", true);
1040 if (enableDatabase) {
1041 String databasePath = cordova.getActivity().getApplicationContext().getDir("inAppBrowserDB", Context.MODE_PRIVATE).getPath();
1042 settings.setDatabasePath(databasePath);
1043 settings.setDatabaseEnabled(true);
1044 }
1045 settings.setDomStorageEnabled(true);
1046
1047 if (clearAllCache) {
1048 CookieManager.getInstance().removeAllCookie();
1049 } else if (clearSessionCache) {
1050 CookieManager.getInstance().removeSessionCookie();
1051 }
1052
1053 // Enable Thirdparty Cookies on >=Android 5.0 device
1054 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
1055 CookieManager.getInstance().setAcceptThirdPartyCookies(inAppWebView,true);
1056 }
1057 if (headers.isEmpty()) {
1058 inAppWebView.loadUrl(url);
1059 } else {
1060 inAppWebView.loadUrl(url, headers);
1061 }
1062
1063 inAppWebView.setId(Integer.valueOf(6));
1064 inAppWebView.getSettings().setLoadWithOverviewMode(true);
1065 inAppWebView.getSettings().setUseWideViewPort(useWideViewPort);
1066 inAppWebView.requestFocus();
1067 inAppWebView.requestFocusFromTouch();
1068
1069 // Add the back and forward buttons to our action button container layout
1070 actionButtonContainer.addView(back);
1071 actionButtonContainer.addView(forward);
1072
1073 // Add the views to our toolbar if they haven't been disabled
1074 if (!hideNavigationButtons) toolbar.addView(actionButtonContainer);
1075 if (!hideUrlBar) toolbar.addView(edittext);
1076
1077 // Don't add the toolbar if its been disabled
1078 if (getShowLocationBar()) {
1079 // Add our toolbar to our main view/layout
1080 main.addView(toolbar);
1081 }
1082
1083 // Add our webview to our main view/layout
1084 RelativeLayout webViewLayout = new RelativeLayout(cordova.getActivity());
1085 webViewLayout.addView(inAppWebView);
1086 main.addView(webViewLayout);
1087
1088 // Don't add the footer unless it's been enabled
1089 if (showFooter) {
1090 webViewLayout.addView(footer);
1091 }
1092
1093 WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
1094 lp.copyFrom(dialog.getWindow().getAttributes());
1095 lp.width = WindowManager.LayoutParams.MATCH_PARENT;
1096 lp.height = WindowManager.LayoutParams.MATCH_PARENT;
1097
1098 if (dialog != null) {
1099 dialog.setContentView(main);
1100 dialog.show();
1101 dialog.getWindow().setAttributes(lp);
1102 }
1103 // the goal of openhidden is to load the url and not display it
1104 // Show() needs to be called to cause the URL to be loaded
1105 if (openWindowHidden && dialog != null) {
1106 dialog.hide();
1107 }
1108 }
1109 };
1110 this.cordova.getActivity().runOnUiThread(runnable);
1111 return "";
1112 }
1113
1114 /**
1115 * Create a new plugin success result and send it back to JavaScript
1116 *
1117 * @param obj a JSONObject contain event payload information
1118 */
1119 private void sendUpdate(JSONObject obj, boolean keepCallback) {
1120 sendUpdate(obj, keepCallback, PluginResult.Status.OK);
1121 }
1122
1123 /**
1124 * Create a new plugin result and send it back to JavaScript
1125 *
1126 * @param obj a JSONObject contain event payload information
1127 * @param status the status code to return to the JavaScript environment
1128 */
1129 private void sendUpdate(JSONObject obj, boolean keepCallback, PluginResult.Status status) {
1130 if (callbackContext != null) {
1131 PluginResult result = new PluginResult(status, obj);
1132 result.setKeepCallback(keepCallback);
1133 callbackContext.sendPluginResult(result);
1134 if (!keepCallback) {
1135 callbackContext = null;
1136 }
1137 }
1138 }
1139
1140 /**
1141 * Receive File Data from File Chooser
1142 *
1143 * @param requestCode the requested code from chromeclient
1144 * @param resultCode the result code returned from android system
1145 * @param intent the data from android file chooser
1146 */
1147 public void onActivityResult(int requestCode, int resultCode, Intent intent) {
1148 // For Android >= 5.0
1149 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
1150 LOG.d(LOG_TAG, "onActivityResult (For Android >= 5.0)");
1151 // If RequestCode or Callback is Invalid
1152 if(requestCode != FILECHOOSER_REQUESTCODE_LOLLIPOP || mUploadCallbackLollipop == null) {
1153 super.onActivityResult(requestCode, resultCode, intent);
1154 return;
1155 }
1156 mUploadCallbackLollipop.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, intent));
1157 mUploadCallbackLollipop = null;
1158 }
1159 // For Android < 5.0
1160 else {
1161 LOG.d(LOG_TAG, "onActivityResult (For Android < 5.0)");
1162 // If RequestCode or Callback is Invalid
1163 if(requestCode != FILECHOOSER_REQUESTCODE || mUploadCallback == null) {
1164 super.onActivityResult(requestCode, resultCode, intent);
1165 return;
1166 }
1167
1168 if (null == mUploadCallback) return;
1169 Uri result = intent == null || resultCode != cordova.getActivity().RESULT_OK ? null : intent.getData();
1170
1171 mUploadCallback.onReceiveValue(result);
1172 mUploadCallback = null;
1173 }
1174 }
1175
1176 /**
1177 * The webview client receives notifications about appView
1178 */
1179 public class InAppBrowserClient extends WebViewClient {
1180 EditText edittext;
1181 CordovaWebView webView;
1182 String beforeload;
1183 boolean waitForBeforeload;
1184
1185 /**
1186 * Constructor.
1187 *
1188 * @param webView
1189 * @param mEditText
1190 */
1191 public InAppBrowserClient(CordovaWebView webView, EditText mEditText, String beforeload) {
1192 this.webView = webView;
1193 this.edittext = mEditText;
1194 this.beforeload = beforeload;
1195 this.waitForBeforeload = beforeload != null;
1196 }
1197
1198 /**
1199 * Override the URL that should be loaded
1200 *
1201 * Legacy (deprecated in API 24)
1202 * For Android 6 and below.
1203 *
1204 * @param webView
1205 * @param url
1206 */
1207 @SuppressWarnings("deprecation")
1208 @Override
1209 public boolean shouldOverrideUrlLoading(WebView webView, String url) {
1210 return shouldOverrideUrlLoading(url, null);
1211 }
1212
1213 /**
1214 * Override the URL that should be loaded
1215 *
1216 * New (added in API 24)
1217 * For Android 7 and above.
1218 *
1219 * @param webView
1220 * @param request
1221 */
1222 @TargetApi(Build.VERSION_CODES.N)
1223 @Override
1224 public boolean shouldOverrideUrlLoading(WebView webView, WebResourceRequest request) {
1225 String url = request.getUrl().toString();
1226
1227 if(url.startsWith("https://www.getresto-stage.sn77.net") || url.startsWith("https://www.get-resto.com")) {
1228 Map<String, String> headers = new HashMap<>();
1229 final String authorizationHeaderValue = "Bearer " + accessToken;
1230 headers.put("Authorization", authorizationHeaderValue);
1231 showWebPage(url, features, headers);
1232
1233 return true;
1234 }
1235
1236 return shouldOverrideUrlLoading(request.getUrl().toString(), request.getMethod());
1237 }
1238
1239 /**
1240 * Override the URL that should be loaded
1241 *
1242 * This handles a small subset of all the URIs that would be encountered.
1243 *
1244 * @param url
1245 * @param method
1246 */
1247 public boolean shouldOverrideUrlLoading(String url, String method) {
1248 boolean override = false;
1249 boolean useBeforeload = false;
1250 String errorMessage = null;
1251
1252 if (beforeload.equals("yes") && method == null) {
1253 useBeforeload = true;
1254 } else if(beforeload.equals("yes")
1255 //TODO handle POST requests then this condition can be removed:
1256 && !method.equals("POST"))
1257 {
1258 useBeforeload = true;
1259 } else if(beforeload.equals("get") && (method == null || method.equals("GET"))) {
1260 useBeforeload = true;
1261 } else if(beforeload.equals("post") && (method == null || method.equals("POST"))) {
1262 //TODO handle POST requests
1263 errorMessage = "beforeload doesn't yet support POST requests";
1264 }
1265
1266 // On first URL change, initiate JS callback. Only after the beforeload event, continue.
1267 if (useBeforeload && this.waitForBeforeload) {
1268 if(sendBeforeLoad(url, method)) {
1269 return false;
1270 }
1271 }
1272
1273 if(errorMessage != null) {
1274 try {
1275 LOG.e(LOG_TAG, errorMessage);
1276 JSONObject obj = new JSONObject();
1277 obj.put("type", LOAD_ERROR_EVENT);
1278 obj.put("url", url);
1279 obj.put("code", -1);
1280 obj.put("message", errorMessage);
1281 sendUpdate(obj, true, PluginResult.Status.ERROR);
1282 } catch(Exception e) {
1283 LOG.e(LOG_TAG, "Error sending loaderror for " + url + ": " + e.toString());
1284 }
1285 }
1286
1287 if (url.startsWith(WebView.SCHEME_TEL)) {
1288 try {
1289 Intent intent = new Intent(Intent.ACTION_DIAL);
1290 intent.setData(Uri.parse(url));
1291 cordova.getActivity().startActivity(intent);
1292 override = true;
1293 } catch (android.content.ActivityNotFoundException e) {
1294 LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString());
1295 }
1296 } else if (url.startsWith("geo:") || url.startsWith(WebView.SCHEME_MAILTO) || url.startsWith("market:") || url.startsWith("intent:")) {
1297 try {
1298 Intent intent = new Intent(Intent.ACTION_VIEW);
1299 intent.setData(Uri.parse(url));
1300 cordova.getActivity().startActivity(intent);
1301 override = true;
1302 } catch (android.content.ActivityNotFoundException e) {
1303 LOG.e(LOG_TAG, "Error with " + url + ": " + e.toString());
1304 }
1305 }
1306 // If sms:5551212?body=This is the message
1307 else if (url.startsWith("sms:")) {
1308 try {
1309 Intent intent = new Intent(Intent.ACTION_VIEW);
1310
1311 // Get address
1312 String address = null;
1313 int parmIndex = url.indexOf('?');
1314 if (parmIndex == -1) {
1315 address = url.substring(4);
1316 } else {
1317 address = url.substring(4, parmIndex);
1318
1319 // If body, then set sms body
1320 Uri uri = Uri.parse(url);
1321 String query = uri.getQuery();
1322 if (query != null) {
1323 if (query.startsWith("body=")) {
1324 intent.putExtra("sms_body", query.substring(5));
1325 }
1326 }
1327 }
1328 intent.setData(Uri.parse("sms:" + address));
1329 intent.putExtra("address", address);
1330 intent.setType("vnd.android-dir/mms-sms");
1331 cordova.getActivity().startActivity(intent);
1332 override = true;
1333 } catch (android.content.ActivityNotFoundException e) {
1334 LOG.e(LOG_TAG, "Error sending sms " + url + ":" + e.toString());
1335 }
1336 }
1337 // Test for whitelisted custom scheme names like mycoolapp:// or twitteroauthresponse:// (Twitter Oauth Response)
1338 else if (!url.startsWith("http:") && !url.startsWith("https:") && url.matches("^[A-Za-z0-9+.-]*://.*?$")) {
1339 if (allowedSchemes == null) {
1340 String allowed = preferences.getString("AllowedSchemes", null);
1341 if(allowed != null) {
1342 allowedSchemes = allowed.split(",");
1343 }
1344 }
1345 if (allowedSchemes != null) {
1346 for (String scheme : allowedSchemes) {
1347 if (url.startsWith(scheme)) {
1348 try {
1349 JSONObject obj = new JSONObject();
1350 obj.put("type", "customscheme");
1351 obj.put("url", url);
1352 sendUpdate(obj, true);
1353 override = true;
1354 } catch (JSONException ex) {
1355 LOG.e(LOG_TAG, "Custom Scheme URI passed in has caused a JSON error.");
1356 }
1357 }
1358 }
1359 }
1360 }
1361
1362 if (useBeforeload) {
1363 this.waitForBeforeload = true;
1364 }
1365 return override;
1366 }
1367
1368 private boolean sendBeforeLoad(String url, String method) {
1369 try {
1370 JSONObject obj = new JSONObject();
1371 obj.put("type", BEFORELOAD);
1372 obj.put("url", url);
1373 if(method != null) {
1374 obj.put("method", method);
1375 }
1376 sendUpdate(obj, true);
1377 return true;
1378 } catch (JSONException ex) {
1379 LOG.e(LOG_TAG, "URI passed in has caused a JSON error.");
1380 }
1381 return false;
1382 }
1383
1384
1385 /**
1386 * Legacy (deprecated in API 21)
1387 * For Android 4.4 and below.
1388 * @param view
1389 * @param url
1390 * @return
1391 */
1392 @SuppressWarnings("deprecation")
1393 @Override
1394 public WebResourceResponse shouldInterceptRequest (final WebView view, String url) {
1395 return shouldInterceptRequest(url, super.shouldInterceptRequest(view, url), null);
1396 }
1397
1398 /**
1399 * New (added in API 21)
1400 * For Android 5.0 and above.
1401 *
1402 * @param webView
1403 * @param request
1404 */
1405 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
1406 @Override
1407 public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
1408 String url = request.getUrl().toString();
1409 if ((url.startsWith("https://www.get-resto.com") || url.startsWith("https://getresto-stage.sn77.net"))
1410 && (!request.getRequestHeaders().containsKey("authorization") || !request.getRequestHeaders().containsKey("Authorization"))) {
1411
1412 //request.getRequestHeaders().put("Authorization", "Bearer " + "accessToken");
1413
1414 final Handler handler = new Handler(Looper.getMainLooper());
1415 handler.postDelayed(() -> view.loadUrl(url, request.getRequestHeaders()), 100);
1416 }
1417
1418 return super.shouldInterceptRequest(view, request);
1419 }
1420
1421 public WebResourceResponse shouldInterceptRequest(String url, WebResourceResponse response, String method) {
1422 return response;
1423 }
1424
1425 /*
1426 * onPageStarted fires the LOAD_START_EVENT
1427 *
1428 * @param view
1429 * @param url
1430 * @param favicon
1431 */
1432 @Override
1433 public void onPageStarted(WebView view, String url, Bitmap favicon) {
1434 super.onPageStarted(view, url, favicon);
1435 String newloc = "";
1436 if (url.startsWith("http:") || url.startsWith("https:") || url.startsWith("file:")) {
1437 newloc = url;
1438 }
1439 else
1440 {
1441 // Assume that everything is HTTP at this point, because if we don't specify,
1442 // it really should be. Complain loudly about this!!!
1443 LOG.e(LOG_TAG, "Possible Uncaught/Unknown URI");
1444 newloc = "http://" + url;
1445 }
1446
1447 // Update the UI if we haven't already
1448 if (!newloc.equals(edittext.getText().toString())) {
1449 edittext.setText(newloc);
1450 }
1451
1452 try {
1453 JSONObject obj = new JSONObject();
1454 obj.put("type", LOAD_START_EVENT);
1455 obj.put("url", newloc);
1456 sendUpdate(obj, true);
1457 } catch (JSONException ex) {
1458 LOG.e(LOG_TAG, "URI passed in has caused a JSON error.");
1459 }
1460 }
1461
1462 public void onPageFinished(WebView view, String url) {
1463 super.onPageFinished(view, url);
1464
1465 // Set the namespace for postMessage()
1466 if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
1467 injectDeferredObject("window.webkit={messageHandlers:{cordova_iab:cordova_iab}}", null);
1468 }
1469
1470 // CB-10395 InAppBrowser's WebView not storing cookies reliable to local device storage
1471 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
1472 CookieManager.getInstance().flush();
1473 } else {
1474 CookieSyncManager.getInstance().sync();
1475 }
1476
1477 // https://issues.apache.org/jira/browse/CB-11248
1478 view.clearFocus();
1479 view.requestFocus();
1480
1481 try {
1482 JSONObject obj = new JSONObject();
1483 obj.put("type", LOAD_STOP_EVENT);
1484 obj.put("url", url);
1485
1486 sendUpdate(obj, true);
1487 } catch (JSONException ex) {
1488 LOG.d(LOG_TAG, "Should never happen");
1489 }
1490 }
1491
1492 public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
1493 super.onReceivedError(view, errorCode, description, failingUrl);
1494
1495 try {
1496 JSONObject obj = new JSONObject();
1497 obj.put("type", LOAD_ERROR_EVENT);
1498 obj.put("url", failingUrl);
1499 obj.put("code", errorCode);
1500 obj.put("message", description);
1501
1502 sendUpdate(obj, true, PluginResult.Status.ERROR);
1503 } catch (JSONException ex) {
1504 LOG.d(LOG_TAG, "Should never happen");
1505 }
1506 }
1507
1508 @Override
1509 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
1510 super.onReceivedSslError(view, handler, error);
1511 try {
1512 JSONObject obj = new JSONObject();
1513 obj.put("type", LOAD_ERROR_EVENT);
1514 obj.put("url", error.getUrl());
1515 obj.put("code", 0);
1516 obj.put("sslerror", error.getPrimaryError());
1517 String message;
1518 switch (error.getPrimaryError()) {
1519 case SslError.SSL_DATE_INVALID:
1520 message = "The date of the certificate is invalid";
1521 break;
1522 case SslError.SSL_EXPIRED:
1523 message = "The certificate has expired";
1524 break;
1525 case SslError.SSL_IDMISMATCH:
1526 message = "Hostname mismatch";
1527 break;
1528 default:
1529 case SslError.SSL_INVALID:
1530 message = "A generic error occurred";
1531 break;
1532 case SslError.SSL_NOTYETVALID:
1533 message = "The certificate is not yet valid";
1534 break;
1535 case SslError.SSL_UNTRUSTED:
1536 message = "The certificate authority is not trusted";
1537 break;
1538 }
1539 obj.put("message", message);
1540
1541 sendUpdate(obj, true, PluginResult.Status.ERROR);
1542 } catch (JSONException ex) {
1543 LOG.d(LOG_TAG, "Should never happen");
1544 }
1545 handler.cancel();
1546 }
1547
1548 /**
1549 * On received http auth request.
1550 */
1551 @Override
1552 public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
1553
1554 // Check if there is some plugin which can resolve this auth challenge
1555 PluginManager pluginManager = null;
1556 try {
1557 Method gpm = webView.getClass().getMethod("getPluginManager");
1558 pluginManager = (PluginManager)gpm.invoke(webView);
1559 } catch (NoSuchMethodException e) {
1560 LOG.d(LOG_TAG, e.getLocalizedMessage());
1561 } catch (IllegalAccessException e) {
1562 LOG.d(LOG_TAG, e.getLocalizedMessage());
1563 } catch (InvocationTargetException e) {
1564 LOG.d(LOG_TAG, e.getLocalizedMessage());
1565 }
1566
1567 if (pluginManager == null) {
1568 try {
1569 Field pmf = webView.getClass().getField("pluginManager");
1570 pluginManager = (PluginManager)pmf.get(webView);
1571 } catch (NoSuchFieldException e) {
1572 LOG.d(LOG_TAG, e.getLocalizedMessage());
1573 } catch (IllegalAccessException e) {
1574 LOG.d(LOG_TAG, e.getLocalizedMessage());
1575 }
1576 }
1577
1578 if (pluginManager != null && pluginManager.onReceivedHttpAuthRequest(webView, new CordovaHttpAuthHandler(handler), host, realm)) {
1579 return;
1580 }
1581
1582 // By default handle 401 like we'd normally do!
1583 super.onReceivedHttpAuthRequest(view, handler, host, realm);
1584 }
1585 }
1586}
1587