├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── plugins ├── ios.json └── org.apache.cordova.core.inappbrowser │ ├── .fetch.json │ ├── README.md │ ├── docs │ ├── inappbrowser.md │ └── window.open.md │ ├── plugin.xml │ ├── src │ ├── android │ │ └── InAppBrowser.java │ ├── blackberry10 │ │ └── README.md │ ├── ios │ │ ├── CDVInAppBrowser.h │ │ └── CDVInAppBrowser.m │ └── wp │ │ └── InAppBrowser.cs │ ├── test │ ├── cordova-incl.js │ ├── inappbrowser │ │ ├── index.html │ │ ├── inject.css │ │ ├── inject.html │ │ ├── inject.js │ │ ├── local.html │ │ └── local.pdf │ ├── index.html │ ├── main.js │ └── master.css │ └── www │ └── InAppBrowser.js └── www ├── config.xml ├── css └── index.css ├── img └── logo.png ├── index.html ├── js ├── index.js └── jquery-1.10.1.min.js └── res ├── icon ├── android │ ├── icon-36-ldpi.png │ ├── icon-48-mdpi.png │ ├── icon-72-hdpi.png │ └── icon-96-xhdpi.png ├── blackberry │ └── icon-80.png ├── ios │ ├── icon-57-2x.png │ ├── icon-57.png │ ├── icon-72-2x.png │ └── icon-72.png └── windows-phone │ ├── icon-173-tile.png │ ├── icon-48.png │ └── icon-62-tile.png └── screen ├── android ├── screen-hdpi-landscape.png ├── screen-hdpi-portrait.png ├── screen-ldpi-landscape.png ├── screen-ldpi-portrait.png ├── screen-mdpi-landscape.png ├── screen-mdpi-portrait.png ├── screen-xhdpi-landscape.png └── screen-xhdpi-portrait.png ├── blackberry └── screen-225.png ├── ios ├── screen-ipad-landscape-2x.png ├── screen-ipad-landscape.png ├── screen-ipad-portrait-2x.png ├── screen-ipad-portrait.png ├── screen-iphone-landscape-2x.png ├── screen-iphone-landscape.png ├── screen-iphone-portrait-2x.png ├── screen-iphone-portrait-568h-2x.png └── screen-iphone-portrait.png └── windows-phone └── screen-portrait.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | platforms 3 | .cordova 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2103 Michael Dellanoce 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Example demonstrating how to use Google's OAuth with PhoneGap 2 | 3 | # Running the example 4 | 5 | 1. Clone this repo, and run ```npm install``` 6 | 1. Add a platform, for example ios ```cordova platform add ios``` 7 | 1. Build the platform's project ```cordova build ios``` 8 | 1. Launch the platform's simulator ```cordova emulate ios``` 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "google-api-oauth-phonegap", 3 | "version": "0.0.3", 4 | "dependencies": { 5 | "cordova": "~3.0.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /plugins/ios.json: -------------------------------------------------------------------------------- 1 | {"prepare_queue":{"installed":[],"uninstalled":[]},"config_munge":{"config.xml":{"/*":{"":1}}},"installed_plugins":{"org.apache.cordova.core.inappbrowser":{"PACKAGE_NAME":"com.phonegaptips.googleoauth"}},"dependent_plugins":{}} -------------------------------------------------------------------------------- /plugins/org.apache.cordova.core.inappbrowser/.fetch.json: -------------------------------------------------------------------------------- 1 | {"source":{"type":"git","url":"https://git-wip-us.apache.org/repos/asf/cordova-plugin-inappbrowser.git","subdir":"."}} -------------------------------------------------------------------------------- /plugins/org.apache.cordova.core.inappbrowser/README.md: -------------------------------------------------------------------------------- 1 | cordova-plugin-inappbrowser 2 | ----------------------------- 3 | To install this plugin, follow the [Command-line Interface Guide](http://cordova.apache.org/docs/en/edge/guide_cli_index.md.html#The%20Command-line%20Interface). 4 | 5 | If you are not using the Cordova Command-line Interface, follow [Using Plugman to Manage Plugins](http://cordova.apache.org/docs/en/edge/guide_plugin_ref_plugman.md.html). 6 | -------------------------------------------------------------------------------- /plugins/org.apache.cordova.core.inappbrowser/docs/inappbrowser.md: -------------------------------------------------------------------------------- 1 | --- 2 | license: 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 | 20 | InAppBrowser 21 | ============ 22 | 23 | > The `InAppBrowser` is a web browser that displays in the app when calling `window.open`. 24 | 25 | var ref = window.open('http://apache.org', '_blank', 'location=yes'); 26 | 27 | Description 28 | ----------- 29 | 30 | The object returned from a call to `window.open`. 31 | 32 | Methods 33 | ---------- 34 | 35 | - addEventListener 36 | - removeEventListener 37 | - close 38 | 39 | Permissions 40 | ----------- 41 | 42 | ### Android 43 | 44 | #### app/res/xml/config.xml 45 | 46 | 47 | 48 | ### iOS 49 | 50 | #### config.xml 51 | 52 | 53 | 54 | ### Windows Phone 7 + 8 55 | 56 | #### config.xml 57 | 58 | 59 | 60 | addEventListener 61 | ================ 62 | 63 | > Adds a listener for an event from the `InAppBrowser`. 64 | 65 | ref.addEventListener(eventname, callback); 66 | 67 | - __ref__: reference to the `InAppBrowser` window _(InAppBrowser)_ 68 | - __eventname__: the event to listen for _(String)_ 69 | 70 | - __loadstart__: event fires when the `InAppBrowser` starts to load a URL. 71 | - __loadstop__: event fires when the `InAppBrowser` finishes loading a URL. 72 | - __loaderror__: event fires when the `InAppBrowser` encounters an error when loading a URL. 73 | - __exit__: event fires when the `InAppBrowser` window is closed. 74 | 75 | - __callback__: the function that executes when the event fires. The function is passed an `InAppBrowserEvent` object as a parameter. 76 | 77 | Supported Platforms 78 | ------------------- 79 | 80 | - Android 81 | - iOS 82 | - Windows Phone 7 + 8 83 | 84 | Quick Example 85 | ------------- 86 | 87 | var ref = window.open('http://apache.org', '_blank', 'location=yes'); 88 | ref.addEventListener('loadstart', function() { alert(event.url); }); 89 | 90 | Full Example 91 | ------------ 92 | 93 | 94 | 95 | 96 | InAppBrowser.addEventListener Example 97 | 98 | 99 | 116 | 117 | 118 | 119 | 120 | 121 | removeEventListener 122 | =================== 123 | 124 | > Removes a listener for an event from the `InAppBrowser`. 125 | 126 | ref.removeEventListener(eventname, callback); 127 | 128 | - __ref__: reference to the `InAppBrowser` window. _(InAppBrowser)_ 129 | - __eventname__: the event to stop listening for. _(String)_ 130 | 131 | - __loadstart__: event fires when the `InAppBrowser` starts to load a URL. 132 | - __loadstop__: event fires when the `InAppBrowser` finishes loading a URL. 133 | - __loaderror__: event fires when the `InAppBrowser` encounters an error loading a URL. 134 | - __exit__: event fires when the `InAppBrowser` window is closed. 135 | 136 | - __callback__: the function to execute when the event fires. 137 | The function is passed an `InAppBrowserEvent` object. 138 | 139 | Supported Platforms 140 | ------------------- 141 | 142 | - Android 143 | - iOS 144 | - Windows Phone 7 + 8 145 | 146 | Quick Example 147 | ------------- 148 | 149 | var ref = window.open('http://apache.org', '_blank', 'location=yes'); 150 | var myCallback = function() { alert(event.url); } 151 | ref.addEventListener('loadstart', myCallback); 152 | ref.removeEventListener('loadstart', myCallback); 153 | 154 | Full Example 155 | ------------ 156 | 157 | 158 | 159 | 160 | InAppBrowser.removeEventListener Example 161 | 162 | 163 | 203 | 204 | 205 | 206 | 207 | 208 | close 209 | ===== 210 | 211 | > Closes the `InAppBrowser` window. 212 | 213 | ref.close(); 214 | 215 | - __ref__: reference to the `InAppBrowser` window _(InAppBrowser)_ 216 | 217 | Supported Platforms 218 | ------------------- 219 | 220 | - Android 221 | - iOS 222 | - Windows Phone 7 + 8 223 | - BlackBerry 10 224 | 225 | Quick Example 226 | ------------- 227 | 228 | var ref = window.open('http://apache.org', '_blank', 'location=yes'); 229 | ref.close(); 230 | 231 | Full Example 232 | ------------ 233 | 234 | 235 | 236 | 237 | InAppBrowser.close Example 238 | 239 | 240 | 257 | 258 | 259 | 260 | 261 | 262 | executeScript 263 | ============= 264 | 265 | > Injects JavaScript code into the `InAppBrowser` window 266 | 267 | ref.executeScript(details, callback); 268 | 269 | - __ref__: reference to the `InAppBrowser` window. _(InAppBrowser)_ 270 | - __injectDetails__: details of the script to run, specifying either a `file` or `code` key. _(Object)_ 271 | - __file__: URL of the script to inject. 272 | - __code__: Text of the script to inject. 273 | - __callback__: the function that executes after the JavaScript code is injected. 274 | - If the injected script is of type `code`, the callback executes 275 | with a single parameter, which is the return value of the 276 | script, wrapped in an `Array`. For multi-line scripts, this is 277 | the return value of the last statement, or the last expression 278 | evaluated. 279 | 280 | Supported Platforms 281 | ------------------- 282 | 283 | - Android 284 | - iOS 285 | 286 | Quick Example 287 | ------------- 288 | 289 | var ref = window.open('http://apache.org', '_blank', 'location=yes'); 290 | ref.addEventListener('loadstop', function() { 291 | ref.executeSript({file: "myscript.js"}); 292 | }); 293 | 294 | Full Example 295 | ------------ 296 | 297 | 298 | 299 | 300 | InAppBrowser.executeScript Example 301 | 302 | 303 | 336 | 337 | 338 | 339 | 340 | 341 | insertCSS 342 | ========= 343 | 344 | > Injects CSS into the `InAppBrowser` window. 345 | 346 | ref.insertCSS(details, callback); 347 | 348 | - __ref__: reference to the `InAppBrowser` window _(InAppBrowser)_ 349 | - __injectDetails__: details of the script to run, specifying either a `file` or `code` key. _(Object)_ 350 | - __file__: URL of the stylesheet to inject. 351 | - __code__: Text of the stylesheet to inject. 352 | - __callback__: the function that executes after the CSS is injected. 353 | 354 | Supported Platforms 355 | ------------------- 356 | 357 | - Android 358 | - iOS 359 | 360 | Quick Example 361 | ------------- 362 | 363 | var ref = window.open('http://apache.org', '_blank', 'location=yes'); 364 | ref.addEventListener('loadstop', function() { 365 | ref.insertCSS({file: "mystyles.css"}); 366 | }); 367 | 368 | Full Example 369 | ------------ 370 | 371 | 372 | 373 | 374 | InAppBrowser.executeScript Example 375 | 376 | 377 | 410 | 411 | 412 | 413 | 414 | 415 | InAppBrowserEvent 416 | ================= 417 | 418 | The object that is passed to the callback function from an 419 | `addEventListener` call on an `InAppBrowser` object. 420 | 421 | Properties 422 | ---------- 423 | 424 | - __type__: the eventname, either `loadstart`, `loadstop`, `loaderror`, or `exit`. _(String)_ 425 | - __url__: the URL that was loaded. _(String)_ 426 | - __code__: the error code, only in the case of `loaderror`. _(Number)_ 427 | - __message__: the error message, only in the case of `loaderror`. _(String)_ 428 | -------------------------------------------------------------------------------- /plugins/org.apache.cordova.core.inappbrowser/docs/window.open.md: -------------------------------------------------------------------------------- 1 | --- 2 | license: 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 | 20 | window.open 21 | =========== 22 | 23 | Opens a URL in a new `InAppBrowser` instance, the current browser 24 | instance, or the system browser. 25 | 26 | var ref = window.open(url, target, options); 27 | 28 | - __ref__: Reference to the `InAppBrowser` window. _(InAppBrowser)_ 29 | - __url__: The URL to load _(String)_. Call `encodeURI()` on this if the URL contains Unicode characters. 30 | - __target__: The target in which to load the URL, an optional parameter that defaults to `_self`. _(String)_ 31 | 32 | - `_self`: Opens in the Cordova WebView if the URL is in the white list, otherwise it opens in the `InAppBrowser`. 33 | - `_blank`: Opens in the `InAppBrowser`. 34 | - `_system`: Opens in the system's web browser. 35 | 36 | - __options__: Options for the `InAppBrowser`. Optional, defaulting to: `location=yes`. _(String)_ 37 | 38 | The `options` string must not contain any blank space, and each feature's name/value pairs must be separated by a comma. Feature names are case insensitive. All platforms support the value below: 39 | 40 | - __location__: Set to `yes` or `no` to turn the `InAppBrowser`'s location bar on or off. 41 | 42 | Android only 43 | ------------ 44 | - __closebuttoncaption__ - set to a string that will be the caption for the "Done" button. 45 | 46 | iOS only 47 | -------- 48 | - __closebuttoncaption__ - set to a string that will be the caption for the "Done" button. Note that you will have to localize this value yourself. 49 | - __toolbar__ - set to 'yes' or 'no' to turn the toolbar on or off for the InAppBrowser (defaults to 'yes') 50 | - __enableViewportScale__: Set to `yes` or `no` to prevent viewport scaling through a meta tag (defaults to `no`). 51 | - __mediaPlaybackRequiresUserAction__: Set to `yes` or `no` to prevent HTML5 audio or video from autoplaying (defaults to `no`). 52 | - __allowInlineMediaPlayback__: Set to `yes` or `no` to allow inline HTML5 media playback, displaying within the browser window rather than a device-specific playback interface. The HTML's `video` element must also include the `webkit-playsinline` attribute (defaults to `no`) 53 | - __keyboardDisplayRequiresUserAction__: Set to `yes` or `no` to open the keyboard when form elements receive focus via JavaScript's `focus()` call (defaults to `yes`). 54 | - __suppressesIncrementalRendering__: Set to `yes` or `no` to wait until all new view content is received before being rendered (defaults to `no`). 55 | - __presentationstyle__: Set to `pagesheet`, `formsheet` or `fullscreen` to set the [presentation style](http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html#//apple_ref/occ/instp/UIViewController/modalPresentationStyle) (defaults to `fullscreen`). 56 | - __transitionstyle__: Set to `fliphorizontal`, `crossdissolve` or `coververtical` to set the [transition style](http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html#//apple_ref/occ/instp/UIViewController/modalTransitionStyle) (defaults to `coververtical`). 57 | 58 | Supported Platforms 59 | ------------------- 60 | 61 | - Android 62 | - iOS 63 | - BlackBerry 10 64 | - Windows Phone 7 + 8 65 | 66 | Quick Example 67 | ------------- 68 | 69 | var ref = window.open('http://apache.org', '_blank', 'location=yes'); 70 | var ref2 = window.open(encodeURI('http://ja.m.wikipedia.org/wiki/ハングル'), '_blank', 'location=yes'); 71 | 72 | Full Example 73 | ------------ 74 | 75 | 76 | 77 | 78 | window.open Example 79 | 80 | 81 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /plugins/org.apache.cordova.core.inappbrowser/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | InAppBrowser 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /plugins/org.apache.cordova.core.inappbrowser/src/android/InAppBrowser.java: -------------------------------------------------------------------------------- 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 org.apache.cordova.core; 20 | 21 | import java.util.HashMap; 22 | import java.util.StringTokenizer; 23 | 24 | 25 | import org.apache.cordova.Config; 26 | import org.apache.cordova.CordovaWebView; 27 | import org.apache.cordova.CallbackContext; 28 | import org.apache.cordova.CordovaPlugin; 29 | import org.apache.cordova.LOG; 30 | import org.apache.cordova.PluginResult; 31 | import org.json.JSONArray; 32 | import org.json.JSONException; 33 | import org.json.JSONObject; 34 | 35 | import android.annotation.SuppressLint; 36 | import android.app.Dialog; 37 | import android.content.Context; 38 | import android.content.DialogInterface; 39 | import android.content.Intent; 40 | import android.graphics.Bitmap; 41 | import android.net.Uri; 42 | import android.os.Bundle; 43 | import android.text.InputType; 44 | import android.util.Log; 45 | import android.util.TypedValue; 46 | import android.view.Gravity; 47 | import android.view.KeyEvent; 48 | import android.view.View; 49 | import android.view.Window; 50 | import android.view.WindowManager; 51 | import android.view.WindowManager.LayoutParams; 52 | import android.view.inputmethod.EditorInfo; 53 | import android.view.inputmethod.InputMethodManager; 54 | import android.webkit.WebChromeClient; 55 | import android.webkit.GeolocationPermissions.Callback; 56 | import android.webkit.JsPromptResult; 57 | import android.webkit.WebSettings; 58 | import android.webkit.WebStorage; 59 | import android.webkit.WebView; 60 | import android.webkit.WebViewClient; 61 | import android.widget.Button; 62 | import android.widget.EditText; 63 | import android.widget.LinearLayout; 64 | import android.widget.RelativeLayout; 65 | 66 | @SuppressLint("SetJavaScriptEnabled") 67 | public class InAppBrowser extends CordovaPlugin { 68 | 69 | private static final String NULL = "null"; 70 | protected static final String LOG_TAG = "InAppBrowser"; 71 | private static final String SELF = "_self"; 72 | private static final String SYSTEM = "_system"; 73 | // private static final String BLANK = "_blank"; 74 | private static final String EXIT_EVENT = "exit"; 75 | private static final String LOCATION = "location"; 76 | private static final String HIDDEN = "hidden"; 77 | private static final String LOAD_START_EVENT = "loadstart"; 78 | private static final String LOAD_STOP_EVENT = "loadstop"; 79 | private static final String LOAD_ERROR_EVENT = "loaderror"; 80 | private static final String CLOSE_BUTTON_CAPTION = "closebuttoncaption"; 81 | private long MAX_QUOTA = 100 * 1024 * 1024; 82 | 83 | private Dialog dialog; 84 | private WebView inAppWebView; 85 | private EditText edittext; 86 | private CallbackContext callbackContext; 87 | private boolean showLocationBar = true; 88 | private boolean openWindowHidden = false; 89 | private String buttonLabel = "Done"; 90 | 91 | /** 92 | * Executes the request and returns PluginResult. 93 | * 94 | * @param action The action to execute. 95 | * @param args JSONArry of arguments for the plugin. 96 | * @param callbackId The callback id used when calling back into JavaScript. 97 | * @return A PluginResult object with a status and message. 98 | */ 99 | public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { 100 | try { 101 | if (action.equals("open")) { 102 | this.callbackContext = callbackContext; 103 | String url = args.getString(0); 104 | String target = args.optString(1); 105 | if (target == null || target.equals("") || target.equals(NULL)) { 106 | target = SELF; 107 | } 108 | HashMap features = parseFeature(args.optString(2)); 109 | 110 | Log.d(LOG_TAG, "target = " + target); 111 | 112 | url = updateUrl(url); 113 | String result = ""; 114 | 115 | // SELF 116 | if (SELF.equals(target)) { 117 | Log.d(LOG_TAG, "in self"); 118 | // load in webview 119 | if (url.startsWith("file://") || url.startsWith("javascript:") 120 | || Config.isUrlWhiteListed(url)) { 121 | this.webView.loadUrl(url); 122 | } 123 | //Load the dialer 124 | else if (url.startsWith(WebView.SCHEME_TEL)) 125 | { 126 | try { 127 | Intent intent = new Intent(Intent.ACTION_DIAL); 128 | intent.setData(Uri.parse(url)); 129 | this.cordova.getActivity().startActivity(intent); 130 | } catch (android.content.ActivityNotFoundException e) { 131 | LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString()); 132 | } 133 | } 134 | // load in InAppBrowser 135 | else { 136 | result = this.showWebPage(url, features); 137 | } 138 | } 139 | // SYSTEM 140 | else if (SYSTEM.equals(target)) { 141 | Log.d(LOG_TAG, "in system"); 142 | result = this.openExternal(url); 143 | } 144 | // BLANK - or anything else 145 | else { 146 | Log.d(LOG_TAG, "in blank"); 147 | result = this.showWebPage(url, features); 148 | } 149 | 150 | PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, result); 151 | pluginResult.setKeepCallback(true); 152 | this.callbackContext.sendPluginResult(pluginResult); 153 | } 154 | else if (action.equals("close")) { 155 | closeDialog(); 156 | this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK)); 157 | } 158 | else if (action.equals("injectScriptCode")) { 159 | String jsWrapper = null; 160 | if (args.getBoolean(1)) { 161 | jsWrapper = String.format("prompt(JSON.stringify([eval(%%s)]), 'gap-iab://%s')", callbackContext.getCallbackId()); 162 | } 163 | injectDeferredObject(args.getString(0), jsWrapper); 164 | } 165 | else if (action.equals("injectScriptFile")) { 166 | String jsWrapper; 167 | if (args.getBoolean(1)) { 168 | 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()); 169 | } else { 170 | jsWrapper = "(function(d) { var c = d.createElement('script'); c.src = %s; d.body.appendChild(c); })(document)"; 171 | } 172 | injectDeferredObject(args.getString(0), jsWrapper); 173 | } 174 | else if (action.equals("injectStyleCode")) { 175 | String jsWrapper; 176 | if (args.getBoolean(1)) { 177 | jsWrapper = String.format("(function(d) { var c = d.createElement('style'); c.innerHTML = %%s; d.body.appendChild(c); prompt('', 'gap-iab://%s');})(document)", callbackContext.getCallbackId()); 178 | } else { 179 | jsWrapper = "(function(d) { var c = d.createElement('style'); c.innerHTML = %s; d.body.appendChild(c); })(document)"; 180 | } 181 | injectDeferredObject(args.getString(0), jsWrapper); 182 | } 183 | else if (action.equals("injectStyleFile")) { 184 | String jsWrapper; 185 | if (args.getBoolean(1)) { 186 | 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()); 187 | } else { 188 | jsWrapper = "(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %s; d.head.appendChild(c); })(document)"; 189 | } 190 | injectDeferredObject(args.getString(0), jsWrapper); 191 | } 192 | else if (action.equals("show")) { 193 | Runnable runnable = new Runnable() { 194 | @Override 195 | public void run() { 196 | dialog.show(); 197 | } 198 | }; 199 | this.cordova.getActivity().runOnUiThread(runnable); 200 | this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK)); 201 | } 202 | else { 203 | return false; 204 | } 205 | } catch (JSONException e) { 206 | this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); 207 | } 208 | return true; 209 | } 210 | 211 | /** 212 | * Inject an object (script or style) into the InAppBrowser WebView. 213 | * 214 | * This is a helper method for the inject{Script|Style}{Code|File} API calls, which 215 | * provides a consistent method for injecting JavaScript code into the document. 216 | * 217 | * If a wrapper string is supplied, then the source string will be JSON-encoded (adding 218 | * quotes) and wrapped using string formatting. (The wrapper string should have a single 219 | * '%s' marker) 220 | * 221 | * @param source The source object (filename or script/style text) to inject into 222 | * the document. 223 | * @param jsWrapper A JavaScript string to wrap the source string in, so that the object 224 | * is properly injected, or null if the source string is JavaScript text 225 | * which should be executed directly. 226 | */ 227 | private void injectDeferredObject(String source, String jsWrapper) { 228 | String scriptToInject; 229 | if (jsWrapper != null) { 230 | org.json.JSONArray jsonEsc = new org.json.JSONArray(); 231 | jsonEsc.put(source); 232 | String jsonRepr = jsonEsc.toString(); 233 | String jsonSourceString = jsonRepr.substring(1, jsonRepr.length()-1); 234 | scriptToInject = String.format(jsWrapper, jsonSourceString); 235 | } else { 236 | scriptToInject = source; 237 | } 238 | // This action will have the side-effect of blurring the currently focused element 239 | this.inAppWebView.loadUrl("javascript:" + scriptToInject); 240 | } 241 | 242 | /** 243 | * Put the list of features into a hash map 244 | * 245 | * @param optString 246 | * @return 247 | */ 248 | private HashMap parseFeature(String optString) { 249 | if (optString.equals(NULL)) { 250 | return null; 251 | } else { 252 | HashMap map = new HashMap(); 253 | StringTokenizer features = new StringTokenizer(optString, ","); 254 | StringTokenizer option; 255 | while(features.hasMoreElements()) { 256 | option = new StringTokenizer(features.nextToken(), "="); 257 | if (option.hasMoreElements()) { 258 | String key = option.nextToken(); 259 | if (key.equalsIgnoreCase(CLOSE_BUTTON_CAPTION)) { 260 | this.buttonLabel = option.nextToken(); 261 | } else { 262 | Boolean value = option.nextToken().equals("no") ? Boolean.FALSE : Boolean.TRUE; 263 | map.put(key, value); 264 | } 265 | } 266 | } 267 | return map; 268 | } 269 | } 270 | 271 | /** 272 | * Convert relative URL to full path 273 | * 274 | * @param url 275 | * @return 276 | */ 277 | private String updateUrl(String url) { 278 | Uri newUrl = Uri.parse(url); 279 | if (newUrl.isRelative()) { 280 | url = this.webView.getUrl().substring(0, this.webView.getUrl().lastIndexOf("/")+1) + url; 281 | } 282 | return url; 283 | } 284 | 285 | /** 286 | * Display a new browser with the specified URL. 287 | * 288 | * @param url The url to load. 289 | * @param usePhoneGap Load url in PhoneGap webview 290 | * @return "" if ok, or error message. 291 | */ 292 | public String openExternal(String url) { 293 | try { 294 | Intent intent = null; 295 | intent = new Intent(Intent.ACTION_VIEW); 296 | intent.setData(Uri.parse(url)); 297 | this.cordova.getActivity().startActivity(intent); 298 | return ""; 299 | } catch (android.content.ActivityNotFoundException e) { 300 | Log.d(LOG_TAG, "InAppBrowser: Error loading url "+url+":"+ e.toString()); 301 | return e.toString(); 302 | } 303 | } 304 | 305 | /** 306 | * Closes the dialog 307 | */ 308 | private void closeDialog() { 309 | try { 310 | this.inAppWebView.loadUrl("about:blank"); 311 | JSONObject obj = new JSONObject(); 312 | obj.put("type", EXIT_EVENT); 313 | 314 | sendUpdate(obj, false); 315 | } catch (JSONException ex) { 316 | Log.d(LOG_TAG, "Should never happen"); 317 | } 318 | 319 | if (dialog != null) { 320 | dialog.dismiss(); 321 | } 322 | } 323 | 324 | /** 325 | * Checks to see if it is possible to go back one page in history, then does so. 326 | */ 327 | private void goBack() { 328 | if (this.inAppWebView.canGoBack()) { 329 | this.inAppWebView.goBack(); 330 | } 331 | } 332 | 333 | /** 334 | * Checks to see if it is possible to go forward one page in history, then does so. 335 | */ 336 | private void goForward() { 337 | if (this.inAppWebView.canGoForward()) { 338 | this.inAppWebView.goForward(); 339 | } 340 | } 341 | 342 | /** 343 | * Navigate to the new page 344 | * 345 | * @param url to load 346 | */ 347 | private void navigate(String url) { 348 | InputMethodManager imm = (InputMethodManager)this.cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); 349 | imm.hideSoftInputFromWindow(edittext.getWindowToken(), 0); 350 | 351 | if (!url.startsWith("http") && !url.startsWith("file:")) { 352 | this.inAppWebView.loadUrl("http://" + url); 353 | } else { 354 | this.inAppWebView.loadUrl(url); 355 | } 356 | this.inAppWebView.requestFocus(); 357 | } 358 | 359 | 360 | /** 361 | * Should we show the location bar? 362 | * 363 | * @return boolean 364 | */ 365 | private boolean getShowLocationBar() { 366 | return this.showLocationBar; 367 | } 368 | 369 | /** 370 | * Display a new browser with the specified URL. 371 | * 372 | * @param url The url to load. 373 | * @param jsonObject 374 | */ 375 | public String showWebPage(final String url, HashMap features) { 376 | // Determine if we should hide the location bar. 377 | showLocationBar = true; 378 | openWindowHidden = false; 379 | if (features != null) { 380 | Boolean show = features.get(LOCATION); 381 | if (show != null) { 382 | showLocationBar = show.booleanValue(); 383 | } 384 | Boolean hidden = features.get(HIDDEN); 385 | if(hidden != null) { 386 | openWindowHidden = hidden.booleanValue(); 387 | } 388 | } 389 | 390 | final CordovaWebView thatWebView = this.webView; 391 | 392 | // Create dialog in new thread 393 | Runnable runnable = new Runnable() { 394 | /** 395 | * Convert our DIP units to Pixels 396 | * 397 | * @return int 398 | */ 399 | private int dpToPixels(int dipValue) { 400 | int value = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 401 | (float) dipValue, 402 | cordova.getActivity().getResources().getDisplayMetrics() 403 | ); 404 | 405 | return value; 406 | } 407 | 408 | public void run() { 409 | // Let's create the main dialog 410 | dialog = new Dialog(cordova.getActivity(), android.R.style.Theme_NoTitleBar); 411 | dialog.getWindow().getAttributes().windowAnimations = android.R.style.Animation_Dialog; 412 | dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); 413 | dialog.setCancelable(true); 414 | dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { 415 | public void onDismiss(DialogInterface dialog) { 416 | try { 417 | JSONObject obj = new JSONObject(); 418 | obj.put("type", EXIT_EVENT); 419 | 420 | sendUpdate(obj, false); 421 | } catch (JSONException e) { 422 | Log.d(LOG_TAG, "Should never happen"); 423 | } 424 | } 425 | }); 426 | 427 | // Main container layout 428 | LinearLayout main = new LinearLayout(cordova.getActivity()); 429 | main.setOrientation(LinearLayout.VERTICAL); 430 | 431 | // Toolbar layout 432 | RelativeLayout toolbar = new RelativeLayout(cordova.getActivity()); 433 | toolbar.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, this.dpToPixels(44))); 434 | toolbar.setPadding(this.dpToPixels(2), this.dpToPixels(2), this.dpToPixels(2), this.dpToPixels(2)); 435 | toolbar.setHorizontalGravity(Gravity.LEFT); 436 | toolbar.setVerticalGravity(Gravity.TOP); 437 | 438 | // Action Button Container layout 439 | RelativeLayout actionButtonContainer = new RelativeLayout(cordova.getActivity()); 440 | actionButtonContainer.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); 441 | actionButtonContainer.setHorizontalGravity(Gravity.LEFT); 442 | actionButtonContainer.setVerticalGravity(Gravity.CENTER_VERTICAL); 443 | actionButtonContainer.setId(1); 444 | 445 | // Back button 446 | Button back = new Button(cordova.getActivity()); 447 | RelativeLayout.LayoutParams backLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); 448 | backLayoutParams.addRule(RelativeLayout.ALIGN_LEFT); 449 | back.setLayoutParams(backLayoutParams); 450 | back.setContentDescription("Back Button"); 451 | back.setId(2); 452 | back.setText("<"); 453 | back.setOnClickListener(new View.OnClickListener() { 454 | public void onClick(View v) { 455 | goBack(); 456 | } 457 | }); 458 | 459 | // Forward button 460 | Button forward = new Button(cordova.getActivity()); 461 | RelativeLayout.LayoutParams forwardLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); 462 | forwardLayoutParams.addRule(RelativeLayout.RIGHT_OF, 2); 463 | forward.setLayoutParams(forwardLayoutParams); 464 | forward.setContentDescription("Forward Button"); 465 | forward.setId(3); 466 | forward.setText(">"); 467 | forward.setOnClickListener(new View.OnClickListener() { 468 | public void onClick(View v) { 469 | goForward(); 470 | } 471 | }); 472 | 473 | // Edit Text Box 474 | edittext = new EditText(cordova.getActivity()); 475 | RelativeLayout.LayoutParams textLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 476 | textLayoutParams.addRule(RelativeLayout.RIGHT_OF, 1); 477 | textLayoutParams.addRule(RelativeLayout.LEFT_OF, 5); 478 | edittext.setLayoutParams(textLayoutParams); 479 | edittext.setId(4); 480 | edittext.setSingleLine(true); 481 | edittext.setText(url); 482 | edittext.setInputType(InputType.TYPE_TEXT_VARIATION_URI); 483 | edittext.setImeOptions(EditorInfo.IME_ACTION_GO); 484 | edittext.setInputType(InputType.TYPE_NULL); // Will not except input... Makes the text NON-EDITABLE 485 | edittext.setOnKeyListener(new View.OnKeyListener() { 486 | public boolean onKey(View v, int keyCode, KeyEvent event) { 487 | // If the event is a key-down event on the "enter" button 488 | if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { 489 | navigate(edittext.getText().toString()); 490 | return true; 491 | } 492 | return false; 493 | } 494 | }); 495 | 496 | // Close button 497 | Button close = new Button(cordova.getActivity()); 498 | RelativeLayout.LayoutParams closeLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); 499 | closeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); 500 | close.setLayoutParams(closeLayoutParams); 501 | forward.setContentDescription("Close Button"); 502 | close.setId(5); 503 | close.setText(buttonLabel); 504 | close.setOnClickListener(new View.OnClickListener() { 505 | public void onClick(View v) { 506 | closeDialog(); 507 | } 508 | }); 509 | 510 | // WebView 511 | inAppWebView = new WebView(cordova.getActivity()); 512 | inAppWebView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); 513 | inAppWebView.setWebChromeClient(new InAppChromeClient(thatWebView)); 514 | WebViewClient client = new InAppBrowserClient(thatWebView, edittext); 515 | inAppWebView.setWebViewClient(client); 516 | WebSettings settings = inAppWebView.getSettings(); 517 | settings.setJavaScriptEnabled(true); 518 | settings.setJavaScriptCanOpenWindowsAutomatically(true); 519 | settings.setBuiltInZoomControls(true); 520 | settings.setPluginState(android.webkit.WebSettings.PluginState.ON); 521 | 522 | //Toggle whether this is enabled or not! 523 | Bundle appSettings = cordova.getActivity().getIntent().getExtras(); 524 | boolean enableDatabase = appSettings == null ? true : appSettings.getBoolean("InAppBrowserStorageEnabled", true); 525 | if(enableDatabase) 526 | { 527 | String databasePath = cordova.getActivity().getApplicationContext().getDir("inAppBrowserDB", Context.MODE_PRIVATE).getPath(); 528 | settings.setDatabasePath(databasePath); 529 | settings.setDatabaseEnabled(true); 530 | } 531 | settings.setDomStorageEnabled(true); 532 | 533 | inAppWebView.loadUrl(url); 534 | inAppWebView.setId(6); 535 | inAppWebView.getSettings().setLoadWithOverviewMode(true); 536 | inAppWebView.getSettings().setUseWideViewPort(true); 537 | inAppWebView.requestFocus(); 538 | inAppWebView.requestFocusFromTouch(); 539 | 540 | // Add the back and forward buttons to our action button container layout 541 | actionButtonContainer.addView(back); 542 | actionButtonContainer.addView(forward); 543 | 544 | // Add the views to our toolbar 545 | toolbar.addView(actionButtonContainer); 546 | toolbar.addView(edittext); 547 | toolbar.addView(close); 548 | 549 | // Don't add the toolbar if its been disabled 550 | if (getShowLocationBar()) { 551 | // Add our toolbar to our main view/layout 552 | main.addView(toolbar); 553 | } 554 | 555 | // Add our webview to our main view/layout 556 | main.addView(inAppWebView); 557 | 558 | WindowManager.LayoutParams lp = new WindowManager.LayoutParams(); 559 | lp.copyFrom(dialog.getWindow().getAttributes()); 560 | lp.width = WindowManager.LayoutParams.MATCH_PARENT; 561 | lp.height = WindowManager.LayoutParams.MATCH_PARENT; 562 | 563 | dialog.setContentView(main); 564 | dialog.show(); 565 | dialog.getWindow().setAttributes(lp); 566 | // the goal of openhidden is to load the url and not display it 567 | // Show() needs to be called to cause the URL to be loaded 568 | if(openWindowHidden) { 569 | dialog.hide(); 570 | } 571 | } 572 | }; 573 | this.cordova.getActivity().runOnUiThread(runnable); 574 | return ""; 575 | } 576 | 577 | /** 578 | * Create a new plugin success result and send it back to JavaScript 579 | * 580 | * @param obj a JSONObject contain event payload information 581 | */ 582 | private void sendUpdate(JSONObject obj, boolean keepCallback) { 583 | sendUpdate(obj, keepCallback, PluginResult.Status.OK); 584 | } 585 | 586 | /** 587 | * Create a new plugin result and send it back to JavaScript 588 | * 589 | * @param obj a JSONObject contain event payload information 590 | * @param status the status code to return to the JavaScript environment 591 | */ private void sendUpdate(JSONObject obj, boolean keepCallback, PluginResult.Status status) { 592 | PluginResult result = new PluginResult(status, obj); 593 | result.setKeepCallback(keepCallback); 594 | this.callbackContext.sendPluginResult(result); 595 | } 596 | 597 | public class InAppChromeClient extends WebChromeClient { 598 | 599 | private CordovaWebView webView; 600 | 601 | public InAppChromeClient(CordovaWebView webView) { 602 | super(); 603 | this.webView = webView; 604 | } 605 | /** 606 | * Handle database quota exceeded notification. 607 | * 608 | * @param url 609 | * @param databaseIdentifier 610 | * @param currentQuota 611 | * @param estimatedSize 612 | * @param totalUsedQuota 613 | * @param quotaUpdater 614 | */ 615 | @Override 616 | public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize, 617 | long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) 618 | { 619 | LOG.d(LOG_TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota); 620 | 621 | if (estimatedSize < MAX_QUOTA) 622 | { 623 | //increase for 1Mb 624 | long newQuota = estimatedSize; 625 | LOG.d(LOG_TAG, "calling quotaUpdater.updateQuota newQuota: %d", newQuota); 626 | quotaUpdater.updateQuota(newQuota); 627 | } 628 | else 629 | { 630 | // Set the quota to whatever it is and force an error 631 | // TODO: get docs on how to handle this properly 632 | quotaUpdater.updateQuota(currentQuota); 633 | } 634 | } 635 | 636 | /** 637 | * Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin. 638 | * 639 | * @param origin 640 | * @param callback 641 | */ 642 | @Override 643 | public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) { 644 | super.onGeolocationPermissionsShowPrompt(origin, callback); 645 | callback.invoke(origin, true, false); 646 | } 647 | 648 | /** 649 | * Tell the client to display a prompt dialog to the user. 650 | * If the client returns true, WebView will assume that the client will 651 | * handle the prompt dialog and call the appropriate JsPromptResult method. 652 | * 653 | * The prompt bridge provided for the InAppBrowser is capable of executing any 654 | * oustanding callback belonging to the InAppBrowser plugin. Care has been 655 | * taken that other callbacks cannot be triggered, and that no other code 656 | * execution is possible. 657 | * 658 | * To trigger the bridge, the prompt default value should be of the form: 659 | * 660 | * gap-iab:// 661 | * 662 | * where is the string id of the callback to trigger (something 663 | * like "InAppBrowser0123456789") 664 | * 665 | * If present, the prompt message is expected to be a JSON-encoded value to 666 | * pass to the callback. A JSON_EXCEPTION is returned if the JSON is invalid. 667 | * 668 | * @param view 669 | * @param url 670 | * @param message 671 | * @param defaultValue 672 | * @param result 673 | */ 674 | @Override 675 | public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { 676 | // See if the prompt string uses the 'gap-iab' protocol. If so, the remainder should be the id of a callback to execute. 677 | if (defaultValue != null && defaultValue.startsWith("gap-iab://")) { 678 | PluginResult scriptResult; 679 | String scriptCallbackId = defaultValue.substring(10); 680 | if (scriptCallbackId.startsWith("InAppBrowser")) { 681 | if(message == null || message.length() == 0) { 682 | scriptResult = new PluginResult(PluginResult.Status.OK, new JSONArray()); 683 | } else { 684 | try { 685 | scriptResult = new PluginResult(PluginResult.Status.OK, new JSONArray(message)); 686 | } catch(JSONException e) { 687 | scriptResult = new PluginResult(PluginResult.Status.JSON_EXCEPTION, e.getMessage()); 688 | } 689 | } 690 | this.webView.sendPluginResult(scriptResult, scriptCallbackId); 691 | result.confirm(""); 692 | return true; 693 | } 694 | } 695 | return false; 696 | } 697 | 698 | } 699 | 700 | /** 701 | * The webview client receives notifications about appView 702 | */ 703 | public class InAppBrowserClient extends WebViewClient { 704 | EditText edittext; 705 | CordovaWebView webView; 706 | 707 | /** 708 | * Constructor. 709 | * 710 | * @param mContext 711 | * @param edittext 712 | */ 713 | public InAppBrowserClient(CordovaWebView webView, EditText mEditText) { 714 | this.webView = webView; 715 | this.edittext = mEditText; 716 | } 717 | 718 | /** 719 | * Notify the host application that a page has started loading. 720 | * 721 | * @param view The webview initiating the callback. 722 | * @param url The url of the page. 723 | */ 724 | @Override 725 | public void onPageStarted(WebView view, String url, Bitmap favicon) { 726 | super.onPageStarted(view, url, favicon); 727 | String newloc = ""; 728 | if (url.startsWith("http:") || url.startsWith("https:") || url.startsWith("file:")) { 729 | newloc = url; 730 | } 731 | // If dialing phone (tel:5551212) 732 | else if (url.startsWith(WebView.SCHEME_TEL)) { 733 | try { 734 | Intent intent = new Intent(Intent.ACTION_DIAL); 735 | intent.setData(Uri.parse(url)); 736 | cordova.getActivity().startActivity(intent); 737 | } catch (android.content.ActivityNotFoundException e) { 738 | LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString()); 739 | } 740 | } 741 | 742 | else if (url.startsWith("geo:") || url.startsWith(WebView.SCHEME_MAILTO) || url.startsWith("market:")) { 743 | try { 744 | Intent intent = new Intent(Intent.ACTION_VIEW); 745 | intent.setData(Uri.parse(url)); 746 | cordova.getActivity().startActivity(intent); 747 | } catch (android.content.ActivityNotFoundException e) { 748 | LOG.e(LOG_TAG, "Error with " + url + ": " + e.toString()); 749 | } 750 | } 751 | // If sms:5551212?body=This is the message 752 | else if (url.startsWith("sms:")) { 753 | try { 754 | Intent intent = new Intent(Intent.ACTION_VIEW); 755 | 756 | // Get address 757 | String address = null; 758 | int parmIndex = url.indexOf('?'); 759 | if (parmIndex == -1) { 760 | address = url.substring(4); 761 | } 762 | else { 763 | address = url.substring(4, parmIndex); 764 | 765 | // If body, then set sms body 766 | Uri uri = Uri.parse(url); 767 | String query = uri.getQuery(); 768 | if (query != null) { 769 | if (query.startsWith("body=")) { 770 | intent.putExtra("sms_body", query.substring(5)); 771 | } 772 | } 773 | } 774 | intent.setData(Uri.parse("sms:" + address)); 775 | intent.putExtra("address", address); 776 | intent.setType("vnd.android-dir/mms-sms"); 777 | cordova.getActivity().startActivity(intent); 778 | } catch (android.content.ActivityNotFoundException e) { 779 | LOG.e(LOG_TAG, "Error sending sms " + url + ":" + e.toString()); 780 | } 781 | } 782 | else { 783 | newloc = "http://" + url; 784 | } 785 | 786 | if (!newloc.equals(edittext.getText().toString())) { 787 | edittext.setText(newloc); 788 | } 789 | 790 | try { 791 | JSONObject obj = new JSONObject(); 792 | obj.put("type", LOAD_START_EVENT); 793 | obj.put("url", newloc); 794 | 795 | sendUpdate(obj, true); 796 | } catch (JSONException ex) { 797 | Log.d(LOG_TAG, "Should never happen"); 798 | } 799 | } 800 | 801 | public void onPageFinished(WebView view, String url) { 802 | super.onPageFinished(view, url); 803 | 804 | try { 805 | JSONObject obj = new JSONObject(); 806 | obj.put("type", LOAD_STOP_EVENT); 807 | obj.put("url", url); 808 | 809 | sendUpdate(obj, true); 810 | } catch (JSONException ex) { 811 | Log.d(LOG_TAG, "Should never happen"); 812 | } 813 | } 814 | 815 | public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { 816 | super.onReceivedError(view, errorCode, description, failingUrl); 817 | 818 | try { 819 | JSONObject obj = new JSONObject(); 820 | obj.put("type", LOAD_ERROR_EVENT); 821 | obj.put("url", failingUrl); 822 | obj.put("code", errorCode); 823 | obj.put("message", description); 824 | 825 | sendUpdate(obj, true, PluginResult.Status.ERROR); 826 | } catch (JSONException ex) { 827 | Log.d(LOG_TAG, "Should never happen"); 828 | } 829 | 830 | } 831 | } 832 | } 833 | -------------------------------------------------------------------------------- /plugins/org.apache.cordova.core.inappbrowser/src/blackberry10/README.md: -------------------------------------------------------------------------------- 1 | # BlackBerry 10 In-App-Browser Plugin 2 | 3 | The in app browser functionality is entirely contained within common js. There is no native implementation required. 4 | To install this plugin, follow the [Command-line Interface Guide](http://cordova.apache.org/docs/en/edge/guide_cli_index.md.html#The%20Command-line%20Interface). 5 | 6 | If you are not using the Cordova Command-line Interface, follow [Using Plugman to Manage Plugins](http://cordova.apache.org/docs/en/edge/guide_plugin_ref_plugman.md.html). 7 | ./cordova-plugin-battery-status/README.md 8 | ./cordova-plugin-camera/README.md 9 | ./cordova-plugin-console/README.md 10 | ./cordova-plugin-contacts/README.md 11 | ./cordova-plugin-device/README.md 12 | ./cordova-plugin-device-motion/README.md 13 | ./cordova-plugin-device-orientation/README.md 14 | ./cordova-plugin-device-orientation/src/blackberry10/README.md 15 | ./cordova-plugin-file/README.md 16 | ./cordova-plugin-file-transfer/README.md 17 | ./cordova-plugin-geolocation/README.md 18 | ./cordova-plugin-globalization/README.md 19 | ./cordova-plugin-inappbrowser/README.md 20 | ./cordova-plugin-inappbrowser/src/blackberry10/README.md 21 | ./cordova-plugin-media/README.md 22 | ./cordova-plugin-media-capture/README.md 23 | ./cordova-plugin-network-information/README.md 24 | ./cordova-plugin-splashscreen/README.md 25 | ./cordova-plugin-vibration/README.md 26 | -------------------------------------------------------------------------------- /plugins/org.apache.cordova.core.inappbrowser/src/ios/CDVInAppBrowser.h: -------------------------------------------------------------------------------- 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 | 20 | #import 21 | #import 22 | #import 23 | #import 24 | 25 | @class CDVInAppBrowserViewController; 26 | 27 | @interface CDVInAppBrowser : CDVPlugin { 28 | BOOL _injectedIframeBridge; 29 | } 30 | 31 | @property (nonatomic, retain) CDVInAppBrowserViewController* inAppBrowserViewController; 32 | @property (nonatomic, copy) NSString* callbackId; 33 | 34 | - (void)open:(CDVInvokedUrlCommand*)command; 35 | - (void)close:(CDVInvokedUrlCommand*)command; 36 | - (void)injectScriptCode:(CDVInvokedUrlCommand*)command; 37 | - (void)show:(CDVInvokedUrlCommand*)command; 38 | 39 | @end 40 | 41 | @interface CDVInAppBrowserViewController : UIViewController { 42 | @private 43 | NSString* _userAgent; 44 | NSString* _prevUserAgent; 45 | NSInteger _userAgentLockToken; 46 | CDVWebViewDelegate* _webViewDelegate; 47 | } 48 | 49 | @property (nonatomic, strong) IBOutlet UIWebView* webView; 50 | @property (nonatomic, strong) IBOutlet UIBarButtonItem* closeButton; 51 | @property (nonatomic, strong) IBOutlet UILabel* addressLabel; 52 | @property (nonatomic, strong) IBOutlet UIBarButtonItem* backButton; 53 | @property (nonatomic, strong) IBOutlet UIBarButtonItem* forwardButton; 54 | @property (nonatomic, strong) IBOutlet UIActivityIndicatorView* spinner; 55 | @property (nonatomic, strong) IBOutlet UIToolbar* toolbar; 56 | 57 | @property (nonatomic, weak) id orientationDelegate; 58 | @property (nonatomic, weak) CDVInAppBrowser* navigationDelegate; 59 | @property (nonatomic) NSURL* currentURL; 60 | 61 | - (void)close; 62 | - (void)navigateTo:(NSURL*)url; 63 | - (void)showLocationBar:(BOOL)show; 64 | - (void)showToolBar:(BOOL)show; 65 | - (void)setCloseButtonTitle:(NSString*)title; 66 | 67 | - (id)initWithUserAgent:(NSString*)userAgent prevUserAgent:(NSString*)prevUserAgent; 68 | 69 | @end 70 | 71 | @interface CDVInAppBrowserOptions : NSObject {} 72 | 73 | @property (nonatomic, assign) BOOL location; 74 | @property (nonatomic, assign) BOOL toolbar; 75 | @property (nonatomic, copy) NSString* closebuttoncaption; 76 | 77 | @property (nonatomic, copy) NSString* presentationstyle; 78 | @property (nonatomic, copy) NSString* transitionstyle; 79 | 80 | @property (nonatomic, assign) BOOL enableviewportscale; 81 | @property (nonatomic, assign) BOOL mediaplaybackrequiresuseraction; 82 | @property (nonatomic, assign) BOOL allowinlinemediaplayback; 83 | @property (nonatomic, assign) BOOL keyboarddisplayrequiresuseraction; 84 | @property (nonatomic, assign) BOOL suppressesincrementalrendering; 85 | @property (nonatomic, assign) BOOL hidden; 86 | 87 | + (CDVInAppBrowserOptions*)parseOptions:(NSString*)options; 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /plugins/org.apache.cordova.core.inappbrowser/src/ios/CDVInAppBrowser.m: -------------------------------------------------------------------------------- 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 | 20 | #import "CDVInAppBrowser.h" 21 | #import 22 | #import 23 | #import 24 | 25 | #define kInAppBrowserTargetSelf @"_self" 26 | #define kInAppBrowserTargetSystem @"_system" 27 | #define kInAppBrowserTargetBlank @"_blank" 28 | 29 | #define TOOLBAR_HEIGHT 44.0 30 | #define LOCATIONBAR_HEIGHT 21.0 31 | #define FOOTER_HEIGHT ((TOOLBAR_HEIGHT) + (LOCATIONBAR_HEIGHT)) 32 | 33 | #pragma mark CDVInAppBrowser 34 | 35 | @implementation CDVInAppBrowser 36 | 37 | - (CDVInAppBrowser*)initWithWebView:(UIWebView*)theWebView 38 | { 39 | self = [super initWithWebView:theWebView]; 40 | if (self != nil) { 41 | // your initialization here 42 | } 43 | 44 | return self; 45 | } 46 | 47 | - (void)onReset 48 | { 49 | [self close:nil]; 50 | } 51 | 52 | - (void)close:(CDVInvokedUrlCommand*)command 53 | { 54 | if (self.inAppBrowserViewController != nil) { 55 | [self.inAppBrowserViewController close]; 56 | self.inAppBrowserViewController = nil; 57 | } 58 | 59 | self.callbackId = nil; 60 | } 61 | 62 | - (BOOL) isSystemUrl:(NSURL*)url 63 | { 64 | if ([[url host] isEqualToString:@"itunes.apple.com"]) { 65 | return YES; 66 | } 67 | 68 | return NO; 69 | } 70 | 71 | - (void)open:(CDVInvokedUrlCommand*)command 72 | { 73 | CDVPluginResult* pluginResult; 74 | 75 | NSString* url = [command argumentAtIndex:0]; 76 | NSString* target = [command argumentAtIndex:1 withDefault:kInAppBrowserTargetSelf]; 77 | NSString* options = [command argumentAtIndex:2 withDefault:@"" andClass:[NSString class]]; 78 | 79 | self.callbackId = command.callbackId; 80 | 81 | if (url != nil) { 82 | NSURL* baseUrl = [self.webView.request URL]; 83 | NSURL* absoluteUrl = [[NSURL URLWithString:url relativeToURL:baseUrl] absoluteURL]; 84 | 85 | if ([self isSystemUrl:absoluteUrl]) { 86 | target = kInAppBrowserTargetSystem; 87 | } 88 | 89 | if ([target isEqualToString:kInAppBrowserTargetSelf]) { 90 | [self openInCordovaWebView:absoluteUrl withOptions:options]; 91 | } else if ([target isEqualToString:kInAppBrowserTargetSystem]) { 92 | [self openInSystem:absoluteUrl]; 93 | } else { // _blank or anything else 94 | [self openInInAppBrowser:absoluteUrl withOptions:options]; 95 | } 96 | 97 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 98 | } else { 99 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"incorrect number of arguments"]; 100 | } 101 | 102 | [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; 103 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 104 | } 105 | 106 | - (void)openInInAppBrowser:(NSURL*)url withOptions:(NSString*)options 107 | { 108 | if (self.inAppBrowserViewController == nil) { 109 | NSString* originalUA = [CDVUserAgentUtil originalUserAgent]; 110 | self.inAppBrowserViewController = [[CDVInAppBrowserViewController alloc] initWithUserAgent:originalUA prevUserAgent:[self.commandDelegate userAgent]]; 111 | self.inAppBrowserViewController.navigationDelegate = self; 112 | 113 | if ([self.viewController conformsToProtocol:@protocol(CDVScreenOrientationDelegate)]) { 114 | self.inAppBrowserViewController.orientationDelegate = (UIViewController *)self.viewController; 115 | } 116 | } 117 | 118 | 119 | CDVInAppBrowserOptions* browserOptions = [CDVInAppBrowserOptions parseOptions:options]; 120 | [self.inAppBrowserViewController showLocationBar:browserOptions.location]; 121 | [self.inAppBrowserViewController showToolBar:browserOptions.toolbar]; 122 | if (browserOptions.closebuttoncaption != nil) { 123 | [self.inAppBrowserViewController setCloseButtonTitle:browserOptions.closebuttoncaption]; 124 | } 125 | // Set Presentation Style 126 | UIModalPresentationStyle presentationStyle = UIModalPresentationFullScreen; // default 127 | if (browserOptions.presentationstyle != nil) { 128 | if ([[browserOptions.presentationstyle lowercaseString] isEqualToString:@"pagesheet"]) { 129 | presentationStyle = UIModalPresentationPageSheet; 130 | } else if ([[browserOptions.presentationstyle lowercaseString] isEqualToString:@"formsheet"]) { 131 | presentationStyle = UIModalPresentationFormSheet; 132 | } 133 | } 134 | self.inAppBrowserViewController.modalPresentationStyle = presentationStyle; 135 | 136 | // Set Transition Style 137 | UIModalTransitionStyle transitionStyle = UIModalTransitionStyleCoverVertical; // default 138 | if (browserOptions.transitionstyle != nil) { 139 | if ([[browserOptions.transitionstyle lowercaseString] isEqualToString:@"fliphorizontal"]) { 140 | transitionStyle = UIModalTransitionStyleFlipHorizontal; 141 | } else if ([[browserOptions.transitionstyle lowercaseString] isEqualToString:@"crossdissolve"]) { 142 | transitionStyle = UIModalTransitionStyleCrossDissolve; 143 | } 144 | } 145 | self.inAppBrowserViewController.modalTransitionStyle = transitionStyle; 146 | 147 | 148 | // UIWebView options 149 | self.inAppBrowserViewController.webView.scalesPageToFit = browserOptions.enableviewportscale; 150 | self.inAppBrowserViewController.webView.mediaPlaybackRequiresUserAction = browserOptions.mediaplaybackrequiresuseraction; 151 | self.inAppBrowserViewController.webView.allowsInlineMediaPlayback = browserOptions.allowinlinemediaplayback; 152 | if (IsAtLeastiOSVersion(@"6.0")) { 153 | self.inAppBrowserViewController.webView.keyboardDisplayRequiresUserAction = browserOptions.keyboarddisplayrequiresuseraction; 154 | self.inAppBrowserViewController.webView.suppressesIncrementalRendering = browserOptions.suppressesincrementalrendering; 155 | } 156 | 157 | if (! browserOptions.hidden) { 158 | if (self.viewController.modalViewController != self.inAppBrowserViewController) { 159 | [self.viewController presentModalViewController:self.inAppBrowserViewController animated:YES]; 160 | } 161 | } 162 | [self.inAppBrowserViewController navigateTo:url]; 163 | } 164 | 165 | - (void)show:(CDVInvokedUrlCommand*)command 166 | { 167 | if ([self.inAppBrowserViewController isViewLoaded] && self.inAppBrowserViewController.view.window) 168 | return; 169 | [self.viewController presentModalViewController:self.inAppBrowserViewController animated:YES]; 170 | } 171 | 172 | - (void)openInCordovaWebView:(NSURL*)url withOptions:(NSString*)options 173 | { 174 | if ([self.commandDelegate URLIsWhitelisted:url]) { 175 | NSURLRequest* request = [NSURLRequest requestWithURL:url]; 176 | [self.webView loadRequest:request]; 177 | } else { // this assumes the InAppBrowser can be excepted from the white-list 178 | [self openInInAppBrowser:url withOptions:options]; 179 | } 180 | } 181 | 182 | - (void)openInSystem:(NSURL*)url 183 | { 184 | if ([[UIApplication sharedApplication] canOpenURL:url]) { 185 | [[UIApplication sharedApplication] openURL:url]; 186 | } else { // handle any custom schemes to plugins 187 | [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]]; 188 | } 189 | } 190 | 191 | // This is a helper method for the inject{Script|Style}{Code|File} API calls, which 192 | // provides a consistent method for injecting JavaScript code into the document. 193 | // 194 | // If a wrapper string is supplied, then the source string will be JSON-encoded (adding 195 | // quotes) and wrapped using string formatting. (The wrapper string should have a single 196 | // '%@' marker). 197 | // 198 | // If no wrapper is supplied, then the source string is executed directly. 199 | 200 | - (void)injectDeferredObject:(NSString*)source withWrapper:(NSString*)jsWrapper 201 | { 202 | if (!_injectedIframeBridge) { 203 | _injectedIframeBridge = YES; 204 | // Create an iframe bridge in the new document to communicate with the CDVInAppBrowserViewController 205 | [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:@"(function(d){var e = _cdvIframeBridge = d.createElement('iframe');e.style.display='none';d.body.appendChild(e);})(document)"]; 206 | } 207 | 208 | if (jsWrapper != nil) { 209 | NSString* sourceArrayString = [@[source] JSONString]; 210 | if (sourceArrayString) { 211 | NSString* sourceString = [sourceArrayString substringWithRange:NSMakeRange(1, [sourceArrayString length] - 2)]; 212 | NSString* jsToInject = [NSString stringWithFormat:jsWrapper, sourceString]; 213 | [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:jsToInject]; 214 | } 215 | } else { 216 | [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:source]; 217 | } 218 | } 219 | 220 | - (void)injectScriptCode:(CDVInvokedUrlCommand*)command 221 | { 222 | NSString* jsWrapper = nil; 223 | 224 | if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) { 225 | jsWrapper = [NSString stringWithFormat:@"_cdvIframeBridge.src='gap-iab://%@/'+window.escape(JSON.stringify([eval(%%@)]));", command.callbackId]; 226 | } 227 | [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper]; 228 | } 229 | 230 | - (void)injectScriptFile:(CDVInvokedUrlCommand*)command 231 | { 232 | NSString* jsWrapper; 233 | 234 | if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) { 235 | jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('script'); c.src = %%@; c.onload = function() { _cdvIframeBridge.src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId]; 236 | } else { 237 | jsWrapper = @"(function(d) { var c = d.createElement('script'); c.src = %@; d.body.appendChild(c); })(document)"; 238 | } 239 | [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper]; 240 | } 241 | 242 | - (void)injectStyleCode:(CDVInvokedUrlCommand*)command 243 | { 244 | NSString* jsWrapper; 245 | 246 | if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) { 247 | jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('style'); c.innerHTML = %%@; c.onload = function() { _cdvIframeBridge.src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId]; 248 | } else { 249 | jsWrapper = @"(function(d) { var c = d.createElement('style'); c.innerHTML = %@; d.body.appendChild(c); })(document)"; 250 | } 251 | [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper]; 252 | } 253 | 254 | - (void)injectStyleFile:(CDVInvokedUrlCommand*)command 255 | { 256 | NSString* jsWrapper; 257 | 258 | if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) { 259 | jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %%@; c.onload = function() { _cdvIframeBridge.src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId]; 260 | } else { 261 | jsWrapper = @"(function(d) { var c = d.createElement('link'); c.rel='stylesheet', c.type='text/css'; c.href = %@; d.body.appendChild(c); })(document)"; 262 | } 263 | [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper]; 264 | } 265 | 266 | /** 267 | * The iframe bridge provided for the InAppBrowser is capable of executing any oustanding callback belonging 268 | * to the InAppBrowser plugin. Care has been taken that other callbacks cannot be triggered, and that no 269 | * other code execution is possible. 270 | * 271 | * To trigger the bridge, the iframe (or any other resource) should attempt to load a url of the form: 272 | * 273 | * gap-iab:/// 274 | * 275 | * where is the string id of the callback to trigger (something like "InAppBrowser0123456789") 276 | * 277 | * If present, the path component of the special gap-iab:// url is expected to be a URL-escaped JSON-encoded 278 | * value to pass to the callback. [NSURL path] should take care of the URL-unescaping, and a JSON_EXCEPTION 279 | * is returned if the JSON is invalid. 280 | */ 281 | - (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType 282 | { 283 | NSURL* url = request.URL; 284 | BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]]; 285 | 286 | // See if the url uses the 'gap-iab' protocol. If so, the host should be the id of a callback to execute, 287 | // and the path, if present, should be a JSON-encoded value to pass to the callback. 288 | if ([[url scheme] isEqualToString:@"gap-iab"]) { 289 | NSString* scriptCallbackId = [url host]; 290 | CDVPluginResult* pluginResult = nil; 291 | 292 | if ([scriptCallbackId hasPrefix:@"InAppBrowser"]) { 293 | NSString* scriptResult = [url path]; 294 | NSError* __autoreleasing error = nil; 295 | 296 | // The message should be a JSON-encoded array of the result of the script which executed. 297 | if ((scriptResult != nil) && ([scriptResult length] > 1)) { 298 | scriptResult = [scriptResult substringFromIndex:1]; 299 | NSData* decodedResult = [NSJSONSerialization JSONObjectWithData:[scriptResult dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error]; 300 | if ((error == nil) && [decodedResult isKindOfClass:[NSArray class]]) { 301 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:(NSArray*)decodedResult]; 302 | } else { 303 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_JSON_EXCEPTION]; 304 | } 305 | } else { 306 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:@[]]; 307 | } 308 | [self.commandDelegate sendPluginResult:pluginResult callbackId:scriptCallbackId]; 309 | return NO; 310 | } 311 | } else if ((self.callbackId != nil) && isTopLevelNavigation) { 312 | // Send a loadstart event for each top-level navigation (includes redirects). 313 | CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK 314 | messageAsDictionary:@{@"type":@"loadstart", @"url":[url absoluteString]}]; 315 | [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; 316 | 317 | [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; 318 | } 319 | 320 | return YES; 321 | } 322 | 323 | - (void)webViewDidStartLoad:(UIWebView*)theWebView 324 | { 325 | _injectedIframeBridge = NO; 326 | } 327 | 328 | - (void)webViewDidFinishLoad:(UIWebView*)theWebView 329 | { 330 | if (self.callbackId != nil) { 331 | // TODO: It would be more useful to return the URL the page is actually on (e.g. if it's been redirected). 332 | NSString* url = [self.inAppBrowserViewController.currentURL absoluteString]; 333 | CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK 334 | messageAsDictionary:@{@"type":@"loadstop", @"url":url}]; 335 | [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; 336 | 337 | [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; 338 | } 339 | } 340 | 341 | - (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error 342 | { 343 | if (self.callbackId != nil) { 344 | NSString* url = [self.inAppBrowserViewController.currentURL absoluteString]; 345 | CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR 346 | messageAsDictionary:@{@"type":@"loaderror", @"url":url, @"code": [NSNumber numberWithInt:error.code], @"message": error.localizedDescription}]; 347 | [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; 348 | 349 | [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; 350 | } 351 | } 352 | 353 | - (void)browserExit 354 | { 355 | if (self.callbackId != nil) { 356 | CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK 357 | messageAsDictionary:@{@"type":@"exit"}]; 358 | [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; 359 | 360 | [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; 361 | } 362 | // Don't recycle the ViewController since it may be consuming a lot of memory. 363 | // Also - this is required for the PDF/User-Agent bug work-around. 364 | self.inAppBrowserViewController = nil; 365 | } 366 | 367 | @end 368 | 369 | #pragma mark CDVInAppBrowserViewController 370 | 371 | @implementation CDVInAppBrowserViewController 372 | 373 | @synthesize currentURL; 374 | 375 | - (id)initWithUserAgent:(NSString*)userAgent prevUserAgent:(NSString*)prevUserAgent 376 | { 377 | self = [super init]; 378 | if (self != nil) { 379 | _userAgent = userAgent; 380 | _prevUserAgent = prevUserAgent; 381 | _webViewDelegate = [[CDVWebViewDelegate alloc] initWithDelegate:self]; 382 | [self createViews]; 383 | } 384 | 385 | return self; 386 | } 387 | 388 | - (void)createViews 389 | { 390 | // We create the views in code for primarily for ease of upgrades and not requiring an external .xib to be included 391 | 392 | CGRect webViewBounds = self.view.bounds; 393 | 394 | webViewBounds.size.height -= FOOTER_HEIGHT; 395 | 396 | self.webView = [[UIWebView alloc] initWithFrame:webViewBounds]; 397 | self.webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); 398 | 399 | [self.view addSubview:self.webView]; 400 | [self.view sendSubviewToBack:self.webView]; 401 | 402 | self.webView.delegate = _webViewDelegate; 403 | self.webView.backgroundColor = [UIColor whiteColor]; 404 | 405 | self.webView.clearsContextBeforeDrawing = YES; 406 | self.webView.clipsToBounds = YES; 407 | self.webView.contentMode = UIViewContentModeScaleToFill; 408 | self.webView.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}"); 409 | self.webView.multipleTouchEnabled = YES; 410 | self.webView.opaque = YES; 411 | self.webView.scalesPageToFit = NO; 412 | self.webView.userInteractionEnabled = YES; 413 | 414 | self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; 415 | self.spinner.alpha = 1.000; 416 | self.spinner.autoresizesSubviews = YES; 417 | self.spinner.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin; 418 | self.spinner.clearsContextBeforeDrawing = NO; 419 | self.spinner.clipsToBounds = NO; 420 | self.spinner.contentMode = UIViewContentModeScaleToFill; 421 | self.spinner.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}"); 422 | self.spinner.frame = CGRectMake(454.0, 231.0, 20.0, 20.0); 423 | self.spinner.hidden = YES; 424 | self.spinner.hidesWhenStopped = YES; 425 | self.spinner.multipleTouchEnabled = NO; 426 | self.spinner.opaque = NO; 427 | self.spinner.userInteractionEnabled = NO; 428 | [self.spinner stopAnimating]; 429 | 430 | self.closeButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(close)]; 431 | self.closeButton.enabled = YES; 432 | 433 | UIBarButtonItem* flexibleSpaceButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; 434 | 435 | UIBarButtonItem* fixedSpaceButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil]; 436 | fixedSpaceButton.width = 20; 437 | 438 | self.toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0.0, (self.view.bounds.size.height - TOOLBAR_HEIGHT), self.view.bounds.size.width, TOOLBAR_HEIGHT)]; 439 | self.toolbar.alpha = 1.000; 440 | self.toolbar.autoresizesSubviews = YES; 441 | self.toolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin; 442 | self.toolbar.barStyle = UIBarStyleBlackOpaque; 443 | self.toolbar.clearsContextBeforeDrawing = NO; 444 | self.toolbar.clipsToBounds = NO; 445 | self.toolbar.contentMode = UIViewContentModeScaleToFill; 446 | self.toolbar.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}"); 447 | self.toolbar.hidden = NO; 448 | self.toolbar.multipleTouchEnabled = NO; 449 | self.toolbar.opaque = NO; 450 | self.toolbar.userInteractionEnabled = YES; 451 | 452 | CGFloat labelInset = 5.0; 453 | self.addressLabel = [[UILabel alloc] initWithFrame:CGRectMake(labelInset, (self.view.bounds.size.height - FOOTER_HEIGHT), self.view.bounds.size.width - labelInset, LOCATIONBAR_HEIGHT)]; 454 | self.addressLabel.adjustsFontSizeToFitWidth = NO; 455 | self.addressLabel.alpha = 1.000; 456 | self.addressLabel.autoresizesSubviews = YES; 457 | self.addressLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin; 458 | self.addressLabel.backgroundColor = [UIColor clearColor]; 459 | self.addressLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters; 460 | self.addressLabel.clearsContextBeforeDrawing = YES; 461 | self.addressLabel.clipsToBounds = YES; 462 | self.addressLabel.contentMode = UIViewContentModeScaleToFill; 463 | self.addressLabel.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}"); 464 | self.addressLabel.enabled = YES; 465 | self.addressLabel.hidden = NO; 466 | self.addressLabel.lineBreakMode = UILineBreakModeTailTruncation; 467 | self.addressLabel.minimumFontSize = 10.000; 468 | self.addressLabel.multipleTouchEnabled = NO; 469 | self.addressLabel.numberOfLines = 1; 470 | self.addressLabel.opaque = NO; 471 | self.addressLabel.shadowOffset = CGSizeMake(0.0, -1.0); 472 | self.addressLabel.text = @"Loading..."; 473 | self.addressLabel.textAlignment = UITextAlignmentLeft; 474 | self.addressLabel.textColor = [UIColor colorWithWhite:1.000 alpha:1.000]; 475 | self.addressLabel.userInteractionEnabled = NO; 476 | 477 | NSString* frontArrowString = @"►"; // create arrow from Unicode char 478 | self.forwardButton = [[UIBarButtonItem alloc] initWithTitle:frontArrowString style:UIBarButtonItemStylePlain target:self action:@selector(goForward:)]; 479 | self.forwardButton.enabled = YES; 480 | self.forwardButton.imageInsets = UIEdgeInsetsZero; 481 | 482 | NSString* backArrowString = @"◄"; // create arrow from Unicode char 483 | self.backButton = [[UIBarButtonItem alloc] initWithTitle:backArrowString style:UIBarButtonItemStylePlain target:self action:@selector(goBack:)]; 484 | self.backButton.enabled = YES; 485 | self.backButton.imageInsets = UIEdgeInsetsZero; 486 | 487 | [self.toolbar setItems:@[self.closeButton, flexibleSpaceButton, self.backButton, fixedSpaceButton, self.forwardButton]]; 488 | 489 | self.view.backgroundColor = [UIColor grayColor]; 490 | [self.view addSubview:self.toolbar]; 491 | [self.view addSubview:self.addressLabel]; 492 | [self.view addSubview:self.spinner]; 493 | } 494 | 495 | - (void)setCloseButtonTitle:(NSString*)title 496 | { 497 | // the advantage of using UIBarButtonSystemItemDone is the system will localize it for you automatically 498 | // but, if you want to set this yourself, knock yourself out (we can't set the title for a system Done button, so we have to create a new one) 499 | self.closeButton = nil; 500 | self.closeButton = [[UIBarButtonItem alloc] initWithTitle:title style:UIBarButtonItemStyleBordered target:self action:@selector(close)]; 501 | self.closeButton.enabled = YES; 502 | self.closeButton.tintColor = [UIColor colorWithRed:60.0 / 255.0 green:136.0 / 255.0 blue:230.0 / 255.0 alpha:1]; 503 | 504 | NSMutableArray* items = [self.toolbar.items mutableCopy]; 505 | [items replaceObjectAtIndex:0 withObject:self.closeButton]; 506 | [self.toolbar setItems:items]; 507 | } 508 | 509 | - (void)showLocationBar:(BOOL)show 510 | { 511 | CGRect locationbarFrame = self.addressLabel.frame; 512 | 513 | BOOL toolbarVisible = !self.toolbar.hidden; 514 | 515 | // prevent double show/hide 516 | if (show == !(self.addressLabel.hidden)) { 517 | return; 518 | } 519 | 520 | if (show) { 521 | self.addressLabel.hidden = NO; 522 | 523 | if (toolbarVisible) { 524 | // toolBar at the bottom, leave as is 525 | // put locationBar on top of the toolBar 526 | 527 | CGRect webViewBounds = self.view.bounds; 528 | webViewBounds.size.height -= FOOTER_HEIGHT; 529 | self.webView.frame = webViewBounds; 530 | 531 | locationbarFrame.origin.y = webViewBounds.size.height; 532 | self.addressLabel.frame = locationbarFrame; 533 | } else { 534 | // no toolBar, so put locationBar at the bottom 535 | 536 | CGRect webViewBounds = self.view.bounds; 537 | webViewBounds.size.height -= LOCATIONBAR_HEIGHT; 538 | self.webView.frame = webViewBounds; 539 | 540 | locationbarFrame.origin.y = webViewBounds.size.height; 541 | self.addressLabel.frame = locationbarFrame; 542 | } 543 | } else { 544 | self.addressLabel.hidden = YES; 545 | 546 | if (toolbarVisible) { 547 | // locationBar is on top of toolBar, hide locationBar 548 | 549 | // webView take up whole height less toolBar height 550 | CGRect webViewBounds = self.view.bounds; 551 | webViewBounds.size.height -= TOOLBAR_HEIGHT; 552 | self.webView.frame = webViewBounds; 553 | } else { 554 | // no toolBar, expand webView to screen dimensions 555 | 556 | CGRect webViewBounds = self.view.bounds; 557 | self.webView.frame = webViewBounds; 558 | } 559 | } 560 | } 561 | 562 | - (void)showToolBar:(BOOL)show 563 | { 564 | CGRect toolbarFrame = self.toolbar.frame; 565 | CGRect locationbarFrame = self.addressLabel.frame; 566 | 567 | BOOL locationbarVisible = !self.addressLabel.hidden; 568 | 569 | // prevent double show/hide 570 | if (show == !(self.toolbar.hidden)) { 571 | return; 572 | } 573 | 574 | if (show) { 575 | self.toolbar.hidden = NO; 576 | 577 | if (locationbarVisible) { 578 | // locationBar at the bottom, move locationBar up 579 | // put toolBar at the bottom 580 | 581 | CGRect webViewBounds = self.view.bounds; 582 | webViewBounds.size.height -= FOOTER_HEIGHT; 583 | self.webView.frame = webViewBounds; 584 | 585 | locationbarFrame.origin.y = webViewBounds.size.height; 586 | self.addressLabel.frame = locationbarFrame; 587 | 588 | toolbarFrame.origin.y = (webViewBounds.size.height + LOCATIONBAR_HEIGHT); 589 | self.toolbar.frame = toolbarFrame; 590 | } else { 591 | // no locationBar, so put toolBar at the bottom 592 | 593 | CGRect webViewBounds = self.view.bounds; 594 | webViewBounds.size.height -= TOOLBAR_HEIGHT; 595 | self.webView.frame = webViewBounds; 596 | 597 | toolbarFrame.origin.y = webViewBounds.size.height; 598 | self.toolbar.frame = toolbarFrame; 599 | } 600 | } else { 601 | self.toolbar.hidden = YES; 602 | 603 | if (locationbarVisible) { 604 | // locationBar is on top of toolBar, hide toolBar 605 | // put locationBar at the bottom 606 | 607 | // webView take up whole height less locationBar height 608 | CGRect webViewBounds = self.view.bounds; 609 | webViewBounds.size.height -= LOCATIONBAR_HEIGHT; 610 | self.webView.frame = webViewBounds; 611 | 612 | // move locationBar down 613 | locationbarFrame.origin.y = webViewBounds.size.height; 614 | self.addressLabel.frame = locationbarFrame; 615 | } else { 616 | // no locationBar, expand webView to screen dimensions 617 | 618 | CGRect webViewBounds = self.view.bounds; 619 | self.webView.frame = webViewBounds; 620 | } 621 | } 622 | } 623 | 624 | - (void)viewDidLoad 625 | { 626 | [super viewDidLoad]; 627 | } 628 | 629 | - (void)viewDidUnload 630 | { 631 | [self.webView loadHTMLString:nil baseURL:nil]; 632 | [CDVUserAgentUtil releaseLock:&_userAgentLockToken]; 633 | [super viewDidUnload]; 634 | } 635 | 636 | - (void)close 637 | { 638 | [CDVUserAgentUtil releaseLock:&_userAgentLockToken]; 639 | 640 | if ([self respondsToSelector:@selector(presentingViewController)]) { 641 | [[self presentingViewController] dismissViewControllerAnimated:YES completion:nil]; 642 | } else { 643 | [[self parentViewController] dismissModalViewControllerAnimated:YES]; 644 | } 645 | 646 | self.currentURL = nil; 647 | 648 | if ((self.navigationDelegate != nil) && [self.navigationDelegate respondsToSelector:@selector(browserExit)]) { 649 | [self.navigationDelegate browserExit]; 650 | } 651 | } 652 | 653 | - (void)navigateTo:(NSURL*)url 654 | { 655 | NSURLRequest* request = [NSURLRequest requestWithURL:url]; 656 | 657 | if (_userAgentLockToken != 0) { 658 | [self.webView loadRequest:request]; 659 | } else { 660 | [CDVUserAgentUtil acquireLock:^(NSInteger lockToken) { 661 | _userAgentLockToken = lockToken; 662 | [CDVUserAgentUtil setUserAgent:_userAgent lockToken:lockToken]; 663 | [self.webView loadRequest:request]; 664 | }]; 665 | } 666 | } 667 | 668 | - (void)goBack:(id)sender 669 | { 670 | [self.webView goBack]; 671 | } 672 | 673 | - (void)goForward:(id)sender 674 | { 675 | [self.webView goForward]; 676 | } 677 | 678 | #pragma mark UIWebViewDelegate 679 | 680 | - (void)webViewDidStartLoad:(UIWebView*)theWebView 681 | { 682 | // loading url, start spinner, update back/forward 683 | 684 | self.addressLabel.text = @"Loading..."; 685 | self.backButton.enabled = theWebView.canGoBack; 686 | self.forwardButton.enabled = theWebView.canGoForward; 687 | 688 | [self.spinner startAnimating]; 689 | 690 | return [self.navigationDelegate webViewDidStartLoad:theWebView]; 691 | } 692 | 693 | - (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType 694 | { 695 | BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]]; 696 | 697 | if (isTopLevelNavigation) { 698 | self.currentURL = request.URL; 699 | } 700 | return [self.navigationDelegate webView:theWebView shouldStartLoadWithRequest:request navigationType:navigationType]; 701 | } 702 | 703 | - (void)webViewDidFinishLoad:(UIWebView*)theWebView 704 | { 705 | // update url, stop spinner, update back/forward 706 | 707 | self.addressLabel.text = [self.currentURL absoluteString]; 708 | self.backButton.enabled = theWebView.canGoBack; 709 | self.forwardButton.enabled = theWebView.canGoForward; 710 | 711 | [self.spinner stopAnimating]; 712 | 713 | // Work around a bug where the first time a PDF is opened, all UIWebViews 714 | // reload their User-Agent from NSUserDefaults. 715 | // This work-around makes the following assumptions: 716 | // 1. The app has only a single Cordova Webview. If not, then the app should 717 | // take it upon themselves to load a PDF in the background as a part of 718 | // their start-up flow. 719 | // 2. That the PDF does not require any additional network requests. We change 720 | // the user-agent here back to that of the CDVViewController, so requests 721 | // from it must pass through its white-list. This *does* break PDFs that 722 | // contain links to other remote PDF/websites. 723 | // More info at https://issues.apache.org/jira/browse/CB-2225 724 | BOOL isPDF = [@"true" isEqualToString :[theWebView stringByEvaluatingJavaScriptFromString:@"document.body==null"]]; 725 | if (isPDF) { 726 | [CDVUserAgentUtil setUserAgent:_prevUserAgent lockToken:_userAgentLockToken]; 727 | } 728 | 729 | [self.navigationDelegate webViewDidFinishLoad:theWebView]; 730 | } 731 | 732 | - (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error 733 | { 734 | // log fail message, stop spinner, update back/forward 735 | NSLog(@"webView:didFailLoadWithError - %@", [error localizedDescription]); 736 | 737 | self.backButton.enabled = theWebView.canGoBack; 738 | self.forwardButton.enabled = theWebView.canGoForward; 739 | [self.spinner stopAnimating]; 740 | 741 | self.addressLabel.text = @"Load Error"; 742 | 743 | [self.navigationDelegate webView:theWebView didFailLoadWithError:error]; 744 | } 745 | 746 | #pragma mark CDVScreenOrientationDelegate 747 | 748 | - (BOOL)shouldAutorotate 749 | { 750 | if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(shouldAutorotate)]) { 751 | return [self.orientationDelegate shouldAutorotate]; 752 | } 753 | return YES; 754 | } 755 | 756 | - (NSUInteger)supportedInterfaceOrientations 757 | { 758 | if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(supportedInterfaceOrientations)]) { 759 | return [self.orientationDelegate supportedInterfaceOrientations]; 760 | } 761 | 762 | return 1 << UIInterfaceOrientationPortrait; 763 | } 764 | 765 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 766 | { 767 | if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(shouldAutorotateToInterfaceOrientation:)]) { 768 | return [self.orientationDelegate shouldAutorotateToInterfaceOrientation:interfaceOrientation]; 769 | } 770 | 771 | return YES; 772 | } 773 | 774 | @end 775 | 776 | @implementation CDVInAppBrowserOptions 777 | 778 | - (id)init 779 | { 780 | if (self = [super init]) { 781 | // default values 782 | self.location = YES; 783 | self.toolbar = YES; 784 | self.closebuttoncaption = nil; 785 | 786 | self.enableviewportscale = NO; 787 | self.mediaplaybackrequiresuseraction = NO; 788 | self.allowinlinemediaplayback = NO; 789 | self.keyboarddisplayrequiresuseraction = YES; 790 | self.suppressesincrementalrendering = NO; 791 | self.hidden = NO; 792 | } 793 | 794 | return self; 795 | } 796 | 797 | + (CDVInAppBrowserOptions*)parseOptions:(NSString*)options 798 | { 799 | CDVInAppBrowserOptions* obj = [[CDVInAppBrowserOptions alloc] init]; 800 | 801 | // NOTE: this parsing does not handle quotes within values 802 | NSArray* pairs = [options componentsSeparatedByString:@","]; 803 | 804 | // parse keys and values, set the properties 805 | for (NSString* pair in pairs) { 806 | NSArray* keyvalue = [pair componentsSeparatedByString:@"="]; 807 | 808 | if ([keyvalue count] == 2) { 809 | NSString* key = [[keyvalue objectAtIndex:0] lowercaseString]; 810 | NSString* value = [keyvalue objectAtIndex:1]; 811 | NSString* value_lc = [value lowercaseString]; 812 | 813 | BOOL isBoolean = [value_lc isEqualToString:@"yes"] || [value_lc isEqualToString:@"no"]; 814 | NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init]; 815 | [numberFormatter setAllowsFloats:YES]; 816 | BOOL isNumber = [numberFormatter numberFromString:value_lc] != nil; 817 | 818 | // set the property according to the key name 819 | if ([obj respondsToSelector:NSSelectorFromString(key)]) { 820 | if (isNumber) { 821 | [obj setValue:[numberFormatter numberFromString:value_lc] forKey:key]; 822 | } else if (isBoolean) { 823 | [obj setValue:[NSNumber numberWithBool:[value_lc isEqualToString:@"yes"]] forKey:key]; 824 | } else { 825 | [obj setValue:value forKey:key]; 826 | } 827 | } 828 | } 829 | } 830 | 831 | return obj; 832 | } 833 | 834 | @end 835 | -------------------------------------------------------------------------------- /plugins/org.apache.cordova.core.inappbrowser/src/wp/InAppBrowser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | using System.Windows.Documents; 6 | using System.Windows.Ink; 7 | using System.Windows.Input; 8 | using System.Windows.Media; 9 | using System.Windows.Media.Animation; 10 | using System.Windows.Shapes; 11 | using Microsoft.Phone.Controls; 12 | using System.Diagnostics; 13 | using System.Runtime.Serialization; 14 | using WPCordovaClassLib.Cordova; 15 | using WPCordovaClassLib.Cordova.Commands; 16 | using WPCordovaClassLib.Cordova.JSON; 17 | using Microsoft.Phone.Shell; 18 | using Microsoft.Phone.Tasks; 19 | 20 | namespace WPCordovaClassLib.Cordova.Commands 21 | { 22 | [DataContract] 23 | public class BrowserOptions 24 | { 25 | [DataMember] 26 | public string url; 27 | 28 | [DataMember] 29 | public bool isGeolocationEnabled; 30 | } 31 | 32 | public class InAppBrowser : BaseCommand 33 | { 34 | 35 | private static WebBrowser browser; 36 | private static ApplicationBarIconButton backButton; 37 | private static ApplicationBarIconButton fwdButton; 38 | 39 | public void open(string options) 40 | { 41 | string[] args = JSON.JsonHelper.Deserialize(options); 42 | //BrowserOptions opts = JSON.JsonHelper.Deserialize(options); 43 | string urlLoc = args[0]; 44 | string target = args[1]; 45 | /* 46 | _self - opens in the Cordova WebView if url is in the white-list, else it opens in the InAppBrowser 47 | _blank - always open in the InAppBrowser 48 | _system - always open in the system web browser 49 | */ 50 | switch (target) 51 | { 52 | case "_blank": 53 | ShowInAppBrowser(urlLoc); 54 | break; 55 | case "_self": 56 | ShowCordovaBrowser(urlLoc); 57 | break; 58 | case "_system": 59 | ShowSystemBrowser(urlLoc); 60 | break; 61 | } 62 | 63 | 64 | } 65 | 66 | private void ShowCordovaBrowser(string url) 67 | { 68 | Uri loc = new Uri(url, UriKind.RelativeOrAbsolute); 69 | Deployment.Current.Dispatcher.BeginInvoke(() => 70 | { 71 | PhoneApplicationFrame frame = Application.Current.RootVisual as PhoneApplicationFrame; 72 | if (frame != null) 73 | { 74 | PhoneApplicationPage page = frame.Content as PhoneApplicationPage; 75 | if (page != null) 76 | { 77 | CordovaView cView = page.FindName("CordovaView") as CordovaView; 78 | if (cView != null) 79 | { 80 | WebBrowser br = cView.Browser; 81 | br.Navigate(loc); 82 | } 83 | } 84 | 85 | } 86 | }); 87 | } 88 | 89 | private void ShowSystemBrowser(string url) 90 | { 91 | WebBrowserTask webBrowserTask = new WebBrowserTask(); 92 | webBrowserTask.Uri = new Uri(url, UriKind.Absolute); 93 | webBrowserTask.Show(); 94 | } 95 | 96 | 97 | private void ShowInAppBrowser(string url) 98 | { 99 | Uri loc = new Uri(url); 100 | 101 | Deployment.Current.Dispatcher.BeginInvoke(() => 102 | { 103 | if (browser != null) 104 | { 105 | //browser.IsGeolocationEnabled = opts.isGeolocationEnabled; 106 | browser.Navigate(loc); 107 | } 108 | else 109 | { 110 | PhoneApplicationFrame frame = Application.Current.RootVisual as PhoneApplicationFrame; 111 | if (frame != null) 112 | { 113 | PhoneApplicationPage page = frame.Content as PhoneApplicationPage; 114 | 115 | string baseImageUrl = "Images/"; 116 | 117 | if (page != null) 118 | { 119 | Grid grid = page.FindName("LayoutRoot") as Grid; 120 | if (grid != null) 121 | { 122 | browser = new WebBrowser(); 123 | browser.IsScriptEnabled = true; 124 | browser.LoadCompleted += new System.Windows.Navigation.LoadCompletedEventHandler(browser_LoadCompleted); 125 | 126 | browser.Navigating += new EventHandler(browser_Navigating); 127 | browser.NavigationFailed += new System.Windows.Navigation.NavigationFailedEventHandler(browser_NavigationFailed); 128 | browser.Navigated += new EventHandler(browser_Navigated); 129 | browser.Navigate(loc); 130 | //browser.IsGeolocationEnabled = opts.isGeolocationEnabled; 131 | grid.Children.Add(browser); 132 | } 133 | 134 | ApplicationBar bar = new ApplicationBar(); 135 | bar.BackgroundColor = Colors.Gray; 136 | bar.IsMenuEnabled = false; 137 | 138 | backButton = new ApplicationBarIconButton(); 139 | backButton.Text = "Back"; 140 | 141 | backButton.IconUri = new Uri(baseImageUrl + "appbar.back.rest.png", UriKind.Relative); 142 | backButton.Click += new EventHandler(backButton_Click); 143 | bar.Buttons.Add(backButton); 144 | 145 | 146 | fwdButton = new ApplicationBarIconButton(); 147 | fwdButton.Text = "Forward"; 148 | fwdButton.IconUri = new Uri(baseImageUrl + "appbar.next.rest.png", UriKind.Relative); 149 | fwdButton.Click += new EventHandler(fwdButton_Click); 150 | bar.Buttons.Add(fwdButton); 151 | 152 | ApplicationBarIconButton closeBtn = new ApplicationBarIconButton(); 153 | closeBtn.Text = "Close"; 154 | closeBtn.IconUri = new Uri(baseImageUrl + "appbar.close.rest.png", UriKind.Relative); 155 | closeBtn.Click += new EventHandler(closeBtn_Click); 156 | bar.Buttons.Add(closeBtn); 157 | 158 | page.ApplicationBar = bar; 159 | } 160 | 161 | } 162 | } 163 | }); 164 | } 165 | 166 | void browser_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e) 167 | { 168 | 169 | } 170 | 171 | void fwdButton_Click(object sender, EventArgs e) 172 | { 173 | if (browser != null) 174 | { 175 | try 176 | { 177 | #if WP8 178 | browser.GoForward(); 179 | #else 180 | browser.InvokeScript("execScript", "history.forward();"); 181 | #endif 182 | } 183 | catch (Exception) 184 | { 185 | 186 | } 187 | } 188 | } 189 | 190 | void backButton_Click(object sender, EventArgs e) 191 | { 192 | if (browser != null) 193 | { 194 | try 195 | { 196 | #if WP8 197 | browser.GoBack(); 198 | #else 199 | browser.InvokeScript("execScript", "history.back();"); 200 | #endif 201 | } 202 | catch (Exception) 203 | { 204 | 205 | } 206 | } 207 | } 208 | 209 | void closeBtn_Click(object sender, EventArgs e) 210 | { 211 | this.close(); 212 | } 213 | 214 | 215 | public void close(string options = "") 216 | { 217 | if (browser != null) 218 | { 219 | Deployment.Current.Dispatcher.BeginInvoke(() => 220 | { 221 | PhoneApplicationFrame frame = Application.Current.RootVisual as PhoneApplicationFrame; 222 | if (frame != null) 223 | { 224 | PhoneApplicationPage page = frame.Content as PhoneApplicationPage; 225 | if (page != null) 226 | { 227 | Grid grid = page.FindName("LayoutRoot") as Grid; 228 | if (grid != null) 229 | { 230 | grid.Children.Remove(browser); 231 | } 232 | page.ApplicationBar = null; 233 | } 234 | } 235 | browser = null; 236 | string message = "{\"type\":\"exit\"}"; 237 | PluginResult result = new PluginResult(PluginResult.Status.OK, message); 238 | result.KeepCallback = false; 239 | this.DispatchCommandResult(result); 240 | }); 241 | } 242 | } 243 | 244 | void browser_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e) 245 | { 246 | #if WP8 247 | if (browser != null) 248 | { 249 | backButton.IsEnabled = browser.CanGoBack; 250 | fwdButton.IsEnabled = browser.CanGoForward; 251 | 252 | } 253 | #endif 254 | string message = "{\"type\":\"loadstop\", \"url\":\"" + e.Uri.AbsoluteUri + "\"}"; 255 | PluginResult result = new PluginResult(PluginResult.Status.OK, message); 256 | result.KeepCallback = true; 257 | this.DispatchCommandResult(result); 258 | } 259 | 260 | void browser_NavigationFailed(object sender, System.Windows.Navigation.NavigationFailedEventArgs e) 261 | { 262 | string message = "{\"type\":\"error\",\"url\":\"" + e.Uri.AbsoluteUri + "\"}"; 263 | PluginResult result = new PluginResult(PluginResult.Status.ERROR, message); 264 | result.KeepCallback = true; 265 | this.DispatchCommandResult(result); 266 | } 267 | 268 | void browser_Navigating(object sender, NavigatingEventArgs e) 269 | { 270 | string message = "{\"type\":\"loadstart\",\"url\":\"" + e.Uri.AbsoluteUri + "\"}"; 271 | PluginResult result = new PluginResult(PluginResult.Status.OK, message); 272 | result.KeepCallback = true; 273 | this.DispatchCommandResult(result); 274 | } 275 | 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /plugins/org.apache.cordova.core.inappbrowser/test/cordova-incl.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one 4 | * or more contributor license agreements. See the NOTICE file 5 | * distributed with this work for additional information 6 | * regarding copyright ownership. The ASF licenses this file 7 | * to you under the Apache License, Version 2.0 (the 8 | * "License"); you may not use this file except in compliance 9 | * with the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, 14 | * software distributed under the License is distributed on an 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | * KIND, either express or implied. See the License for the 17 | * specific language governing permissions and limitations 18 | * under the License. 19 | * 20 | */ 21 | 22 | var PLAT; 23 | if (/Android/.exec(navigator.userAgent)) { 24 | PLAT = 'android'; 25 | } else if (/(iPad)|(iPhone)|(iPod)/.exec(navigator.userAgent)) { 26 | PLAT = 'ios'; 27 | } else if (/(BB10)|(PlayBook)|(BlackBerry)/.exec(navigator.userAgent)) { 28 | PLAT = 'blackberry'; 29 | } 30 | 31 | var scripts = document.getElementsByTagName('script'); 32 | var currentPath = scripts[scripts.length - 1].src; 33 | var platformCordovaPath = currentPath.replace("cordova-incl.js", "cordova." + PLAT + ".js"); 34 | var normalCordovaPath = currentPath.replace("cordova-incl.js", "cordova.js"); 35 | var cordovaPath = normalCordovaPath; 36 | 37 | if (PLAT) { 38 | // XHR to local file is an error on some platforms, windowsphone for one 39 | try { 40 | var xhr = new XMLHttpRequest(); 41 | xhr.open("GET", platformCordovaPath, false); 42 | xhr.onreadystatechange = function() { 43 | 44 | if (this.readyState == this.DONE && this.responseText.length > 0) { 45 | if(parseInt(this.status) >= 400){ 46 | cordovaPath = normalCordovaPath; 47 | }else{ 48 | cordovaPath = platformCordovaPath; 49 | } 50 | } 51 | }; 52 | xhr.send(null); 53 | } 54 | catch(e){ 55 | cordovaPath = normalCordovaPath; 56 | } // access denied! 57 | } 58 | 59 | if (!window._doNotWriteCordovaScript) { 60 | document.write(''); 61 | } 62 | 63 | function backHome() { 64 | if (window.device && device.platform && device.platform.toLowerCase() == 'android') { 65 | navigator.app.backHistory(); 66 | } 67 | else { 68 | window.history.go(-1); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /plugins/org.apache.cordova.core.inappbrowser/test/inappbrowser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Cordova Mobile Spec 29 | 30 | 31 | 32 | 33 | 200 | 201 | 202 | 203 |

InAppBrowser

204 |
205 | Make sure http://www.google.com is white listed.
206 | Make sure http://www.apple.com is not in the white list.
In iOS, starred * tests will leave the app with no way to return.
207 |

User-Agent:

208 |
209 |
Back
210 |

Local URL

211 |
Default: CordovaWebView
212 |
Target=Self: CordovaWebView
213 |
Target=System: Error
214 |
Target=Blank: InAppBrowser
215 |
Target=Random: InAppBrowser
216 |
Target=Random, no location bar: InAppBrowser
217 |

White Listed URL

218 |
Default: CordovaWebView*
219 |
Target=Self: CordovaWebView*
220 |
Target=System: System Browser
221 |
Target=Blank: InAppBrowser
222 |
Target=Random: InAppBrowser
223 |
Target=Random, no location bar: InAppBrowser
224 |

Non White Listed URL

225 |
Default: InAppBrowser
226 |
Target=Self: InAppBrowser
227 |
Target=System: System
228 |
Target=Blank: InAppBrowser
229 |
Target=Random: InAppBrowser
230 |
Target=Random, no location bar: InAppBrowser
231 |

Page with redirect

232 |
http://google.com (should 301)
233 |
http://www.zhihu.com/answer/16714076 (should 302)
234 |

PDF URL

235 |
Remote URL
236 |
Local URL
237 |

INVALID URL

238 |
Invalid Scheme
239 |
Invalid Host
240 |
Missing File
241 |

CSS / JS Injection

242 |
Original Document
243 |
CSS File Injection
244 |
CSS File Injection (CB)
245 |
CSS Literal Injection
246 |
CSS Literal Injection (CB)
247 |
Script File Injection
248 |
Script File Injection (CB)
249 |
Script Literal Injection
250 |
Script Literal Injection (CB)
251 |

Open Hidden

252 |
google.com hidden
253 |
show hidden
254 |
close hidden
255 |
google.com not hidden
256 |

Back
257 | 258 | 259 | -------------------------------------------------------------------------------- /plugins/org.apache.cordova.core.inappbrowser/test/inappbrowser/inject.css: -------------------------------------------------------------------------------- 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 | #style-update-file { 20 | display: block !important; 21 | } 22 | -------------------------------------------------------------------------------- /plugins/org.apache.cordova.core.inappbrowser/test/inappbrowser/inject.html: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Cordova Mobile Spec 29 | 30 | 31 | 32 |

InAppBrowser - Script / Style Injection Test

33 | 34 | 35 | 36 | 43 | 44 | -------------------------------------------------------------------------------- /plugins/org.apache.cordova.core.inappbrowser/test/inappbrowser/inject.js: -------------------------------------------------------------------------------- 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 | var d = document.getElementById("header") 20 | d.innerHTML = "Script file successfully injected"; 21 | -------------------------------------------------------------------------------- /plugins/org.apache.cordova.core.inappbrowser/test/inappbrowser/local.html: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Cordova Mobile Spec 29 | 30 | 31 | 32 | 33 |

InAppBrowser - Local URL

34 |
35 | You have successfully loaded a local URL 36 |
37 |
User-Agent =
38 | 39 | 40 | 41 | 42 |

Back
43 | 44 | 51 | 52 | -------------------------------------------------------------------------------- /plugins/org.apache.cordova.core.inappbrowser/test/inappbrowser/local.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/plugins/org.apache.cordova.core.inappbrowser/test/inappbrowser/local.pdf -------------------------------------------------------------------------------- /plugins/org.apache.cordova.core.inappbrowser/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Cordova Mobile Spec 29 | 30 | 31 | 32 | 33 | 34 | 35 |

Apache Cordova Tests

36 |
37 |

Platform:

38 |

Version:

39 |

UUID:

40 |

Name:

41 |

Model:

42 |

Width: , Height: 43 | , Color Depth:

44 |

User-Agent:

45 |
46 | Automatic Test 47 | Accelerometer 48 | Audio Play/Record 49 | Battery 50 | Camera 51 | Compass 52 | Contacts 53 | Events 54 | Location 55 | Lazy Loading of cordova-incl.js 56 | Misc Content 57 | Network 58 | Notification 59 | Splashscreen 60 | Web SQL 61 | Local Storage 62 | Benchmarks 63 | In App Browser 64 | 65 | 66 | -------------------------------------------------------------------------------- /plugins/org.apache.cordova.core.inappbrowser/test/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one 4 | * or more contributor license agreements. See the NOTICE file 5 | * distributed with this work for additional information 6 | * regarding copyright ownership. The ASF licenses this file 7 | * to you under the Apache License, Version 2.0 (the 8 | * "License"); you may not use this file except in compliance 9 | * with the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, 14 | * software distributed under the License is distributed on an 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | * KIND, either express or implied. See the License for the 17 | * specific language governing permissions and limitations 18 | * under the License. 19 | * 20 | */ 21 | 22 | var deviceInfo = function() { 23 | document.getElementById("platform").innerHTML = device.platform; 24 | document.getElementById("version").innerHTML = device.version; 25 | document.getElementById("uuid").innerHTML = device.uuid; 26 | document.getElementById("name").innerHTML = device.name; 27 | document.getElementById("model").innerHTML = device.model; 28 | document.getElementById("width").innerHTML = screen.width; 29 | document.getElementById("height").innerHTML = screen.height; 30 | document.getElementById("colorDepth").innerHTML = screen.colorDepth; 31 | }; 32 | 33 | var getLocation = function() { 34 | var suc = function(p) { 35 | alert(p.coords.latitude + " " + p.coords.longitude); 36 | }; 37 | var locFail = function() { 38 | }; 39 | navigator.geolocation.getCurrentPosition(suc, locFail); 40 | }; 41 | 42 | var beep = function() { 43 | navigator.notification.beep(2); 44 | }; 45 | 46 | var vibrate = function() { 47 | navigator.notification.vibrate(0); 48 | }; 49 | 50 | function roundNumber(num) { 51 | var dec = 3; 52 | var result = Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec); 53 | return result; 54 | } 55 | 56 | var accelerationWatch = null; 57 | 58 | function updateAcceleration(a) { 59 | document.getElementById('x').innerHTML = roundNumber(a.x); 60 | document.getElementById('y').innerHTML = roundNumber(a.y); 61 | document.getElementById('z').innerHTML = roundNumber(a.z); 62 | } 63 | 64 | var toggleAccel = function() { 65 | if (accelerationWatch !== null) { 66 | navigator.accelerometer.clearWatch(accelerationWatch); 67 | updateAcceleration({ 68 | x : "", 69 | y : "", 70 | z : "" 71 | }); 72 | accelerationWatch = null; 73 | } else { 74 | var options = {}; 75 | options.frequency = 1000; 76 | accelerationWatch = navigator.accelerometer.watchAcceleration( 77 | updateAcceleration, function(ex) { 78 | alert("accel fail (" + ex.name + ": " + ex.message + ")"); 79 | }, options); 80 | } 81 | }; 82 | 83 | var preventBehavior = function(e) { 84 | e.preventDefault(); 85 | }; 86 | 87 | function dump_pic(data) { 88 | var viewport = document.getElementById('viewport'); 89 | console.log(data); 90 | viewport.style.display = ""; 91 | viewport.style.position = "absolute"; 92 | viewport.style.top = "10px"; 93 | viewport.style.left = "10px"; 94 | document.getElementById("test_img").src = "data:image/jpeg;base64," + data; 95 | } 96 | 97 | function fail(msg) { 98 | alert(msg); 99 | } 100 | 101 | function show_pic() { 102 | navigator.camera.getPicture(dump_pic, fail, { 103 | quality : 50 104 | }); 105 | } 106 | 107 | function close() { 108 | var viewport = document.getElementById('viewport'); 109 | viewport.style.position = "relative"; 110 | viewport.style.display = "none"; 111 | } 112 | 113 | // This is just to do this. 114 | function readFile() { 115 | navigator.file.read('/sdcard/cordova.txt', fail, fail); 116 | } 117 | 118 | function writeFile() { 119 | navigator.file.write('foo.txt', "This is a test of writing to a file", 120 | fail, fail); 121 | } 122 | 123 | function contacts_success(contacts) { 124 | alert(contacts.length 125 | + ' contacts returned.' 126 | + (contacts[2] && contacts[2].name ? (' Third contact is ' + contacts[2].name.formatted) 127 | : '')); 128 | } 129 | 130 | function get_contacts() { 131 | var obj = new ContactFindOptions(); 132 | obj.filter = ""; 133 | obj.multiple = true; 134 | obj.limit = 5; 135 | navigator.service.contacts.find( 136 | [ "displayName", "name" ], contacts_success, 137 | fail, obj); 138 | } 139 | 140 | var networkReachableCallback = function(reachability) { 141 | // There is no consistency on the format of reachability 142 | var networkState = reachability.code || reachability; 143 | 144 | var currentState = {}; 145 | currentState[NetworkStatus.NOT_REACHABLE] = 'No network connection'; 146 | currentState[NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK] = 'Carrier data connection'; 147 | currentState[NetworkStatus.REACHABLE_VIA_WIFI_NETWORK] = 'WiFi connection'; 148 | 149 | confirm("Connection type:\n" + currentState[networkState]); 150 | }; 151 | 152 | function check_network() { 153 | navigator.network.isReachable("www.mobiledevelopersolutions.com", 154 | networkReachableCallback, {}); 155 | } 156 | 157 | function init() { 158 | // the next line makes it impossible to see Contacts on the HTC Evo since it 159 | // doesn't have a scroll button 160 | // document.addEventListener("touchmove", preventBehavior, false); 161 | document.addEventListener("deviceready", deviceInfo, true); 162 | document.getElementById("user-agent").textContent = navigator.userAgent; 163 | } 164 | -------------------------------------------------------------------------------- /plugins/org.apache.cordova.core.inappbrowser/test/master.css: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one 4 | * or more contributor license agreements. See the NOTICE file 5 | * distributed with this work for additional information 6 | * regarding copyright ownership. The ASF licenses this file 7 | * to you under the Apache License, Version 2.0 (the 8 | * "License"); you may not use this file except in compliance 9 | * with the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, 14 | * software distributed under the License is distributed on an 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | * KIND, either express or implied. See the License for the 17 | * specific language governing permissions and limitations 18 | * under the License. 19 | * 20 | */ 21 | 22 | body { 23 | background:#222 none repeat scroll 0 0; 24 | color:#666; 25 | font-family:Helvetica; 26 | font-size:72%; 27 | line-height:1.5em; 28 | margin:0; 29 | border-top:1px solid #393939; 30 | } 31 | 32 | #info{ 33 | background:#ffa; 34 | border: 1px solid #ffd324; 35 | -webkit-border-radius: 5px; 36 | border-radius: 5px; 37 | clear:both; 38 | margin:15px 6px 0; 39 | min-width:295px; 40 | max-width:97%; 41 | padding:4px 0px 2px 10px; 42 | word-wrap:break-word; 43 | margin-bottom:10px; 44 | display:inline-block; 45 | min-height: 160px; 46 | max-height: 300px; 47 | overflow: auto; 48 | -webkit-overflow-scrolling: touch; 49 | } 50 | 51 | #info > h4{ 52 | font-size:.95em; 53 | margin:5px 0; 54 | } 55 | 56 | #stage.theme{ 57 | padding-top:3px; 58 | } 59 | 60 | /* Definition List */ 61 | #stage.theme > dl{ 62 | padding-top:10px; 63 | clear:both; 64 | margin:0; 65 | list-style-type:none; 66 | padding-left:10px; 67 | overflow:auto; 68 | } 69 | 70 | #stage.theme > dl > dt{ 71 | font-weight:bold; 72 | float:left; 73 | margin-left:5px; 74 | } 75 | 76 | #stage.theme > dl > dd{ 77 | width:45px; 78 | float:left; 79 | color:#a87; 80 | font-weight:bold; 81 | } 82 | 83 | /* Content Styling */ 84 | #stage.theme > h1, #stage.theme > h2, #stage.theme > p{ 85 | margin:1em 0 .5em 13px; 86 | } 87 | 88 | #stage.theme > h1{ 89 | color:#eee; 90 | font-size:1.6em; 91 | text-align:center; 92 | margin:0; 93 | margin-top:15px; 94 | padding:0; 95 | } 96 | 97 | #stage.theme > h2{ 98 | clear:both; 99 | margin:0; 100 | padding:3px; 101 | font-size:1em; 102 | text-align:center; 103 | } 104 | 105 | /* Stage Buttons */ 106 | #stage.theme .btn{ 107 | border: 1px solid #555; 108 | -webkit-border-radius: 5px; 109 | border-radius: 5px; 110 | text-align:center; 111 | display:inline-block; 112 | background:#444; 113 | width:150px; 114 | color:#9ab; 115 | font-size:1.1em; 116 | text-decoration:none; 117 | padding:1.2em 0; 118 | margin:3px 0px 3px 5px; 119 | } 120 | 121 | #stage.theme .large{ 122 | width:308px; 123 | padding:1.2em 0; 124 | } 125 | 126 | #stage.theme .wide{ 127 | width:100%; 128 | padding:1.2em 0; 129 | } 130 | 131 | #stage.theme .backBtn{ 132 | border: 1px solid #555; 133 | -webkit-border-radius: 5px; 134 | border-radius: 5px; 135 | text-align:center; 136 | display:block; 137 | float:right; 138 | background:#666; 139 | width:75px; 140 | color:#9ab; 141 | font-size:1.1em; 142 | text-decoration:none; 143 | padding:1.2em 0; 144 | margin:3px 5px 3px 5px; 145 | } 146 | 147 | #stage.theme .input{ 148 | border: 1px solid #555; 149 | -webkit-border-radius: 5px; 150 | border-radius: 5px; 151 | text-align:center; 152 | display:block; 153 | float:light; 154 | background:#888; 155 | color:#9cd; 156 | font-size:1.1em; 157 | text-decoration:none; 158 | padding:1.2em 0; 159 | margin:3px 0px 3px 5px; 160 | } 161 | 162 | #stage.theme .numeric{ 163 | width:100%; 164 | } 165 | -------------------------------------------------------------------------------- /plugins/org.apache.cordova.core.inappbrowser/www/InAppBrowser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one 4 | * or more contributor license agreements. See the NOTICE file 5 | * distributed with this work for additional information 6 | * regarding copyright ownership. The ASF licenses this file 7 | * to you under the Apache License, Version 2.0 (the 8 | * "License"); you may not use this file except in compliance 9 | * with the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, 14 | * software distributed under the License is distributed on an 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | * KIND, either express or implied. See the License for the 17 | * specific language governing permissions and limitations 18 | * under the License. 19 | * 20 | */ 21 | 22 | var exec = require('cordova/exec'); 23 | var channel = require('cordova/channel'); 24 | var modulemapper = require('cordova/modulemapper'); 25 | 26 | function InAppBrowser() { 27 | this.channels = { 28 | 'loadstart': channel.create('loadstart'), 29 | 'loadstop' : channel.create('loadstop'), 30 | 'loaderror' : channel.create('loaderror'), 31 | 'exit' : channel.create('exit') 32 | }; 33 | } 34 | 35 | InAppBrowser.prototype = { 36 | _eventHandler: function (event) { 37 | if (event.type in this.channels) { 38 | this.channels[event.type].fire(event); 39 | } 40 | }, 41 | close: function (eventname) { 42 | exec(null, null, "InAppBrowser", "close", []); 43 | }, 44 | show: function (eventname) { 45 | exec(null, null, "InAppBrowser", "show", []); 46 | }, 47 | addEventListener: function (eventname,f) { 48 | if (eventname in this.channels) { 49 | this.channels[eventname].subscribe(f); 50 | } 51 | }, 52 | removeEventListener: function(eventname, f) { 53 | if (eventname in this.channels) { 54 | this.channels[eventname].unsubscribe(f); 55 | } 56 | }, 57 | 58 | executeScript: function(injectDetails, cb) { 59 | if (injectDetails.code) { 60 | exec(cb, null, "InAppBrowser", "injectScriptCode", [injectDetails.code, !!cb]); 61 | } else if (injectDetails.file) { 62 | exec(cb, null, "InAppBrowser", "injectScriptFile", [injectDetails.file, !!cb]); 63 | } else { 64 | throw new Error('executeScript requires exactly one of code or file to be specified'); 65 | } 66 | }, 67 | 68 | insertCSS: function(injectDetails, cb) { 69 | if (injectDetails.code) { 70 | exec(cb, null, "InAppBrowser", "injectStyleCode", [injectDetails.code, !!cb]); 71 | } else if (injectDetails.file) { 72 | exec(cb, null, "InAppBrowser", "injectStyleFile", [injectDetails.file, !!cb]); 73 | } else { 74 | throw new Error('insertCSS requires exactly one of code or file to be specified'); 75 | } 76 | } 77 | }; 78 | 79 | module.exports = function(strUrl, strWindowName, strWindowFeatures) { 80 | var iab = new InAppBrowser(); 81 | var cb = function(eventname) { 82 | iab._eventHandler(eventname); 83 | }; 84 | 85 | // Don't catch calls that write to existing frames (e.g. named iframes). 86 | if (window.frames && window.frames[strWindowName]) { 87 | var origOpenFunc = modulemapper.getOriginalSymbol(window, 'open'); 88 | return origOpenFunc.apply(window, arguments); 89 | } 90 | 91 | exec(cb, cb, "InAppBrowser", "open", [strUrl, strWindowName, strWindowFeatures]); 92 | return iab; 93 | }; 94 | 95 | -------------------------------------------------------------------------------- /www/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | GoogleOauthPhonegap 4 | 5 | A sample Apache Cordova application that responds to the deviceready event. 6 | 7 | 8 | Apache Cordova Team 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /www/css/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-tap-highlight-color: rgba(0,0,0,0); /* make transparent link selection, adjust last value opacity 0 to 1.0 */ 3 | } 4 | 5 | body { 6 | -webkit-touch-callout: none; /* prevent callout to copy image, etc when tap to hold */ 7 | -webkit-text-size-adjust: none; /* prevent webkit from resizing text to fit */ 8 | -webkit-user-select: none; /* prevent copy paste, to allow, change 'none' to 'text' */ 9 | background-color:#E4E4E4; 10 | background-image:linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%); 11 | background-image:-webkit-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%); 12 | background-image:-ms-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%); 13 | background-image:-webkit-gradient( 14 | linear, 15 | left top, 16 | left bottom, 17 | color-stop(0, #A7A7A7), 18 | color-stop(0.51, #E4E4E4) 19 | ); 20 | background-attachment:fixed; 21 | font-family:'HelveticaNeue-Light', 'HelveticaNeue', Helvetica, Arial, sans-serif; 22 | font-size:12px; 23 | height:100%; 24 | margin:0px; 25 | padding:0px; 26 | width:100%; 27 | } 28 | 29 | /* Portrait layout (default) */ 30 | .app { 31 | position:absolute; 32 | left:50%; 33 | top:50%; 34 | height:50px; 35 | width:225px; 36 | margin-top: -25px; 37 | margin-left: -112px; 38 | text-align:center; 39 | } 40 | 41 | .app > div { 42 | display: none; 43 | } 44 | 45 | #login a { 46 | border-radius: 5px; 47 | color: white; 48 | background: blue; 49 | font-size: larger; 50 | padding: 10px 20px; 51 | } 52 | 53 | #login a:active { 54 | background: darkblue; 55 | } 56 | 57 | #login p { 58 | word-wrap: break-word; 59 | } 60 | -------------------------------------------------------------------------------- /www/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/img/logo.png -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Google OAuth with PhoneGap 9 | 10 | 11 |
12 |
13 | Sign In With Google! 14 |

15 |
16 |
17 |

18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /www/js/index.js: -------------------------------------------------------------------------------- 1 | var googleapi = { 2 | setToken: function(data) { 3 | //Cache the token 4 | localStorage.access_token = data.access_token; 5 | //Cache the refresh token, if there is one 6 | localStorage.refresh_token = data.refresh_token || localStorage.refresh_token; 7 | //Figure out when the token will expire by using the current 8 | //time, plus the valid time (in seconds), minus a 1 minute buffer 9 | var expiresAt = new Date().getTime() + parseInt(data.expires_in, 10) * 1000 - 60000; 10 | localStorage.expires_at = expiresAt; 11 | }, 12 | authorize: function(options) { 13 | var deferred = $.Deferred(); 14 | 15 | //Build the OAuth consent page URL 16 | var authUrl = 'https://accounts.google.com/o/oauth2/auth?' + $.param({ 17 | client_id: options.client_id, 18 | redirect_uri: options.redirect_uri, 19 | response_type: 'code', 20 | scope: options.scope 21 | }); 22 | 23 | //Open the OAuth consent page in the InAppBrowser 24 | var authWindow = window.open(authUrl, '_blank', 'location=no,toolbar=no'); 25 | 26 | //The recommendation is to use the redirect_uri "urn:ietf:wg:oauth:2.0:oob" 27 | //which sets the authorization code in the browser's title. However, we can't 28 | //access the title of the InAppBrowser. 29 | // 30 | //Instead, we pass a bogus redirect_uri of "http://localhost", which means the 31 | //authorization code will get set in the url. We can access the url in the 32 | //loadstart and loadstop events. So if we bind the loadstart event, we can 33 | //find the authorization code and close the InAppBrowser after the user 34 | //has granted us access to their data. 35 | authWindow.addEventListener('loadstart', googleCallback); 36 | function googleCallback(e){ 37 | var url = (typeof e.url !== 'undefined' ? e.url : e.originalEvent.url); 38 | var code = /\?code=(.+)$/.exec(url); 39 | var error = /\?error=(.+)$/.exec(url); 40 | 41 | if (code || error) { 42 | //Always close the browser when match is found 43 | authWindow.close(); 44 | } 45 | 46 | if (code) { 47 | //Exchange the authorization code for an access token 48 | $.post('https://accounts.google.com/o/oauth2/token', { 49 | code: code[1], 50 | client_id: options.client_id, 51 | client_secret: options.client_secret, 52 | redirect_uri: options.redirect_uri, 53 | grant_type: 'authorization_code' 54 | }).done(function(data) { 55 | googleapi.setToken(data); 56 | deferred.resolve(data); 57 | }).fail(function(response) { 58 | deferred.reject(response.responseJSON); 59 | }); 60 | } else if (error) { 61 | //The user denied access to the app 62 | deferred.reject({ 63 | error: error[1] 64 | }); 65 | } 66 | } 67 | 68 | return deferred.promise(); 69 | }, 70 | getToken: function(options) { 71 | var deferred = $.Deferred(); 72 | 73 | if (new Date().getTime() < localStorage.expires_at) { 74 | deferred.resolve({ 75 | access_token: localStorage.access_token 76 | }); 77 | } else if (localStorage.refresh_token) { 78 | $.post('https://accounts.google.com/o/oauth2/token', { 79 | refresh_token: localStorage.refresh_token, 80 | client_id: options.client_id, 81 | client_secret: options.client_secret, 82 | grant_type: 'refresh_token' 83 | }).done(function(data) { 84 | googleapi.setToken(data); 85 | deferred.resolve(data); 86 | }).fail(function(response) { 87 | deferred.reject(response.responseJSON); 88 | }); 89 | } else { 90 | deferred.reject(); 91 | } 92 | 93 | return deferred.promise(); 94 | }, 95 | userInfo: function(options) { 96 | return $.getJSON('https://www.googleapis.com/oauth2/v1/userinfo', options); 97 | } 98 | }; 99 | 100 | var app = { 101 | client_id: '14325430122.apps.googleusercontent.com', 102 | client_secret: 'kjG4087WLUuvC4AIrQEV0lAe', 103 | redirect_uri: 'http://localhost', 104 | scope: 'https://www.googleapis.com/auth/userinfo.profile', 105 | 106 | init: function() { 107 | $('#login a').on('click', function() { 108 | app.onLoginButtonClick(); 109 | }); 110 | 111 | //Check if we have a valid token 112 | //cached or if we can get a new 113 | //one using a refresh token. 114 | googleapi.getToken({ 115 | client_id: app.client_id, 116 | client_secret: app.client_secret 117 | }).done(function() { 118 | //Show the greet view if we get a valid token 119 | app.showGreetView(); 120 | }).fail(function() { 121 | //Show the login view if we have no valid token 122 | app.showLoginView(); 123 | }); 124 | }, 125 | showLoginView: function() { 126 | $('#login').show(); 127 | $('#greet').hide(); 128 | }, 129 | showGreetView: function() { 130 | $('#login').hide(); 131 | $('#greet').show(); 132 | 133 | //Get the token, either from the cache 134 | //or by using the refresh token. 135 | googleapi.getToken({ 136 | client_id: app.client_id, 137 | client_secret: app.client_secret 138 | }).then(function(data) { 139 | //Pass the token to the API call and return a new promise object 140 | return googleapi.userInfo({ access_token: data.access_token }); 141 | }).done(function(user) { 142 | //Display a greeting if the API call was successful 143 | $('#greet h1').html('Hello ' + user.name + '!'); 144 | }).fail(function() { 145 | //If getting the token fails, or the token has been 146 | //revoked, show the login view. 147 | app.showLoginView(); 148 | }); 149 | }, 150 | onLoginButtonClick: function() { 151 | //Show the consent page 152 | googleapi.authorize({ 153 | client_id: app.client_id, 154 | client_secret: app.client_secret, 155 | redirect_uri: app.redirect_uri, 156 | scope: app.scope 157 | }).done(function() { 158 | //Show the greet view if access is granted 159 | app.showGreetView(); 160 | }).fail(function(data) { 161 | //Show an error message if access was denied 162 | $('#login p').html(data.error); 163 | }); 164 | } 165 | }; 166 | 167 | $(document).on('deviceready', function() { 168 | app.init(); 169 | }); 170 | -------------------------------------------------------------------------------- /www/res/icon/android/icon-36-ldpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/icon/android/icon-36-ldpi.png -------------------------------------------------------------------------------- /www/res/icon/android/icon-48-mdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/icon/android/icon-48-mdpi.png -------------------------------------------------------------------------------- /www/res/icon/android/icon-72-hdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/icon/android/icon-72-hdpi.png -------------------------------------------------------------------------------- /www/res/icon/android/icon-96-xhdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/icon/android/icon-96-xhdpi.png -------------------------------------------------------------------------------- /www/res/icon/blackberry/icon-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/icon/blackberry/icon-80.png -------------------------------------------------------------------------------- /www/res/icon/ios/icon-57-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/icon/ios/icon-57-2x.png -------------------------------------------------------------------------------- /www/res/icon/ios/icon-57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/icon/ios/icon-57.png -------------------------------------------------------------------------------- /www/res/icon/ios/icon-72-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/icon/ios/icon-72-2x.png -------------------------------------------------------------------------------- /www/res/icon/ios/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/icon/ios/icon-72.png -------------------------------------------------------------------------------- /www/res/icon/windows-phone/icon-173-tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/icon/windows-phone/icon-173-tile.png -------------------------------------------------------------------------------- /www/res/icon/windows-phone/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/icon/windows-phone/icon-48.png -------------------------------------------------------------------------------- /www/res/icon/windows-phone/icon-62-tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/icon/windows-phone/icon-62-tile.png -------------------------------------------------------------------------------- /www/res/screen/android/screen-hdpi-landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/screen/android/screen-hdpi-landscape.png -------------------------------------------------------------------------------- /www/res/screen/android/screen-hdpi-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/screen/android/screen-hdpi-portrait.png -------------------------------------------------------------------------------- /www/res/screen/android/screen-ldpi-landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/screen/android/screen-ldpi-landscape.png -------------------------------------------------------------------------------- /www/res/screen/android/screen-ldpi-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/screen/android/screen-ldpi-portrait.png -------------------------------------------------------------------------------- /www/res/screen/android/screen-mdpi-landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/screen/android/screen-mdpi-landscape.png -------------------------------------------------------------------------------- /www/res/screen/android/screen-mdpi-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/screen/android/screen-mdpi-portrait.png -------------------------------------------------------------------------------- /www/res/screen/android/screen-xhdpi-landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/screen/android/screen-xhdpi-landscape.png -------------------------------------------------------------------------------- /www/res/screen/android/screen-xhdpi-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/screen/android/screen-xhdpi-portrait.png -------------------------------------------------------------------------------- /www/res/screen/blackberry/screen-225.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/screen/blackberry/screen-225.png -------------------------------------------------------------------------------- /www/res/screen/ios/screen-ipad-landscape-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/screen/ios/screen-ipad-landscape-2x.png -------------------------------------------------------------------------------- /www/res/screen/ios/screen-ipad-landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/screen/ios/screen-ipad-landscape.png -------------------------------------------------------------------------------- /www/res/screen/ios/screen-ipad-portrait-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/screen/ios/screen-ipad-portrait-2x.png -------------------------------------------------------------------------------- /www/res/screen/ios/screen-ipad-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/screen/ios/screen-ipad-portrait.png -------------------------------------------------------------------------------- /www/res/screen/ios/screen-iphone-landscape-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/screen/ios/screen-iphone-landscape-2x.png -------------------------------------------------------------------------------- /www/res/screen/ios/screen-iphone-landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/screen/ios/screen-iphone-landscape.png -------------------------------------------------------------------------------- /www/res/screen/ios/screen-iphone-portrait-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/screen/ios/screen-iphone-portrait-2x.png -------------------------------------------------------------------------------- /www/res/screen/ios/screen-iphone-portrait-568h-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/screen/ios/screen-iphone-portrait-568h-2x.png -------------------------------------------------------------------------------- /www/res/screen/ios/screen-iphone-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/screen/ios/screen-iphone-portrait.png -------------------------------------------------------------------------------- /www/res/screen/windows-phone/screen-portrait.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdellanoce/google-api-oauth-phonegap/89bfb908960ed1d91671926bad29a39fad68bc4f/www/res/screen/windows-phone/screen-portrait.jpg --------------------------------------------------------------------------------