├── .gitignore
├── LICENSE
├── NOTICE
├── PlatformScriptsWorkflow.md
├── README.md
├── hooks
├── after_build
│ └── 000-build_64_bit.js
├── after_plugin_install
│ └── 000-shared_mode_special.js
├── before_build
│ └── 000-build_64_bit.js
├── before_plugin_uninstall
│ └── 000-shared_mode_special.js
└── update_config.js
├── package.json
├── platforms
└── android
│ ├── src
│ └── org
│ │ └── crosswalk
│ │ └── engine
│ │ ├── XWalkCordovaClientCertRequest.java
│ │ ├── XWalkCordovaCookieManager.java
│ │ ├── XWalkCordovaHttpAuthHandler.java
│ │ ├── XWalkCordovaResourceClient.java
│ │ ├── XWalkCordovaUiClient.java
│ │ ├── XWalkCordovaView.java
│ │ ├── XWalkExposedJsApi.java
│ │ ├── XWalkFileChooser.java
│ │ └── XWalkWebViewEngine.java
│ └── xwalk.gradle
└── plugin.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | platforms/android/libs/xwalk_core_library/build/
2 | platforms/android/libs/xwalk_core_library/build.gradle
3 | *.iml
4 |
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
204 | /************************************************/
205 | This product bundles CordovaXWalkCoreExtensionBridge.java as well
206 | as the XWalk.pak and the Crosswalk JSApi which is available under a
207 | "3-clause BSD" license. For details, see below:
208 |
209 | Copyright (c) 2013 Intel Corporation. All rights reserved.
210 |
211 | Redistribution and use in source and binary forms, with or without
212 | modification, are permitted provided that the following conditions are
213 | met:
214 |
215 | * Redistributions of source code must retain the above copyright
216 | notice, this list of conditions and the following disclaimer.
217 | * Redistributions in binary form must reproduce the above
218 | copyright notice, this list of conditions and the following disclaimer
219 | in the documentation and/or other materials provided with the
220 | distribution.
221 | * Neither the name of Intel Corporation nor the names of its
222 | contributors may be used to endorse or promote products derived from
223 | this software without specific prior written permission.
224 |
225 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
226 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
227 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
228 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
229 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
230 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
231 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
232 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
233 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
234 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
235 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
236 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Apache Cordova
2 | Copyright 2014 The Apache Software Foundation
3 |
4 | This product includes software developed at
5 | The Apache Software Foundation (http://www.apache.org)
6 |
7 | This software includes software developed at Intel Corporation.
8 | Copyright 2014 Intel Corporation
9 |
--------------------------------------------------------------------------------
/PlatformScriptsWorkflow.md:
--------------------------------------------------------------------------------
1 | ### Directions for Non-CLI Android-Only cordova project
2 |
3 | * Pull down the Cordova Android
4 | ```
5 | $ git clone https://github.com/apache/cordova-android.git
6 | ```
7 | * Generate a project, e.g creating HelloWorld
8 | ```
9 | $ /path/to/cordova-android/bin/create hello com.example.hello HelloWorld
10 | ```
11 | * Navigate to the project folder
12 | ```
13 | $ cd hello
14 | ```
15 | * Install Crosswalk engine plugin by plugman (version >= 0.22.17)
16 | ```
17 | $ plugman install --platform android --plugin https://github.com/MobileChromeApps/cordova-crosswalk-engine.git --project .
18 | ```
19 | * Build
20 | ```
21 | $ ./cordova/build
22 | ```
23 | The build script will automatically fetch the Crosswalk WebView libraries from Crosswalk project download site (https://download.01.org/crosswalk/releases/crosswalk/android/) and build for both X86 and ARM architectures.
24 |
25 | For example, building HelloWorld generates:
26 |
27 | ```
28 | /path/to/hello/build/outputs/apk/hello-x86-debug.apk
29 | /path/to/hello/build/outputs/apk/hello-armv7-debug.apk
30 | ```
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # cordova-plugin-crosswalk-webview
2 |
3 | Makes your Cordova application use the [Crosswalk WebView](https://crosswalk-project.org/)
4 | instead of the System WebView. Requires cordova-android 4.0 or greater.
5 |
6 | ### Benefits
7 |
8 | * WebView doesn't change depending on Android version
9 | * Capabilities: such as WebRTC, WebAudio, Web Components
10 | * Performance improvements (compared to older system webviews)
11 |
12 |
13 | ### Drawbacks
14 |
15 | * Increased memory footprint
16 | * An overhead of ~30MB (as reported by the RSS column of ps)
17 | * Increased APK size (about 17MB)
18 | * Increased size on disk when installed (about 50MB)
19 | * Crosswalk WebView stores data (IndexedDB, LocalStorage, etc) separately from System WebView
20 | * You'll need to manually migrate local data when switching between the two (note: this is fixed in Crosswalk 15)
21 |
22 | ### Install
23 |
24 | The following directions are for cordova-cli (most people). Alternatively you can use the [Android platform scripts workflow](PlatformScriptsWorkflow.md).
25 |
26 | * Open an existing cordova project, with cordova-android 4.0.0+, and using the latest CLI. Crosswalk variables can be configured as an option when installing the plugin
27 | * Add this plugin
28 |
29 | ```
30 | $ cordova plugin add cordova-plugin-crosswalk-webview
31 | ```
32 |
33 | * Build
34 | ```
35 | $ cordova build android
36 | ```
37 | The build script will automatically fetch the Crosswalk WebView libraries from Crosswalk project download site (https://download.01.org/crosswalk/releases/crosswalk/android/maven2/) and build for both X86 and ARM architectures.
38 |
39 | For example, building android with Crosswalk generates:
40 |
41 | ```
42 | /path/to/hello/platforms/android/build/outputs/apk/hello-x86-debug.apk
43 | /path/to/hello/platforms/android/build/outputs/apk/hello-armv7-debug.apk
44 | ```
45 |
46 | Note that you might have to run `cordova clean` before building, if you previously built the app without cordova-plugin-crosswalk-webview. Also, manually uninstall the app from the device/emulator before attempting to install the crosswalk-enabled version.
47 |
48 | Also note that it is also possible to publish a multi-APK application on the Play Store that uses Crosswalk for Pre-L devices, and the (updatable) system webview for L+:
49 |
50 | To build Crosswalk-enabled apks, add this plugin and run:
51 |
52 | $ cordova build --release
53 |
54 | To build System-webview apk, remove this plugin and run:
55 |
56 | $ cordova build --release -- --minSdkVersion=21
57 |
58 | ### Configure
59 |
60 | You can try out a different Crosswalk version by specifying certain variables while installing the plugin, or by changing the value of `xwalkVersion` in your `config.xml` after installing the plugin. Some examples:
61 |
62 |
63 | cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_VERSION="org.xwalk:xwalk_core_library:14+"
64 | cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_VERSION="xwalk_core_library:14+"
65 | cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_VERSION="14+"
66 | cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_VERSION="14"
67 |
68 |
69 |
70 |
71 |
72 | You can also use a Crosswalk beta version. Some examples:
73 |
74 |
75 | cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_VERSION="org.xwalk:xwalk_core_library_beta:14+"
76 |
77 |
78 | You can set [command-line flags](http://peter.sh/experiments/chromium-command-line-switches/) as well:
79 |
80 |
81 | cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_COMMANDLINE="--disable-pull-to-refresh-effect"
82 |
83 |
84 | You can use the Crosswalk [shared mode](https://crosswalk-project.org/documentation/shared_mode.html) which allows multiple Crosswalk applications to share one Crosswalk runtime downloaded from the Play Store.
85 |
86 |
87 | cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_MODE="shared"
88 |
89 |
90 | You can also use a Crosswalk beta version on shared mode, e.g.:
91 |
92 |
93 | cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_VERSION="org.xwalk:xwalk_shared_library_beta:14+"
94 |
95 | You can use the Crosswalk [lite mode](https://crosswalk-project.org/documentation/crosswalk_lite.html) which is the Crosswalk runtime designed to be as small as possible by removing less common libraries and features and compressing the APK.
96 |
97 |
98 | cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_MODE="lite"
99 |
100 |
101 | You can set background color with the preference of BackgroundColor.
102 |
103 |
104 |
105 |
106 | You can also set user agent with the preference of xwalkUserAgent.
107 |
108 |
109 |
110 | ### Release Notes
111 |
112 | #### 2.3.0 (January 21, 2017)
113 | * Uses the latest Crosswalk 23 stable version by default
114 |
115 | #### 2.2.0 (November 4, 2016)
116 | * Uses the latest Crosswalk 22 stable version by default
117 | * Keep compatible for Cordova-android 6.0 with evaluating Javascript bridge
118 | * This version requires cordova-android 6.0.0 or newer
119 |
120 | #### 2.1.0 (September 9, 2016)
121 | * Uses the latest Crosswalk 21 stable version by default
122 |
123 | #### 2.0.0 (August 17, 2016)
124 | * Uses the latest Crosswalk 20 stable version by default
125 | * Discontinue support for Android 4.0 (ICS) in Crosswalk starting with version 20
126 |
127 | #### 1.8.0 (June 30, 2016)
128 | * Uses the latest Crosswalk 19 stable version by default
129 |
130 | #### 1.7.0 (May 4, 2016)
131 | * Uses the latest Crosswalk 18 stable version by default
132 | * Support to use [Crosswalk Lite](https://crosswalk-project.org/documentation/crosswalk_lite.html), It's possible to specify lite value with the variable of XWALK_MODE at install plugin time.
133 | * [Cordova screenshot plugin](https://github.com/gitawego/cordova-screenshot.git) can capture the visible content of web page with Crosswalk library.
134 | * Doesn't work with Crosswalk 17 and earlier
135 |
136 | #### 1.6.0 (March 11, 2016)
137 | * Uses the latest Crosswalk 17 stable version by default
138 | * Support to [package apps for 64-bit devices](https://crosswalk-project.org/documentation/android/android_64bit.html), it's possible to specify 64-bit targets using the `--xwalk64bit` option in the build command:
139 |
140 | cordova build android --xwalk64bit
141 |
142 | #### 1.5.0 (January 18, 2016)
143 | * Uses the latest Crosswalk 16 stable version by default
144 | * The message of xwalk's ready can be listened
145 |
146 | #### 1.4.0 (November 5, 2015)
147 | * Uses the latest Crosswalk 15 stable version by default
148 | * Support User Agent and Background Color configuration preferences
149 | * Compatible with the newest Cordova version 5.3.4
150 |
151 | #### 1.3.0 (August 28, 2015)
152 | * Crosswalk variables can be configured as an option via CLI
153 | * Support for [Crosswalk's shared mode](https://crosswalk-project.org/documentation/shared_mode.html) via the XWALK_MODE install variable or xwalkMode preference
154 | * Uses the latest Crosswalk 14 stable version by default
155 | * The ANIMATABLE_XWALK_VIEW preference is false by default
156 | * Doesn't work with Crosswalk 14.43.343.17 and earlier
157 |
158 | #### 1.2.0 (April 22, 2015)
159 | * Made Crosswalk command-line configurable via ``
160 | * Disabled pull-down-to-refresh by default
161 |
162 | #### 1.1.0 (April 21, 2015)
163 | * Based on Crosswalk v13
164 | * Made Crosswalk version configurable via ``
165 |
166 | #### 1.0.0 (Mar 25, 2015)
167 | * Initial release
168 | * Based on Crosswalk v11
169 |
--------------------------------------------------------------------------------
/hooks/after_build/000-build_64_bit.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | module.exports = function(context) {
4 |
5 | /** @external */
6 | var deferral = context.requireCordovaModule('q').defer(),
7 | UpdateConfig = require('./../update_config.js'),
8 | updateConfig = new UpdateConfig(context);
9 |
10 | /** Main method */
11 | var main = function() {
12 | // Remove the xwalk variables
13 | updateConfig.afterBuild64bit();
14 |
15 | deferral.resolve();
16 | };
17 |
18 | main();
19 |
20 | return deferral.promise;
21 |
22 | };
23 |
--------------------------------------------------------------------------------
/hooks/after_plugin_install/000-shared_mode_special.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | module.exports = function(context) {
4 |
5 | /** @external */
6 | var deferral = context.requireCordovaModule('q').defer(),
7 | UpdateConfig = require('./../update_config.js'),
8 | updateConfig = new UpdateConfig(context);
9 |
10 | /** Main method */
11 | var main = function() {
12 | // Add xwalk preference to config.xml
13 | updateConfig.addPreferences();
14 |
15 | deferral.resolve();
16 | };
17 |
18 | main();
19 |
20 | return deferral.promise;
21 |
22 | };
23 |
--------------------------------------------------------------------------------
/hooks/before_build/000-build_64_bit.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | module.exports = function(context) {
4 |
5 | /** @external */
6 | var deferral = context.requireCordovaModule('q').defer(),
7 | UpdateConfig = require('./../update_config.js'),
8 | updateConfig = new UpdateConfig(context);
9 |
10 | /** Main method */
11 | var main = function() {
12 | // Remove the xwalk variables
13 | updateConfig.beforeBuild64bit();
14 |
15 | deferral.resolve();
16 | };
17 |
18 | main();
19 |
20 | return deferral.promise;
21 |
22 | };
23 |
--------------------------------------------------------------------------------
/hooks/before_plugin_uninstall/000-shared_mode_special.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | module.exports = function(context) {
4 |
5 | /** @external */
6 | var deferral = context.requireCordovaModule('q').defer(),
7 | UpdateConfig = require('./../update_config.js'),
8 | updateConfig = new UpdateConfig(context);
9 |
10 | /** Main method */
11 | var main = function() {
12 | // Remove the xwalk variables
13 | updateConfig.removePreferences();
14 |
15 | deferral.resolve();
16 | };
17 |
18 | main();
19 |
20 | return deferral.promise;
21 |
22 | };
23 |
--------------------------------------------------------------------------------
/hooks/update_config.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | module.exports = function(context) {
4 |
5 | var ConfigParser, XmlHelpers;
6 | try {
7 | // cordova-lib >= 5.3.4 doesn't contain ConfigParser and xml-helpers anymore
8 | ConfigParser = context.requireCordovaModule("cordova-common").ConfigParser;
9 | XmlHelpers = context.requireCordovaModule("cordova-common").xmlHelpers;
10 | } catch (e) {
11 | ConfigParser = context.requireCordovaModule("cordova-lib/src/configparser/ConfigParser");
12 | XmlHelpers = context.requireCordovaModule("cordova-lib/src/util/xml-helpers");
13 | }
14 |
15 | /** @external */
16 | var fs = context.requireCordovaModule('fs'),
17 | path = context.requireCordovaModule('path'),
18 | et = context.requireCordovaModule('elementtree');
19 |
20 | /** @defaults */
21 | var xwalkVariables = {},
22 | argumentsString = context.cmdLine,
23 | pluginConfigurationFile = path.join(context.opts.plugin.dir, 'plugin.xml'),
24 | androidPlatformDir = path.join(context.opts.projectRoot,
25 | 'platforms', 'android'),
26 | projectConfigurationFile = path.join(context.opts.projectRoot,
27 | 'config.xml'),
28 | platformConfigurationFile = path.join(androidPlatformDir,
29 | 'res', 'xml', 'config.xml'),
30 | projectManifestFile = path.join(androidPlatformDir,
31 | 'AndroidManifest.xml'),
32 | xwalk64bit = "xwalk64bit",
33 | xwalkLiteVersion = "",
34 | specificVersion = false;
35 |
36 | /** Init */
37 | var CordovaConfig = new ConfigParser(platformConfigurationFile);
38 |
39 | var addPermission = function() {
40 | var projectManifestXmlRoot = XmlHelpers.parseElementtreeSync(projectManifestFile);
41 | var child = et.XML('');
42 | XmlHelpers.graftXML(projectManifestXmlRoot, [child], '/manifest');
43 | fs.writeFileSync(projectManifestFile, projectManifestXmlRoot.write({indent: 4}), 'utf-8');
44 | }
45 |
46 | var removePermission = function() {
47 | var projectManifestXmlRoot = XmlHelpers.parseElementtreeSync(projectManifestFile);
48 | var child = et.XML('');
49 | XmlHelpers.pruneXML(projectManifestXmlRoot, [child], '/manifest');
50 | fs.writeFileSync(projectManifestFile, projectManifestXmlRoot.write({indent: 4}), 'utf-8');
51 | }
52 |
53 | var defaultPreferences = function() {
54 | var pluginPreferences = {};
55 |
56 | var pluginXmlRoot = XmlHelpers.parseElementtreeSync(pluginConfigurationFile),
57 | tagName = "preference",
58 | containerName = "config-file",
59 | targetPlatform = 'android',
60 | targetPlatformTag = pluginXmlRoot.find('./platform[@name="' + targetPlatform + '"]');
61 |
62 | var tagsInRoot = pluginXmlRoot.findall(tagName) || [],
63 | tagsInPlatform = targetPlatformTag ? targetPlatformTag.findall(tagName) : [],
64 | tagsInContainer = targetPlatformTag ? targetPlatformTag.findall(containerName) : [],
65 | tagsList = tagsInRoot.concat(tagsInContainer);
66 |
67 | // Parses tags within -blocks
68 | tagsList.map(function(prefTag) {
69 | prefTag.getchildren().forEach(function(element) {
70 | if ((element.tag == 'preference') && (element.attrib['name']) && element.attrib['default']) {
71 | // Don't add xwalkLiteVersion in the app/config.xml
72 | if (element.attrib['name'] == "xwalkLiteVersion") {
73 | xwalkLiteVersion = element.attrib['default'];
74 | } else {
75 | pluginPreferences[element.attrib['name']] = element.attrib['default'];
76 | }
77 | }
78 | });
79 | });
80 |
81 | return pluginPreferences;
82 | }
83 |
84 | /** The style of name align with config.xml */
85 | var setConfigPreference = function(name, value) {
86 | var trimName = name.replace('_', '');
87 | for (var localName in xwalkVariables) {
88 | if (localName.toUpperCase() == trimName.toUpperCase()) {
89 | xwalkVariables[localName] = value;
90 | if (localName == 'xwalkVersion') {
91 | specificVersion = true;
92 | }
93 | }
94 | }
95 | }
96 |
97 | /** Pase the cli command to get the specific preference*/
98 | var parseCliPreference = function() {
99 | var commandlineVariablesList = argumentsString.split('--variable');
100 | if (commandlineVariablesList) {
101 | commandlineVariablesList.forEach(function(element) {
102 | element = element.trim();
103 | if(element && element.indexOf('XWALK') == 0) {
104 | var preference = element.split('=');
105 | if (preference && preference.length == 2) {
106 | setConfigPreference(preference[0], preference[1]);
107 | }
108 | }
109 | });
110 | }
111 | }
112 |
113 | /** Add preference */
114 | this.addPreferences = function() {
115 | // Pick the xwalk variables with the cli preferences
116 | // parseCliPreference();
117 |
118 | // Add the permission of writing external storage when using shared mode
119 | if (CordovaConfig.getGlobalPreference('xwalkMode') == 'shared') {
120 | addPermission();
121 | }
122 |
123 | // Configure the final value in the config.xml
124 | // var configXmlRoot = XmlHelpers.parseElementtreeSync(projectConfigurationFile);
125 | // var preferenceUpdated = false;
126 | // for (var name in xwalkVariables) {
127 | // var child = configXmlRoot.find('./preference[@name="' + name + '"]');
128 | // if(!child) {
129 | // preferenceUpdated = true;
130 | // child = et.XML('');
131 | // XmlHelpers.graftXML(configXmlRoot, [child], '/*');
132 | // }
133 | // }
134 | // if(preferenceUpdated) {
135 | // fs.writeFileSync(projectConfigurationFile, configXmlRoot.write({indent: 4}), 'utf-8');
136 | // }
137 | }
138 |
139 | /** Remove preference*/
140 | this.removePreferences = function() {
141 | if (CordovaConfig.getGlobalPreference('xwalkMode') == 'shared') {
142 | // Add the permission of write_external_storage in shared mode
143 | removePermission();
144 | }
145 |
146 | // var configXmlRoot = XmlHelpers.parseElementtreeSync(projectConfigurationFile);
147 | // for (var name in xwalkVariables) {
148 | // var child = configXmlRoot.find('./preference[@name="' + name + '"]');
149 | // if (child) {
150 | // XmlHelpers.pruneXML(configXmlRoot, [child], '/*');
151 | // }
152 | // }
153 | // fs.writeFileSync(projectConfigurationFile, configXmlRoot.write({indent: 4}), 'utf-8');
154 | }
155 |
156 | var build64bit = function() {
157 | var build64bit = false;
158 | var commandlineVariablesList = argumentsString.split('--');
159 |
160 | if (commandlineVariablesList) {
161 | commandlineVariablesList.forEach(function(element) {
162 | element = element.trim();
163 | if(element && element.indexOf(xwalk64bit) == 0) {
164 | build64bit = true;
165 | }
166 | });
167 | }
168 | return build64bit;
169 | }
170 |
171 | this.beforeBuild64bit = function() {
172 | if(build64bit()) {
173 | var configXmlRoot = XmlHelpers.parseElementtreeSync(projectConfigurationFile);
174 | var child = configXmlRoot.find('./preference[@name="' + xwalk64bit + '"]');
175 | if(!child) {
176 | child = et.XML('');
177 | XmlHelpers.graftXML(configXmlRoot, [child], '/*');
178 | fs.writeFileSync(projectConfigurationFile, configXmlRoot.write({indent: 4}), 'utf-8');
179 | }
180 | }
181 | }
182 |
183 | this.afterBuild64bit = function() {
184 | if(build64bit()) {
185 | var configXmlRoot = XmlHelpers.parseElementtreeSync(projectConfigurationFile);
186 | var child = configXmlRoot.find('./preference[@name="' + xwalk64bit + '"]');
187 | if (child) {
188 | XmlHelpers.pruneXML(configXmlRoot, [child], '/*');
189 | fs.writeFileSync(projectConfigurationFile, configXmlRoot.write({indent: 4}), 'utf-8');
190 | }
191 | }
192 |
193 | console.log("Crosswalk info:");
194 | console.log(" After much discussion and analysis of the market,");
195 | console.log(" we have decided to discontinue support for Android 4.0 (ICS) in Crosswalk starting with version 20,");
196 | console.log(" so the minSdkVersion of Cordova project is configured to 16 by default. \n");
197 | }
198 |
199 | xwalkVariables = defaultPreferences();
200 |
201 | };
202 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cordova-plugin-crosswalk-webview",
3 | "version": "2.3.0",
4 | "description": "Changes the default WebView to CrossWalk",
5 | "cordova": {
6 | "id": "cordova-plugin-crosswalk-webview",
7 | "platforms": [
8 | "android"
9 | ]
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/crosswalk-project/cordova-plugin-crosswalk-webview.git"
14 | },
15 | "keywords": [
16 | "cordova",
17 | "chromium",
18 | "crosswalk",
19 | "webview",
20 | "engine",
21 | "ecosystem:cordova",
22 | "cordova-android"
23 | ],
24 | "engines": {
25 | "cordovaDependencies": {
26 | "2.0.0": {
27 | "cordova": ">=5.2.0",
28 | "cordova-android": "4 - 5"
29 | },
30 | "2.1.0": {
31 | "cordova": ">=5.2.0",
32 | "cordova-android": "4 - 5"
33 | },
34 | "2.2.0": {
35 | "cordova": ">=5.2.0",
36 | "cordova-android": ">=6"
37 | },
38 | "3.0.0": {
39 | "cordova": ">100"
40 | }
41 | }
42 | },
43 | "author": "",
44 | "license": "Apache 2.0",
45 | "bugs": {
46 | "url": "https://crosswalk-project.org/jira"
47 | },
48 | "homepage": "https://github.com/crosswalk-project/cordova-plugin-crosswalk-webview"
49 | }
50 |
--------------------------------------------------------------------------------
/platforms/android/src/org/crosswalk/engine/XWalkCordovaClientCertRequest.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.crosswalk.engine;
20 |
21 | import org.apache.cordova.ICordovaClientCertRequest;
22 | import org.xwalk.core.ClientCertRequest;
23 |
24 | import java.security.cert.X509Certificate;
25 | import java.security.Principal;
26 | import java.security.PrivateKey;
27 | import java.util.Arrays;
28 |
29 | public class XWalkCordovaClientCertRequest implements ICordovaClientCertRequest {
30 |
31 | private final ClientCertRequest request;
32 |
33 | public XWalkCordovaClientCertRequest(ClientCertRequest request) {
34 | this.request = request;
35 | }
36 |
37 | /**
38 | * Cancel this request
39 | */
40 | public void cancel() {
41 | request.cancel();
42 | }
43 |
44 | /*
45 | * Returns the host name of the server requesting the certificate.
46 | */
47 | public String getHost() {
48 | return request.getHost();
49 | }
50 |
51 | /*
52 | * Returns the acceptable types of asymmetric keys (can be null).
53 | */
54 | public String[] getKeyTypes() {
55 | return request.getKeyTypes();
56 | }
57 |
58 | /*
59 | * Returns the port number of the server requesting the certificate.
60 | */
61 | public int getPort() {
62 | return request.getPort();
63 | }
64 |
65 | /*
66 | * Returns the acceptable certificate issuers for the certificate matching the private
67 | * key (can be null).
68 | */
69 | public Principal[] getPrincipals() {
70 | return request.getPrincipals();
71 | }
72 |
73 | /*
74 | * Ignore the request for now. Do not remember user's choice.
75 | */
76 | public void ignore() {
77 | request.ignore();
78 | }
79 |
80 | /*
81 | * Proceed with the specified private key and client certificate chain. Remember the user's
82 | * positive choice and use it for future requests.
83 | *
84 | * @param privateKey The privateKey
85 | * @param chain The certificate chain
86 | */
87 | public void proceed(PrivateKey privateKey, X509Certificate[] chain) {
88 | request.proceed(privateKey, Arrays.asList(chain));
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/platforms/android/src/org/crosswalk/engine/XWalkCordovaCookieManager.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.crosswalk.engine;
20 |
21 | import org.apache.cordova.ICordovaCookieManager;
22 | import org.xwalk.core.XWalkCookieManager;
23 |
24 | class XWalkCordovaCookieManager implements ICordovaCookieManager {
25 |
26 | protected XWalkCookieManager cookieManager = null;
27 |
28 | public XWalkCordovaCookieManager() {
29 | cookieManager = new XWalkCookieManager();
30 | }
31 |
32 | public void setCookiesEnabled(boolean accept) {
33 | cookieManager.setAcceptCookie(accept);
34 | }
35 |
36 | public void setCookie(final String url, final String value) {
37 | cookieManager.setCookie(url, value);
38 | }
39 |
40 | public String getCookie(final String url) {
41 | return cookieManager.getCookie(url);
42 | }
43 |
44 | public void clearCookies() {
45 | cookieManager.removeAllCookie();
46 | }
47 |
48 | public void flush() {
49 | cookieManager.flushCookieStore();
50 | }
51 | };
52 |
53 |
54 |
--------------------------------------------------------------------------------
/platforms/android/src/org/crosswalk/engine/XWalkCordovaHttpAuthHandler.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.crosswalk.engine;
20 |
21 | import org.apache.cordova.ICordovaHttpAuthHandler;
22 | import org.xwalk.core.XWalkHttpAuthHandler;
23 |
24 | /**
25 | * Specifies interface for HTTP auth handler object which is used to handle auth requests and
26 | * specifying user credentials.
27 | */
28 | public class XWalkCordovaHttpAuthHandler implements ICordovaHttpAuthHandler {
29 |
30 | private final XWalkHttpAuthHandler handler;
31 |
32 | public XWalkCordovaHttpAuthHandler(XWalkHttpAuthHandler handler) {
33 | this.handler = handler;
34 | }
35 |
36 | /**
37 | * Instructs the XWalkView to cancel the authentication request.
38 | */
39 | public void cancel() {
40 | handler.cancel();
41 | }
42 |
43 | /**
44 | * Instructs the XWalkView to proceed with the authentication with the given credentials.
45 | *
46 | * @param username
47 | * @param password
48 | */
49 | public void proceed(String username, String password) {
50 | handler.proceed(username, password);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/platforms/android/src/org/crosswalk/engine/XWalkCordovaResourceClient.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.crosswalk.engine;
20 |
21 | import android.content.pm.ApplicationInfo;
22 | import android.content.pm.PackageManager;
23 | import android.net.Uri;
24 | import android.net.http.SslError;
25 | import android.webkit.ValueCallback;
26 | import android.webkit.WebResourceResponse;
27 |
28 | import org.apache.cordova.CordovaResourceApi;
29 | import org.apache.cordova.CordovaResourceApi.OpenForReadResult;
30 | import org.apache.cordova.LOG;
31 | import org.apache.cordova.PluginManager;
32 | import org.xwalk.core.ClientCertRequest;
33 | import org.xwalk.core.XWalkHttpAuthHandler;
34 | import org.xwalk.core.XWalkResourceClient;
35 | import org.xwalk.core.XWalkView;
36 |
37 | import java.io.FileNotFoundException;
38 | import java.io.IOException;
39 |
40 | public class XWalkCordovaResourceClient extends XWalkResourceClient {
41 |
42 | private static final String TAG = "XWalkCordovaResourceClient";
43 | protected XWalkWebViewEngine parentEngine;
44 |
45 | public XWalkCordovaResourceClient(XWalkWebViewEngine parentEngine) {
46 | super(parentEngine.webView);
47 | this.parentEngine = parentEngine;
48 | }
49 |
50 | /**
51 | * Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable).
52 | * The errorCode parameter corresponds to one of the ERROR_* constants.
53 | *
54 | * @param view The WebView that is initiating the callback.
55 | * @param errorCode The error code corresponding to an ERROR_* value.
56 | * @param description A String describing the error.
57 | * @param failingUrl The url that failed to load.
58 | */
59 | @Override
60 | public void onReceivedLoadError(XWalkView view, int errorCode, String description,
61 | String failingUrl) {
62 | LOG.d(TAG, "CordovaWebViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl);
63 |
64 | parentEngine.client.onReceivedError(errorCode, description, failingUrl);
65 | }
66 |
67 | @Override
68 | public WebResourceResponse shouldInterceptLoadRequest(XWalkView view, String url) {
69 | try {
70 | // Check the against the white-list.
71 | if (!parentEngine.pluginManager.shouldAllowRequest(url)) {
72 | LOG.w(TAG, "URL blocked by whitelist: " + url);
73 | // Results in a 404.
74 | return new WebResourceResponse("text/plain", "UTF-8", null);
75 | }
76 |
77 | CordovaResourceApi resourceApi = parentEngine.resourceApi;
78 | Uri origUri = Uri.parse(url);
79 | // Allow plugins to intercept WebView requests.
80 | Uri remappedUri = resourceApi.remapUri(origUri);
81 |
82 | if (!origUri.equals(remappedUri)) {
83 | OpenForReadResult result = resourceApi.openForRead(remappedUri, true);
84 | return new WebResourceResponse(result.mimeType, "UTF-8", result.inputStream);
85 | }
86 | // If we don't need to special-case the request, let the browser load it.
87 | return null;
88 | } catch (IOException e) {
89 | if (!(e instanceof FileNotFoundException)) {
90 | LOG.e(TAG, "Error occurred while loading a file (returning a 404).", e);
91 | }
92 | // Results in a 404.
93 | return new WebResourceResponse("text/plain", "UTF-8", null);
94 | }
95 | }
96 |
97 | @Override
98 | public boolean shouldOverrideUrlLoading(XWalkView view, String url) {
99 | return parentEngine.client.onNavigationAttempt(url);
100 | }
101 |
102 |
103 | /**
104 | * Notify the host application that an SSL error occurred while loading a
105 | * resource. The host application must call either callback.onReceiveValue(true)
106 | * or callback.onReceiveValue(false). Note that the decision may be
107 | * retained for use in response to future SSL errors. The default behavior
108 | * is to pop up a dialog.
109 | */
110 | @Override
111 | public void onReceivedSslError(XWalkView view, ValueCallback callback, SslError error) {
112 | final String packageName = parentEngine.cordova.getActivity().getPackageName();
113 | final PackageManager pm = parentEngine.cordova.getActivity().getPackageManager();
114 |
115 | ApplicationInfo appInfo;
116 | try {
117 | appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
118 | if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
119 | // debug = true
120 | callback.onReceiveValue(true);
121 | } else {
122 | // debug = false
123 | callback.onReceiveValue(false);
124 | }
125 | } catch (PackageManager.NameNotFoundException e) {
126 | // When it doubt, lock it out!
127 | callback.onReceiveValue(false);
128 | }
129 | }
130 |
131 | @Override
132 | public void onReceivedHttpAuthRequest(XWalkView view, XWalkHttpAuthHandler handler,
133 | String host, String realm) {
134 | // Check if there is some plugin which can resolve this auth challenge
135 | PluginManager pluginManager = parentEngine.pluginManager;
136 | if (pluginManager != null && pluginManager.onReceivedHttpAuthRequest(
137 | parentEngine.parentWebView,
138 | new XWalkCordovaHttpAuthHandler(handler), host, realm)) {
139 | parentEngine.client.clearLoadTimeoutTimer();
140 | return;
141 | }
142 |
143 | // By default handle 401 like we'd normally do!
144 | super.onReceivedHttpAuthRequest(view, handler, host, realm);
145 | }
146 |
147 | @Override
148 | public void onReceivedClientCertRequest(XWalkView view, ClientCertRequest request) {
149 | // Check if there is some plugin which can resolve this certificate request
150 | PluginManager pluginManager = parentEngine.pluginManager;
151 | if (pluginManager != null && pluginManager.onReceivedClientCertRequest(
152 | parentEngine.parentWebView, new XWalkCordovaClientCertRequest(request))) {
153 | parentEngine.client.clearLoadTimeoutTimer();
154 | return;
155 | }
156 |
157 | super.onReceivedClientCertRequest(view, request);
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/platforms/android/src/org/crosswalk/engine/XWalkCordovaUiClient.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.crosswalk.engine;
20 |
21 | import android.app.Activity;
22 | import android.content.ActivityNotFoundException;
23 | import android.content.Intent;
24 | import android.net.Uri;
25 | import android.util.Log;
26 | import android.webkit.ValueCallback;
27 |
28 | import org.apache.cordova.CordovaDialogsHelper;
29 | import org.apache.cordova.CordovaPlugin;
30 | import org.apache.cordova.LOG;
31 | import org.xwalk.core.XWalkJavascriptResult;
32 | import org.xwalk.core.XWalkUIClient;
33 | import org.xwalk.core.XWalkView;
34 |
35 | import org.crosswalk.engine.XWalkWebViewEngine.PermissionRequestListener;
36 |
37 | public class XWalkCordovaUiClient extends XWalkUIClient {
38 | private static final String TAG = "XWalkCordovaUiClient";
39 | protected final CordovaDialogsHelper dialogsHelper;
40 | protected final XWalkWebViewEngine parentEngine;
41 |
42 | private XWalkFileChooser mFileChooser;
43 | private CordovaPlugin mFileChooserResultPlugin;
44 |
45 | private static final int FILECHOOSER_RESULTCODE = 5173;
46 |
47 | public XWalkCordovaUiClient(XWalkWebViewEngine parentEngine) {
48 | super(parentEngine.webView);
49 | this.parentEngine = parentEngine;
50 | dialogsHelper = new CordovaDialogsHelper(parentEngine.webView.getContext());
51 | }
52 |
53 | @Override
54 | public boolean onJavascriptModalDialog(XWalkView view, JavascriptMessageType type, String url,
55 | String message, String defaultValue, XWalkJavascriptResult result) {
56 | switch (type) {
57 | case JAVASCRIPT_ALERT:
58 | return onJsAlert(view, url, message, result);
59 | case JAVASCRIPT_CONFIRM:
60 | return onJsConfirm(view, url, message, result);
61 | case JAVASCRIPT_PROMPT:
62 | return onJsPrompt(view, url, message, defaultValue, result);
63 | case JAVASCRIPT_BEFOREUNLOAD:
64 | // Reuse onJsConfirm to show the dialog.
65 | return onJsConfirm(view, url, message, result);
66 | default:
67 | break;
68 | }
69 | assert (false);
70 | return false;
71 | }
72 |
73 | /**
74 | * Tell the client to display a javascript alert dialog.
75 | */
76 | public boolean onJsAlert(XWalkView view, String url, String message,
77 | final XWalkJavascriptResult result) {
78 | dialogsHelper.showAlert(message, new CordovaDialogsHelper.Result() {
79 | @Override
80 | public void gotResult(boolean success, String value) {
81 | if (success) {
82 | result.confirm();
83 | } else {
84 | result.cancel();
85 | }
86 | }
87 | });
88 | return true;
89 | }
90 |
91 | /**
92 | * Tell the client to display a confirm dialog to the user.
93 | */
94 | public boolean onJsConfirm(XWalkView view, String url, String message,
95 | final XWalkJavascriptResult result) {
96 | dialogsHelper.showConfirm(message, new CordovaDialogsHelper.Result() {
97 | @Override
98 | public void gotResult(boolean success, String value) {
99 | if (success) {
100 | result.confirm();
101 | } else {
102 | result.cancel();
103 | }
104 | }
105 | });
106 | return true;
107 | }
108 |
109 | /**
110 | * Tell the client to display a prompt dialog to the user.
111 | * If the client returns true, WebView will assume that the client will
112 | * handle the prompt dialog and call the appropriate JsPromptResult method.
113 | *
114 | * Since we are hacking prompts for our own purposes, we should not be using them for
115 | * this purpose, perhaps we should hack console.log to do this instead!
116 | */
117 | public boolean onJsPrompt(XWalkView view, String origin, String message, String defaultValue,
118 | final XWalkJavascriptResult result) {
119 | // Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
120 | String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message, defaultValue);
121 | if (handledRet != null) {
122 | result.confirmWithResult(handledRet);
123 | } else {
124 | dialogsHelper.showPrompt(message, defaultValue, new CordovaDialogsHelper.Result() {
125 | @Override
126 | public void gotResult(boolean success, String value) {
127 | if (success) {
128 | result.confirmWithResult(value);
129 | } else {
130 | result.cancel();
131 | }
132 | }
133 | });
134 |
135 | }
136 | return true;
137 | }
138 |
139 | /**
140 | * Notify the host application that a page has started loading.
141 | * This method is called once for each main frame load so a page with iframes or framesets will call onPageLoadStarted
142 | * one time for the main frame. This also means that onPageLoadStarted will not be called when the contents of an
143 | * embedded frame changes, i.e. clicking a link whose target is an iframe.
144 | *
145 | * @param view The webView initiating the callback.
146 | * @param url The url of the page.
147 | */
148 | @Override
149 | public void onPageLoadStarted(XWalkView view, String url) {
150 | LOG.d(TAG, "onPageLoadStarted(" + url + ")");
151 | if (view.getUrl() != null) {
152 | // Flush stale messages.
153 | parentEngine.client.onPageStarted(url);
154 | parentEngine.bridge.reset();
155 | }
156 | }
157 |
158 | /**
159 | * Notify the host application that a page has stopped loading.
160 | * This method is called only for main frame. When onPageLoadStopped() is called, the rendering picture may not be updated yet.
161 | *
162 | * @param view The webView initiating the callback.
163 | * @param url The url of the page.
164 | * @param status The load status of the webView, can be FINISHED, CANCELLED or FAILED.
165 | */
166 | @Override
167 | public void onPageLoadStopped(XWalkView view, String url, LoadStatus status) {
168 | LOG.d(TAG, "onPageLoadStopped(" + url + ")");
169 | if (status == LoadStatus.FINISHED) {
170 | parentEngine.client.onPageFinishedLoading(url);
171 | } else if (status == LoadStatus.FAILED) {
172 | // TODO: Should this call parentEngine.client.onReceivedError()?
173 | // Right now we call this from ResourceClient, but maybe that is just for sub-resources?
174 | }
175 | }
176 |
177 | // File Chooser
178 | @Override
179 | public void openFileChooser(XWalkView view, final ValueCallback uploadFile,
180 | final String acceptType, final String capture) {
181 | if (mFileChooser == null) {
182 | mFileChooser = new XWalkFileChooser(parentEngine.cordova.getActivity());
183 | mFileChooserResultPlugin = new CordovaPlugin() {
184 | @Override
185 | public void onActivityResult(int requestCode, int resultCode, Intent intent) {
186 | mFileChooser.onActivityResult(requestCode, resultCode, intent);
187 | }
188 | };
189 | }
190 |
191 | PermissionRequestListener listener = new PermissionRequestListener() {
192 | @Override
193 | public void onRequestPermissionResult(int requestCode, String[] permissions,
194 | int[] grantResults) {
195 | for (int i = 0; i < permissions.length; ++i) {
196 | Log.d(TAG, "permission:" + permissions[i] + " result:" + grantResults[i]);
197 | }
198 | parentEngine.cordova.setActivityResultCallback(mFileChooserResultPlugin);
199 | mFileChooser.showFileChooser(uploadFile, acceptType, capture);
200 | }
201 | };
202 |
203 | if (!parentEngine.requestPermissionsForFileChooser(listener)) {
204 | parentEngine.cordova.setActivityResultCallback(mFileChooserResultPlugin);
205 | mFileChooser.showFileChooser(uploadFile, acceptType, capture);
206 | }
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/platforms/android/src/org/crosswalk/engine/XWalkCordovaView.java:
--------------------------------------------------------------------------------
1 | package org.crosswalk.engine;
2 |
3 | import org.apache.cordova.CordovaPreferences;
4 | import org.xwalk.core.XWalkPreferences;
5 | import org.xwalk.core.XWalkResourceClient;
6 | import org.xwalk.core.XWalkUIClient;
7 | import org.xwalk.core.XWalkView;
8 |
9 | import android.content.Context;
10 | import android.content.Intent;
11 | import android.content.pm.ApplicationInfo;
12 | import android.content.pm.PackageManager;
13 | import android.os.Bundle;
14 | import android.util.AttributeSet;
15 | import android.util.Log;
16 | import android.view.KeyEvent;
17 |
18 | import org.apache.cordova.CordovaPlugin;
19 | import org.apache.cordova.CordovaWebView;
20 | import org.apache.cordova.CordovaWebViewEngine;
21 |
22 | public class XWalkCordovaView extends XWalkView implements CordovaWebViewEngine.EngineView {
23 |
24 | public static final String TAG = "XWalkCordovaView";
25 |
26 | protected XWalkCordovaResourceClient resourceClient;
27 | protected XWalkCordovaUiClient uiClient;
28 | protected XWalkWebViewEngine parentEngine;
29 |
30 | private static boolean hasSetStaticPref;
31 | // This needs to run before the super's constructor.
32 | private static Context setGlobalPrefs(Context context, CordovaPreferences preferences) {
33 | if (!hasSetStaticPref) {
34 | hasSetStaticPref = true;
35 | ApplicationInfo ai = null;
36 | try {
37 | ai = context.getPackageManager().getApplicationInfo(context.getApplicationContext().getPackageName(), PackageManager.GET_META_DATA);
38 | } catch (PackageManager.NameNotFoundException e) {
39 | throw new RuntimeException(e);
40 | }
41 | boolean prefAnimatable = preferences == null ? false : preferences.getBoolean("CrosswalkAnimatable", false);
42 | boolean manifestAnimatable = ai.metaData == null ? false : ai.metaData.getBoolean("CrosswalkAnimatable");
43 | // Selects between a TextureView (obeys framework transforms applied to view) or a SurfaceView (better performance).
44 | XWalkPreferences.setValue(XWalkPreferences.ANIMATABLE_XWALK_VIEW, prefAnimatable || manifestAnimatable);
45 | if ((ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
46 | XWalkPreferences.setValue(XWalkPreferences.REMOTE_DEBUGGING, true);
47 | }
48 | XWalkPreferences.setValue(XWalkPreferences.JAVASCRIPT_CAN_OPEN_WINDOW, true);
49 | XWalkPreferences.setValue(XWalkPreferences.ALLOW_UNIVERSAL_ACCESS_FROM_FILE, true);
50 | }
51 | return context;
52 | }
53 |
54 | public XWalkCordovaView(Context context, CordovaPreferences preferences) {
55 | super(setGlobalPrefs(context, preferences), (AttributeSet)null);
56 | }
57 |
58 | public XWalkCordovaView(Context context, AttributeSet attrs) {
59 | super(setGlobalPrefs(context, null), attrs);
60 | }
61 |
62 | void init(XWalkWebViewEngine parentEngine) {
63 | this.parentEngine = parentEngine;
64 | if (resourceClient == null) {
65 | setResourceClient(new XWalkCordovaResourceClient(parentEngine));
66 | }
67 | if (uiClient == null) {
68 | setUIClient(new XWalkCordovaUiClient(parentEngine));
69 | }
70 | }
71 |
72 | @Override
73 | public void setResourceClient(XWalkResourceClient client) {
74 | // XWalk calls this method from its constructor.
75 | if (client instanceof XWalkCordovaResourceClient) {
76 | this.resourceClient = (XWalkCordovaResourceClient)client;
77 | }
78 | super.setResourceClient(client);
79 | }
80 |
81 | @Override
82 | public void setUIClient(XWalkUIClient client) {
83 | // XWalk calls this method from its constructor.
84 | if (client instanceof XWalkCordovaUiClient) {
85 | this.uiClient = (XWalkCordovaUiClient)client;
86 | }
87 | super.setUIClient(client);
88 | }
89 |
90 | // Call CordovaInterface to start activity for result to make sure
91 | // onActivityResult() callback will be triggered from CordovaActivity correctly.
92 | // Todo(leonhsl) How to handle |options|?
93 | @Override
94 | public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
95 | parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
96 | @Override
97 | public void onActivityResult(int requestCode, int resultCode, Intent intent) {
98 | // Route to XWalkView.
99 | Log.i(TAG, "Route onActivityResult() to XWalkView");
100 | XWalkCordovaView.this.onActivityResult(requestCode, resultCode, intent);
101 | }
102 | }, intent, requestCode);
103 | }
104 |
105 | @Override
106 | public boolean dispatchKeyEvent(KeyEvent event) {
107 | Boolean ret = parentEngine.client.onDispatchKeyEvent(event);
108 | if (ret != null) {
109 | return ret.booleanValue();
110 | }
111 | return super.dispatchKeyEvent(event);
112 | }
113 |
114 | @Override
115 | public void pauseTimers() {
116 | // This is called by XWalkViewInternal.onActivityStateChange().
117 | // We don't want them paused by default though.
118 | }
119 |
120 | public void pauseTimersForReal() {
121 | super.pauseTimers();
122 | }
123 |
124 | @Override
125 | public CordovaWebView getCordovaWebView() {
126 | return parentEngine == null ? null : parentEngine.getCordovaWebView();
127 | }
128 |
129 | @Override
130 | public void setBackgroundColor(int color) {
131 | if (parentEngine != null && parentEngine.isXWalkReady()) {
132 | super.setBackgroundColor(color);
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/platforms/android/src/org/crosswalk/engine/XWalkExposedJsApi.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.crosswalk.engine;
20 |
21 | import android.os.Looper;
22 |
23 | import org.apache.cordova.CordovaBridge;
24 | import org.apache.cordova.ExposedJsApi;
25 | import org.json.JSONException;
26 | import org.xwalk.core.JavascriptInterface;
27 |
28 | class XWalkExposedJsApi implements ExposedJsApi {
29 | private final CordovaBridge bridge;
30 |
31 | XWalkExposedJsApi(CordovaBridge bridge) {
32 | this.bridge = bridge;
33 | }
34 |
35 | @JavascriptInterface
36 | public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
37 | if (Looper.myLooper() == null) {
38 | Looper.prepare();
39 | }
40 | return bridge.jsExec(bridgeSecret, service, action, callbackId, arguments);
41 | }
42 |
43 | @JavascriptInterface
44 | public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
45 | bridge.jsSetNativeToJsBridgeMode(bridgeSecret, value);
46 | }
47 |
48 | @JavascriptInterface
49 | public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
50 | return bridge.jsRetrieveJsMessages(bridgeSecret, fromOnlineEvent);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/platforms/android/src/org/crosswalk/engine/XWalkFileChooser.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 |
20 |
21 | package org.crosswalk.engine;
22 |
23 | import android.app.Activity;
24 | import android.content.Intent;
25 | import android.content.pm.PackageInfo;
26 | import android.content.pm.PackageManager;
27 | import android.content.pm.PackageManager.NameNotFoundException;
28 | import android.Manifest;
29 | import android.net.Uri;
30 | import android.os.Environment;
31 | import android.provider.MediaStore;
32 | import android.util.Log;
33 | import android.webkit.ValueCallback;
34 |
35 | import java.io.File;
36 | import java.io.IOException;
37 | import java.text.SimpleDateFormat;
38 | import java.util.ArrayList;
39 | import java.util.Arrays;
40 | import java.util.Date;
41 |
42 | public class XWalkFileChooser {
43 | private static final String IMAGE_TYPE = "image/";
44 | private static final String VIDEO_TYPE = "video/";
45 | private static final String AUDIO_TYPE = "audio/";
46 | private static final String ALL_IMAGE_TYPES = IMAGE_TYPE + "*";
47 | private static final String ALL_VIDEO_TYPES = VIDEO_TYPE + "*";
48 | private static final String ALL_AUDIO_TYPES = AUDIO_TYPE + "*";
49 | private static final String ANY_TYPES = "*/*";
50 | private static final String SPLIT_EXPRESSION = ",";
51 | private static final String PATH_PREFIX = "file:";
52 | private static final String WRITE_EXTERNAL_STORAGE= "android.permission.WRITE_EXTERNAL_STORAGE";
53 |
54 | public static final int INPUT_FILE_REQUEST_CODE = 1;
55 |
56 | private static final String TAG = "XWalkFileChooser";
57 |
58 | private Activity mActivity;
59 | private ValueCallback mFilePathCallback;
60 | private String mCameraPhotoPath;
61 |
62 | public XWalkFileChooser(Activity activity) {
63 | mActivity = activity;
64 | }
65 |
66 | public boolean showFileChooser(ValueCallback uploadFile, String acceptType,
67 | String capture) {
68 | mFilePathCallback = uploadFile;
69 |
70 | Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
71 | if (takePictureIntent.resolveActivity(mActivity.getPackageManager()) != null) {
72 | // Create the File where the photo should go
73 | File photoFile = createImageFile();
74 | // Continue only if the File was successfully created
75 | if (photoFile != null) {
76 | mCameraPhotoPath = PATH_PREFIX + photoFile.getAbsolutePath();
77 | takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath);
78 | takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
79 | } else {
80 | takePictureIntent = null;
81 | }
82 | }
83 |
84 | Intent camcorder = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
85 | Intent soundRecorder = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
86 | Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
87 | contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
88 | ArrayList extraIntents = new ArrayList();
89 |
90 | // A single mime type.
91 | if (!(acceptType.contains(SPLIT_EXPRESSION) || acceptType.contains(ANY_TYPES))) {
92 | if (capture.equals("true")) {
93 | if (acceptType.startsWith(IMAGE_TYPE)) {
94 | if (takePictureIntent != null) {
95 | mActivity.startActivityForResult(takePictureIntent, INPUT_FILE_REQUEST_CODE);
96 | Log.d(TAG, "Started taking picture");
97 | return true;
98 | }
99 | } else if (acceptType.startsWith(VIDEO_TYPE)) {
100 | mActivity.startActivityForResult(camcorder, INPUT_FILE_REQUEST_CODE);
101 | Log.d(TAG, "Started camcorder");
102 | return true;
103 | } else if (acceptType.startsWith(AUDIO_TYPE)) {
104 | mActivity.startActivityForResult(soundRecorder, INPUT_FILE_REQUEST_CODE);
105 | Log.d(TAG, "Started sound recorder");
106 | return true;
107 | }
108 | } else {
109 | if (acceptType.startsWith(IMAGE_TYPE)) {
110 | if (takePictureIntent != null) {
111 | extraIntents.add(takePictureIntent);
112 | }
113 | contentSelectionIntent.setType(ALL_IMAGE_TYPES);
114 | } else if (acceptType.startsWith(VIDEO_TYPE)) {
115 | extraIntents.add(camcorder);
116 | contentSelectionIntent.setType(ALL_VIDEO_TYPES);
117 | } else if (acceptType.startsWith(AUDIO_TYPE)) {
118 | extraIntents.add(soundRecorder);
119 | contentSelectionIntent.setType(ALL_AUDIO_TYPES);
120 | }
121 | }
122 | }
123 |
124 | // Couldn't resolve an accept type.
125 | if (extraIntents.isEmpty() && canWriteExternalStorage()) {
126 | if (takePictureIntent != null) {
127 | extraIntents.add(takePictureIntent);
128 | }
129 | extraIntents.add(camcorder);
130 | extraIntents.add(soundRecorder);
131 | contentSelectionIntent.setType(ANY_TYPES);
132 | }
133 |
134 | Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
135 | chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
136 | if (!extraIntents.isEmpty()) {
137 | chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,
138 | extraIntents.toArray(new Intent[] { }));
139 | }
140 | mActivity.startActivityForResult(chooserIntent, INPUT_FILE_REQUEST_CODE);
141 | Log.d(TAG, "Started chooser");
142 | return true;
143 | }
144 |
145 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
146 | if(requestCode == INPUT_FILE_REQUEST_CODE && mFilePathCallback != null) {
147 | Log.d(TAG, "Activity result: " + resultCode);
148 | Uri results = null;
149 |
150 | // Check that the response is a good one
151 | if(Activity.RESULT_OK == resultCode) {
152 | // In Android M, camera results return an empty Intent rather than null.
153 | if(data == null || (data.getAction() == null && data.getData() == null)) {
154 | // If there is not data, then we may have taken a photo
155 | if(mCameraPhotoPath != null) {
156 | results = Uri.parse(mCameraPhotoPath);
157 | }
158 | } else {
159 | String dataString = data.getDataString();
160 | if (dataString != null) {
161 | results = Uri.parse(dataString);
162 | }
163 | deleteImageFile();
164 | }
165 | } else if (Activity.RESULT_CANCELED == resultCode) {
166 | deleteImageFile();
167 | }
168 |
169 | if (results != null) {
170 | Log.d(TAG, "Received file: " + results.toString());
171 | }
172 | mFilePathCallback.onReceiveValue(results);
173 | mFilePathCallback = null;
174 | }
175 | }
176 |
177 | private boolean canWriteExternalStorage() {
178 | try {
179 | PackageManager packageManager = mActivity.getPackageManager();
180 | PackageInfo packageInfo = packageManager.getPackageInfo(
181 | mActivity.getPackageName(), PackageManager.GET_PERMISSIONS);
182 | return Arrays.asList(packageInfo.requestedPermissions).contains(WRITE_EXTERNAL_STORAGE);
183 | } catch (NameNotFoundException e) {
184 | return false;
185 | } catch (NullPointerException e) {
186 | return false;
187 | }
188 | }
189 |
190 | private File createImageFile() {
191 | // FIXME: If the external storage state is not "MEDIA_MOUNTED", we need to get
192 | // other volume paths by "getVolumePaths()" when it was exposed.
193 | String state = Environment.getExternalStorageState();
194 | if (!state.equals(Environment.MEDIA_MOUNTED)) {
195 | Log.e(TAG, "External storage is not mounted.");
196 | return null;
197 | }
198 |
199 | // Create an image file name
200 | String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
201 | String imageFileName = "JPEG_" + timeStamp + "_";
202 | File storageDir = Environment.getExternalStoragePublicDirectory(
203 | Environment.DIRECTORY_PICTURES);
204 | if (!storageDir.exists()) {
205 | storageDir.mkdirs();
206 | }
207 |
208 | try {
209 | File file = File.createTempFile(imageFileName, ".jpg", storageDir);
210 | Log.d(TAG, "Created image file: " + file.getAbsolutePath());
211 | return file;
212 | } catch (IOException e) {
213 | // Error occurred while creating the File
214 | Log.e(TAG, "Unable to create Image File, " +
215 | "please make sure permission 'WRITE_EXTERNAL_STORAGE' was added.");
216 | return null;
217 | }
218 | }
219 |
220 | private boolean deleteImageFile() {
221 | if (mCameraPhotoPath == null || !mCameraPhotoPath.contains(PATH_PREFIX)) {
222 | return false;
223 | }
224 | String filePath = mCameraPhotoPath.split(PATH_PREFIX)[1];
225 | boolean result = new File(filePath).delete();
226 | Log.d(TAG, "Delete image file: " + filePath + " result: " + result);
227 | return result;
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/platforms/android/src/org/crosswalk/engine/XWalkWebViewEngine.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 |
20 | package org.crosswalk.engine;
21 |
22 | import android.app.Activity;
23 | import android.content.Context;
24 | import android.content.Intent;
25 | import android.content.pm.PackageInfo;
26 | import android.content.pm.PackageManager;
27 | import android.content.pm.PackageManager.NameNotFoundException;
28 | import android.content.res.AssetManager;
29 | import android.graphics.Bitmap;
30 | import android.graphics.Color;
31 | import android.Manifest;
32 | import android.util.Log;
33 | import android.view.View;
34 | import android.webkit.ValueCallback;
35 |
36 | import java.io.File;
37 | import java.io.IOException;
38 | import java.util.ArrayList;
39 |
40 | import org.apache.cordova.CordovaBridge;
41 | import org.apache.cordova.CordovaInterface;
42 | import org.apache.cordova.CordovaPlugin;
43 | import org.apache.cordova.CordovaPreferences;
44 | import org.apache.cordova.CordovaResourceApi;
45 | import org.apache.cordova.CordovaWebView;
46 | import org.apache.cordova.CordovaWebViewEngine;
47 | import org.apache.cordova.ICordovaCookieManager;
48 | import org.apache.cordova.NativeToJsMessageQueue;
49 | import org.apache.cordova.PluginEntry;
50 | import org.apache.cordova.PluginManager;
51 | import org.xwalk.core.XWalkActivityDelegate;
52 | import org.xwalk.core.XWalkNavigationHistory;
53 | import org.xwalk.core.XWalkView;
54 | import org.xwalk.core.XWalkGetBitmapCallback;
55 |
56 | /**
57 | * Glue class between CordovaWebView (main Cordova logic) and XWalkCordovaView (the actual View).
58 | */
59 | public class XWalkWebViewEngine implements CordovaWebViewEngine {
60 |
61 | public static final String TAG = "XWalkWebViewEngine";
62 | public static final String XWALK_USER_AGENT = "xwalkUserAgent";
63 | public static final String XWALK_Z_ORDER_ON_TOP = "xwalkZOrderOnTop";
64 |
65 | private static final String XWALK_EXTENSIONS_FOLDER = "xwalk-extensions";
66 |
67 | private static final int PERMISSION_REQUEST_CODE = 100;
68 |
69 | protected final XWalkCordovaView webView;
70 | protected XWalkCordovaCookieManager cookieManager;
71 | protected CordovaBridge bridge;
72 | protected CordovaWebViewEngine.Client client;
73 | protected CordovaWebView parentWebView;
74 | protected CordovaInterface cordova;
75 | protected PluginManager pluginManager;
76 | protected CordovaResourceApi resourceApi;
77 | protected NativeToJsMessageQueue nativeToJsMessageQueue;
78 | protected XWalkActivityDelegate activityDelegate;
79 | protected String startUrl;
80 | protected CordovaPreferences preferences;
81 |
82 | /** Used when created via reflection. */
83 | public XWalkWebViewEngine(Context context, CordovaPreferences preferences) {
84 | this.preferences = preferences;
85 | Runnable cancelCommand = new Runnable() {
86 | @Override
87 | public void run() {
88 | cordova.getActivity().finish();
89 | }
90 | };
91 | Runnable completeCommand = new Runnable() {
92 | @Override
93 | public void run() {
94 | cookieManager = new XWalkCordovaCookieManager();
95 |
96 | initWebViewSettings();
97 | exposeJsInterface(webView, bridge);
98 | loadExtensions();
99 |
100 | CordovaPlugin notifPlugin = new CordovaPlugin() {
101 | @Override
102 | public void onNewIntent(Intent intent) {
103 | Log.i(TAG, "notifPlugin route onNewIntent() to XWalkView: " + intent.toString());
104 | XWalkWebViewEngine.this.webView.onNewIntent(intent);
105 | }
106 |
107 | @Override
108 | public Object onMessage(String id, Object data) {
109 | if (id.equals("captureXWalkBitmap")) {
110 | // Capture bitmap on UI thread.
111 | XWalkWebViewEngine.this.cordova.getActivity().runOnUiThread(new Runnable() {
112 | public void run() {
113 | XWalkWebViewEngine.this.webView.captureBitmapAsync(
114 | new XWalkGetBitmapCallback() {
115 | @Override
116 | public void onFinishGetBitmap(Bitmap bitmap,
117 | int response) {
118 | pluginManager.postMessage(
119 | "onGotXWalkBitmap", bitmap);
120 | }
121 | });
122 | }
123 | });
124 | }
125 | return null;
126 | }
127 | };
128 | pluginManager.addService(new PluginEntry("XWalkNotif", notifPlugin));
129 |
130 | // Send the massage of xwalk's ready to plugin.
131 | if (pluginManager != null) {
132 | pluginManager.postMessage("onXWalkReady", this);
133 | }
134 |
135 | if (startUrl != null) {
136 | webView.load(startUrl, null);
137 | }
138 | }
139 | };
140 | activityDelegate = new XWalkActivityDelegate((Activity) context, cancelCommand, completeCommand);
141 |
142 | webView = new XWalkCordovaView(context, preferences);
143 | }
144 |
145 | // Use two-phase init so that the control will work with XML layouts.
146 |
147 | @Override
148 | public void init(CordovaWebView parentWebView, CordovaInterface cordova, CordovaWebViewEngine.Client client,
149 | CordovaResourceApi resourceApi, PluginManager pluginManager,
150 | NativeToJsMessageQueue nativeToJsMessageQueue) {
151 | if (this.cordova != null) {
152 | throw new IllegalStateException();
153 | }
154 | this.parentWebView = parentWebView;
155 | this.cordova = cordova;
156 | this.client = client;
157 | this.resourceApi = resourceApi;
158 | this.pluginManager = pluginManager;
159 | this.nativeToJsMessageQueue = nativeToJsMessageQueue;
160 |
161 | CordovaPlugin activityDelegatePlugin = new CordovaPlugin() {
162 | @Override
163 | public void onResume(boolean multitasking) {
164 | activityDelegate.onResume();
165 | }
166 | };
167 | pluginManager.addService(new PluginEntry("XWalkActivityDelegate", activityDelegatePlugin));
168 |
169 | webView.init(this);
170 |
171 | nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode(
172 | new NativeToJsMessageQueue.OnlineEventsBridgeMode.OnlineEventsBridgeModeDelegate() {
173 | @Override
174 | public void setNetworkAvailable(boolean value) {
175 | webView.setNetworkAvailable(value);
176 | }
177 | @Override
178 | public void runOnUiThread(Runnable r) {
179 | XWalkWebViewEngine.this.cordova.getActivity().runOnUiThread(r);
180 | }
181 | }));
182 | nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.EvalBridgeMode(this, cordova));
183 | bridge = new CordovaBridge(pluginManager, nativeToJsMessageQueue);
184 | }
185 |
186 | @Override
187 | public CordovaWebView getCordovaWebView() {
188 | return parentWebView;
189 | }
190 |
191 | @Override
192 | public View getView() {
193 | return webView;
194 | }
195 |
196 | private void initWebViewSettings() {
197 | webView.setVerticalScrollBarEnabled(false);
198 |
199 | boolean zOrderOnTop = preferences == null ? false : preferences.getBoolean(XWALK_Z_ORDER_ON_TOP, false);
200 | webView.setZOrderOnTop(zOrderOnTop);
201 |
202 | // Set xwalk webview settings by Cordova preferences.
203 | String xwalkUserAgent = preferences == null ? "" : preferences.getString(XWALK_USER_AGENT, "");
204 | if (!xwalkUserAgent.isEmpty()) {
205 | webView.setUserAgentString(xwalkUserAgent);
206 | }
207 |
208 | String appendUserAgent = preferences.getString("AppendUserAgent", "");
209 | if (!appendUserAgent.isEmpty()) {
210 | webView.setUserAgentString(webView.getUserAgentString() + " " + appendUserAgent);
211 | }
212 |
213 | if (preferences.contains("BackgroundColor")) {
214 | int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK);
215 | webView.setBackgroundColor(backgroundColor);
216 | }
217 | }
218 |
219 | private static void exposeJsInterface(XWalkView webView, CordovaBridge bridge) {
220 | XWalkExposedJsApi exposedJsApi = new XWalkExposedJsApi(bridge);
221 | webView.addJavascriptInterface(exposedJsApi, "_cordovaNative");
222 | }
223 |
224 | private void loadExtensions() {
225 | AssetManager assetManager = cordova.getActivity().getAssets();
226 | String[] extList;
227 | try {
228 | Log.i(TAG, "Iterate assets/xwalk-extensions folder");
229 | extList = assetManager.list(XWALK_EXTENSIONS_FOLDER);
230 | } catch (IOException e) {
231 | Log.w(TAG, "Failed to iterate assets/xwalk-extensions folder");
232 | return;
233 | }
234 |
235 | for (String path : extList) {
236 | // Load the extension.
237 | Log.i(TAG, "Start to load extension: " + path);
238 | webView.getExtensionManager().loadExtension(XWALK_EXTENSIONS_FOLDER + File.separator + path);
239 | }
240 | }
241 |
242 | @Override
243 | public boolean canGoBack() {
244 | if (!activityDelegate.isXWalkReady()) return false;
245 | return this.webView.getNavigationHistory().canGoBack();
246 | }
247 |
248 | @Override
249 | public boolean goBack() {
250 | if (this.webView.getNavigationHistory().canGoBack()) {
251 | this.webView.getNavigationHistory().navigate(XWalkNavigationHistory.Direction.BACKWARD, 1);
252 | return true;
253 | }
254 | return false;
255 | }
256 |
257 | @Override
258 | public void setPaused(boolean value) {
259 | if (!activityDelegate.isXWalkReady()) return;
260 | if (value) {
261 | // TODO: I think this has been fixed upstream and we don't need to override pauseTimers() anymore.
262 | webView.pauseTimersForReal();
263 | } else {
264 | webView.resumeTimers();
265 | }
266 | }
267 |
268 | @Override
269 | public void destroy() {
270 | if (!activityDelegate.isXWalkReady()) return;
271 | webView.onDestroy();
272 | }
273 |
274 | @Override
275 | public void clearHistory() {
276 | if (!activityDelegate.isXWalkReady()) return;
277 | this.webView.getNavigationHistory().clear();
278 | }
279 |
280 | @Override
281 | public void stopLoading() {
282 | if (!activityDelegate.isXWalkReady()) return;
283 | this.webView.stopLoading();
284 | }
285 |
286 | @Override
287 | public void clearCache() {
288 | if (!activityDelegate.isXWalkReady()) return;
289 | webView.clearCache(true);
290 | }
291 |
292 | @Override
293 | public String getUrl() {
294 | if (!activityDelegate.isXWalkReady()) return null;
295 | return this.webView.getUrl();
296 | }
297 |
298 | @Override
299 | public ICordovaCookieManager getCookieManager() {
300 | return cookieManager;
301 | }
302 |
303 | @Override
304 | public void loadUrl(String url, boolean clearNavigationStack) {
305 | if (!activityDelegate.isXWalkReady()) {
306 | startUrl = url;
307 | return;
308 | }
309 | webView.load(url, null);
310 | }
311 |
312 | /**
313 | * This API is used in Cordova-Android 6.0.0 override from
314 | *
315 | * CordovaWebViewEngine.java
316 | * @since Cordova 6.0
317 | */
318 | public void evaluateJavascript(String js, ValueCallback callback) {
319 | webView.evaluateJavascript(js, callback);
320 | }
321 |
322 | public boolean isXWalkReady() {
323 | return activityDelegate.isXWalkReady();
324 | }
325 |
326 | public interface PermissionRequestListener {
327 | public void onRequestPermissionResult(int requestCode, String[] permissions,
328 | int[] grantResults);
329 | }
330 |
331 | public boolean requestPermissionsForFileChooser(final PermissionRequestListener listener) {
332 | ArrayList dangerous_permissions = new ArrayList();
333 | try {
334 | PackageManager packageManager = cordova.getActivity().getPackageManager();
335 | PackageInfo packageInfo = packageManager.getPackageInfo(
336 | cordova.getActivity().getPackageName(), PackageManager.GET_PERMISSIONS);
337 | for (String permission : packageInfo.requestedPermissions) {
338 | if (permission.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)
339 | || permission.equals(Manifest.permission.CAMERA)) {
340 | dangerous_permissions.add(permission);
341 | }
342 | }
343 | } catch (NameNotFoundException e) {
344 | }
345 |
346 | if (dangerous_permissions.isEmpty()) {
347 | return false;
348 | }
349 |
350 | CordovaPlugin permissionRequestPlugin = new CordovaPlugin() {
351 | @Override
352 | public void onRequestPermissionResult(int requestCode, String[] permissions,
353 | int[] grantResults) {
354 | if (requestCode != PERMISSION_REQUEST_CODE) return;
355 | listener.onRequestPermissionResult(requestCode, permissions, grantResults);
356 | }
357 | };
358 | try {
359 | cordova.requestPermissions(permissionRequestPlugin, PERMISSION_REQUEST_CODE,
360 | dangerous_permissions.toArray(new String[dangerous_permissions.size()]));
361 | } catch (NoSuchMethodError e) {
362 | return false;
363 | }
364 | return true;
365 | }
366 | }
367 |
--------------------------------------------------------------------------------
/platforms/android/xwalk.gradle:
--------------------------------------------------------------------------------
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 | def EMBEDDED_MODE = "embedded"
21 | def SHARED_MODE = "shared"
22 | def LITE_MODE = "lite"
23 | def DEFAULT_GROUP_ID = "org.xwalk:"
24 | def SHARED_ARTIFACT_ID = "xwalk_shared_library:"
25 | def EMBEDD_ARTIFACT_ID = "xwalk_core_library:"
26 | def CANARY_ARTIFACT_ID = "xwalk_core_library_canary:"
27 | def BIT_64 = ":64bit@aar"
28 | def DEFAULT_MIN_SDK_VERSION = 14
29 |
30 | def getConfigPreference(name) {
31 | name = name.toLowerCase()
32 | def xml = file("res/xml/config.xml").getText()
33 | // Disable namespace awareness since Cordova doesn't use them properly
34 | def root = new XmlParser(false, false).parseText(xml)
35 |
36 | def ret, defaultValue
37 | root.preference.each { it ->
38 | def attrName = it.attribute("name")
39 | if (attrName && attrName.toLowerCase() == name) {
40 | if (it.attribute('default') != null) {
41 | defaultValue = it.attribute('default');
42 | } else {
43 | ret = it.attribute("value")
44 | }
45 | }
46 | }
47 | return ret ? ret : defaultValue
48 | }
49 |
50 | if (!project.hasProperty('xwalk64bit')) {
51 | ext.xwalk64bit = getConfigPreference("xwalk64bit");
52 | println xwalk64bit
53 | }
54 | if (cdvBuildMultipleApks == null) {
55 | ext.xwalkMultipleApk = getConfigPreference("xwalkMultipleApk").toBoolean();
56 | } else {
57 | ext.xwalkMultipleApk = cdvBuildMultipleApks.toBoolean();
58 | }
59 |
60 | def minSdk = getConfigPreference("android-minSdkVersion");
61 | if (cdvMinSdkVersion == null) {
62 | ext.cdvMinSdkVersion = minSdk && Integer.parseInt(minSdk) > DEFAULT_MIN_SDK_VERSION ? minSdk : DEFAULT_MIN_SDK_VERSION;
63 | } else if (Integer.parseInt('' + cdvMinSdkVersion) < Integer.parseInt(minSdk)) {
64 | ext.cdvMinSdkVersion = minSdk;
65 | }
66 |
67 | if (!project.hasProperty('xwalkMode')) {
68 | ext.xwalkMode = getConfigPreference("xwalkMode");
69 | }
70 |
71 |
72 | if (ext.xwalkMode == SHARED_MODE) {
73 | // Build one apk at shared mode because the value of
74 | // ext.cdvBuildMultipleApks is false by default.
75 | xwalk64bit = null;
76 | } else if (xwalk64bit == null) {
77 | // Build embedded 32 bit crosswalk will generate two apks by default.
78 | ext.cdvBuildMultipleApks = xwalkMultipleApk;
79 | }
80 |
81 | // Set defaults before project's build-extras.gradle
82 | if (!project.hasProperty('xwalkVersion')) {
83 | ext.xwalkVersion = getConfigPreference("xwalkVersion")
84 | }
85 |
86 | // Set defaults before project's build-extras.gradle
87 | if (!project.hasProperty('xwalkLiteVersion')) {
88 | ext.xwalkLiteVersion = getConfigPreference("xwalkLiteVersion")
89 | }
90 |
91 | if (!project.hasProperty('xwalkCommandLine')) {
92 | ext.xwalkCommandLine = getConfigPreference("xwalkCommandLine")
93 | }
94 | // Apply values after project's build-extras.gradle
95 | cdvPluginPostBuildExtras.add({
96 | def xwalkMavenRepo = 'https://download.01.org/crosswalk/releases/crosswalk/android/maven2';
97 | if (xwalkMode == LITE_MODE) {
98 | xwalkMavenRepo = 'https://download.01.org/crosswalk/releases/crosswalk-lite/android/maven2';
99 | }
100 | repositories {
101 | maven {
102 | url xwalkMavenRepo
103 | }
104 | }
105 |
106 | android {
107 | if (xwalk64bit != null) {
108 | productFlavors {
109 | x86_64 {
110 | versionCode defaultConfig.versionCode + 6
111 | ndk {
112 | abiFilters "x86_64", ""
113 | }
114 | }
115 | arm64 {
116 | versionCode defaultConfig.versionCode + 9
117 | ndk {
118 | abiFilters "arm64-v8a", ""
119 | }
120 | }
121 | }
122 | }
123 | }
124 |
125 | def xwalkSpec = xwalkVersion
126 | if (ext.xwalkMode == LITE_MODE) {
127 | xwalkSpec = xwalkLiteVersion;
128 | }
129 |
130 | if ((xwalkSpec =~ /:/).count == 1) {
131 | xwalkSpec = DEFAULT_GROUP_ID + xwalkSpec
132 | } else if ((xwalkSpec =~ /:/).count == 0) {
133 | if (xwalkSpec ==~ /\d+/) {
134 | xwalkSpec = "${xwalkSpec}+"
135 | }
136 |
137 | def artifactid = EMBEDD_ARTIFACT_ID;
138 | if (ext.xwalkMode == SHARED_MODE) {
139 | artifactid = SHARED_ARTIFACT_ID;
140 | } else if (ext.xwalkMode == LITE_MODE) {
141 | artifactid = CANARY_ARTIFACT_ID;
142 | }
143 | xwalkSpec = DEFAULT_GROUP_ID + artifactid + xwalkSpec
144 | }
145 | if (xwalk64bit != null) {
146 | xwalkSpec = xwalkSpec + BIT_64
147 | }
148 | println xwalkSpec
149 |
150 | dependencies {
151 | compile xwalkSpec
152 | }
153 |
154 | if (file('assets/xwalk-command-line').exists()) {
155 | println('Not writing assets/xwalk-command-line since file already exists.')
156 | return
157 | }
158 | android.applicationVariants.all { variant ->
159 | def variantName = variant.name.capitalize()
160 | def mergeTask = tasks["merge${variantName}Assets"]
161 | def processTask = tasks["process${variantName}Resources"]
162 | def outFile = new File (mergeTask.outputDir, "xwalk-command-line")
163 | def newTask = project.task("createXwalkCommandLineFile${variantName}") << {
164 | mergeTask.outputDir.mkdirs()
165 | outFile.write("xwalk ${xwalkCommandLine}\n")
166 | }
167 | newTask.dependsOn(mergeTask)
168 | processTask.dependsOn(newTask)
169 | }
170 | })
171 |
--------------------------------------------------------------------------------
/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 | Crosswalk WebView Engine
9 | Changes the default WebView to CrossWalk
10 | Apache 2.0
11 | cordova,chromium,crosswalk,webview
12 | https://github.com/crosswalk-project/cordova-plugin-crosswalk-webview
13 | https://crosswalk-project.org/jira
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 |
60 |
61 | After much discussion and analysis of the market, we have decided to discontinue support for Android 4.0 (ICS) in Crosswalk starting with version 20.
62 |
63 | So the minSdkVersion of Cordova project is configured to 16 by default.
64 |
65 |
66 |
--------------------------------------------------------------------------------