cordova混合App开发:Cordova+Vue实现Android APP开发 (app内打开浏览器及横竖屏) (七)
app内打开浏览器目前主要2个cordova插件
第一步
在已有项目目录下添加插件
cordova plugin add cordova-plugin-themeablebrowser
第二步 准备图片
vue项目下新建static/browserIcons文件夹, 放入准备好的 back.png 和 close.png
第三步 修改webpack打包规则
根据插件要求, wwwImage
从Cordova的www
目录加载图像。所以我们需要把插件需要的第二步图片 放到cordova www目录下
因为www目录 我们之前修改过vue项目的build目录 每次vue build 会把www目录删掉从新生成, 所以手工拷贝不靠谱
安装 copy-webpack-plugin
npm install --save copy-webpack-plugin
修改 vue项目 vue.config.js
from 定义要拷贝的源文件 from:__dirname+'/src/components'
to 定义要拷贝到的目标文件夹 to: __dirname+'/dist'
toType file 或者 dir 可选,默认是文件
force 强制覆盖前面的插件 可选,默认是文件
context 可选,默认base context可用specific context
flatten 只拷贝指定的文件 可以用模糊匹配
ignore 忽略拷贝指定的文件 可以模糊匹配
第四步 修改相应vue页面 我这里是修改的 helloword.vue
相关按钮及事件 参考 插件github https://github.com/initialxy/cordova-plugin-themeablebrowser
<template> <div class="hello"> <h1>{{ msg }}</h1> <button @click="handleAxiosRequest">axios调用</button> <h1>{{ bjtime }}</h1> <button @click="openAppBrowser('https://www.google.com/')">跳转页面1</button> <button @click="openAppBrowser('https://www.nginx.com/')">跳转页面2</button> </div> </template> <script> export default { name: "HelloWorld", props: { msg: String, }, data() { return { bjtime: null, }; }, methods: { openAppBrowser(url) { // Keep in mind that you must add your own images to native resource. // Images below are for sample only. They are not imported by this plugin. window.cordova.ThemeableBrowser.open(url, "_blank", { statusbar: { color: "#EBCE9C", }, title: { color: "#000000", showPageTitle: true, staticText:"换成自己的标题,或者不显示" }, backButton: { wwwImage: "browserIcons/back.png", wwwImagePressed: "browserIcons/back.png", align: "left", wwwImageDensity: 2, event: "backPressed", }, closeButton: { wwwImage: "browserIcons/close.png", wwwImagePressed: "browserIcons/close.png", align: "right", wwwImageDensity: 2, event: "closePressed", }, backButtonCanClose: true, }) .addEventListener("backPressed", function (e) { alert("back pressed"+e); }) .addEventListener("helloPressed", function (e) { alert("hello pressed"+e); }) .addEventListener("sharePressed", function (e) { alert(e.url); }) // .addEventListener(window.cordova.ThemeableBrowser.EVT_ERR, function (e) { // console.error(e.message); // }) // .addEventListener(window.cordova.ThemeableBrowser.EVT_WRN, function (e) { // console.log(e.message); // }); }, handleAxiosRequest() { this.$axios .get("https://quan.suning.com/getSysTime.do") .then(({ data }) => { this.bjtime = data; }) .catch((err) => { console.log(err); }); }, }, }; </script>
就可以看到页面了 点击会返回和关闭 会触发设定好的事件
第五步 修改源码支持横竖屏打开
cordova-plugin-themeablebrowser
这个插件 并不支持横竖屏打开webview页面 如何让它支持呢
用 Android studio打开 platforms/android/build.gradle 文件 找到下面 ThemeableBrowser 类
把代码贴出来
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 */ 19 package com.initialxy.cordova.themeablebrowser; 20 21 import android.annotation.SuppressLint; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.res.Resources; 25 import android.graphics.Bitmap; 26 import android.graphics.BitmapFactory; 27 import android.graphics.Canvas; 28 import android.graphics.Paint; 29 import android.graphics.drawable.BitmapDrawable; 30 import android.graphics.drawable.Drawable; 31 import android.graphics.drawable.StateListDrawable; 32 import android.net.Uri; 33 import android.os.Build; 34 import android.os.Bundle; 35 import android.provider.Browser; 36 import android.text.InputType; 37 import android.text.TextUtils; 38 import android.util.DisplayMetrics; 39 import android.util.Log; 40 import android.util.TypedValue; 41 import android.view.Gravity; 42 import android.view.KeyEvent; 43 import android.view.MotionEvent; 44 import android.view.View; 45 import android.view.ViewGroup; 46 import android.view.ViewGroup.LayoutParams; 47 import android.view.Window; 48 import android.view.WindowManager; 49 import android.view.inputmethod.EditorInfo; 50 import android.view.inputmethod.InputMethodManager; 51 import android.webkit.CookieManager; 52 import android.webkit.WebSettings; 53 import android.webkit.WebView; 54 import android.webkit.WebViewClient; 55 import android.widget.AdapterView; 56 import android.widget.ArrayAdapter; 57 import android.widget.Button; 58 import android.widget.EditText; 59 import android.widget.FrameLayout; 60 import android.widget.LinearLayout; 61 import android.widget.RelativeLayout; 62 import android.widget.Spinner; 63 import android.widget.TextView; 64 65 import org.apache.cordova.CallbackContext; 66 import org.apache.cordova.CordovaArgs; 67 import org.apache.cordova.CordovaPlugin; 68 import org.apache.cordova.CordovaWebView; 69 import org.apache.cordova.PluginManager; 70 import org.apache.cordova.PluginResult; 71 import org.apache.cordova.Whitelist; 72 import org.json.JSONException; 73 import org.json.JSONObject; 74 75 import java.io.File; 76 import java.io.IOException; 77 import java.io.InputStream; 78 import java.lang.reflect.InvocationTargetException; 79 import java.lang.reflect.Method; 80 81 @SuppressLint("SetJavaScriptEnabled") 82 public class ThemeableBrowser extends CordovaPlugin { 83 84 private static final String NULL = "null"; 85 protected static final String LOG_TAG = "ThemeableBrowser"; 86 private static final String SELF = "_self"; 87 private static final String SYSTEM = "_system"; 88 // private static final String BLANK = "_blank"; 89 private static final String EXIT_EVENT = "exit"; 90 private static final String LOAD_START_EVENT = "loadstart"; 91 private static final String LOAD_STOP_EVENT = "loadstop"; 92 private static final String LOAD_ERROR_EVENT = "loaderror"; 93 94 private static final String ALIGN_LEFT = "left"; 95 private static final String ALIGN_RIGHT = "right"; 96 97 private static final int TOOLBAR_DEF_HEIGHT = 44; 98 private static final int DISABLED_ALPHA = 127; // 50% AKA 127/255. 99 100 private static final String EVT_ERR = "ThemeableBrowserError"; 101 private static final String EVT_WRN = "ThemeableBrowserWarning"; 102 private static final String ERR_CRITICAL = "critical"; 103 private static final String ERR_LOADFAIL = "loadfail"; 104 private static final String WRN_UNEXPECTED = "unexpected"; 105 private static final String WRN_UNDEFINED = "undefined"; 106 107 private ThemeableBrowserDialog dialog; 108 private WebView inAppWebView; 109 private EditText edittext; 110 private CallbackContext callbackContext; 111 112 /** 113 * Executes the request and returns PluginResult. 114 * 115 * @param action The action to execute. 116 * @param args The exec() arguments, wrapped with some Cordova helpers. 117 * @param callbackContext The callback context used when calling back into JavaScript. 118 * @return 119 * @throws JSONException 120 */ 121 public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException { 122 if (action.equals("open")) { 123 this.callbackContext = callbackContext; 124 final String url = args.getString(0); 125 String t = args.optString(1); 126 if (t == null || t.equals("") || t.equals(NULL)) { 127 t = SELF; 128 } 129 final String target = t; 130 final Options features = parseFeature(args.optString(2)); 131 132 this.cordova.getActivity().runOnUiThread(new Runnable() { 133 @Override 134 public void run() { 135 String result = ""; 136 // SELF 137 if (SELF.equals(target)) { 138 /* This code exists for compatibility between 3.x and 4.x versions of Cordova. 139 * Previously the Config class had a static method, isUrlWhitelisted(). That 140 * responsibility has been moved to the plugins, with an aggregating method in 141 * PluginManager. 142 */ 143 Boolean shouldAllowNavigation = null; 144 if (url.startsWith("javascript:")) { 145 shouldAllowNavigation = true; 146 } 147 if (shouldAllowNavigation == null) { 148 shouldAllowNavigation = new Whitelist().isUrlWhiteListed(url); 149 } 150 if (shouldAllowNavigation == null) { 151 try { 152 Method gpm = webView.getClass().getMethod("getPluginManager"); 153 PluginManager pm = (PluginManager)gpm.invoke(webView); 154 Method san = pm.getClass().getMethod("shouldAllowNavigation", String.class); 155 shouldAllowNavigation = (Boolean)san.invoke(pm, url); 156 } catch (NoSuchMethodException e) { 157 } catch (IllegalAccessException e) { 158 } catch (InvocationTargetException e) { 159 } 160 } 161 // load in webview 162 if (Boolean.TRUE.equals(shouldAllowNavigation)) { 163 webView.loadUrl(url); 164 } 165 //Load the dialer 166 else if (url.startsWith(WebView.SCHEME_TEL)) 167 { 168 try { 169 Intent intent = new Intent(Intent.ACTION_DIAL); 170 intent.setData(Uri.parse(url)); 171 cordova.getActivity().startActivity(intent); 172 } catch (android.content.ActivityNotFoundException e) { 173 emitError(ERR_CRITICAL, 174 String.format("Error dialing %s: %s", url, e.toString())); 175 } 176 } 177 // load in ThemeableBrowser 178 else { 179 result = showWebPage(url, features); 180 } 181 } 182 // SYSTEM 183 else if (SYSTEM.equals(target)) { 184 result = openExternal(url); 185 } 186 // BLANK - or anything else 187 else { 188 result = showWebPage(url, features); 189 } 190 191 PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, result); 192 pluginResult.setKeepCallback(true); 193 callbackContext.sendPluginResult(pluginResult); 194 } 195 }); 196 } 197 else if (action.equals("close")) { 198 closeDialog(); 199 } 200 else if (action.equals("injectScriptCode")) { 201 String jsWrapper = null; 202 if (args.getBoolean(1)) { 203 jsWrapper = String.format("prompt(JSON.stringify([eval(%%s)]), 'gap-iab://%s')", callbackContext.getCallbackId()); 204 } 205 injectDeferredObject(args.getString(0), jsWrapper); 206 } 207 else if (action.equals("injectScriptFile")) { 208 String jsWrapper; 209 if (args.getBoolean(1)) { 210 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()); 211 } else { 212 jsWrapper = "(function(d) { var c = d.createElement('script'); c.src = %s; d.body.appendChild(c); })(document)"; 213 } 214 injectDeferredObject(args.getString(0), jsWrapper); 215 } 216 else if (action.equals("injectStyleCode")) { 217 String jsWrapper; 218 if (args.getBoolean(1)) { 219 jsWrapper = String.format("(function(d) { var c = d.createElement('style'); c.innerHTML = %%s; d.body.appendChild(c); prompt('', 'gap-iab://%s');})(document)", callbackContext.getCallbackId()); 220 } else { 221 jsWrapper = "(function(d) { var c = d.createElement('style'); c.innerHTML = %s; d.body.appendChild(c); })(document)"; 222 } 223 injectDeferredObject(args.getString(0), jsWrapper); 224 } 225 else if (action.equals("injectStyleFile")) { 226 String jsWrapper; 227 if (args.getBoolean(1)) { 228 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()); 229 } else { 230 jsWrapper = "(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %s; d.head.appendChild(c); })(document)"; 231 } 232 injectDeferredObject(args.getString(0), jsWrapper); 233 } 234 else if (action.equals("show")) { 235 this.cordova.getActivity().runOnUiThread(new Runnable() { 236 @Override 237 public void run() { 238 dialog.show(); 239 } 240 }); 241 PluginResult pluginResult = new PluginResult(PluginResult.Status.OK); 242 pluginResult.setKeepCallback(true); 243 this.callbackContext.sendPluginResult(pluginResult); 244 } 245 else if (action.equals("reload")) { 246 if (inAppWebView != null) { 247 this.cordova.getActivity().runOnUiThread(new Runnable() { 248 @Override 249 public void run() { 250 inAppWebView.reload(); 251 } 252 }); 253 } 254 } 255 else { 256 return false; 257 } 258 return true; 259 } 260 261 /** 262 * Called when the view navigates. 263 */ 264 @Override 265 public void onReset() { 266 closeDialog(); 267 } 268 269 /** 270 * Called by AccelBroker when listener is to be shut down. 271 * Stop listener. 272 */ 273 public void onDestroy() { 274 closeDialog(); 275 } 276 277 /** 278 * Inject an object (script or style) into the ThemeableBrowser WebView. 279 * 280 * This is a helper method for the inject{Script|Style}{Code|File} API calls, which 281 * provides a consistent method for injecting JavaScript code into the document. 282 * 283 * If a wrapper string is supplied, then the source string will be JSON-encoded (adding 284 * quotes) and wrapped using string formatting. (The wrapper string should have a single 285 * '%s' marker) 286 * 287 * @param source The source object (filename or script/style text) to inject into 288 * the document. 289 * @param jsWrapper A JavaScript string to wrap the source string in, so that the object 290 * is properly injected, or null if the source string is JavaScript text 291 * which should be executed directly. 292 */ 293 private void injectDeferredObject(String source, String jsWrapper) { 294 String scriptToInject; 295 if (jsWrapper != null) { 296 org.json.JSONArray jsonEsc = new org.json.JSONArray(); 297 jsonEsc.put(source); 298 String jsonRepr = jsonEsc.toString(); 299 String jsonSourceString = jsonRepr.substring(1, jsonRepr.length()-1); 300 scriptToInject = String.format(jsWrapper, jsonSourceString); 301 } else { 302 scriptToInject = source; 303 } 304 final String finalScriptToInject = scriptToInject; 305 this.cordova.getActivity().runOnUiThread(new Runnable() { 306 @SuppressLint("NewApi") 307 @Override 308 public void run() { 309 if (inAppWebView != null) { 310 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 311 // This action will have the side-effect of blurring the currently focused 312 // element 313 inAppWebView.loadUrl("javascript:" + finalScriptToInject); 314 } else { 315 inAppWebView.evaluateJavascript(finalScriptToInject, null); 316 } 317 } 318 } 319 }); 320 } 321 322 /** 323 * Put the list of features into a hash map 324 * 325 * @param optString 326 * @return 327 */ 328 private Options parseFeature(String optString) { 329 Options result = null; 330 if (optString != null && !optString.isEmpty()) { 331 try { 332 result = ThemeableBrowserUnmarshaller.JSONToObj( 333 optString, Options.class); 334 } catch (Exception e) { 335 emitError(ERR_CRITICAL, 336 String.format("Invalid JSON @s", e.toString())); 337 } 338 } else { 339 emitWarning(WRN_UNDEFINED, 340 "No config was given, defaults will be used, " 341 + "which is quite boring."); 342 } 343 344 if (result == null) { 345 result = new Options(); 346 } 347 348 // Always show location, this property is overwritten. 349 result.location = true; 350 351 return result; 352 } 353 354 /** 355 * Display a new browser with the specified URL. 356 * 357 * @param url 358 * @return 359 */ 360 public String openExternal(String url) { 361 try { 362 Intent intent = null; 363 intent = new Intent(Intent.ACTION_VIEW); 364 // Omitting the MIME type for file: URLs causes "No Activity found to handle Intent". 365 // Adding the MIME type to http: URLs causes them to not be handled by the downloader. 366 Uri uri = Uri.parse(url); 367 if ("file".equals(uri.getScheme())) { 368 intent.setDataAndType(uri, webView.getResourceApi().getMimeType(uri)); 369 } else { 370 intent.setData(uri); 371 } 372 intent.putExtra(Browser.EXTRA_APPLICATION_ID, cordova.getActivity().getPackageName()); 373 this.cordova.getActivity().startActivity(intent); 374 return ""; 375 } catch (android.content.ActivityNotFoundException e) { 376 Log.d(LOG_TAG, "ThemeableBrowser: Error loading url "+url+":"+ e.toString()); 377 return e.toString(); 378 } 379 } 380 381 /** 382 * Closes the dialog 383 */ 384 public void closeDialog() { 385 this.cordova.getActivity().runOnUiThread(new Runnable() { 386 @Override 387 public void run() { 388 // The JS protects against multiple calls, so this should happen only when 389 // closeDialog() is called by other native code. 390 if (inAppWebView == null) { 391 emitWarning(WRN_UNEXPECTED, "Close called but already closed."); 392 return; 393 } 394 395 inAppWebView.setWebViewClient(new WebViewClient() { 396 // NB: wait for about:blank before dismissing 397 public void onPageFinished(WebView view, String url) { 398 if (dialog != null) { 399 dialog.dismiss(); 400 } 401 402 // Clean up. 403 dialog = null; 404 inAppWebView = null; 405 edittext = null; 406 callbackContext = null; 407 } 408 }); 409 410 // NB: From SDK 19: "If you call methods on WebView from any 411 // thread other than your app's UI thread, it can cause 412 // unexpected results." 413 // http://developer.android.com/guide/webapps/migrating.html#Threads 414 inAppWebView.loadUrl("about:blank"); 415 416 try { 417 JSONObject obj = new JSONObject(); 418 obj.put("type", EXIT_EVENT); 419 sendUpdate(obj, false); 420 } catch (JSONException ex) { 421 } 422 } 423 }); 424 } 425 426 private void emitButtonEvent(Event event, String url) { 427 emitButtonEvent(event, url, null); 428 } 429 430 private void emitButtonEvent(Event event, String url, Integer index) { 431 if (event != null && event.event != null) { 432 try { 433 JSONObject obj = new JSONObject(); 434 obj.put("type", event.event); 435 obj.put("url", url); 436 if (index != null) { 437 obj.put("index", index.intValue()); 438 } 439 sendUpdate(obj, true); 440 } catch (JSONException e) { 441 // Ignore, should never happen. 442 } 443 } else { 444 emitWarning(WRN_UNDEFINED, 445 "Button clicked, but event property undefined. " 446 + "No event will be raised."); 447 } 448 } 449 450 private void emitError(String code, String message) { 451 emitLog(EVT_ERR, code, message); 452 } 453 454 private void emitWarning(String code, String message) { 455 emitLog(EVT_WRN, code, message); 456 } 457 458 private void emitLog(String type, String code, String message) { 459 if (type != null) { 460 try { 461 JSONObject obj = new JSONObject(); 462 obj.put("type", type); 463 obj.put("code", code); 464 obj.put("message", message); 465 sendUpdate(obj, true); 466 } catch (JSONException e) { 467 // Ignore, should never happen. 468 } 469 } 470 } 471 472 /** 473 * Checks to see if it is possible to go back one page in history, then does so. 474 */ 475 public void goBack() { 476 if (this.inAppWebView != null && this.inAppWebView.canGoBack()) { 477 this.inAppWebView.goBack(); 478 } 479 } 480 481 /** 482 * Can the web browser go back? 483 * @return boolean 484 */ 485 public boolean canGoBack() { 486 return this.inAppWebView != null && this.inAppWebView.canGoBack(); 487 } 488 489 /** 490 * Checks to see if it is possible to go forward one page in history, then does so. 491 */ 492 private void goForward() { 493 if (this.inAppWebView != null && this.inAppWebView.canGoForward()) { 494 this.inAppWebView.goForward(); 495 } 496 } 497 498 /** 499 * Navigate to the new page 500 * 501 * @param url to load 502 */ 503 private void navigate(String url) { 504 InputMethodManager imm = (InputMethodManager)this.cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); 505 imm.hideSoftInputFromWindow(edittext.getWindowToken(), 0); 506 507 if (!url.startsWith("http") && !url.startsWith("file:")) { 508 this.inAppWebView.loadUrl("http://" + url); 509 } else { 510 this.inAppWebView.loadUrl(url); 511 } 512 this.inAppWebView.requestFocus(); 513 } 514 515 private ThemeableBrowser getThemeableBrowser() { 516 return this; 517 } 518 519 /** 520 * Display a new browser with the specified URL. 521 * 522 * @param url 523 * @param features 524 * @return 525 */ 526 public String showWebPage(final String url, final Options features) { 527 final CordovaWebView thatWebView = this.webView; 528 529 // Create dialog in new thread 530 Runnable runnable = new Runnable() { 531 @SuppressLint("NewApi") 532 public void run() { 533 // Let's create the main dialog 534 dialog = new ThemeableBrowserDialog(cordova.getActivity(), 535 android.R.style.Theme_Black_NoTitleBar, 536 features.hardwareback); 537 if (!features.disableAnimation) { 538 dialog.getWindow().getAttributes().windowAnimations 539 = android.R.style.Animation_Dialog; 540 } 541 dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); 542 dialog.setCancelable(true); 543 dialog.setThemeableBrowser(getThemeableBrowser()); 544 545 // Main container layout 546 ViewGroup main = null; 547 548 if (features.fullscreen) { 549 main = new FrameLayout(cordova.getActivity()); 550 } else { 551 main = new LinearLayout(cordova.getActivity()); 552 ((LinearLayout) main).setOrientation(LinearLayout.VERTICAL); 553 } 554 555 // Toolbar layout 556 Toolbar toolbarDef = features.toolbar; 557 FrameLayout toolbar = new FrameLayout(cordova.getActivity()); 558 toolbar.setBackgroundColor(hexStringToColor( 559 toolbarDef != null && toolbarDef.color != null 560 ? toolbarDef.color : "#ffffffff")); 561 toolbar.setLayoutParams(new ViewGroup.LayoutParams( 562 LayoutParams.MATCH_PARENT, 563 dpToPixels(toolbarDef != null 564 ? toolbarDef.height : TOOLBAR_DEF_HEIGHT))); 565 566 if (toolbarDef != null 567 && (toolbarDef.image != null || toolbarDef.wwwImage != null)) { 568 try { 569 Drawable background = getImage(toolbarDef.image 570 , toolbarDef.wwwImage, toolbarDef.wwwImageDensity); 571 setBackground(toolbar, background); 572 } catch (Resources.NotFoundException e) { 573 emitError(ERR_LOADFAIL, 574 String.format("Image for toolbar, %s, failed to load", 575 toolbarDef.image)); 576 } catch (IOException ioe) { 577 emitError(ERR_LOADFAIL, 578 String.format("Image for toolbar, %s, failed to load", 579 toolbarDef.wwwImage)); 580 } 581 } 582 583 // Left Button Container layout 584 LinearLayout leftButtonContainer = new LinearLayout(cordova.getActivity()); 585 FrameLayout.LayoutParams leftButtonContainerParams = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 586 leftButtonContainerParams.gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL; 587 leftButtonContainer.setLayoutParams(leftButtonContainerParams); 588 leftButtonContainer.setVerticalGravity(Gravity.CENTER_VERTICAL); 589 590 // Right Button Container layout 591 LinearLayout rightButtonContainer = new LinearLayout(cordova.getActivity()); 592 FrameLayout.LayoutParams rightButtonContainerParams = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 593 rightButtonContainerParams.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL; 594 rightButtonContainer.setLayoutParams(rightButtonContainerParams); 595 rightButtonContainer.setVerticalGravity(Gravity.CENTER_VERTICAL); 596 597 // Edit Text Box 598 edittext = new EditText(cordova.getActivity()); 599 RelativeLayout.LayoutParams textLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 600 textLayoutParams.addRule(RelativeLayout.RIGHT_OF, 1); 601 textLayoutParams.addRule(RelativeLayout.LEFT_OF, 5); 602 edittext.setLayoutParams(textLayoutParams); 603 edittext.setSingleLine(true); 604 edittext.setText(url); 605 edittext.setInputType(InputType.TYPE_TEXT_VARIATION_URI); 606 edittext.setImeOptions(EditorInfo.IME_ACTION_GO); 607 edittext.setInputType(InputType.TYPE_NULL); // Will not except input... Makes the text NON-EDITABLE 608 edittext.setOnKeyListener(new View.OnKeyListener() { 609 public boolean onKey(View v, int keyCode, KeyEvent event) { 610 // If the event is a key-down event on the "enter" button 611 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { 612 navigate(edittext.getText().toString()); 613 return true; 614 } 615 return false; 616 } 617 }); 618 619 // Back button 620 final Button back = createButton( 621 features.backButton, 622 "back button", 623 new View.OnClickListener() { 624 public void onClick(View v) { 625 emitButtonEvent( 626 features.backButton, 627 inAppWebView.getUrl()); 628 629 if (features.backButtonCanClose && !canGoBack()) { 630 closeDialog(); 631 } else { 632 goBack(); 633 } 634 } 635 } 636 ); 637 638 if (back != null) { 639 back.setEnabled(features.backButtonCanClose); 640 } 641 642 // Forward button 643 final Button forward = createButton( 644 features.forwardButton, 645 "forward button", 646 new View.OnClickListener() { 647 public void onClick(View v) { 648 emitButtonEvent( 649 features.forwardButton, 650 inAppWebView.getUrl()); 651 652 goForward(); 653 } 654 } 655 ); 656 657 if (back != null) { 658 back.setEnabled(false); 659 } 660 661 662 // Close/Done button 663 Button close = createButton( 664 features.closeButton, 665 "close button", 666 new View.OnClickListener() { 667 public void onClick(View v) { 668 emitButtonEvent( 669 features.closeButton, 670 inAppWebView.getUrl()); 671 closeDialog(); 672 } 673 } 674 ); 675 676 // Menu button 677 Spinner menu = features.menu != null 678 ? new MenuSpinner(cordova.getActivity()) : null; 679 if (menu != null) { 680 menu.setLayoutParams(new LinearLayout.LayoutParams( 681 LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); 682 menu.setContentDescription("menu button"); 683 setButtonImages(menu, features.menu, DISABLED_ALPHA); 684 685 // We are not allowed to use onClickListener for Spinner, so we will use 686 // onTouchListener as a fallback. 687 menu.setOnTouchListener(new View.OnTouchListener() { 688 @Override 689 public boolean onTouch(View v, MotionEvent event) { 690 if (event.getAction() == MotionEvent.ACTION_UP) { 691 emitButtonEvent( 692 features.menu, 693 inAppWebView.getUrl()); 694 } 695 return false; 696 } 697 }); 698 699 if (features.menu.items != null) { 700 HideSelectedAdapter<EventLabel> adapter 701 = new HideSelectedAdapter<EventLabel>( 702 cordova.getActivity(), 703 android.R.layout.simple_spinner_item, 704 features.menu.items); 705 adapter.setDropDownViewResource( 706 android.R.layout.simple_spinner_dropdown_item); 707 menu.setAdapter(adapter); 708 menu.setOnItemSelectedListener( 709 new AdapterView.OnItemSelectedListener() { 710 @Override 711 public void onItemSelected( 712 AdapterView<?> adapterView, 713 View view, int i, long l) { 714 if (inAppWebView != null 715 && i < features.menu.items.length) { 716 emitButtonEvent( 717 features.menu.items[i], 718 inAppWebView.getUrl(), i); 719 } 720 } 721 722 @Override 723 public void onNothingSelected( 724 AdapterView<?> adapterView) { 725 } 726 } 727 ); 728 } 729 } 730 731 // Title 732 final TextView title = features.title != null 733 ? new TextView(cordova.getActivity()) : null; 734 if (title != null) { 735 FrameLayout.LayoutParams titleParams 736 = new FrameLayout.LayoutParams( 737 LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 738 titleParams.gravity = Gravity.CENTER; 739 title.setLayoutParams(titleParams); 740 title.setSingleLine(); 741 title.setEllipsize(TextUtils.TruncateAt.END); 742 title.setGravity(Gravity.CENTER); 743 title.setTextColor(hexStringToColor( 744 features.title.color != null 745 ? features.title.color : "#000000ff")); 746 if (features.title.staticText != null) { 747 title.setText(features.title.staticText); 748 } 749 } 750 751 // WebView 752 inAppWebView = new WebView(cordova.getActivity()); 753 final ViewGroup.LayoutParams inAppWebViewParams = features.fullscreen 754 ? new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) 755 : new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0); 756 if (!features.fullscreen) { 757 ((LinearLayout.LayoutParams) inAppWebViewParams).weight = 1; 758 } 759 inAppWebView.setLayoutParams(inAppWebViewParams); 760 inAppWebView.setWebChromeClient(new InAppChromeClient(thatWebView)); 761 WebViewClient client = new ThemeableBrowserClient(thatWebView, new PageLoadListener() { 762 @Override 763 public void onPageFinished(String url, boolean canGoBack, boolean canGoForward) { 764 if (inAppWebView != null 765 && title != null && features.title != null 766 && features.title.staticText == null 767 && features.title.showPageTitle) { 768 title.setText(inAppWebView.getTitle()); 769 } 770 771 if (back != null) { 772 back.setEnabled(canGoBack || features.backButtonCanClose); 773 } 774 775 if (forward != null) { 776 forward.setEnabled(canGoForward); 777 } 778 } 779 }); 780 inAppWebView.setWebViewClient(client); 781 WebSettings settings = inAppWebView.getSettings(); 782 settings.setJavaScriptEnabled(true); 783 settings.setJavaScriptCanOpenWindowsAutomatically(true); 784 settings.setBuiltInZoomControls(features.zoom); 785 settings.setDisplayZoomControls(false); 786 settings.setPluginState(android.webkit.WebSettings.PluginState.ON); 787 788 //Toggle whether this is enabled or not! 789 Bundle appSettings = cordova.getActivity().getIntent().getExtras(); 790 boolean enableDatabase = appSettings == null || appSettings.getBoolean("ThemeableBrowserStorageEnabled", true); 791 if (enableDatabase) { 792 String databasePath = cordova.getActivity().getApplicationContext().getDir("themeableBrowserDB", Context.MODE_PRIVATE).getPath(); 793 settings.setDatabasePath(databasePath); 794 settings.setDatabaseEnabled(true); 795 } 796 settings.setDomStorageEnabled(true); 797 798 if (features.clearcache) { 799 CookieManager.getInstance().removeAllCookie(); 800 } else if (features.clearsessioncache) { 801 CookieManager.getInstance().removeSessionCookie(); 802 } 803 804 inAppWebView.loadUrl(url); 805 inAppWebView.getSettings().setLoadWithOverviewMode(true); 806 inAppWebView.getSettings().setUseWideViewPort(true); 807 inAppWebView.requestFocus(); 808 inAppWebView.requestFocusFromTouch(); 809 810 // Add buttons to either leftButtonsContainer or 811 // rightButtonsContainer according to user's alignment 812 // configuration. 813 int leftContainerWidth = 0; 814 int rightContainerWidth = 0; 815 816 if (features.customButtons != null) { 817 for (int i = 0; i < features.customButtons.length; i++) { 818 final BrowserButton buttonProps = features.customButtons[i]; 819 final int index = i; 820 Button button = createButton( 821 buttonProps, 822 String.format("custom button at %d", i), 823 new View.OnClickListener() { 824 @Override 825 public void onClick(View view) { 826 if (inAppWebView != null) { 827 emitButtonEvent(buttonProps, 828 inAppWebView.getUrl(), index); 829 } 830 } 831 } 832 ); 833 834 if (ALIGN_RIGHT.equals(buttonProps.align)) { 835 rightButtonContainer.addView(button); 836 rightContainerWidth 837 += button.getLayoutParams().width; 838 } else { 839 leftButtonContainer.addView(button, 0); 840 leftContainerWidth 841 += button.getLayoutParams().width; 842 } 843 } 844 } 845 846 // Back and forward buttons must be added with special ordering logic such 847 // that back button is always on the left of forward button if both buttons 848 // are on the same side. 849 if (forward != null && features.forwardButton != null 850 && !ALIGN_RIGHT.equals(features.forwardButton.align)) { 851 leftButtonContainer.addView(forward, 0); 852 leftContainerWidth 853 += forward.getLayoutParams().width; 854 } 855 856 if (back != null && features.backButton != null 857 && ALIGN_RIGHT.equals(features.backButton.align)) { 858 rightButtonContainer.addView(back); 859 rightContainerWidth 860 += back.getLayoutParams().width; 861 } 862 863 if (back != null && features.backButton != null 864 && !ALIGN_RIGHT.equals(features.backButton.align)) { 865 leftButtonContainer.addView(back, 0); 866 leftContainerWidth 867 += back.getLayoutParams().width; 868 } 869 870 if (forward != null && features.forwardButton != null 871 && ALIGN_RIGHT.equals(features.forwardButton.align)) { 872 rightButtonContainer.addView(forward); 873 rightContainerWidth 874 += forward.getLayoutParams().width; 875 } 876 877 if (menu != null) { 878 if (features.menu != null 879 && ALIGN_RIGHT.equals(features.menu.align)) { 880 rightButtonContainer.addView(menu); 881 rightContainerWidth 882 += menu.getLayoutParams().width; 883 } else { 884 leftButtonContainer.addView(menu, 0); 885 leftContainerWidth 886 += menu.getLayoutParams().width; 887 } 888 } 889 890 if (close != null) { 891 if (features.closeButton != null 892 && ALIGN_RIGHT.equals(features.closeButton.align)) { 893 rightButtonContainer.addView(close); 894 rightContainerWidth 895 += close.getLayoutParams().width; 896 } else { 897 leftButtonContainer.addView(close, 0); 898 leftContainerWidth 899 += close.getLayoutParams().width; 900 } 901 } 902 903 // Add the views to our toolbar 904 toolbar.addView(leftButtonContainer); 905 // Don't show address bar. 906 // toolbar.addView(edittext); 907 toolbar.addView(rightButtonContainer); 908 909 if (title != null) { 910 int titleMargin = Math.max( 911 leftContainerWidth, rightContainerWidth); 912 913 FrameLayout.LayoutParams titleParams 914 = (FrameLayout.LayoutParams) title.getLayoutParams(); 915 titleParams.setMargins(titleMargin, 0, titleMargin, 0); 916 toolbar.addView(title); 917 } 918 919 if (features.fullscreen) { 920 // If full screen mode, we have to add inAppWebView before adding toolbar. 921 main.addView(inAppWebView); 922 } 923 924 // Don't add the toolbar if its been disabled 925 if (features.location) { 926 // Add our toolbar to our main view/layout 927 main.addView(toolbar); 928 } 929 930 if (!features.fullscreen) { 931 // If not full screen, we add inAppWebView after adding toolbar. 932 main.addView(inAppWebView); 933 } 934 935 WindowManager.LayoutParams lp = new WindowManager.LayoutParams(); 936 lp.copyFrom(dialog.getWindow().getAttributes()); 937 lp.width = WindowManager.LayoutParams.MATCH_PARENT; 938 lp.height = WindowManager.LayoutParams.MATCH_PARENT; 939 940 dialog.setContentView(main); 941 dialog.show(); 942 dialog.getWindow().setAttributes(lp); 943 // the goal of openhidden is to load the url and not display it 944 // Show() needs to be called to cause the URL to be loaded 945 if(features.hidden) { 946 dialog.hide(); 947 } 948 } 949 }; 950 this.cordova.getActivity().runOnUiThread(runnable); 951 return ""; 952 } 953 954 /** 955 * Convert our DIP units to Pixels 956 * 957 * @return int 958 */ 959 private int dpToPixels(int dipValue) { 960 int value = (int) TypedValue.applyDimension( 961 TypedValue.COMPLEX_UNIT_DIP, 962 (float) dipValue, 963 cordova.getActivity().getResources().getDisplayMetrics() 964 ); 965 966 return value; 967 } 968 969 private int hexStringToColor(String hex) { 970 int result = 0; 971 972 if (hex != null && !hex.isEmpty()) { 973 if (hex.charAt(0) == '#') { 974 hex = hex.substring(1); 975 } 976 977 // No alpha, that's fine, we will just attach ff. 978 if (hex.length() < 8) { 979 hex += "ff"; 980 } 981 982 result = (int) Long.parseLong(hex, 16); 983 984 // Almost done, but Android color code is in form of ARGB instead of 985 // RGBA, so we gotta shift it a bit. 986 int alpha = (result & 0xff) << 24; 987 result = result >> 8 & 0xffffff | alpha; 988 } 989 990 return result; 991 } 992 993 /** 994 * This is a rather unintuitive helper method to load images. The reason why this method exists 995 * is because due to some service limitations, one may not be able to add images to native 996 * resource bundle. So this method offers a way to load image from www contents instead. 997 * However loading from native resource bundle is already preferred over loading from www. So 998 * if name is given, then it simply loads from resource bundle and the other two parameters are 999 * ignored. If name is not given, then altPath is assumed to be a file path _under_ www and 1000 * altDensity is the desired density of the given image file, because without native resource 1001 * bundle, we can't tell what density the image is supposed to be so it needs to be given 1002 * explicitly. 1003 */ 1004 private Drawable getImage(String name, String altPath, double altDensity) throws IOException { 1005 Drawable result = null; 1006 Resources activityRes = cordova.getActivity().getResources(); 1007 1008 if (name != null) { 1009 int id = activityRes.getIdentifier(name, "drawable", 1010 cordova.getActivity().getPackageName()); 1011 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 1012 result = activityRes.getDrawable(id); 1013 } else { 1014 result = activityRes.getDrawable(id, cordova.getActivity().getTheme()); 1015 } 1016 } else if (altPath != null) { 1017 File file = new File("www", altPath); 1018 InputStream is = null; 1019 try { 1020 is = cordova.getActivity().getAssets().open(file.getPath()); 1021 Bitmap bitmap = BitmapFactory.decodeStream(is); 1022 bitmap.setDensity((int) (DisplayMetrics.DENSITY_MEDIUM * altDensity)); 1023 result = new BitmapDrawable(activityRes, bitmap); 1024 } finally { 1025 // Make sure we close this input stream to prevent resource leak. 1026 try { 1027 is.close(); 1028 } catch (Exception e) {} 1029 } 1030 } 1031 return result; 1032 } 1033 1034 private void setButtonImages(View view, BrowserButton buttonProps, int disabledAlpha) { 1035 Drawable normalDrawable = null; 1036 Drawable disabledDrawable = null; 1037 Drawable pressedDrawable = null; 1038 1039 CharSequence description = view.getContentDescription(); 1040 1041 if (buttonProps.image != null || buttonProps.wwwImage != null) { 1042 try { 1043 normalDrawable = getImage(buttonProps.image, buttonProps.wwwImage, 1044 buttonProps.wwwImageDensity); 1045 ViewGroup.LayoutParams params = view.getLayoutParams(); 1046 params.width = normalDrawable.getIntrinsicWidth(); 1047 params.height = normalDrawable.getIntrinsicHeight(); 1048 } catch (Resources.NotFoundException e) { 1049 emitError(ERR_LOADFAIL, 1050 String.format("Image for %s, %s, failed to load", 1051 description, buttonProps.image)); 1052 } catch (IOException ioe) { 1053 emitError(ERR_LOADFAIL, 1054 String.format("Image for %s, %s, failed to load", 1055 description, buttonProps.wwwImage)); 1056 } 1057 } else { 1058 emitWarning(WRN_UNDEFINED, 1059 String.format("Image for %s is not defined. Button will not be shown", 1060 description)); 1061 } 1062 1063 if (buttonProps.imagePressed != null || buttonProps.wwwImagePressed != null) { 1064 try { 1065 pressedDrawable = getImage(buttonProps.imagePressed, buttonProps.wwwImagePressed, 1066 buttonProps.wwwImageDensity); 1067 } catch (Resources.NotFoundException e) { 1068 emitError(ERR_LOADFAIL, 1069 String.format("Pressed image for %s, %s, failed to load", 1070 description, buttonProps.imagePressed)); 1071 } catch (IOException e) { 1072 emitError(ERR_LOADFAIL, 1073 String.format("Pressed image for %s, %s, failed to load", 1074 description, buttonProps.wwwImagePressed)); 1075 } 1076 } else { 1077 emitWarning(WRN_UNDEFINED, 1078 String.format("Pressed image for %s is not defined.", 1079 description)); 1080 } 1081 1082 if (normalDrawable != null) { 1083 // Create the disabled state drawable by fading the normal state 1084 // drawable. Drawable.setAlpha() stopped working above Android 4.4 1085 // so we gotta bring out some bitmap magic. Credit goes to: 1086 // http://stackoverflow.com/a/7477572 1087 Bitmap enabledBitmap = ((BitmapDrawable) normalDrawable).getBitmap(); 1088 Bitmap disabledBitmap = Bitmap.createBitmap( 1089 normalDrawable.getIntrinsicWidth(), 1090 normalDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 1091 Canvas canvas = new Canvas(disabledBitmap); 1092 1093 Paint paint = new Paint(); 1094 paint.setAlpha(disabledAlpha); 1095 canvas.drawBitmap(enabledBitmap, 0, 0, paint); 1096 1097 Resources activityRes = cordova.getActivity().getResources(); 1098 disabledDrawable = new BitmapDrawable(activityRes, disabledBitmap); 1099 } 1100 1101 StateListDrawable states = new StateListDrawable(); 1102 if (pressedDrawable != null) { 1103 states.addState( 1104 new int[] { 1105 android.R.attr.state_pressed 1106 }, 1107 pressedDrawable 1108 ); 1109 } 1110 if (normalDrawable != null) { 1111 states.addState( 1112 new int[] { 1113 android.R.attr.state_enabled 1114 }, 1115 normalDrawable 1116 ); 1117 } 1118 if (disabledDrawable != null) { 1119 states.addState( 1120 new int[] {}, 1121 disabledDrawable 1122 ); 1123 } 1124 1125 setBackground(view, states); 1126 } 1127 1128 private void setBackground(View view, Drawable drawable) { 1129 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { 1130 view.setBackgroundDrawable(drawable); 1131 } else { 1132 view.setBackground(drawable); 1133 } 1134 } 1135 1136 private Button createButton(BrowserButton buttonProps, String description, 1137 View.OnClickListener listener) { 1138 Button result = null; 1139 if (buttonProps != null) { 1140 result = new Button(cordova.getActivity()); 1141 result.setContentDescription(description); 1142 result.setLayoutParams(new LinearLayout.LayoutParams( 1143 LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); 1144 setButtonImages(result, buttonProps, DISABLED_ALPHA); 1145 if (listener != null) { 1146 result.setOnClickListener(listener); 1147 } 1148 } else { 1149 emitWarning(WRN_UNDEFINED, 1150 String.format("%s is not defined. Button will not be shown.", 1151 description)); 1152 } 1153 return result; 1154 } 1155 1156 /** 1157 * Create a new plugin success result and send it back to JavaScript 1158 * 1159 * @param obj a JSONObject contain event payload information 1160 */ 1161 private void sendUpdate(JSONObject obj, boolean keepCallback) { 1162 sendUpdate(obj, keepCallback, PluginResult.Status.OK); 1163 } 1164 1165 /** 1166 * Create a new plugin result and send it back to JavaScript 1167 * 1168 * @param obj a JSONObject contain event payload information 1169 * @param status the status code to return to the JavaScript environment 1170 */ 1171 private void sendUpdate(JSONObject obj, boolean keepCallback, PluginResult.Status status) { 1172 if (callbackContext != null) { 1173 PluginResult result = new PluginResult(status, obj); 1174 result.setKeepCallback(keepCallback); 1175 callbackContext.sendPluginResult(result); 1176 if (!keepCallback) { 1177 callbackContext = null; 1178 } 1179 } 1180 } 1181 1182 public static interface PageLoadListener { 1183 public void onPageFinished(String url, boolean canGoBack, 1184 boolean canGoForward); 1185 } 1186 1187 /** 1188 * The webview client receives notifications about appView 1189 */ 1190 public class ThemeableBrowserClient extends WebViewClient { 1191 PageLoadListener callback; 1192 CordovaWebView webView; 1193 1194 /** 1195 * Constructor. 1196 * 1197 * @param webView 1198 * @param callback 1199 */ 1200 public ThemeableBrowserClient(CordovaWebView webView, 1201 PageLoadListener callback) { 1202 this.webView = webView; 1203 this.callback = callback; 1204 } 1205 1206 /** 1207 * Override the URL that should be loaded 1208 * 1209 * This handles a small subset of all the URIs that would be encountered. 1210 * 1211 * @param webView 1212 * @param url 1213 */ 1214 @Override 1215 public boolean shouldOverrideUrlLoading(WebView webView, String url) { 1216 if (url.startsWith(WebView.SCHEME_TEL)) { 1217 try { 1218 Intent intent = new Intent(Intent.ACTION_DIAL); 1219 intent.setData(Uri.parse(url)); 1220 cordova.getActivity().startActivity(intent); 1221 return true; 1222 } catch (android.content.ActivityNotFoundException e) { 1223 Log.e(LOG_TAG, "Error dialing " + url + ": " + e.toString()); 1224 } 1225 } else if (url.startsWith("geo:") || url.startsWith(WebView.SCHEME_MAILTO) || url.startsWith("market:")) { 1226 try { 1227 Intent intent = new Intent(Intent.ACTION_VIEW); 1228 intent.setData(Uri.parse(url)); 1229 cordova.getActivity().startActivity(intent); 1230 return true; 1231 } catch (android.content.ActivityNotFoundException e) { 1232 Log.e(LOG_TAG, "Error with " + url + ": " + e.toString()); 1233 } 1234 } 1235 // If sms:5551212?body=This is the message 1236 else if (url.startsWith("sms:")) { 1237 try { 1238 Intent intent = new Intent(Intent.ACTION_VIEW); 1239 1240 // Get address 1241 String address = null; 1242 int parmIndex = url.indexOf('?'); 1243 if (parmIndex == -1) { 1244 address = url.substring(4); 1245 } else { 1246 address = url.substring(4, parmIndex); 1247 1248 // If body, then set sms body 1249 Uri uri = Uri.parse(url); 1250 String query = uri.getQuery(); 1251 if (query != null) { 1252 if (query.startsWith("body=")) { 1253 intent.putExtra("sms_body", query.substring(5)); 1254 } 1255 } 1256 } 1257 intent.setData(Uri.parse("sms:" + address)); 1258 intent.putExtra("address", address); 1259 intent.setType("vnd.android-dir/mms-sms"); 1260 cordova.getActivity().startActivity(intent); 1261 return true; 1262 } catch (android.content.ActivityNotFoundException e) { 1263 Log.e(LOG_TAG, "Error sending sms " + url + ":" + e.toString()); 1264 } 1265 } 1266 return false; 1267 } 1268 1269 1270 /* 1271 * onPageStarted fires the LOAD_START_EVENT 1272 * 1273 * @param view 1274 * @param url 1275 * @param favicon 1276 */ 1277 @Override 1278 public void onPageStarted(WebView view, String url, Bitmap favicon) { 1279 super.onPageStarted(view, url, favicon); 1280 String newloc = ""; 1281 if (url.startsWith("http:") || url.startsWith("https:") || url.startsWith("file:")) { 1282 newloc = url; 1283 } 1284 else 1285 { 1286 // Assume that everything is HTTP at this point, because if we don't specify, 1287 // it really should be. Complain loudly about this!!! 1288 Log.e(LOG_TAG, "Possible Uncaught/Unknown URI"); 1289 newloc = "http://" + url; 1290 } 1291 1292 // Update the UI if we haven't already 1293 if (!newloc.equals(edittext.getText().toString())) { 1294 edittext.setText(newloc); 1295 } 1296 1297 try { 1298 JSONObject obj = new JSONObject(); 1299 obj.put("type", LOAD_START_EVENT); 1300 obj.put("url", newloc); 1301 sendUpdate(obj, true); 1302 } catch (JSONException ex) { 1303 Log.e(LOG_TAG, "URI passed in has caused a JSON error."); 1304 } 1305 } 1306 1307 public void onPageFinished(WebView view, String url) { 1308 super.onPageFinished(view, url); 1309 1310 try { 1311 JSONObject obj = new JSONObject(); 1312 obj.put("type", LOAD_STOP_EVENT); 1313 obj.put("url", url); 1314 1315 sendUpdate(obj, true); 1316 1317 if (this.callback != null) { 1318 this.callback.onPageFinished(url, view.canGoBack(), 1319 view.canGoForward()); 1320 } 1321 } catch (JSONException ex) { 1322 } 1323 } 1324 1325 public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { 1326 super.onReceivedError(view, errorCode, description, failingUrl); 1327 1328 try { 1329 JSONObject obj = new JSONObject(); 1330 obj.put("type", LOAD_ERROR_EVENT); 1331 obj.put("url", failingUrl); 1332 obj.put("code", errorCode); 1333 obj.put("message", description); 1334 1335 sendUpdate(obj, true, PluginResult.Status.ERROR); 1336 } catch (JSONException ex) { 1337 } 1338 } 1339 } 1340 1341 /** 1342 * Like Spinner but will always trigger onItemSelected even if a selected 1343 * item is selected, and always ignore default selection. 1344 */ 1345 public class MenuSpinner extends Spinner { 1346 private OnItemSelectedListener listener; 1347 1348 public MenuSpinner(Context context) { 1349 super(context); 1350 } 1351 1352 @Override 1353 public void setSelection(int position) { 1354 super.setSelection(position); 1355 1356 if (listener != null) { 1357 listener.onItemSelected(null, this, position, 0); 1358 } 1359 } 1360 1361 @Override 1362 public void setOnItemSelectedListener(OnItemSelectedListener listener) { 1363 this.listener = listener; 1364 } 1365 } 1366 1367 /** 1368 * Extension of ArrayAdapter. The only difference is that it hides the 1369 * selected text that's shown inside spinner. 1370 * @param <T> 1371 */ 1372 private static class HideSelectedAdapter<T> extends ArrayAdapter { 1373 1374 public HideSelectedAdapter(Context context, int resource, T[] objects) { 1375 super(context, resource, objects); 1376 } 1377 1378 public View getView (int position, View convertView, ViewGroup parent) { 1379 View v = super.getView(position, convertView, parent); 1380 v.setVisibility(View.GONE); 1381 return v; 1382 } 1383 } 1384 1385 1386 /** 1387 * A class to hold parsed option properties. 1388 */ 1389 private static class Options { 1390 public boolean location = true; 1391 public boolean hidden = false; 1392 public boolean clearcache = false; 1393 public boolean clearsessioncache = false; 1394 public boolean zoom = true; 1395 public boolean hardwareback = true; 1396 1397 public Toolbar toolbar; 1398 public Title title; 1399 public BrowserButton backButton; 1400 public BrowserButton forwardButton; 1401 public BrowserButton closeButton; 1402 public BrowserMenu menu; 1403 public BrowserButton[] customButtons; 1404 public boolean backButtonCanClose; 1405 public boolean disableAnimation; 1406 public boolean fullscreen; 1407 } 1408 1409 private static class Event { 1410 public String event; 1411 } 1412 1413 private static class EventLabel extends Event { 1414 public String label; 1415 1416 public String toString() { 1417 return label; 1418 } 1419 } 1420 1421 private static class BrowserButton extends Event { 1422 public String image; 1423 public String wwwImage; 1424 public String imagePressed; 1425 public String wwwImagePressed; 1426 public double wwwImageDensity = 1; 1427 public String align = ALIGN_LEFT; 1428 } 1429 1430 private static class BrowserMenu extends BrowserButton { 1431 public EventLabel[] items; 1432 } 1433 1434 private static class Toolbar { 1435 public int height = TOOLBAR_DEF_HEIGHT; 1436 public String color; 1437 public String image; 1438 public String wwwImage; 1439 public double wwwImageDensity = 1; 1440 } 1441 1442 private static class Title { 1443 public String color; 1444 public String staticText; 1445 public boolean showPageTitle; 1446 } 1447 }
在搞之前 首先要有常识 在 Android 手机中内置了一款高性能 webkit 内核浏览器,在 SDK 中封装为一个叫做 WebView 组件.
其次 在打开页面时选择横竖屏 , 然后去谷歌搜下 android webview 横竖屏 比如下面这篇文章截图 知道 Activity类可以通过setRequestedOrientation 方法设置 横竖屏显示
我们调用js 是用的 ThemeableBrowser 类的open 看上面的代码copy
发现它重写了父类 CordovaPlugin 的execute方法去执行open操作 我们调用的方法参数是
inAppWebView = new WebView(cordova.getActivity());
inAppWebView.loadUrl(url);
loadUrl就是把webview展示出来, 这个webview实例是通过 752行 cordova.getActivity() 来实例化的
cordova.getActivity() 返回的就是 Activity 类型 上面搜索知道 Activity.setRequestedOrientation 可以设置横竖屏展示
然后搜索 cordova.getActivity() 发现 cordova是个接口 然后搜一下这个接口的实现类 发现是 CordovaInterfaceImpl这个类 发现他的getActivity方法是返回的 构造函数注入的 activity
然后再找 CordovaInterfaceImpl 实在 CordovaActivity.java的 makeCordovaInterface 方法中实例化的
makeCordovaInterface 是在 CordovaActivity的 oncreate方法实例化的
CordovaActivity 继承 Activity MainActivity又继承CordovaActivity
Activity是Android的四大组件之一。是用户操作的可视化界面 MainActivity一般作为主页面展示
Android手机APP启动的第一个Activity是可以自己设置的,不是必须的MainActivity,可以是任何的Activity。
设置Android手机APP启动的第一个Activity得看Android项目里的mainfest.xml文件:
简单理一下就是
app启动 => MainActivity初始化 onCreate方法 => makeCordovaInterface(this) 把MainActivity本身最为参数实例化CordovaInterfaceImpl类 => CordovaInterface作为接口参数cordova 被组合到 ThemeableBrowser类
js调用 ThemeableBrowser.open 方法实例化一个inAppWebView = new WebView(cordova.getActivity()); cordova.getActivity()即是 MainActivity实例本身
我们通过给MainActivity设置横竖屏的显示方式( setRequestedOrientation) 然后展示 url页面 inAppWebView.loadUrl(url);
再看关闭按钮事件 671行 调用 closeDialog
我们在这里用 setRequestedOrientation把 Activity改为竖屏
整个解决方式是
js调用时候