Compare commits

..

142 Commits

Author SHA1 Message Date
Ian Clelland
198364afb9 Update native tests 2014-10-30 15:08:26 -04:00
Ian Clelland
ed78b557cd Remove whitelist config.xml parsing 2014-10-30 12:19:06 -04:00
Ian Clelland
83377d366a Remove whitelists from WebView classes 2014-10-30 12:19:06 -04:00
Ian Clelland
8df2d4fcfd Remove unused Config methods (Breaking Change) 2014-10-30 12:19:06 -04:00
Ian Clelland
a0acb4ce9a Refactor ConfigXmlParser to allow subclasses 2014-10-30 12:19:06 -04:00
Ian Clelland
fe15d34a80 Use /app_webview/ rather than app_webview to filter bad requests 2014-10-30 12:19:06 -04:00
Ian Clelland
23584274d2 Defer whitelist decisions to plugins
There is a default policy, which is implemented in the case where no plugins override any of the whitelist methods:
 * Error URLs must start with file://
 * Navigation is allowed to file:// and data: URLs which do not contain "app_webview"
 * External URLs do not launch intents
 * XHRs are allowed to file:// and data: URLs which do not contain "app_webview"
2014-10-30 12:19:06 -04:00
Ian Clelland
44aa98887f Add hooks in CordovaPlugin and PluginManager for whitelist plugins
This adds three hooks to CordovaPlugin objects. In each case, a null
value can be returned to indicate "I don't care". This null value is
the default.

    public Boolean shouldAllowRequest(String url)
    public Boolean shouldAllowNavigation(String url)
    public Boolean shouldOpenExternalUrl(String url)
2014-10-30 12:19:05 -04:00
Andrew Grieve
cc7d352209 Merge branch 'master' into 4.0.x (gradle signing+SecureRandom) 2014-10-21 13:00:07 -04:00
Andrew Grieve
7ad16e5b0c Merge branch 'master' into 4.0.x (Hardcode activity name) 2014-10-07 15:25:56 -04:00
Andrew Grieve
2af8daff1d Merge branch 'master' into 4.0.x (multipart PluginResult) 2014-10-07 15:18:07 -04:00
Andrew Grieve
9577735ff7 Merge branch 'master' into 4.0.x (check_reqs for brew) 2014-10-06 10:34:19 -04:00
Andrew Grieve
862c223e11 Merge branch 'master' into 4.0.x (.gitignore, create --shared) 2014-10-04 15:30:30 -04:00
Andrew Grieve
7f4d5aeb0e Merge branch 'master' into 4.0.x (move preference activation, alert dialog leak)
Conflicts:
	framework/src/org/apache/cordova/AndroidChromeClient.java
	framework/src/org/apache/cordova/CordovaActivity.java
	framework/src/org/apache/cordova/CordovaWebView.java
	test/src/org/apache/cordova/test/menus.java

closes #123
2014-10-04 15:30:25 -04:00
Andrew Grieve
e5efc91ef4 Merge branch 'master' into 4.0.x (JAVA_HOME on Ubuntu) 2014-09-29 10:16:33 -04:00
Andrew Grieve
6d5b88d7b9 Merge branch 'master' into 4.0.x (per-arch gradle builds) 2014-09-24 16:18:51 -04:00
Andrew Grieve
f7f49d27c5 Merge branch 'master' into 4.0.x (gradle Android Studio) 2014-09-23 21:04:55 -04:00
Max Woghiren
75a0a6752a Improved a regex. 2014-09-23 14:27:54 -04:00
Andrew Grieve
363fc8deb5 Merge branch 'master' into 4.0.x (gradle plugin template)
Conflicts:
	bin/templates/cordova/lib/build.js
	framework/build.gradle
2014-09-22 22:28:59 -04:00
Max Woghiren
b09f973231 Added gradle distribution URL updating. 2014-09-22 16:47:01 -04:00
Andrew Grieve
95815a558c Merge branch 'master' into 4.0.x (fix ant run command) 2014-09-22 15:38:59 -04:00
Andrew Grieve
d022be547b Merge branch 'master' into 4.0.x (gradle) 2014-09-17 21:27:55 -04:00
Ian Clelland
215adab1f9 Merge branch 'master' into 4.0.x (Gradle env vars) 2014-09-17 15:58:46 -04:00
Andrew Grieve
c32bcca67b Merge branch 'master' into 4.0.x (gradle optional password) 2014-09-17 15:30:20 -04:00
Andrew Grieve
6bdc01290d Merge branch 'master' into 4.0.x (gradle fix) 2014-09-16 15:15:11 -04:00
Andrew Grieve
6fb164d200 Merge branch 'master' into 4.0.x (unaligned apk fix) 2014-09-16 15:00:54 -04:00
Andrew Grieve
6eb4409a72 Merge branch 'master' into 4.0.x (gradle debug v release) 2014-09-16 14:14:46 -04:00
Andrew Grieve
8f27b2ab56 Merge branch 'master' into 4.0.x (gradle fixes) 2014-09-16 13:02:55 -04:00
Andrew Grieve
a10106c61a Merge branch 'master' into 4.0.x (x86 deploy) 2014-09-15 14:24:45 -04:00
Ian Clelland
4c1efe7ad4 Merge branch 'master' into 4.0.x 2014-09-15 12:16:03 -04:00
Michal Mocny
be01ce03d0 Fix invalid syntax (missing + in multiline string) 2014-09-12 17:00:29 -04:00
Andrew Grieve
18fda7ec68 Merge branch 'master' into 4.0.x (more error message) 2014-09-12 16:18:12 -04:00
Andrew Grieve
30e8b818f5 Merge branch 'master' into 4.0.x (error messages) 2014-09-12 14:21:47 -04:00
Andrew Grieve
3cd567dc95 Merge branch 'master' into 4.0.x (better auto-detect sdk) 2014-09-11 16:01:01 -04:00
Ian Clelland
d99386ef1e Merge branch 'master' into 4.0.x 2014-09-11 15:12:31 -04:00
Ian Clelland
dd5a337a49 Merge branch 'master' into 4.0.x
Conflicts:
	framework/src/org/apache/cordova/CordovaActivity.java
2014-09-11 10:18:35 -04:00
Ian Clelland
51e634ccb4 Merge branch 'master' into 4.0.x (up to 3.7.0-dev)
Conflicts:
	VERSION
	bin/templates/cordova/version
	framework/src/org/apache/cordova/CordovaWebView.java
	package.json
	test/src/org/apache/cordova/test/basicauth.java
	test/src/org/apache/cordova/test/menus.java
2014-09-11 10:16:46 -04:00
Ian Clelland
31b1a821ca Merge branch 'master' into 4.0.x (External whitelist changes) 2014-09-11 10:08:45 -04:00
Ian Clelland
0b6b068097 CB-3445: Allow build and run scripts to select APK by architecture 2014-08-29 16:00:54 -04:00
Ian Clelland
623b2306ca CB-3445: Add environment variable 'BUILD_MULTIPLE_APKS' for splitting APKs based on architecture 2014-08-28 16:18:51 -04:00
Ian Clelland
233e513860 CB-3445: Ensure that JAR files in libs directory are included 2014-08-28 11:26:58 -04:00
Ian Clelland
7caa96abcd Fix previous merges
Build scripts (create and build) were mangled somewhat by the previous
merge commits. This resets them to (almost) exactly the same state as
the 3.6.x (master) branch.

Conflicts:
	bin/lib/create.js
2014-08-20 11:45:35 -04:00
Ian Clelland
b2776269cf Merge branch 'master' into 4.0.x (Gradle library dependencies) 2014-08-20 11:43:58 -04:00
Andrew Grieve
4c1942e3fe Merge branch 'master' into 4.0.x (build & create script updates)
Conflicts:
	bin/lib/check_reqs.js
	bin/lib/create.js
	bin/node_modules/which/package.json
	bin/templates/cordova/lib/build.js
2014-08-19 12:02:36 -04:00
Joe Bowser
a7ccb9243d Merging latest master, including new tests 2014-08-14 14:20:49 -07:00
Ian Clelland
f9b8f9a45f CB-7159: Fix setBackgroundColor() call to support 4.0.x view classes 2014-08-11 13:25:21 -04:00
Andrew Grieve
5054b714e2 Set version to 4.0.0-dev in package.json 2014-07-25 20:03:42 -04:00
Andrew Grieve
05868b541b Merge branch 'master' into 4.0.x (background color) 2014-07-21 15:47:03 -04:00
Andrew Grieve
a40424e75c Merge branch 'master' into 4.0.x (setButtonPlumbedToJs)
Conflicts:
	framework/src/org/apache/cordova/CordovaWebView.java
2014-07-18 13:49:04 -04:00
Andrew Grieve
a99c8219bd Make private PluginManager.clearPluginObjects, .startupPlugins 2014-07-14 14:30:10 -04:00
Andrew Grieve
a03fdaba39 Merge branch 'master' into 4.0.x (undeprecate) 2014-07-14 14:28:56 -04:00
Andrew Grieve
6f301576eb Mark PluginEntry fields as final
Makes the intention of the class more clear, and the public fields less
bad.
2014-07-14 14:26:21 -04:00
Andrew Grieve
e2b3f76a10 Merge branch 'master' into 4.0.x (PluginEntry refactor)
Conflicts:
	framework/src/org/apache/cordova/PluginEntry.java
	framework/src/org/apache/cordova/PluginManager.java
	test/src/org/apache/cordova/test/CordovaWebViewTestActivity.java
2014-07-14 14:18:10 -04:00
Andrew Grieve
b277202838 Add PluginManager.setPluginEntries, delete setPluginWhitelist 2014-07-10 16:39:46 -04:00
Andrew Grieve
a4f6d9f6e7 Merge branch 'master' into 4.0.x (unbreak compile) 2014-07-10 15:14:57 -04:00
Andrew Grieve
1d4aa44d3d Merge branch 'master' into 4.0.x (CordovaPlugin.pluginInitialize tweak)
Conflicts:
	framework/src/org/apache/cordova/CordovaPlugin.java
2014-07-10 15:05:40 -04:00
Andrew Grieve
b52fcb8aa9 Merge branch 'master' into 4.0.x (CordovaBridge tweaks)
Conflicts:
	framework/src/org/apache/cordova/CordovaActivity.java
2014-07-10 11:36:58 -04:00
Andrew Grieve
f0da63a8ff Merge branch 'master' into 4.0.x (backport of CordovaBridge) 2014-07-10 10:45:55 -04:00
Andrew Grieve
f38c460588 Merge branch 'master' into 4.0.x (Unbreak unit tests)
Conflicts:
	test/src/org/apache/cordova/test/junit/GapClientTest.java
	test/src/org/apache/cordova/test/userwebview.java
	test/src/org/apache/cordova/test/whitelist.java
2014-07-10 10:30:05 -04:00
Andrew Grieve
9b9c59766f Add back CordovaWebView.getUrl() - needed by tests & does make sense to have 2014-07-10 10:15:34 -04:00
Andrew Grieve
fc2a202afa Log friendlier messages when bridge calls are recieved from previous page 2014-07-10 10:14:47 -04:00
Andrew Grieve
4b4b71ff32 CordovaActivity: don't create WebView until loadUrl() so that apps can tweak preferences after super.onCreate() 2014-07-10 10:10:38 -04:00
Andrew Grieve
9358838dab Merge branch 'master' into 4.0.x (unit test tweaks)
Conflicts:
	framework/src/org/apache/cordova/CordovaWebView.java
2014-07-09 21:12:45 -04:00
Andrew Grieve
a4d9f702e4 Merge branch 'master' into 4.0.x (cordova.js snapshot) 2014-07-09 13:32:10 -04:00
Andrew Grieve
efcedabee0 Delete Cordova*Client classes, Create CordovaBridge, Delete more CordovaWebView symbols
Changes made in order to get xwalk working again
2014-07-09 09:29:33 -04:00
Andrew Grieve
25a7b66296 Delete deprecated methods from PluginManager 2014-07-08 14:46:05 -04:00
Andrew Grieve
ac194cd34f Merge branch 'master' into 4.0.x (remove Config.* references)
Conflicts:
	framework/src/org/apache/cordova/CordovaActivity.java
	framework/src/org/apache/cordova/CordovaChromeClient.java
	framework/src/org/apache/cordova/CordovaWebView.java
2014-07-08 14:45:41 -04:00
Andrew Grieve
eca05e6bad Delete deprecated symbols from CordovaActivity (4.0.x) 2014-07-08 12:15:34 -04:00
Andrew Grieve
84bf20152b Merge branch 'master' into 4.0.x (CordovaUriHelper visibility) 2014-07-08 12:06:04 -04:00
Andrew Grieve
200e9f1a8e Delete deprecated classes: DirectoryManager, DroidGap, ExifHelper (4.0.x) 2014-07-08 12:04:40 -04:00
Andrew Grieve
7dc09b4019 Delete JSONUtils.java (in 4.0.x only) 2014-07-08 12:00:02 -04:00
Andrew Grieve
dbb196a17e Delete url-filters logic (in 4.0.x branch only) 2014-07-08 11:58:56 -04:00
Andrew Grieve
05a95c699f Merge branch 'master' into 4.0.x (Fix setPluginEntries) 2014-07-08 11:55:04 -04:00
Andrew Grieve
9c5e340fb8 Merge branch 'master' into 4.0.x (App plugin from config.xml -> code)
Conflicts:
	bin/templates/cordova/defaults.xml
	framework/res/xml/config.xml
	framework/src/org/apache/cordova/CordovaWebView.java
2014-07-08 11:52:44 -04:00
Andrew Grieve
67006add53 Merge branch 'master' into 4.0.x (tweaks to setWebViewClient)
Conflicts:
	framework/src/org/apache/cordova/CordovaWebView.java
	framework/src/org/apache/cordova/CordovaWebViewClient.java
2014-07-07 16:38:50 -04:00
Andrew Grieve
1571b26a65 Merge branch 'master' into 4.0.x (ConfigXmlParser + two-phase init)
Conflicts:
	framework/src/org/apache/cordova/CordovaActivity.java
	framework/src/org/apache/cordova/CordovaChromeClient.java
	framework/src/org/apache/cordova/CordovaWebView.java
	framework/src/org/apache/cordova/CordovaWebViewClient.java
	framework/src/org/apache/cordova/PluginManager.java
2014-07-07 16:23:51 -04:00
Andrew Grieve
bdf2f22f81 Merge branch 'master' into 4.0.x (ConfigXmlParser breakout)
Conflicts:
	framework/src/org/apache/cordova/CordovaActivity.java
	framework/src/org/apache/cordova/PluginManager.java
2014-07-04 11:48:49 -04:00
Andrew Grieve
a8330773ca Add missing changes from previous merge commit 2014-07-04 10:32:02 -04:00
Andrew Grieve
4ca2305693 Merge branch 'master' into 4.0.x (Bridge fixes)
Conflicts:
	framework/src/org/apache/cordova/CordovaChromeClient.java
	framework/src/org/apache/cordova/CordovaUriHelper.java
	framework/src/org/apache/cordova/CordovaWebView.java
	framework/src/org/apache/cordova/CordovaWebViewClient.java
	framework/src/org/apache/cordova/ExposedJsApi.java
	framework/src/org/apache/cordova/NativeToJsMessageQueue.java
	framework/src/org/apache/cordova/PluginManager.java
2014-07-03 23:02:02 -04:00
Andrew Grieve
428e1bc14d Remove fields from CordovaWebView interface
Fields don't make sense in an interface.
2014-06-24 15:28:53 -04:00
Andrew Grieve
d66bb84924 Delete onReset and resetJsMessageQueue from CordovaWebView interface
These are implementation details that do not need to be exposed.
2014-06-24 15:26:43 -04:00
Andrew Grieve
4ce5123a12 Merge branch 'master' into 4.0.x (bindButton changes)
Conflicts:
	framework/src/org/apache/cordova/CordovaWebView.java
	package.json
2014-06-24 15:22:27 -04:00
Andrew Grieve
96a1192474 Merge branch 'master' into 4.0.x (back button default behaviour fix)
Conflicts:
	framework/src/org/apache/cordova/CordovaChromeClient.java
	framework/src/org/apache/cordova/CordovaWebView.java
2014-06-23 14:50:01 -04:00
Andrew Grieve
c052f40ef8 Remove onKey* from CordovaWebView interface (these exist on View already) 2014-06-20 16:09:14 -04:00
Andrew Grieve
98246c0e35 Add a whitelist to PluginManager to be used by App Harness
App Harness needs a way to restrict which plugins get loaded for
embedded apps. This seemed like the simplest way, although a better
API would be to have PluginManager recieve the list of PluginEntry.
2014-06-20 12:34:08 -04:00
Joe Bowser
8ac067da89 Rethinking the URI helper 2014-06-19 13:20:44 -07:00
Ian Clelland
0ffb5d253a CB-3445: android: Copy Gradle wrapper from Android SDK rather than bundling a JAR 2014-06-19 16:12:40 -04:00
Andrew Grieve
3a9898a6a6 CB-6971 Fix infinite recursion for onReceiveError 2014-06-18 13:20:47 -04:00
Andrew Grieve
693ec14df5 Rename App->CoreAndroid in defaults.xml (related to 635a6279a9) 2014-06-17 20:55:55 -04:00
Ian Clelland
fa189b3234 CB-3445: Add an initial set of Gradle build scripts
These scripts will build an android project, in debug and release mode.
They also support additional library projects, such as Crosswalk, being
added to libraries.gradle (and settings.gradle). A flag can be set in
libraries.gradle to enable multi-architecture builds.
2014-06-17 17:36:26 -04:00
Joe Bowser
3b27cd093b CB-6873: Removing from cordova-android, still in the camera plugin 2014-06-17 11:22:42 -07:00
Andrew Grieve
6abb9da88a Merge branch 'master' into 4.0.x
Conflicts:
	bin/templates/project/custom_rules.xml
	framework/src/org/apache/cordova/CordovaWebView.java
	test/src/org/apache/cordova/test/junit/MessageTest.java
2014-06-08 22:54:21 -04:00
Andrew Grieve
d5e8807756 Set version to 4.0.0-dev 2014-06-06 15:00:41 -04:00
Ian Clelland
7e9fdb3555 Remove Ant custom build directories 2014-06-05 13:11:14 -04:00
Ian Clelland
b42faea2eb Merge branch 'pluggable_webview' into 4.0.x 2014-05-29 11:20:35 -04:00
Joe Bowser
635a6279a9 Renaming app plugin CoreAndroid to avoid confusion. It is now trivial to fix the JS away from App, but this will have to be a 4.x change 2014-05-26 13:11:27 -07:00
Joe Bowser
404d3e0959 CB-6315: Wrapping this so it runs on the UI thread 2014-05-23 11:31:13 -07:00
Marcel Kinard
f77b20bbca CB-6723 Update package name for Robotium 2014-05-23 11:31:13 -07:00
Marcel Kinard
1d0a1664e6 CB-6707 Update minSdkVersion to 10 consistently
Update minSdkVersion in the AndroidManifest for the cordova.jar and the
test project.
2014-05-23 11:30:58 -07:00
Martin Gonzalez
410afbf9a1 CB-5652 make visible cordova version
Log the cordova version using version string from CordovaWebView.java

This closes #101
2014-05-23 11:30:58 -07:00
Steven Gill
aaddfa6f3a Update JS snapshot to version 3.6.0-dev (via coho) 2014-05-23 11:30:58 -07:00
Joe Bowser
2d9a16e857 Update JS snapshot to version 3.6.0-dev (via coho) 2014-05-23 11:30:58 -07:00
Joe Bowser
1dcba51092 Set VERSION to 3.6.0-dev (via coho) 2014-05-23 11:30:57 -07:00
Joe Bowser
7c63b30de1 Added dash to test push 2014-05-23 11:23:29 -07:00
Andrew Grieve
c0eae1ad52 Revert accidentally removed lines from NOTICE 2014-05-23 11:23:29 -07:00
Steven Gill
c012b98223 CB-6552: updated author to apache software foundation in pacakge.json 2014-05-23 11:23:29 -07:00
Steven Gill
559493babd CB-6552: updated test field 2014-05-23 11:23:29 -07:00
Steven Gill
990ab2c7ef CB-6552: added top level package.json 2014-05-23 11:23:28 -07:00
Marcel Kinard
437003de29 CB-6491 add CONTRIBUTING.md 2014-05-23 11:23:28 -07:00
Ian Clelland
22b1959333 Manually fix Android sdk location to support library projects without local.properties 2014-05-22 14:04:00 -04:00
Ian Clelland
97008305ff Merge branch 'master' into pluggable_webview
Conflicts:
	framework/src/org/apache/cordova/CordovaWebView.java
2014-05-15 15:59:11 -04:00
Ian Clelland
1a17083e8c Add more required methods on CordovaWebView interface 2014-05-15 15:56:10 -04:00
Joe Bowser
b6664cc859 Added two more required methods to CordovaWebView to get the Junit tests running, removed tests that make no sense 2014-05-14 11:09:21 -07:00
Ian Clelland
e595c313a1 Use correct client object in recent versions of android again 2014-05-02 10:29:53 -04:00
Ian Clelland
955da2e360 Clean up merge commit
Reinstate fix for github issue #96 (b715d20)
Re-remove extra calls to set up client objects (8e31ef7b)
Reinstate license header in CordovaChromeClient.java
2014-05-02 10:22:38 -04:00
Joe Bowser
04b3fc0268 Outsmarted by vim, needed Eclipse to clean this up 2014-04-30 15:09:54 -07:00
Joe Bowser
105ccc81a5 This is an ugly merge commit, because the rebase made even less sense.
This should add the old setProperty methods required for the tests. We
decided to not deprecate them.  I don't make a habit of doing merge
commits, due to their destructive nature, but I think I might have
merged too much stuff in.

Merge branch 'pluggable_webview' of https://git-wip-us.apache.org/repos/asf/cordova-android into pluggable_webview

Conflicts:
	framework/src/org/apache/cordova/AndroidChromeClient.java
	framework/src/org/apache/cordova/AndroidWebView.java
	framework/src/org/apache/cordova/CordovaActivity.java
	framework/src/org/apache/cordova/CordovaWebView.java
2014-04-30 14:59:40 -07:00
Joe Bowser
3571307df5 Adding setIntegerProperty, setBooleanProperty and setStringProperty back, due to possible demand, and due to the fact that I don't want to rewrite my tests 2014-04-30 11:33:26 -07:00
Ian Clelland
df05f3a3c0 Try other constructors besides first 2014-04-29 22:50:12 -04:00
Ian Clelland
8e31ef7be6 Defer construction of client objects to WebView 2014-04-29 22:50:12 -04:00
Joe Bowser
f4555f7c96 Removing the xwalk_core_library reference so we can use this with MozillaView 2014-04-29 22:50:11 -04:00
Ningxin Hu
8408da55ea Add getView() API into CordovaWebView.
This API is to get the actual View.

The concrete webview implementation can use compositing instead of extending
underlying webview.
2014-04-29 22:50:11 -04:00
Ningxin Hu
4a67dd2e28 Crosswalk runtime needs the two permissions to auto detect connection status.
See more details at: https://crosswalk-project.org/jira/browse/XWALK-1324
2014-04-29 22:50:11 -04:00
Joe Bowser
bd806a34d8 Removing XWalkWebView and making it a plugin component 2014-04-29 22:50:11 -04:00
Joe Bowser
2f7e833a79 Got the bridge to work with Crosswalk 2014-04-29 22:50:11 -04:00
Joe Bowser
c17503ab78 w00t! Managed to get XWalk to work. Next Step: Make it installable
like a Cordova Plugin!
2014-04-29 22:50:11 -04:00
Joe Bowser
19f76d34db Hurray! It runs! Now that we have the default WebView working, it's time
to make things a little more pluggable.
2014-04-29 22:50:11 -04:00
Joe Bowser
25c8b2fabb Removing the deprecated setAttribute methods to clean up the codebase 2014-04-29 22:44:05 -04:00
Joe Bowser
bfd8bf9ca4 Merge pull request #3 from huningxin/pluggable_webview
Make correct webview client and chrome client for specific webview engin...
2014-04-29 13:25:19 -07:00
Ningxin Hu
7a5405d2ab Delegate making WebViewClient and ChromeClient to webview engine.
Revert the change of webview preference name.
2014-04-24 09:42:51 +08:00
Joe Bowser
b9a24f00ad Removing the xwalk_core_library reference so we can use this with MozillaView 2014-04-23 14:31:30 -07:00
Ningxin Hu
dbfc292353 Make correct webview client and chrome client for specific webview engine.
It changes the webview preference naming from full name to prefix, since the
prefix is also used to construct the name of WebView, WebViewClient and
ChromeClient.

For example, for Crosswalk webview, config.xml contains:
<preference name="webView" value="org.apache.cordova.engine.crosswalk.XWalkCordova" />
2014-04-23 16:33:31 +08:00
Joe Bowser
a09255b2ff Merge pull request #2 from huningxin/pluggable_webview
Pluggable webview
2014-04-22 15:55:52 -07:00
Ningxin Hu
9d1c72cc07 Add getView() API into CordovaWebView.
This API is to get the actual View.

The concrete webview implementation can use compositing instead of extending
underlying webview.
2014-04-21 15:22:19 +08:00
Ningxin Hu
09ac30ef2e Crosswalk runtime needs the two permissions to auto detect connection status.
See more details at: https://crosswalk-project.org/jira/browse/XWALK-1324
2014-04-21 15:17:23 +08:00
Andrew Grieve
79e313a0c0 Catch uncaught exceptions in from plugins and turn them into error responses.
When a plugin throws an unchecked exception, we're not catching it
anywhere and so the error callback is not being called.

This change adds a try/catch to catch such exceptions.
2014-03-17 11:58:14 -07:00
Andrew Grieve
9f4c75d1c2 Add NOTICE file 2014-03-17 11:58:14 -07:00
Joe Bowser
b37492644c Removing XWalkWebView and making it a plugin component 2014-03-12 15:04:28 -07:00
Joe Bowser
04a792a8c2 Got the bridge to work with Crosswalk 2014-03-10 14:39:43 -07:00
Joe Bowser
35ec24c3f0 w00t! Managed to get XWalk to work. Next Step: Make it installable
like a Cordova Plugin!
2014-03-07 15:03:22 -08:00
Joe Bowser
61b23677d1 Hurray! It runs! Now that we have the default WebView working, it's time
to make things a little more pluggable.
2014-03-05 15:50:02 -08:00
Joe Bowser
90037dc6cd Removing the deprecated setAttribute methods to clean up the codebase 2014-03-04 15:02:30 -08:00
68 changed files with 1930 additions and 3744 deletions

View File

@@ -25,8 +25,8 @@ Anyone can contribute to Cordova. And we need your contributions.
There are multiple ways to contribute: report bugs, improve the docs, and
contribute code.
For instructions on this, start with the
For instructions on this, start with the
[contribution overview](http://cordova.apache.org/#contribute).
The details are explained there, but the important items are:
@@ -35,4 +35,3 @@ The details are explained there, but the important items are:
- Run the tests so your patch doesn't break existing functionality.
We look forward to your contributions!

View File

@@ -20,40 +20,6 @@
-->
## Release Notes for Cordova (Android) ##
### Release 3.7.2 (May 2015) ###
* Removed Intent Functionality from Preferences - Preferences can no longer be set by intents
### Release 3.7.1 (January 2015) ###
* CB-8411 Initialize plugins only after `createViews()` is called (regression in 3.7.0)
### Release 3.7.0 (January 2015) ###
* CB-8328 Allow plugins to handle certificate challenges (close #150)
* CB-8201 Add support for auth dialogs into Cordova Android
* CB-8017 Add support for `<input type=file>` for Lollipop
* CB-8143 Loads of gradle improvements (try it with cordova/build --gradle)
* CB-8329 Cancel outstanding ActivityResult requests when a new startActivityForResult occurs
* CB-8026 Bumping up Android Version and setting it up to allow third-party cookies. This might change later.
* CB-8210 Use PluginResult for various events from native so that content-security-policy <meta> can be used
* CB-8168 Add support for `cordova/run --list` (closes #139)
* CB-8176 Vastly better auto-detection of SDK & JDK locations
* CB-8079 Use activity class package name, but fallback to application package name when looking for splash screen drawable
* CB-8147 Have corodva/build warn about unrecognized flags rather than fail
* CB-7881 Android tooling shouldn't lock application directory
* CB-8112 Turn off mediaPlaybackRequiresUserGesture
* CB-6153 Add a preference for controlling hardware button audio stream (DefaultVolumeStream)
* CB-8031 Fix race condition that shows as ConcurrentModificationException
* CB-7974 Cancel timeout timer if view is destroyed
* CB-7940 Disable exec bridge if bridgeSecret is wrong
* CB-7758 Allow content-url-hosted pages to access the bridge
* CB-6511 Fixes build for android when app name contains unicode characters.
* CB-7707 Added multipart PluginResult
* CB-6837 Fix leaked window when hitting back button while alert being rendered
* CB-7674 Move preference activation back into onCreate()
* CB-7499 Support RTL text direction
* CB-7330 Don't run check_reqs for bin/create.
### 3.6.4 (Sept 30, 2014) ###
* Set VERSION to 3.6.4 (via coho)

View File

@@ -1 +1 @@
3.7.2
4.0.0-dev

View File

@@ -156,26 +156,20 @@ module.exports.check_android = function() {
}
if (!hasAndroidHome && !androidCmdPath) {
if (isWindows) {
// Android Studio 1.0 installer
maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'sdk'));
maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'sdk'));
// Android Studio pre-1.0 installer
// Android Studio installer.
maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'android-studio', 'sdk'));
maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'android-studio', 'sdk'));
// Stand-alone installer
// Stand-alone installer.
maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'android-sdk'));
maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'android-sdk'));
} else if (process.platform == 'darwin') {
// Android Studio 1.0 installer
maybeSetAndroidHome(path.join(process.env['HOME'], 'Library', 'Android', 'sdk'));
// Android Studio pre-1.0 installer
maybeSetAndroidHome('/Applications/Android Studio.app/sdk');
// Stand-alone zip file that user might think to put under /Applications
maybeSetAndroidHome('/Applications/android-sdk-macosx');
maybeSetAndroidHome('/Applications/android-sdk');
}
if (process.env['HOME']) {
// Stand-alone zip file that user might think to put under their home directory
// or their HOME directory.
maybeSetAndroidHome(path.join(process.env['HOME'], 'android-sdk-macosx'));
maybeSetAndroidHome(path.join(process.env['HOME'], 'android-sdk'));
}

View File

@@ -72,7 +72,6 @@ function copyJsAndLibrary(projectPath, shared, projectName) {
shell.cp('-f', path.join(ROOT, 'framework', 'AndroidManifest.xml'), nestedCordovaLibPath);
shell.cp('-f', path.join(ROOT, 'framework', 'project.properties'), nestedCordovaLibPath);
shell.cp('-f', path.join(ROOT, 'framework', 'build.gradle'), nestedCordovaLibPath);
shell.cp('-f', path.join(ROOT, 'framework', 'cordova.gradle'), nestedCordovaLibPath);
shell.cp('-r', path.join(ROOT, 'framework', 'src'), nestedCordovaLibPath);
// Create an eclipse project file and set the name of it to something unique.
// Without this, you can't import multiple CordovaLib projects into the same workspace.
@@ -120,9 +119,9 @@ function writeProjectProperties(projectPath, target_api, shared) {
function copyBuildRules(projectPath) {
var srcDir = path.join(ROOT, 'bin', 'templates', 'project');
shell.cp('-f', path.join(srcDir, 'custom_rules.xml'), projectPath);
shell.cp('-f', path.join(srcDir, 'build.gradle'), projectPath);
shell.cp('-f', path.join(srcDir, 'cordova.gradle'), projectPath);
}
function copyScripts(projectPath) {

View File

@@ -24,7 +24,6 @@ var shell = require('shelljs'),
Q = require('q'),
path = require('path'),
fs = require('fs'),
os = require('os'),
ROOT = path.join(__dirname, '..', '..');
var check_reqs = require('./check_reqs');
var exec = require('./exec');
@@ -69,13 +68,11 @@ function findOutputApksHelper(dir, build_type, arch) {
if (ret.length === 0) {
return ret;
}
// Assume arch-specific build if newest api has -x86 or -arm.
var archSpecific = !!/-x86|-arm/.exec(ret[0]);
// And show only arch-specific ones (or non-arch-specific)
ret = ret.filter(function(p) {
return !!/-x86|-arm/.exec(p) == archSpecific;
});
if (arch && ret.length > 1) {
if (arch) {
ret = ret.filter(function(p) {
return p.indexOf('-' + arch) != -1;
});
@@ -176,20 +173,46 @@ var builders = {
}
},
gradle: {
getArgs: function(cmd, arch, extraArgs) {
if (cmd == 'release') {
cmd = 'cdvBuildRelease';
getArgs: function(cmd, arch) {
var lintSteps;
if (process.env['BUILD_MULTIPLE_APKS']) {
lintSteps = [
'lint',
'lintVitalX86Release',
'lintVitalArmv7Release',
'compileLint',
'copyReleaseLint',
'copyDebugLint'
];
} else {
lintSteps = [
'lint',
'lintVitalRelease',
'compileLint',
'copyReleaseLint',
'copyDebugLint'
];
}
if (arch == 'arm' && cmd == 'debug') {
cmd = 'assembleArmv7Debug';
} else if (arch == 'arm' && cmd == 'release') {
cmd = 'assembleArmv7Release';
} else if (arch == 'x86' && cmd == 'debug') {
cmd = 'assembleX86Debug';
} else if (arch == 'x86' && cmd == 'release') {
cmd = 'assembleX86Release';
} else if (cmd == 'debug') {
cmd = 'cdvBuildDebug';
cmd = 'assembleDebug';
} else if (cmd == 'release') {
cmd = 'assembleRelease';
}
var args = [cmd, '-b', path.join(ROOT, 'build.gradle')];
if (arch) {
args.push('-PcdvBuildArch=' + arch);
}
// 10 seconds -> 6 seconds
args.push('-Dorg.gradle.daemon=true');
args.push.apply(args, extraArgs);
// Excluding lint: 6s-> 1.6s
for (var i = 0; i < lintSteps.length; ++i) {
args.push('-x', lintSteps[i]);
}
// Shaves another 100ms, but produces a "try at own risk" warning. Not worth it (yet):
// args.push('-Dorg.gradle.parallel=true');
return args;
@@ -214,11 +237,10 @@ var builders = {
shell.mkdir('-p', path.join(projectPath, 'gradle'));
shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(projectPath, 'gradle'));
// If the gradle distribution URL is set, make sure it points to version we want.
// If the gradle distribution URL is set, make sure it points to version 1.12.
// If it's not set, do nothing, assuming that we're using a future version of gradle that we don't want to mess with.
// For some reason, using ^ and $ don't work. This does the job, though.
var distributionUrlRegex = /distributionUrl.*zip/;
var distributionUrl = 'distributionUrl=http\\://services.gradle.org/distributions/gradle-2.2.1-all.zip';
var distributionUrlRegex = '/^distributionUrl=.*$/';
var distributionUrl = 'distributionUrl=http\\://services.gradle.org/distributions/gradle-1.12-all.zip';
var gradleWrapperPropertiesPath = path.join(projectPath, 'gradle', 'wrapper', 'gradle-wrapper.properties');
shell.sed('-i', distributionUrlRegex, distributionUrl, gradleWrapperPropertiesPath);
@@ -226,9 +248,7 @@ var builders = {
var pluginBuildGradle = path.join(projectPath, 'cordova', 'lib', 'plugin-build.gradle');
var subProjects = extractSubProjectPaths();
for (var i = 0; i < subProjects.length; ++i) {
if (subProjects[i] !== 'CordovaLib') {
shell.cp('-f', pluginBuildGradle, path.join(ROOT, subProjects[i], 'build.gradle'));
}
shell.cp('-f', pluginBuildGradle, path.join(ROOT, subProjects[i], 'build.gradle'));
}
var subProjectsAsGradlePaths = subProjects.map(function(p) { return ':' + p.replace(/[/\\]/g, ':') });
@@ -253,22 +273,20 @@ var builders = {
* Builds the project with gradle.
* Returns a promise.
*/
build: function(build_type, arch, extraArgs) {
build: function(build_type, arch) {
var builder = this;
var wrapper = path.join(ROOT, 'gradlew');
var args = this.getArgs(build_type == 'debug' ? 'debug' : 'release', arch, extraArgs);
var args = this.getArgs(build_type == 'debug' ? 'debug' : 'release', arch);
return Q().then(function() {
console.log('Running: ' + wrapper + ' ' + args.concat(extraArgs).join(' '));
return spawn(wrapper, args);
});
},
clean: function(extraArgs) {
clean: function() {
var builder = this;
var wrapper = path.join(ROOT, 'gradlew');
var args = builder.getArgs('clean', null, extraArgs);
var args = builder.getArgs('clean');
return Q().then(function() {
console.log('Running: ' + wrapper + ' ' + args.concat(extraArgs).join(' '));
return spawn(wrapper, args);
});
},
@@ -303,61 +321,37 @@ function parseOpts(options, resolvedTarget) {
var ret = {
buildType: 'debug',
buildMethod: process.env['ANDROID_BUILD'] || 'ant',
arch: null,
extraArgs: []
};
var multiValueArgs = {
'versionCode': true,
'minSdkVersion': true,
'gradleArg': true
arch: null
};
// Iterate through command line options
for (var i=0; options && (i < options.length); ++i) {
if (/^--/.exec(options[i])) {
var keyValue = options[i].substring(2).split('=');
var flagName = keyValue.shift();
var flagValue = keyValue.join('=');
if (multiValueArgs[flagName] && !flagValue) {
flagValue = options[i + 1];
++i;
}
switch(flagName) {
var option = options[i].substring(2);
switch(option) {
case 'debug':
case 'release':
ret.buildType = flagName;
ret.buildType = option;
break;
case 'ant':
case 'gradle':
ret.buildMethod = flagName;
break;
case 'device':
case 'emulator':
// Don't need to do anything special to when building for device vs emulator.
// iOS uses this flag to switch on architecture.
ret.buildMethod = option;
break;
case 'nobuild' :
ret.buildMethod = 'none';
break;
case 'versionCode':
ret.extraArgs.push('-PcdvVersionCode=' + flagValue);
break;
case 'minSdkVersion':
ret.extraArgs.push('-PcdvMinSdkVersion=' + flagValue);
break;
case 'gradleArg':
ret.extraArgs.push(flagValue);
break;
default :
console.warn('Build option --\'' + flagName + '\' not recognized (ignoring).');
return Q.reject('Build option \'' + options[i] + '\' not recognized.');
}
} else {
console.warn('Build option \'' + options[i] + '\' not recognized (ignoring).');
return Q.reject('Build option \'' + options[i] + '\' not recognized.');
}
}
ret.arch = resolvedTarget && resolvedTarget.arch;
var multiApk = ret.buildMethod == 'gradle' && process.env['BUILD_MULTIPLE_APKS'];
if (multiApk && !/0|false|no/i.exec(multiApk)) {
ret.arch = resolvedTarget && resolvedTarget.arch;
}
return ret;
}
@@ -371,7 +365,7 @@ module.exports.runClean = function(options) {
var builder = builders[opts.buildMethod];
return builder.prepEnv()
.then(function() {
return builder.clean(opts.extraArgs);
return builder.clean();
}).then(function() {
shell.rm('-rf', path.join(ROOT, 'out'));
});
@@ -386,7 +380,7 @@ module.exports.run = function(options, optResolvedTarget) {
var builder = builders[opts.buildMethod];
return builder.prepEnv()
.then(function() {
return builder.build(opts.buildType, opts.arch, opts.extraArgs);
return builder.build(opts.buildType, opts.arch);
}).then(function() {
var apkPaths = builder.findOutputApks(opts.buildType, opts.arch);
console.log('Built the following apk(s):');
@@ -404,44 +398,12 @@ module.exports.run = function(options, optResolvedTarget) {
* Returns "arm" or "x86".
*/
module.exports.detectArchitecture = function(target) {
function helper() {
return exec('adb -s ' + target + ' shell cat /proc/cpuinfo', os.tmpdir())
.then(function(output) {
if (/intel/i.exec(output)) {
return 'x86';
}
return 'arm';
});
}
// It sometimes happens (at least on OS X), that this command will hang forever.
// To fix it, either unplug & replug device, or restart adb server.
return helper().timeout(1000, 'Device communication timed out. Try unplugging & replugging the device.')
.then(null, function(err) {
if (/timed out/.exec('' + err)) {
// adb kill-server doesn't seem to do the trick.
// Could probably find a x-platform version of killall, but I'm not actually
// sure that this scenario even happens on non-OSX machines.
return exec('killall adb')
.then(function() {
console.log('adb seems hung. retrying.');
return helper()
.then(null, function() {
// The double kill is sadly often necessary, at least on mac.
console.log('Now device not found... restarting adb again.');
return exec('killall adb')
.then(function() {
return helper()
.then(null, function() {
return Q.reject('USB is flakey. Try unplugging & replugging the device.');
});
});
});
}, function() {
// For non-killall OS's.
return Q.reject(err);
})
return exec('adb -s ' + target + ' shell cat /proc/cpuinfo')
.then(function(output) {
if (/intel/i.exec(output)) {
return 'x86';
}
throw err;
return 'arm';
});
};
@@ -467,15 +429,12 @@ module.exports.findBestApkForArchitecture = function(buildResults, arch) {
};
module.exports.help = function() {
console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'build')) + ' [flags]');
console.log('Flags:');
console.log(' \'--debug\': will build project in debug mode (default)');
console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'build')) + ' [build_type]');
console.log('Build Types : ');
console.log(' \'--debug\': Default build, will build project in debug mode');
console.log(' \'--release\': will build project for release');
console.log(' \'--ant\': will build project with ant (default)');
console.log(' \'--ant\': Default build, will build project with ant');
console.log(' \'--gradle\': will build project with gradle');
console.log(' \'--nobuild\': will skip build process (useful when using run command)');
console.log(' \'--versionCode=#\': Override versionCode for this build. Useful for uploading multiple APKs. Requires --gradle.');
console.log(' \'--minSdkVersion=#\': Override minSdkVersion for this build. Useful for uploading multiple APKs. Requires --gradle.');
console.log(' \'--gradleArg=<gradle command line arg>\': Extra args to pass to the gradle command. Use one flag per arg. Ex. --gradleArg=-PcdvBuildMultipleApks=true');
console.log(' \'--nobuild\': will skip build process (can be used with run command)');
process.exit(0);
};

View File

@@ -22,50 +22,29 @@
var exec = require('./exec'),
Q = require('q'),
path = require('path'),
os = require('os'),
build = require('./build'),
appinfo = require('./appinfo'),
ROOT = path.join(__dirname, '..', '..');
/**
* Returns a promise for the list of the device ID's found
* @param lookHarder When true, try restarting adb if no devices are found.
*/
module.exports.list = function(lookHarder) {
function helper() {
return exec('adb devices', os.tmpdir())
.then(function(output) {
var response = output.split('\n');
var device_list = [];
for (var i = 1; i < response.length; i++) {
if (response[i].match(/\w+\tdevice/) && !response[i].match(/emulator/)) {
device_list.push(response[i].replace(/\tdevice/, '').replace('\r', ''));
}
module.exports.list = function() {
return exec('adb devices')
.then(function(output) {
var response = output.split('\n');
var device_list = [];
for (var i = 1; i < response.length; i++) {
if (response[i].match(/\w+\tdevice/) && !response[i].match(/emulator/)) {
device_list.push(response[i].replace(/\tdevice/, '').replace('\r', ''));
}
return device_list;
});
}
return helper()
.then(function(list) {
if (list.length === 0 && lookHarder) {
// adb kill-server doesn't seem to do the trick.
// Could probably find a x-platform version of killall, but I'm not actually
// sure that this scenario even happens on non-OSX machines.
return exec('killall adb')
.then(function() {
console.log('Restarting adb to see if more devices are detected.');
return helper();
}, function() {
// For non-killall OS's.
return list;
});
}
return list;
return device_list;
});
}
module.exports.resolveTarget = function(target) {
return this.list(true)
return this.list()
.then(function(device_list) {
if (!device_list || !device_list.length) {
return Q.reject('ERROR: Failed to deploy to device, no devices found.');
@@ -101,19 +80,19 @@ module.exports.install = function(target, buildResults) {
console.log('Using apk: ' + apk_path);
console.log('Installing app on device...');
var cmd = 'adb -s ' + resolvedTarget.target + ' install -r "' + apk_path + '"';
return exec(cmd, os.tmpdir())
return exec(cmd)
.then(function(output) {
if (output.match(/Failure/)) return Q.reject('ERROR: Failed to install apk to device: ' + output);
//unlock screen
var cmd = 'adb -s ' + resolvedTarget.target + ' shell input keyevent 82';
return exec(cmd, os.tmpdir());
return exec(cmd);
}, function(err) { return Q.reject('ERROR: Failed to install apk to device: ' + err); })
.then(function() {
// launch the application
console.log('Launching application...');
var cmd = 'adb -s ' + resolvedTarget.target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName;
return exec(cmd, os.tmpdir());
return exec(cmd);
}).then(function() {
console.log('LAUNCH SUCCESS');
}, function(err) {

View File

@@ -23,7 +23,6 @@ var shell = require('shelljs'),
exec = require('./exec'),
Q = require('q'),
path = require('path'),
os = require('os'),
appinfo = require('./appinfo'),
build = require('./build'),
ROOT = path.join(__dirname, '..', '..'),
@@ -109,7 +108,7 @@ module.exports.best_image = function() {
// Returns a promise.
module.exports.list_started = function() {
return exec('adb devices', os.tmpdir())
return exec('adb devices')
.then(function(output) {
var response = output.split('\n');
var started_emulator_list = [];
@@ -124,7 +123,7 @@ module.exports.list_started = function() {
// Returns a promise.
module.exports.list_targets = function() {
return exec('android list targets', os.tmpdir())
return exec('android list targets')
.then(function(output) {
var target_out = output.split('\n');
var targets = [];
@@ -202,7 +201,7 @@ module.exports.start = function(emulator_ID) {
console.log('BOOT COMPLETE');
//unlock screen
return exec('adb -s ' + emulator_id + ' shell input keyevent 82', os.tmpdir());
return exec('adb -s ' + emulator_id + ' shell input keyevent 82');
}).then(function() {
//return the new emulator id for the started emulators
return emulator_id;
@@ -232,7 +231,7 @@ module.exports.wait_for_emulator = function(num_running) {
*/
module.exports.wait_for_boot = function(emulator_id) {
var self = this;
return exec('adb -s ' + emulator_id + ' shell getprop init.svc.bootanim', os.tmpdir())
return exec('adb -s ' + emulator_id + ' shell getprop init.svc.bootanim')
.then(function(output) {
if (output.match(/stopped/)) {
return;
@@ -310,7 +309,7 @@ module.exports.install = function(target, buildResults) {
var apk_path = build.findBestApkForArchitecture(buildResults, resolvedTarget.arch);
console.log('Installing app on emulator...');
console.log('Using apk: ' + apk_path);
return exec('adb -s ' + resolvedTarget.target + ' install -r "' + apk_path + '"', os.tmpdir())
return exec('adb -s ' + resolvedTarget.target + ' install -r "' + apk_path + '"')
.then(function(output) {
if (output.match(/Failure/)) {
return Q.reject('Failed to install apk to emulator: ' + output);
@@ -320,13 +319,13 @@ module.exports.install = function(target, buildResults) {
return Q.reject('Failed to install apk to emulator: ' + err);
}).then(function() {
//unlock screen
return exec('adb -s ' + resolvedTarget.target + ' shell input keyevent 82', os.tmpdir());
return exec('adb -s ' + resolvedTarget.target + ' shell input keyevent 82');
}).then(function() {
// launch the application
console.log('Launching application...');
var launchName = appinfo.getActivityName();
cmd = 'adb -s ' + resolvedTarget.target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName;
return exec(cmd, os.tmpdir());
return exec(cmd);
}).then(function(output) {
console.log('LAUNCH SUCCESS');
}, function(err) {

View File

@@ -21,7 +21,6 @@
var shell = require('shelljs'),
path = require('path'),
os = require('os'),
Q = require('q'),
child_process = require('child_process'),
ROOT = path.join(__dirname, '..', '..');
@@ -33,7 +32,7 @@ var shell = require('shelljs'),
module.exports.run = function() {
var cmd = 'adb logcat | grep -v nativeGetEnabledTags';
var d = Q.defer();
var adb = child_process.spawn('adb', ['logcat'], {cwd: os.tmpdir()});
var adb = child_process.spawn('adb', ['logcat']);
adb.stdout.on('data', function(data) {
var lines = data ? data.toString().split('\n') : [];

View File

@@ -23,22 +23,8 @@ buildscript {
mavenCentral()
}
// Switch the Android Gradle plugin version requirement depending on the
// installed version of Gradle. This dependency is documented at
// http://tools.android.com/tech-docs/new-build-system/version-compatibility
// and https://issues.apache.org/jira/browse/CB-8143
if (gradle.gradleVersion >= "2.2") {
dependencies {
classpath 'com.android.tools.build:gradle:1.0.0+'
}
} else if (gradle.gradleVersion >= "2.1") {
dependencies {
classpath 'com.android.tools.build:gradle:0.14.0+'
}
} else {
dependencies {
classpath 'com.android.tools.build:gradle:0.12.0+'
}
dependencies {
classpath 'com.android.tools.build:gradle:0.12.+'
}
}
@@ -46,18 +32,16 @@ apply plugin: 'android-library'
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
debugCompile project(path: ":CordovaLib", configuration: "debug")
releaseCompile project(path: ":CordovaLib", configuration: "release")
}
android {
compileSdkVersion cdvCompileSdkVersion
buildToolsVersion cdvBuildToolsVersion
compileSdkVersion cordova.cordovaSdkVersion
buildToolsVersion cordova.cordovaBuildToolsVersion
publishNonDefault true
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6
targetCompatibility JavaVersion.VERSION_1_6
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
sourceSets {

View File

@@ -23,12 +23,11 @@ var path = require('path'),
build = require('./build'),
emulator = require('./emulator'),
device = require('./device'),
shell = require('shelljs'),
Q = require('q');
/*
* Runs the application on a device if available.
* If no device is found, it will use a started emulator.
* If not device is found, it will use a started emulator.
* If no started emulators are found it will attempt to start an avd.
* If no avds are found it will error out.
* Returns a promise.
@@ -36,51 +35,26 @@ var path = require('path'),
module.exports.run = function(args) {
var buildFlags = [];
var install_target;
var list = false;
for (var i=2; i<args.length; i++) {
if (/^--(debug|release|ant|gradle|nobuild|versionCode=|minSdkVersion=|gradleArg=)/.exec(args[i])) {
buildFlags.push(args[i]);
if (args[i] == '--debug') {
buildFlags.push('--debug');
} else if (args[i] == '--release') {
buildFlags.push('--release');
} else if (args[i] == '--nobuild') {
buildFlags.push('--nobuild');
} else if (args[i] == '--device') {
install_target = '--device';
} else if (args[i] == '--emulator') {
install_target = '--emulator';
} else if (/^--target=/.exec(args[i])) {
} else if (args[i].substring(0, 9) == '--target=') {
install_target = args[i].substring(9, args[i].length);
} else if (args[i] == '--list') {
list = true;
} else {
console.warn('Option \'' + args[i] + '\' not recognized (ignoring).');
console.error('ERROR : Run option \'' + args[i] + '\' not recognized.');
process.exit(2);
}
}
if (list) {
var output = '';
var temp = '';
if (!install_target) {
output += 'Available Android Devices:\n';
temp = shell.exec(path.join(__dirname, 'list-devices'), {silent:true}).output;
temp = temp.replace(/^(?=[^\s])/gm, '\t');
output += temp;
output += 'Available Android Virtual Devices:\n';
temp = shell.exec(path.join(__dirname, 'list-emulator-images'), {silent:true}).output;
temp = temp.replace(/^(?=[^\s])/gm, '\t');
output += temp;
} else if (install_target == '--emulator') {
output += 'Available Android Virtual Devices:\n';
temp = shell.exec(path.join(__dirname, 'list-emulator-images'), {silent:true}).output;
temp = temp.replace(/^(?=[^\s])/gm, '\t');
output += temp;
} else if (install_target == '--device') {
output += 'Available Android Devices:\n';
temp = shell.exec(path.join(__dirname, 'list-devices'), {silent:true}).output;
temp = temp.replace(/^(?=[^\s])/gm, '\t');
output += temp;
}
console.log(output);
return;
}
return Q()
.then(function() {
if (!install_target) {

View File

@@ -26,15 +26,13 @@ var isWindows = process.platform.slice(0, 3) == 'win';
// Takes a command and optional current working directory.
module.exports = function(cmd, args, opt_cwd) {
var d = Q.defer();
var opts = { cwd: opt_cwd, stdio: 'inherit' };
try {
// Work around spawn not being able to find .bat files.
if (isWindows) {
args = [['/s', '/c', '"' + [cmd].concat(args).map(function(a){if (/^[^"].* .*[^"]/.test(a)) return '"' + a + '"'; return a;}).join(' ')+'"'].join(' ')];
cmd = 'cmd';
opts.windowsVerbatimArguments = true;
args.unshift('/s', '/c', cmd);
cmd = 'cmd';
}
var child = child_process.spawn(cmd, args, opts);
var child = child_process.spawn(cmd, args, {cwd: opt_cwd, stdio: 'inherit'});
child.on('exit', function(code) {
if (code) {
d.reject('Error code ' + code + ' for command: ' + cmd + ' with args: ' + args);
@@ -47,4 +45,5 @@ module.exports = function(cmd, args, opt_cwd) {
d.reject(e);
}
return d.promise;
};
}

View File

@@ -20,6 +20,6 @@
*/
// Coho updates this line:
var VERSION = "3.7.2";
var VERSION = "4.0.0-dev";
console.log(VERSION);

View File

@@ -28,7 +28,6 @@ public class __ACTIVITY__ extends CordovaActivity
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
super.init();
// Set by <content src="index.html" /> in config.xml
loadUrl(launchUrl);
}

View File

@@ -29,6 +29,8 @@
/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<application android:icon="@drawable/icon" android:label="@string/app_name"
android:hardwareAccelerated="true" android:supportsRtl="true">

View File

@@ -19,6 +19,11 @@
// GENERATED FILE! DO NOT EDIT!
import java.util.regex.Pattern
import groovy.swing.SwingBuilder
ext.cordova = {}
apply from: 'cordova.gradle', to: ext.cordova
apply plugin: 'android'
buildscript {
@@ -26,117 +31,16 @@ buildscript {
mavenCentral()
}
// Switch the Android Gradle plugin version requirement depending on the
// installed version of Gradle. This dependency is documented at
// http://tools.android.com/tech-docs/new-build-system/version-compatibility
// and https://issues.apache.org/jira/browse/CB-8143
if (gradle.gradleVersion >= "2.2") {
dependencies {
classpath 'com.android.tools.build:gradle:1.0.0+'
}
} else if (gradle.gradleVersion >= "2.1") {
dependencies {
classpath 'com.android.tools.build:gradle:0.14.0+'
}
} else {
dependencies {
classpath 'com.android.tools.build:gradle:0.12.0+'
}
dependencies {
classpath 'com.android.tools.build:gradle:0.12.0+'
}
}
// Allow plugins to declare Maven dependencies via build-extras.gradle.
repositories {
mavenCentral()
}
task wrapper(type: Wrapper) {
gradleVersion = '2.2.1'
gradleVersion = '1.12'
}
// Configuration properties. Set these via environment variables, build-extras.gradle, or gradle.properties.
// Refer to: http://www.gradle.org/docs/current/userguide/tutorial_this_and_that.html
ext {
apply from: 'CordovaLib/cordova.gradle'
// The value for android.compileSdkVersion.
if (!project.hasProperty('cdvCompileSdkVersion')) {
cdvCompileSdkVersion = privateHelpers.getProjectTarget()
}
// The value for android.buildToolsVersion.
if (!project.hasProperty('cdvBuildToolsVersion')) {
cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools()
}
// Sets the versionCode to the given value.
if (!project.hasProperty('cdvVersionCode')) {
cdvVersionCode = null
}
// Sets the minSdkVersion to the given value.
if (!project.hasProperty('cdvMinSdkVersion')) {
cdvMinSdkVersion = null
}
// Whether to build architecture-specific APKs.
if (!project.hasProperty('cdvBuildMultipleApks')) {
cdvBuildMultipleApks = false
}
// .properties files to use for release signing.
if (!project.hasProperty('cdvReleaseSigningPropertiesFile')) {
cdvReleaseSigningPropertiesFile = null
}
// .properties files to use for debug signing.
if (!project.hasProperty('cdvDebugSigningPropertiesFile')) {
cdvDebugSigningPropertiesFile = null
}
// Set by build.js script.
if (!project.hasProperty('cdvBuildArch')) {
cdvBuildArch = null
}
}
def hasBuildExtras = file('build-extras.gradle').exists()
if (hasBuildExtras) {
apply from: 'build-extras.gradle'
}
def computeBuildTargetName(debugBuild) {
def ret = 'assemble'
if (cdvBuildMultipleApks && cdvBuildArch) {
def arch = cdvBuildArch == 'arm' ? 'armv7' : cdvBuildArch
ret += '' + arch.toUpperCase().charAt(0) + arch.substring(1);
}
return ret + (debugBuild ? 'Debug' : 'Release')
}
// Make cdvBuild a task that depends on the debug/arch-sepecific task.
task cdvBuildDebug
cdvBuildDebug.dependsOn {
return computeBuildTargetName(true)
}
task cdvBuildRelease
cdvBuildRelease.dependsOn {
return computeBuildTargetName(false)
}
task cdvPrintProps << {
println('cdvCompileSdkVersion=' + cdvCompileSdkVersion)
println('cdvBuildToolsVersion=' + cdvBuildToolsVersion)
println('cdvVersionCode=' + cdvVersionCode)
println('cdvMinSdkVersion=' + cdvMinSdkVersion)
println('cdvBuildMultipleApks=' + cdvBuildMultipleApks)
println('cdvReleaseSigningPropertiesFile=' + cdvReleaseSigningPropertiesFile)
println('cdvDebugSigningPropertiesFile=' + cdvDebugSigningPropertiesFile)
println('cdvBuildArch=' + cdvBuildArch)
println('computedVersionCode=' + android.defaultConfig.versionCode)
if (android.productFlavors.has('armv7')) {
println('computedArmv7VersionCode=' + android.productFlavors.armv7.versionCode)
}
if (android.productFlavors.has('x86')) {
println('computedx86VersionCode=' + android.productFlavors.x86.versionCode)
}
}
// PLUGIN GRADLE EXTENSIONS START
// PLUGIN GRADLE EXTENSIONS END
ext.multiarch=false
android {
sourceSets {
@@ -151,29 +55,23 @@ android {
}
}
def versionCodeOverride = cdvVersionCode ? Integer.parseInt(cdvVersionCode) : null
def minSdkVersionOverride = cdvMinSdkVersion ? Integer.parseInt(cdvMinSdkVersion) : null
defaultConfig {
versionCode versionCodeOverride ?: Integer.parseInt("" + privateHelpers.extractIntFromManifest("versionCode") + "0")
if (minSdkVersionOverride != null) {
minSdkVersion minSdkVersionOverride
}
versionCode Integer.parseInt(System.env.ANDROID_VERSION_CODE ?: ("" + getVersionCodeFromManifest() + "0"))
}
compileSdkVersion cdvCompileSdkVersion
buildToolsVersion cdvBuildToolsVersion
compileSdkVersion cordova.cordovaSdkVersion
buildToolsVersion cordova.cordovaBuildToolsVersion
if (Boolean.valueOf(cdvBuildMultipleApks)) {
if (multiarch || System.env.BUILD_MULTIPLE_APKS) {
productFlavors {
armv7 {
versionCode versionCodeOverride ?: defaultConfig.versionCode + 2
versionCode defaultConfig.versionCode + 2
ndk {
abiFilters "armeabi-v7a", ""
}
}
x86 {
versionCode versionCodeOverride ?: defaultConfig.versionCode + 4
versionCode defaultConfig.versionCode + 4
ndk {
abiFilters "x86", ""
}
@@ -184,31 +82,21 @@ android {
}
}
}
} else if (!versionCodeOverride) {
def minSdkVersion = minSdkVersionOverride ?: privateHelpers.extractIntFromManifest("minSdkVersion")
// Vary versionCode by the two most common API levels:
// 14 is ICS, which is the lowest API level for many apps.
// 20 is Lollipop, which is the lowest API level for the updatable system webview.
if (minSdkVersion >= 20) {
defaultConfig.versionCode += 9
} else if (minSdkVersion >= 14) {
defaultConfig.versionCode += 8
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6
targetCompatibility JavaVersion.VERSION_1_6
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
if (cdvReleaseSigningPropertiesFile) {
if (System.env.RELEASE_SIGNING_PROPERTIES_FILE) {
signingConfigs {
release {
// These must be set or Gradle will complain (even if they are overridden).
keyAlias = ""
keyPassword = "__unset" // And these must be set to non-empty in order to have the signing step added to the task graph.
keyPassword = ""
storeFile = null
storePassword = "__unset"
storePassword = ""
}
}
buildTypes {
@@ -216,10 +104,10 @@ android {
signingConfig signingConfigs.release
}
}
addSigningProps(cdvReleaseSigningPropertiesFile, signingConfigs.release)
addSigningProps(System.env.RELEASE_SIGNING_PROPERTIES_FILE, signingConfigs.release)
}
if (cdvDebugSigningPropertiesFile) {
addSigningProps(cdvDebugSigningPropertiesFile, signingConfigs.debug)
if (System.env.DEBUG_SIGNING_PROPERTIES_FILE) {
addSigningProps(System.env.DEBUG_SIGNING_PROPERTIES_FILE, signingConfigs.debug)
}
}
@@ -229,15 +117,41 @@ dependencies {
// SUB-PROJECT DEPENDENCIES END
}
def promptForPassword(msg) {
if (System.console() == null) {
def ret = null
new SwingBuilder().edt {
dialog(modal: true, title: 'Enter password', alwaysOnTop: true, resizable: false, locationRelativeTo: null, pack: true, show: true) {
vbox {
label(text: msg)
def input = passwordField()
button(defaultButton: true, text: 'OK', actionPerformed: {
ret = input.password;
dispose();
})
}
}
}
if (!ret) {
throw new GradleException('User canceled build')
}
return new String(ret)
} else {
return System.console().readPassword('\n' + msg);
}
}
def promptForReleaseKeyPassword() {
if (!cdvReleaseSigningPropertiesFile) {
if (!System.env.RELEASE_SIGNING_PROPERTIES_FILE) {
return;
}
if ('__unset'.equals(android.signingConfigs.release.storePassword)) {
android.signingConfigs.release.storePassword = privateHelpers.promptForPassword('Enter key store password: ')
if (!android.signingConfigs.release.storePassword) {
android.signingConfigs.release.storePassword = promptForPassword('Enter key store password: ')
println('set to:' + android.signingConfigs.release.storePassword)
}
if ('__unset'.equals(android.signingConfigs.release.keyPassword)) {
android.signingConfigs.release.keyPassword = privateHelpers.promptForPassword('Enter key password: ');
if (!android.signingConfigs.release.keyPassword) {
android.signingConfigs.release.keyPassword = promptForPassword('Enter key password: ');
}
}
@@ -249,23 +163,38 @@ gradle.taskGraph.whenReady { taskGraph ->
}
}
def getVersionCodeFromManifest() {
def manifestFile = file(android.sourceSets.main.manifest.srcFile)
def pattern = Pattern.compile("versionCode=\"(\\d+)\"")
def matcher = pattern.matcher(manifestFile.getText())
matcher.find()
return Integer.parseInt(matcher.group(1))
}
def ensureValueExists(filePath, props, key) {
if (props.get(key) == null) {
throw new GradleException(filePath + ': Missing key required "' + key + '"')
}
return props.get(key)
}
def addSigningProps(propsFilePath, signingConfig) {
def propsFile = file(propsFilePath)
def props = new Properties()
propsFile.withReader { reader ->
props.load(reader)
}
def storeFile = new File(privateHelpers.ensureValueExists(propsFilePath, props, 'storeFile'))
def storeFile = new File(ensureValueExists(propsFilePath, props, 'storeFile'))
if (!storeFile.isAbsolute()) {
storeFile = RelativePath.parse(true, storeFile.toString()).getFile(propsFile.getParentFile())
}
if (!storeFile.exists()) {
throw new FileNotFoundException('Keystore file does not exist: ' + storeFile.getAbsolutePath())
}
signingConfig.keyAlias = privateHelpers.ensureValueExists(propsFilePath, props, 'keyAlias')
signingConfig.keyPassword = props.get('keyPassword', signingConfig.keyPassword)
signingConfig.keyAlias = ensureValueExists(propsFilePath, props, 'keyAlias')
signingConfig.keyPassword = props.get('keyPassword')
signingConfig.storeFile = storeFile
signingConfig.storePassword = props.get('storePassword', signingConfig.storePassword)
signingConfig.storePassword = props.get('storePassword')
def storeType = props.get('storeType')
if (!storeType) {
def filename = storeFile.getName().toLowerCase();
@@ -278,8 +207,6 @@ def addSigningProps(propsFilePath, signingConfig) {
}
}
// This can be defined within build-extras.gradle as:
// ext.postBuildExtras = { ... code here ... }
if (hasProperty('postBuildExtras')) {
postBuildExtras()
if (file('build-extras.gradle').exists()) {
apply from: 'build-extras.gradle'
}

View File

@@ -18,21 +18,16 @@
*/
import java.util.regex.Pattern
import groovy.swing.SwingBuilder
String doEnsureValueExists(filePath, props, key) {
if (props.get(key) == null) {
throw new GradleException(filePath + ': Missing key required "' + key + '"')
String getProjectTarget(String defaultTarget) {
def manifestFile = file("project.properties")
def pattern = Pattern.compile("target\\s*=\\s*(.*)")
def matcher = pattern.matcher(manifestFile.getText())
if (matcher.find()) {
matcher.group(1)
} else {
defaultTarget
}
return props.get(key)
}
String doGetProjectTarget() {
def props = new Properties()
file('project.properties').withReader { reader ->
props.load(reader)
}
return doEnsureValueExists('project.properties', props, 'target')
}
String[] getAvailableBuildTools() {
@@ -42,7 +37,7 @@ String[] getAvailableBuildTools() {
.sort { a, b -> compareVersions(b, a) }
}
String doFindLatestInstalledBuildTools(String minBuildToolsVersion) {
String latestBuildToolsAvailable(String minBuildToolsVersion) {
def availableBuildToolsVersions
try {
availableBuildToolsVersions = getAvailableBuildTools()
@@ -120,46 +115,6 @@ String getAndroidSdkDir() {
androidSdkDir
}
def doExtractIntFromManifest(name) {
def manifestFile = file(android.sourceSets.main.manifest.srcFile)
def pattern = Pattern.compile(name + "=\"(\\d+)\"")
def matcher = pattern.matcher(manifestFile.getText())
matcher.find()
return Integer.parseInt(matcher.group(1))
}
def doPromptForPassword(msg) {
if (System.console() == null) {
def ret = null
new SwingBuilder().edt {
dialog(modal: true, title: 'Enter password', alwaysOnTop: true, resizable: false, locationRelativeTo: null, pack: true, show: true) {
vbox {
label(text: msg)
def input = passwordField()
button(defaultButton: true, text: 'OK', actionPerformed: {
ret = input.password;
dispose();
})
}
}
}
if (!ret) {
throw new GradleException('User canceled build')
}
return new String(ret)
} else {
return System.console().readPassword('\n' + msg);
}
}
// Properties exported here are visible to all plugins.
ext {
// These helpers are shared, but are not guaranteed to be stable / unchanged.
privateHelpers = {}
privateHelpers.getProjectTarget = { doGetProjectTarget() }
privateHelpers.findLatestInstalledBuildTools = { doFindLatestInstalledBuildTools('19.1.0') }
privateHelpers.extractIntFromManifest = { name -> doExtractIntFromManifest(name) }
privateHelpers.promptForPassword = { msg -> doPromptForPassword(msg) }
privateHelpers.ensureValueExists = { filePath, props, key -> doEnsureValueExists(filePath, props, key) }
}
ext.cordovaSdkVersion = System.env.MIN_SDK_VERSION ?: getProjectTarget("android-19")
ext.cordovaBuildToolsVersion = latestBuildToolsAvailable("19.1.0")

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project>
<target name="-pre-compile">
<!-- Fix library references due to bug in build.xml: See: https://groups.google.com/forum/#!topic/android-developers/0ivH-YqCjzg -->
<pathconvert property="fixedJarsPath" refid="project.all.jars.path">
<filtermapper>
<replacestring from="/bin/" to="/ant-build/"/>
<replacestring from="\bin\" to="\ant-build\"/>
</filtermapper>
</pathconvert>
<path id="project.all.jars.path">
<pathelement path="${fixedJarsPath}"/>
</path>
<echo message="Set jars path to: ${toString:project.all.jars.path}"/>
</target>
<!-- Rename AndroidManifest.xml so that Eclipse's import wizard doesn't detect ant-build as a project -->
<target name="-post-build">
<move file="ant-build/AndroidManifest.xml" tofile="ant-build/AndroidManifest.cordova.xml" failonerror="false" overwrite="true" />
<move file="CordovaLib/ant-build/AndroidManifest.xml" tofile="CordovaLib/ant-build/AndroidManifest.cordova.xml" failonerror="false" overwrite="true" />
</target>
</project>

View File

@@ -5,7 +5,7 @@ local.properties
/gradlew.bat
/gradle
# Ant builds
ant-build
ant-built
ant-gen
# Eclipse builds
gen

View File

@@ -1,5 +1,5 @@
// Platform: android
// 24ab6855470f2dc0662624b597c98585e56a1666
// 1fc2526faa6197e1637ecb48ebe0f876f008ba0f
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
@@ -19,7 +19,7 @@
under the License.
*/
;(function() {
var PLATFORM_VERSION_BUILD_LABEL = '3.7.2';
var PLATFORM_VERSION_BUILD_LABEL = '3.7.0-dev';
// file: src/scripts/require.js
/*jshint -W079 */
@@ -284,16 +284,10 @@ var cordova = {
if (callback) {
if (isSuccess && status == cordova.callbackStatus.OK) {
callback.success && callback.success.apply(null, args);
} else if (!isSuccess) {
} else {
callback.fail && callback.fail.apply(null, args);
}
/*
else
Note, this case is intentionally not caught.
this can happen if isSuccess is true, but callbackStatus is NO_RESULT
which is used to remove a callback from the list without calling the callbacks
typically keepCallback is false in this case
*/
// Clear callback if not expecting any more results
if (!keepCallback) {
delete cordova.callbacks[callbackId];
@@ -515,14 +509,9 @@ function each(objects, func, context) {
function clobber(obj, key, value) {
exports.replaceHookForTesting(obj, key);
var needsProperty = false;
try {
obj[key] = value;
} catch (e) {
needsProperty = true;
}
obj[key] = value;
// Getters can only be overridden by getters.
if (needsProperty || obj[key] !== value) {
if (obj[key] !== value) {
utils.defineGetter(obj, key, function() {
return value;
});
@@ -637,6 +626,7 @@ var utils = require('cordova/utils'),
* onDeviceReady* User event fired to indicate that Cordova is ready
* onResume User event fired to indicate a start/resume lifecycle event
* onPause User event fired to indicate a pause lifecycle event
* onDestroy* Internal event fired when app is being destroyed (User should use window.onunload event, not this one).
*
* The events marked with an * are sticky. Once they have fired, they will stay in the fired state.
* All listeners that subscribe after the event is fired will be executed right away.
@@ -848,6 +838,9 @@ channel.create('onResume');
// Event to indicate a pause lifecycle event
channel.create('onPause');
// Event to indicate a destroy lifecycle event
channel.createSticky('onDestroy');
// Channels that must fire before "deviceready" is fired.
channel.waitForInitialization('onCordovaReady');
channel.waitForInitialization('onDOMContentLoaded');
@@ -1034,7 +1027,12 @@ function buildPayload(payload, message) {
payload.push(+message.slice(1));
} else if (payloadKind == 'A') {
var data = message.slice(1);
payload.push(base64.toArrayBuffer(data));
var bytes = window.atob(data);
var arraybuffer = new Uint8Array(bytes.length);
for (var i = 0; i < bytes.length; i++) {
arraybuffer[i] = bytes.charCodeAt(i);
}
payload.push(arraybuffer.buffer);
} else if (payloadKind == 'S') {
payload.push(window.atob(message.slice(1)));
} else if (payloadKind == 'M') {
@@ -1169,7 +1167,6 @@ var cordova = require('cordova');
var modulemapper = require('cordova/modulemapper');
var platform = require('cordova/platform');
var pluginloader = require('cordova/pluginloader');
var utils = require('cordova/utils');
var platformInitChannelsArray = [channel.onNativeReady, channel.onPluginsReady];
@@ -1201,19 +1198,21 @@ function replaceNavigator(origNavigator) {
for (var key in origNavigator) {
if (typeof origNavigator[key] == 'function') {
newNavigator[key] = origNavigator[key].bind(origNavigator);
}
else {
} else {
(function(k) {
utils.defineGetterSetter(newNavigator,key,function() {
return origNavigator[k];
});
})(key);
Object.defineProperty(newNavigator, k, {
get: function() {
return origNavigator[k];
},
configurable: true,
enumerable: true
});
})(key);
}
}
}
return newNavigator;
}
if (window.navigator) {
window.navigator = replaceNavigator(window.navigator);
}
@@ -1294,7 +1293,6 @@ define("cordova/init_b", function(require, exports, module) {
var channel = require('cordova/channel');
var cordova = require('cordova');
var platform = require('cordova/platform');
var utils = require('cordova/utils');
var platformInitChannelsArray = [channel.onDOMContentLoaded, channel.onNativeReady];
@@ -1329,13 +1327,16 @@ function replaceNavigator(origNavigator) {
for (var key in origNavigator) {
if (typeof origNavigator[key] == 'function') {
newNavigator[key] = origNavigator[key].bind(origNavigator);
}
else {
} else {
(function(k) {
utils.defineGetterSetter(newNavigator,key,function() {
return origNavigator[k];
});
})(key);
Object.defineProperty(newNavigator, k, {
get: function() {
return origNavigator[k];
},
configurable: true,
enumerable: true
});
})(key);
}
}
}
@@ -1384,7 +1385,7 @@ platform.bootstrap && platform.bootstrap();
* Create all cordova objects once native side is ready.
*/
channel.join(function() {
platform.initialize && platform.initialize();
// Fire event to notify that all objects are created
@@ -1519,14 +1520,12 @@ module.exports = {
// TODO: Extract this as a proper plugin.
modulemapper.clobbers('cordova/plugin/android/app', 'navigator.app');
var APP_PLUGIN_NAME = Number(cordova.platformVersion.split('.')[0]) >= 4 ? 'CoreAndroid' : 'App';
// Inject a listener for the backbutton on the document.
var backButtonChannel = cordova.addDocumentEventHandler('backbutton');
backButtonChannel.onHasSubscribersChange = function() {
// If we just attached the first handler or detached the last handler,
// let native know we need to override the back button.
exec(null, null, APP_PLUGIN_NAME, "overrideBackbutton", [this.numHandlers == 1]);
exec(null, null, "App", "overrideBackbutton", [this.numHandlers == 1]);
};
// Add hardware MENU and SEARCH button handlers
@@ -1537,7 +1536,7 @@ module.exports = {
// generic button bind used for volumeup/volumedown buttons
var volumeButtonChannel = cordova.addDocumentEventHandler(buttonName + 'button');
volumeButtonChannel.onHasSubscribersChange = function() {
exec(null, null, APP_PLUGIN_NAME, "overrideButton", [buttonName, this.numHandlers == 1]);
exec(null, null, "App", "overrideButton", [buttonName, this.numHandlers == 1]);
};
}
// Inject a listener for the volume buttons on the document.
@@ -1547,38 +1546,11 @@ module.exports = {
// Let native code know we are all done on the JS side.
// Native code will then un-hide the WebView.
channel.onCordovaReady.subscribe(function() {
exec(onMessageFromNative, null, APP_PLUGIN_NAME, 'messageChannel', []);
exec(null, null, APP_PLUGIN_NAME, "show", []);
exec(null, null, "App", "show", []);
});
}
};
function onMessageFromNative(msg) {
var cordova = require('cordova');
var action = msg.action;
switch (action)
{
// Button events
case 'backbutton':
case 'menubutton':
case 'searchbutton':
// App life cycle events
case 'pause':
case 'resume':
// Keyboard events
case 'hidekeyboard':
case 'showkeyboard':
// Volume events
case 'volumedownbutton':
case 'volumeupbutton':
cordova.fireDocumentEvent(action);
break;
default:
throw new Error('Unknown event action ' + action);
}
}
});
// file: src/android/plugin/android/app.js
@@ -1977,4 +1949,4 @@ window.cordova = require('cordova');
require('cordova/init');
})();
})();

View File

@@ -23,35 +23,30 @@ buildscript {
mavenCentral()
}
// Switch the Android Gradle plugin version requirement depending on the
// installed version of Gradle. This dependency is documented at
// http://tools.android.com/tech-docs/new-build-system/version-compatibility
// and https://issues.apache.org/jira/browse/CB-8143
if (gradle.gradleVersion >= "2.2") {
dependencies {
classpath 'com.android.tools.build:gradle:1.0.0+'
}
} else if (gradle.gradleVersion >= "2.1") {
dependencies {
classpath 'com.android.tools.build:gradle:0.14.0+'
}
} else {
dependencies {
classpath 'com.android.tools.build:gradle:0.12.0+'
}
dependencies {
// This should be updated with each cordova-android release.
// It can affect things like where the .apk is generated.
// It also dictates what the minimum android build-tools version
// that you need (Set in bin/templates/project/cordova.gradle).
// Make sure the value is the same in all locations:
// * framework/build.gradle
// * bin/templates/project/cordova.gradle
// * bin/templates/cordova/lib/plugin-build.gradle
// * distributionUrl within bin/templates/cordova/lib/build.js.
classpath 'com.android.tools.build:gradle:0.12.+'
}
}
apply plugin: 'android-library'
android {
compileSdkVersion cdvCompileSdkVersion
buildToolsVersion cdvBuildToolsVersion
compileSdkVersion cordova.cordovaSdkVersion
buildToolsVersion cordova.cordovaBuildToolsVersion
publishNonDefault true
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6
targetCompatibility JavaVersion.VERSION_1_6
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
sourceSets {

View File

@@ -10,7 +10,7 @@
# Indicates whether an apk should be generated for each density.
split.density=false
# Project target.
target=android-21
target=android-19
apk-configurations=
renderscript.opt.level=O0
android.library=true

View File

@@ -49,6 +49,7 @@
<content src="index.html" />
<preference name="loglevel" value="DEBUG" />
<!--
<preference name="splashscreen" value="resourceName" />
<preference name="backgroundColor" value="0xFFF" />

View File

@@ -22,14 +22,10 @@ import org.apache.cordova.CordovaInterface;
import org.apache.cordova.LOG;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
@@ -58,13 +54,13 @@ import android.widget.RelativeLayout;
* @see CordovaWebViewClient
* @see CordovaWebView
*/
public class CordovaChromeClient extends WebChromeClient {
public class AndroidChromeClient extends WebChromeClient {
public static final int FILECHOOSER_RESULTCODE = 5173;
private String TAG = "CordovaLog";
private static final String LOG_TAG = "AndroidChromeClient";
private long MAX_QUOTA = 100 * 1024 * 1024;
protected CordovaInterface cordova;
protected CordovaWebView appView;
protected final CordovaInterface cordova;
protected final AndroidWebView appView;
// the video progress view
private View mVideoProgressView;
@@ -72,21 +68,14 @@ public class CordovaChromeClient extends WebChromeClient {
//Keep track of last AlertDialog showed
private AlertDialog lastHandledDialog;
@Deprecated
public CordovaChromeClient(CordovaInterface cordova) {
this.cordova = cordova;
}
public CordovaChromeClient(CordovaInterface ctx, CordovaWebView app) {
// File Chooser
protected ValueCallback<Uri> mUploadMessage;
public AndroidChromeClient(CordovaInterface ctx, AndroidWebView app) {
this.cordova = ctx;
this.appView = app;
}
@Deprecated
public void setWebView(CordovaWebView view) {
this.appView = view;
}
/**
* Tell the client to display a javascript alert dialog.
*
@@ -232,7 +221,7 @@ public class CordovaChromeClient extends WebChromeClient {
public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize,
long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater)
{
LOG.d(TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
LOG.d(LOG_TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
quotaUpdater.updateQuota(MAX_QUOTA);
}
@@ -245,7 +234,7 @@ public class CordovaChromeClient extends WebChromeClient {
//This is only for Android 2.1
if(android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.ECLAIR_MR1)
{
LOG.d(TAG, "%s: Line %d : %s", sourceID, lineNumber, message);
LOG.d(LOG_TAG, "%s: Line %d : %s", sourceID, lineNumber, message);
super.onConsoleMessage(message, lineNumber, sourceID);
}
}
@@ -255,7 +244,7 @@ public class CordovaChromeClient extends WebChromeClient {
public boolean onConsoleMessage(ConsoleMessage consoleMessage)
{
if (consoleMessage.message() != null)
LOG.d(TAG, "%s: Line %d : %s" , consoleMessage.sourceId() , consoleMessage.lineNumber(), consoleMessage.message());
LOG.d(LOG_TAG, "%s: Line %d : %s" , consoleMessage.sourceId() , consoleMessage.lineNumber(), consoleMessage.message());
return super.onConsoleMessage(consoleMessage);
}
@@ -310,51 +299,23 @@ public class CordovaChromeClient extends WebChromeClient {
}
return mVideoProgressView;
}
// <input type=file> support:
// openFileChooser() is for pre KitKat and in KitKat mr1 (it's known broken in KitKat).
// For Lollipop, we use onShowFileChooser().
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
this.openFileChooser(uploadMsg, "*/*");
}
public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) {
this.openFileChooser(uploadMsg, acceptType, null);
}
public void openFileChooser(final ValueCallback<Uri> uploadMsg, String acceptType, String capture)
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture)
{
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
cordova.startActivityForResult(new CordovaPlugin() {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
Log.d(TAG, "Receive file chooser URL: " + result);
uploadMsg.onReceiveValue(result);
}
}, intent, FILECHOOSER_RESULTCODE);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> filePathsCallback, final WebChromeClient.FileChooserParams fileChooserParams) {
Intent intent = fileChooserParams.createIntent();
try {
cordova.startActivityForResult(new CordovaPlugin() {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
Uri[] result = WebChromeClient.FileChooserParams.parseResult(resultCode, intent);
Log.d(TAG, "Receive file chooser URL: " + result);
filePathsCallback.onReceiveValue(result);
}
}, intent, FILECHOOSER_RESULTCODE);
} catch (ActivityNotFoundException e) {
Log.w("No activity found to handle file chooser intent.", e);
filePathsCallback.onReceiveValue(null);
}
return true;
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("*/*");
this.cordova.getActivity().startActivityForResult(Intent.createChooser(i, "File Browser"),
FILECHOOSER_RESULTCODE);
}
public void destroyLastDialog(){

View File

@@ -0,0 +1,50 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import android.webkit.JavascriptInterface;
import org.json.JSONException;
/**
* Contains APIs that the JS can call. All functions in here should also have
* an equivalent entry in CordovaChromeClient.java, and be added to
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
*/
class AndroidExposedJsApi implements ExposedJsApi {
private final CordovaBridge bridge;
AndroidExposedJsApi(CordovaBridge bridge) {
this.bridge = bridge;
}
@JavascriptInterface
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
return bridge.jsExec(bridgeSecret, service, action, callbackId, arguments);
}
@JavascriptInterface
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
bridge.jsSetNativeToJsBridgeMode(bridgeSecret, value);
}
@JavascriptInterface
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
return bridge.jsRetrieveJsMessages(bridgeSecret, fromOnlineEvent);
}
}

View File

@@ -0,0 +1,765 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebBackForwardList;
import android.webkit.WebHistoryItem;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebSettings.LayoutAlgorithm;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
/*
* This class is our web view.
*
* @see <a href="http://developer.android.com/guide/webapps/webview.html">WebView guide</a>
* @see <a href="http://developer.android.com/reference/android/webkit/WebView.html">WebView</a>
*/
public class AndroidWebView extends WebView implements CordovaWebView {
public static final String TAG = "AndroidWebView";
private HashSet<Integer> boundKeyCodes = new HashSet<Integer>();
PluginManager pluginManager;
private BroadcastReceiver receiver;
/** Activities and other important classes **/
private CordovaInterface cordova;
AndroidWebViewClient viewClient;
private AndroidChromeClient chromeClient;
// Flag to track that a loadUrl timeout occurred
int loadUrlTimeout = 0;
private long lastMenuEventTime = 0;
CordovaBridge bridge;
/** custom view created by the browser (a video player for example) */
private View mCustomView;
private WebChromeClient.CustomViewCallback mCustomViewCallback;
private CordovaResourceApi resourceApi;
private CordovaPreferences preferences;
private CordovaUriHelper helper;
// The URL passed to loadUrl(), not necessarily the URL of the current page.
String loadedUrl;
static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
Gravity.CENTER);
/** Used when created via reflection. */
public AndroidWebView(Context context) {
this(context, null);
}
/** Required to allow view to be used within XML layouts. */
public AndroidWebView(Context context, AttributeSet attrs) {
super(context, attrs);
}
// Use two-phase init so that the control will work with XML layouts.
@Override
public void init(CordovaInterface cordova, List<PluginEntry> pluginEntries,
CordovaPreferences preferences) {
if (this.cordova != null) {
throw new IllegalStateException();
}
this.cordova = cordova;
this.preferences = preferences;
this.helper = new CordovaUriHelper(cordova, this);
pluginManager = new PluginManager(this, this.cordova, pluginEntries);
resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);
bridge = new CordovaBridge(pluginManager, new NativeToJsMessageQueue(this, cordova), helper);
pluginManager.addService("App", "org.apache.cordova.CoreAndroid");
initWebViewSettings();
if (this.viewClient == null) {
setWebViewClient(Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH ?
new AndroidWebViewClient(cordova, this) :
new IceCreamCordovaWebViewClient(cordova, this));
}
if (this.chromeClient == null) {
setWebChromeClient(new AndroidChromeClient(cordova, this));
}
exposeJsInterface();
}
@SuppressLint("SetJavaScriptEnabled")
@SuppressWarnings("deprecation")
private void initWebViewSettings() {
this.setInitialScale(0);
this.setVerticalScrollBarEnabled(false);
// TODO: The Activity is the one that should call requestFocus().
if (shouldRequestFocusOnInit()) {
this.requestFocusFromTouch();
}
this.setInitialScale(0);
this.setVerticalScrollBarEnabled(false);
if (shouldRequestFocusOnInit()) {
this.requestFocusFromTouch();
}
// Enable JavaScript
final WebSettings settings = this.getSettings();
settings.setJavaScriptEnabled(true);
settings.setJavaScriptCanOpenWindowsAutomatically(true);
settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
// Set the nav dump for HTC 2.x devices (disabling for ICS, deprecated entirely for Jellybean 4.2)
try {
Method gingerbread_getMethod = WebSettings.class.getMethod("setNavDump", new Class[] { boolean.class });
String manufacturer = android.os.Build.MANUFACTURER;
Log.d(TAG, "CordovaWebView is running on device made by: " + manufacturer);
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB &&
android.os.Build.MANUFACTURER.contains("HTC"))
{
gingerbread_getMethod.invoke(settings, true);
}
} catch (NoSuchMethodException e) {
Log.d(TAG, "We are on a modern version of Android, we will deprecate HTC 2.3 devices in 2.8");
} catch (IllegalArgumentException e) {
Log.d(TAG, "Doing the NavDump failed with bad arguments");
} catch (IllegalAccessException e) {
Log.d(TAG, "This should never happen: IllegalAccessException means this isn't Android anymore");
} catch (InvocationTargetException e) {
Log.d(TAG, "This should never happen: InvocationTargetException means this isn't Android anymore.");
}
//We don't save any form data in the application
settings.setSaveFormData(false);
settings.setSavePassword(false);
// Jellybean rightfully tried to lock this down. Too bad they didn't give us a whitelist
// while we do this
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
Level16Apis.enableUniversalAccess(settings);
// Enable database
// We keep this disabled because we use or shim to get around DOM_EXCEPTION_ERROR_16
String databasePath = getContext().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
settings.setDatabaseEnabled(true);
settings.setDatabasePath(databasePath);
//Determine whether we're in debug or release mode, and turn on Debugging!
ApplicationInfo appInfo = getContext().getApplicationContext().getApplicationInfo();
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 &&
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
enableRemoteDebugging();
}
settings.setGeolocationDatabasePath(databasePath);
// Enable DOM storage
settings.setDomStorageEnabled(true);
// Enable built-in geolocation
settings.setGeolocationEnabled(true);
// Enable AppCache
// Fix for CB-2282
settings.setAppCacheMaxSize(5 * 1048576);
settings.setAppCachePath(databasePath);
settings.setAppCacheEnabled(true);
// Fix for CB-1405
// Google issue 4641
settings.getUserAgentString();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
if (this.receiver == null) {
this.receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
settings.getUserAgentString();
}
};
getContext().registerReceiver(this.receiver, intentFilter);
}
// end CB-1405
}
@TargetApi(Build.VERSION_CODES.KITKAT)
private void enableRemoteDebugging() {
try {
WebView.setWebContentsDebuggingEnabled(true);
} catch (IllegalArgumentException e) {
Log.d(TAG, "You have one job! To turn on Remote Web Debugging! YOU HAVE FAILED! ");
e.printStackTrace();
}
}
/**
* Override this method to decide whether or not you need to request the
* focus when your application start
*
* @return true unless this method is overriden to return a different value
*/
protected boolean shouldRequestFocusOnInit() {
return true;
}
private void exposeJsInterface() {
if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)) {
Log.i(TAG, "Disabled addJavascriptInterface() bridge since Android version is old.");
// Bug being that Java Strings do not get converted to JS strings automatically.
// This isn't hard to work-around on the JS side, but it's easier to just
// use the prompt bridge instead.
return;
}
AndroidExposedJsApi exposedJsApi = new AndroidExposedJsApi(bridge);
this.addJavascriptInterface(exposedJsApi, "_cordovaNative");
}
@Override
public void setWebViewClient(WebViewClient client) {
this.viewClient = (AndroidWebViewClient)client;
super.setWebViewClient(client);
}
@Override
public void setWebChromeClient(WebChromeClient client) {
this.chromeClient = (AndroidChromeClient)client;
super.setWebChromeClient(client);
}
/**
* Load the url into the webview.
*/
@Override
public void loadUrl(String url) {
this.loadUrlIntoView(url, true);
}
/**
* Load the url into the webview.
*/
@Override
public void loadUrlIntoView(final String url, boolean recreatePlugins) {
if (url.equals("about:blank") || url.startsWith("javascript:")) {
this.loadUrlNow(url);
return;
}
LOG.d(TAG, ">>> loadUrl(" + url + ")");
recreatePlugins = recreatePlugins || (loadedUrl == null);
if (recreatePlugins) {
this.loadedUrl = url;
this.pluginManager.init();
}
// Create a timeout timer for loadUrl
final AndroidWebView me = this;
final int currentLoadUrlTimeout = me.loadUrlTimeout;
final int loadUrlTimeoutValue = preferences.getInteger("LoadUrlTimeoutValue", 20000);
// Timeout error method
final Runnable loadError = new Runnable() {
public void run() {
me.stopLoading();
LOG.e(TAG, "CordovaWebView: TIMEOUT ERROR!");
if (viewClient != null) {
viewClient.onReceivedError(AndroidWebView.this, -6, "The connection to the server was unsuccessful.", url);
}
}
};
// Timeout timer method
final Runnable timeoutCheck = new Runnable() {
public void run() {
try {
synchronized (this) {
wait(loadUrlTimeoutValue);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
// If timeout, then stop loading and handle error
if (me.loadUrlTimeout == currentLoadUrlTimeout) {
me.cordova.getActivity().runOnUiThread(loadError);
}
}
};
// Load url
this.cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
cordova.getThreadPool().execute(timeoutCheck);
me.loadUrlNow(url);
}
});
}
/**
* Load URL in webview.
*/
private void loadUrlNow(String url) {
if (LOG.isLoggable(LOG.DEBUG) && !url.startsWith("javascript:")) {
LOG.d(TAG, ">>> loadUrlNow()");
}
if (url.startsWith("javascript:") || helper.shouldAllowNavigation(url)) {
super.loadUrl(url);
}
}
@Override
public void stopLoading() {
//viewClient.isCurrentlyLoading = false;
super.stopLoading();
}
public void onScrollChanged(int l, int t, int oldl, int oldt)
{
super.onScrollChanged(l, t, oldl, oldt);
//We should post a message that the scroll changed
ScrollEvent myEvent = new ScrollEvent(l, t, oldl, oldt, this);
pluginManager.postMessage("onScrollChanged", myEvent);
}
/**
* Send JavaScript statement back to JavaScript.
* (This is a convenience method)
*/
public void sendJavascript(String statement) {
bridge.getMessageQueue().addJavaScript(statement);
}
/**
* Send a plugin result back to JavaScript.
*/
public void sendPluginResult(PluginResult result, String callbackId) {
bridge.getMessageQueue().addPluginResult(result, callbackId);
}
/**
* Go to previous page in history. (We manage our own history)
*
* @return true if we went back, false if we are already at top
*/
public boolean backHistory() {
// Check webview first to see if there is a history
// This is needed to support curPage#diffLink, since they are added to appView's history, but not our history url array (JQMobile behavior)
if (super.canGoBack()) {
printBackForwardList();
super.goBack();
return true;
}
return false;
}
/**
* Load the specified URL in the Cordova webview or a new browser instance.
*
* NOTE: If openExternal is false, only URLs listed in whitelist can be loaded.
*
* @param url The url to load.
* @param openExternal Load url in browser instead of Cordova webview.
* @param clearHistory Clear the history stack, so new page becomes top of history
* @param params Parameters for new app
*/
public void showWebPage(String url, boolean openExternal, boolean clearHistory, HashMap<String, Object> params) {
LOG.d(TAG, "showWebPage(%s, %b, %b, HashMap", url, openExternal, clearHistory);
// If clearing history
if (clearHistory) {
this.clearHistory();
}
// If loading into our webview
if (!openExternal) {
// Make sure url is in whitelist
if (helper.shouldAllowNavigation(url)) {
// TODO: What about params?
// Load new URL
loadUrlIntoView(url, true);
return;
}
// Load in default viewer if not
LOG.w(TAG, "showWebPage: Cannot load URL into webview since it is not in white list. Loading into browser instead. (URL=" + url + ")");
}
try {
// Omitting the MIME type for file: URLs causes "No Activity found to handle Intent".
// Adding the MIME type to http: URLs causes them to not be handled by the downloader.
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.parse(url);
if ("file".equals(uri.getScheme())) {
intent.setDataAndType(uri, resourceApi.getMimeType(uri));
} else {
intent.setData(uri);
}
cordova.getActivity().startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
LOG.e(TAG, "Error loading url " + url, e);
}
}
/*
* onKeyDown
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
if(boundKeyCodes.contains(keyCode))
{
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
this.loadUrl("javascript:cordova.fireDocumentEvent('volumedownbutton');");
return true;
}
else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
this.loadUrl("javascript:cordova.fireDocumentEvent('volumeupbutton');");
return true;
}
else
{
return super.onKeyDown(keyCode, event);
}
}
else if(keyCode == KeyEvent.KEYCODE_BACK)
{
return !(this.startOfHistory()) || isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK);
}
else if(keyCode == KeyEvent.KEYCODE_MENU)
{
//How did we get here? Is there a childView?
View childView = this.getFocusedChild();
if(childView != null)
{
//Make sure we close the keyboard if it's present
InputMethodManager imm = (InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(childView.getWindowToken(), 0);
cordova.getActivity().openOptionsMenu();
return true;
} else {
return super.onKeyDown(keyCode, event);
}
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event)
{
// If back key
if (keyCode == KeyEvent.KEYCODE_BACK) {
// A custom view is currently displayed (e.g. playing a video)
if(mCustomView != null) {
this.hideCustomView();
return true;
} else {
// The webview is currently displayed
// If back key is bound, then send event to JavaScript
if (isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK)) {
this.loadUrl("javascript:cordova.fireDocumentEvent('backbutton');");
return true;
} else {
// If not bound
// Go to previous page in webview if it is possible to go back
if (this.backHistory()) {
return true;
}
// If not, then invoke default behavior
}
}
}
// Legacy
else if (keyCode == KeyEvent.KEYCODE_MENU) {
if (this.lastMenuEventTime < event.getEventTime()) {
this.loadUrl("javascript:cordova.fireDocumentEvent('menubutton');");
}
this.lastMenuEventTime = event.getEventTime();
return super.onKeyUp(keyCode, event);
}
// If search key
else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
this.loadUrl("javascript:cordova.fireDocumentEvent('searchbutton');");
return true;
}
//Does webkit change this behavior?
return super.onKeyUp(keyCode, event);
}
@Override
public void setButtonPlumbedToJs(int keyCode, boolean override) {
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_BACK:
// TODO: Why are search and menu buttons handled separately?
if (override) {
boundKeyCodes.add(keyCode);
} else {
boundKeyCodes.remove(keyCode);
}
return;
default:
throw new IllegalArgumentException("Unsupported keycode: " + keyCode);
}
}
@Override
public boolean isButtonPlumbedToJs(int keyCode)
{
return boundKeyCodes.contains(keyCode);
}
public void handlePause(boolean keepRunning)
{
LOG.d(TAG, "Handle the pause");
// Send pause event to JavaScript
this.loadUrl("javascript:try{cordova.fireDocumentEvent('pause');}catch(e){console.log('exception firing pause event from native');};");
// Forward to plugins
if (this.pluginManager != null) {
this.pluginManager.onPause(keepRunning);
}
// If app doesn't want to run in background
if (!keepRunning) {
// Pause JavaScript timers (including setInterval)
this.pauseTimers();
}
}
public void handleResume(boolean keepRunning, boolean activityResultKeepRunning)
{
this.loadUrl("javascript:try{cordova.fireDocumentEvent('resume');}catch(e){console.log('exception firing resume event from native');};");
// Forward to plugins
if (this.pluginManager != null) {
this.pluginManager.onResume(keepRunning);
}
// Resume JavaScript timers (including setInterval)
this.resumeTimers();
}
public void handleDestroy()
{
// Send destroy event to JavaScript
this.loadUrl("javascript:try{cordova.require('cordova/channel').onDestroy.fire();}catch(e){console.log('exception firing destroy event from native');};");
// Load blank page so that JavaScript onunload is called
this.loadUrl("about:blank");
//Remove last AlertDialog
this.chromeClient.destroyLastDialog();
// Forward to plugins
if (this.pluginManager != null) {
this.pluginManager.onDestroy();
}
// unregister the receiver
if (this.receiver != null) {
try {
getContext().unregisterReceiver(this.receiver);
} catch (Exception e) {
Log.e(TAG, "Error unregistering configuration receiver: " + e.getMessage(), e);
}
}
}
public void onNewIntent(Intent intent)
{
//Forward to plugins
if (this.pluginManager != null) {
this.pluginManager.onNewIntent(intent);
}
}
// Wrapping these functions in their own class prevents warnings in adb like:
// VFY: unable to resolve virtual method 285: Landroid/webkit/WebSettings;.setAllowUniversalAccessFromFileURLs
@TargetApi(16)
private static class Level16Apis {
static void enableUniversalAccess(WebSettings settings) {
settings.setAllowUniversalAccessFromFileURLs(true);
}
}
public void printBackForwardList() {
WebBackForwardList currentList = this.copyBackForwardList();
int currentSize = currentList.getSize();
for(int i = 0; i < currentSize; ++i)
{
WebHistoryItem item = currentList.getItemAtIndex(i);
String url = item.getUrl();
LOG.d(TAG, "The URL at index: " + Integer.toString(i) + " is " + url );
}
}
//Can Go Back is BROKEN!
public boolean startOfHistory()
{
WebBackForwardList currentList = this.copyBackForwardList();
WebHistoryItem item = currentList.getItemAtIndex(0);
if( item!=null){ // Null-fence in case they haven't called loadUrl yet (CB-2458)
String url = item.getUrl();
String currentUrl = this.getUrl();
LOG.d(TAG, "The current URL is: " + currentUrl);
LOG.d(TAG, "The URL at item 0 is: " + url);
return currentUrl.equals(url);
}
return false;
}
public void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
Log.d(TAG, "showing Custom View");
// if a view already exists then immediately terminate the new one
if (mCustomView != null) {
callback.onCustomViewHidden();
return;
}
// Store the view and its callback for later (to kill it properly)
mCustomView = view;
mCustomViewCallback = callback;
// Add the custom view to its container.
ViewGroup parent = (ViewGroup) this.getParent();
parent.addView(view, COVER_SCREEN_GRAVITY_CENTER);
// Hide the content view.
this.setVisibility(View.GONE);
// Finally show the custom view container.
parent.setVisibility(View.VISIBLE);
parent.bringToFront();
}
public void hideCustomView() {
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
Log.d(TAG, "Hiding Custom View");
if (mCustomView == null) return;
// Hide the custom view.
mCustomView.setVisibility(View.GONE);
// Remove the custom view from its container.
ViewGroup parent = (ViewGroup) this.getParent();
parent.removeView(mCustomView);
mCustomView = null;
mCustomViewCallback.onCustomViewHidden();
// Show the content view.
this.setVisibility(View.VISIBLE);
}
/**
* if the video overlay is showing then we need to know
* as it effects back button handling
*
* @return true if custom view is showing
*/
public boolean isCustomViewShowing() {
return mCustomView != null;
}
public WebBackForwardList restoreState(Bundle savedInstanceState)
{
WebBackForwardList myList = super.restoreState(savedInstanceState);
Log.d(TAG, "WebView restoration crew now restoring!");
//Initialize the plugin manager once more
this.pluginManager.init();
return myList;
}
public CordovaResourceApi getResourceApi() {
return resourceApi;
}
void onPageReset() {
boundKeyCodes.clear();
pluginManager.onReset();
bridge.reset(loadedUrl);
}
@Override
public PluginManager getPluginManager() {
return this.pluginManager;
}
@Override
public View getView() {
return this;
}
@Override
public CordovaPreferences getPreferences() {
return preferences;
}
@Override
public void onFilePickerResult(Uri uri) {
if (null == chromeClient.mUploadMessage)
return;
chromeClient.mUploadMessage.onReceiveValue(uri);
chromeClient.mUploadMessage = null;
}
@Override
public Object postMessage(String id, Object data) {
return pluginManager.postMessage(id, data);
}
}

View File

@@ -20,8 +20,6 @@ package org.apache.cordova;
import java.util.Hashtable;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.LOG;
import org.json.JSONException;
import org.json.JSONObject;
@@ -32,12 +30,12 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.net.http.SslError;
import android.view.View;
import android.webkit.ClientCertRequest;
import android.webkit.HttpAuthHandler;
import android.webkit.SslErrorHandler;
import android.webkit.WebView;
import android.webkit.WebViewClient;
/**
* This class is the WebViewClient that implements callbacks for our web view.
* The kind of callbacks that happen here are regarding the rendering of the
@@ -50,42 +48,20 @@ import android.webkit.WebViewClient;
* @see CordovaChromeClient
* @see CordovaWebView
*/
public class CordovaWebViewClient extends WebViewClient {
public class AndroidWebViewClient extends WebViewClient {
private static final String TAG = "CordovaWebViewClient";
CordovaInterface cordova;
CordovaWebView appView;
CordovaUriHelper helper;
private static final String TAG = "AndroidWebViewClient";
protected final CordovaInterface cordova;
protected final AndroidWebView appView;
protected final CordovaUriHelper helper;
private boolean doClearHistory = false;
boolean isCurrentlyLoading;
/** The authorization tokens. */
private Hashtable<String, AuthenticationToken> authenticationTokens = new Hashtable<String, AuthenticationToken>();
@Deprecated
public CordovaWebViewClient(CordovaInterface cordova) {
public AndroidWebViewClient(CordovaInterface cordova, AndroidWebView view) {
this.cordova = cordova;
}
/**
* Constructor.
*
* @param cordova
* @param view
*/
public CordovaWebViewClient(CordovaInterface cordova, CordovaWebView view) {
this.cordova = cordova;
this.appView = view;
helper = new CordovaUriHelper(cordova, view);
}
/**
* Constructor.
*
* @param view
*/
@Deprecated
public void setWebView(CordovaWebView view) {
this.appView = view;
helper = new CordovaUriHelper(cordova, view);
}
@@ -100,60 +76,25 @@ public class CordovaWebViewClient extends WebViewClient {
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return helper.shouldOverrideUrlLoading(view, url);
return helper.shouldOverrideUrlLoading(url);
}
/**
* On received http auth request.
* The method reacts on all registered authentication tokens. There is one and only one authentication token for any host + realm combination
*
* @param view
* @param handler
* @param host
* @param realm
*/
@Override
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
// Get the authentication token (if specified)
// Get the authentication token
AuthenticationToken token = this.getAuthenticationToken(host, realm);
if (token != null) {
handler.proceed(token.getUserName(), token.getPassword());
return;
}
// Check if there is some plugin which can resolve this auth challenge
PluginManager pluginManager = this.appView.pluginManager;
if (pluginManager != null && pluginManager.onReceivedHttpAuthRequest(this.appView, new CordovaHttpAuthHandler(handler), host, realm)) {
this.appView.loadUrlTimeout++;
return;
else {
// Handle 401 like we'd normally do!
super.onReceivedHttpAuthRequest(view, handler, host, realm);
}
// By default handle 401 like we'd normally do!
super.onReceivedHttpAuthRequest(view, handler, host, realm);
}
/**
* On received client cert request.
* The method forwards the request to any running plugins before using the default implementation.
*
* @param view
* @param request
*/
@Override
@TargetApi(21)
public void onReceivedClientCertRequest (WebView view, ClientCertRequest request)
{
// Check if there is some plugin which can resolve this certificate request
PluginManager pluginManager = this.appView.pluginManager;
if (pluginManager != null && pluginManager.onReceivedClientCertRequest(this.appView, new CordovaClientCertRequest(request))) {
this.appView.loadUrlTimeout++;
return;
}
// By default pass to WebViewClient
super.onReceivedClientCertRequest(view, request);
}
/**
@@ -170,16 +111,11 @@ public class CordovaWebViewClient extends WebViewClient {
super.onPageStarted(view, url, favicon);
isCurrentlyLoading = true;
LOG.d(TAG, "onPageStarted(" + url + ")");
// Flush stale messages.
this.appView.bridge.reset(url);
// Flush stale messages & reset plugins.
this.appView.onPageReset();
// Broadcast message that page has loaded
this.appView.postMessage("onPageStarted", url);
// Notify all plugins of the navigation, so they can clean up if necessary.
if (this.appView.pluginManager != null) {
this.appView.pluginManager.onReset();
}
this.appView.getPluginManager().postMessage("onPageStarted", url);
}
/**
@@ -193,8 +129,8 @@ public class CordovaWebViewClient extends WebViewClient {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
// Ignore excessive calls, if url is not about:blank (CB-8317).
if (!isCurrentlyLoading && !url.startsWith("about:")) {
// Ignore excessive calls.
if (!isCurrentlyLoading) {
return;
}
isCurrentlyLoading = false;
@@ -212,10 +148,10 @@ public class CordovaWebViewClient extends WebViewClient {
}
// Clear timeout flag
this.appView.loadUrlTimeout++;
appView.loadUrlTimeout++;
// Broadcast message that page has loaded
this.appView.postMessage("onPageFinished", url);
this.appView.getPluginManager().postMessage("onPageFinished", url);
// Make app visible after 2 sec in case there was a JS error and Cordova JS never initialized correctly
if (this.appView.getVisibility() == View.INVISIBLE) {
@@ -225,7 +161,7 @@ public class CordovaWebViewClient extends WebViewClient {
Thread.sleep(2000);
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
appView.postMessage("spinner", "stop");
appView.getPluginManager().postMessage("spinner", "stop");
}
});
} catch (InterruptedException e) {
@@ -237,7 +173,7 @@ public class CordovaWebViewClient extends WebViewClient {
// Shutdown if blank loaded
if (url.equals("about:blank")) {
appView.postMessage("exit", null);
appView.getPluginManager().postMessage("exit", null);
}
}
@@ -259,7 +195,7 @@ public class CordovaWebViewClient extends WebViewClient {
LOG.d(TAG, "CordovaWebViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl);
// Clear timeout flag
this.appView.loadUrlTimeout++;
appView.loadUrlTimeout++;
// If this is a "Protocol Not Supported" error, then revert to the previous
// page. If there was no previous page, then punt. The application's config
@@ -282,7 +218,7 @@ public class CordovaWebViewClient extends WebViewClient {
} catch (JSONException e) {
e.printStackTrace();
}
this.appView.postMessage("onReceivedError", data);
this.appView.getPluginManager().postMessage("onReceivedError", data);
}
/**
@@ -391,5 +327,4 @@ public class CordovaWebViewClient extends WebViewClient {
public void clearAuthenticationTokens() {
this.authenticationTokens.clear();
}
}

View File

@@ -37,6 +37,7 @@ public class Config {
parser = new ConfigXmlParser();
parser.parse(action);
parser.getPreferences().setPreferencesBundle(action.getIntent().getExtras());
parser.getPreferences().copyIntoIntentExtras(action);
}
// Intended to be used for testing only; creates an empty configuration.
@@ -45,48 +46,6 @@ public class Config {
parser = new ConfigXmlParser();
}
}
/**
* Add entry to approved list of URLs (whitelist)
*
* @param origin URL regular expression to allow
* @param subdomains T=include all subdomains under origin
*/
public static void addWhiteListEntry(String origin, boolean subdomains) {
if (parser == null) {
Log.e(TAG, "Config was not initialised. Did you forget to Config.init(this)?");
return;
}
parser.getInternalWhitelist().addWhiteListEntry(origin, subdomains);
}
/**
* Determine if URL is in approved list of URLs to load.
*
* @param url
* @return true if whitelisted
*/
public static boolean isUrlWhiteListed(String url) {
if (parser == null) {
Log.e(TAG, "Config was not initialised. Did you forget to Config.init(this)?");
return false;
}
return parser.getInternalWhitelist().isUrlWhiteListed(url);
}
/**
* Determine if URL is in approved list of URLs to launch external applications.
*
* @param url
* @return true if whitelisted
*/
public static boolean isUrlExternallyWhiteListed(String url) {
if (parser == null) {
Log.e(TAG, "Config was not initialised. Did you forget to Config.init(this)?");
return false;
}
return parser.getExternalWhitelist().isUrlWhiteListed(url);
}
public static String getStartUrl() {
if (parser == null) {
@@ -99,14 +58,6 @@ public class Config {
return parser.getPreferences().getString("errorurl", null);
}
public static Whitelist getWhitelist() {
return parser.getInternalWhitelist();
}
public static Whitelist getExternalWhitelist() {
return parser.getExternalWhitelist();
}
public static List<PluginEntry> getPluginEntries() {
return parser.getPluginEntries();
}

View File

@@ -30,25 +30,14 @@ import org.xmlpull.v1.XmlPullParserException;
import android.app.Activity;
import android.content.res.XmlResourceParser;
import android.util.Log;
public class ConfigXmlParser {
private static String TAG = "ConfigXmlParser";
private String launchUrl = "file:///android_asset/www/index.html";
private CordovaPreferences prefs = new CordovaPreferences();
private Whitelist internalWhitelist = new Whitelist();
private Whitelist externalWhitelist = new Whitelist();
private ArrayList<PluginEntry> pluginEntries = new ArrayList<PluginEntry>(20);
public Whitelist getInternalWhitelist() {
return internalWhitelist;
}
public Whitelist getExternalWhitelist() {
return externalWhitelist;
}
public CordovaPreferences getPreferences() {
return prefs;
}
@@ -60,7 +49,7 @@ public class ConfigXmlParser {
public String getLaunchUrl() {
return launchUrl;
}
public void parse(Activity action) {
// First checking the class namespace for config.xml
int id = action.getResources().getIdentifier("config", "xml", action.getClass().getPackage().getName());
@@ -75,86 +64,20 @@ public class ConfigXmlParser {
parse(action.getResources().getXml(id));
}
boolean insideFeature = false;
String service = "", pluginClass = "", paramType = "";
boolean onload = false;
public void parse(XmlResourceParser xml) {
int eventType = -1;
String service = "", pluginClass = "", paramType = "";
boolean onload = false;
boolean insideFeature = false;
ArrayList<String> urlMap = null;
// Add implicitly allowed URLs
internalWhitelist.addWhiteListEntry("file:///*", false);
internalWhitelist.addWhiteListEntry("content:///*", false);
internalWhitelist.addWhiteListEntry("data:*", false);
while (eventType != XmlResourceParser.END_DOCUMENT) {
if (eventType == XmlResourceParser.START_TAG) {
String strNode = xml.getName();
if (strNode.equals("url-filter")) {
Log.w(TAG, "Plugin " + service + " is using deprecated tag <url-filter>");
if (urlMap == null) {
urlMap = new ArrayList<String>(2);
}
urlMap.add(xml.getAttributeValue(null, "value"));
} else if (strNode.equals("feature")) {
//Check for supported feature sets aka. plugins (Accelerometer, Geolocation, etc)
//Set the bit for reading params
insideFeature = true;
service = xml.getAttributeValue(null, "name");
}
else if (insideFeature && strNode.equals("param")) {
paramType = xml.getAttributeValue(null, "name");
if (paramType.equals("service")) // check if it is using the older service param
service = xml.getAttributeValue(null, "value");
else if (paramType.equals("package") || paramType.equals("android-package"))
pluginClass = xml.getAttributeValue(null,"value");
else if (paramType.equals("onload"))
onload = "true".equals(xml.getAttributeValue(null, "value"));
}
else if (strNode.equals("access")) {
String origin = xml.getAttributeValue(null, "origin");
String subdomains = xml.getAttributeValue(null, "subdomains");
boolean external = (xml.getAttributeValue(null, "launch-external") != null);
if (origin != null) {
if (external) {
externalWhitelist.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
} else {
if ("*".equals(origin)) {
// Special-case * origin to mean http and https when used for internal
// whitelist. This prevents external urls like sms: and geo: from being
// handled internally.
internalWhitelist.addWhiteListEntry("http://*/*", false);
internalWhitelist.addWhiteListEntry("https://*/*", false);
} else {
internalWhitelist.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
}
}
}
}
else if (strNode.equals("preference")) {
String name = xml.getAttributeValue(null, "name").toLowerCase(Locale.ENGLISH);
String value = xml.getAttributeValue(null, "value");
prefs.set(name, value);
}
else if (strNode.equals("content")) {
String src = xml.getAttributeValue(null, "src");
if (src != null) {
setStartUrl(src);
}
}
handleStartTag(xml);
}
else if (eventType == XmlResourceParser.END_TAG)
{
String strNode = xml.getName();
if (strNode.equals("feature")) {
pluginEntries.add(new PluginEntry(service, pluginClass, onload, urlMap));
service = "";
pluginClass = "";
insideFeature = false;
onload = false;
urlMap = null;
}
handleEndTag(xml);
}
try {
eventType = xml.next();
@@ -166,6 +89,48 @@ public class ConfigXmlParser {
}
}
public void handleStartTag(XmlResourceParser xml) {
String strNode = xml.getName();
if (strNode.equals("feature")) {
//Check for supported feature sets aka. plugins (Accelerometer, Geolocation, etc)
//Set the bit for reading params
insideFeature = true;
service = xml.getAttributeValue(null, "name");
}
else if (insideFeature && strNode.equals("param")) {
paramType = xml.getAttributeValue(null, "name");
if (paramType.equals("service")) // check if it is using the older service param
service = xml.getAttributeValue(null, "value");
else if (paramType.equals("package") || paramType.equals("android-package"))
pluginClass = xml.getAttributeValue(null,"value");
else if (paramType.equals("onload"))
onload = "true".equals(xml.getAttributeValue(null, "value"));
}
else if (strNode.equals("preference")) {
String name = xml.getAttributeValue(null, "name").toLowerCase(Locale.ENGLISH);
String value = xml.getAttributeValue(null, "value");
prefs.set(name, value);
}
else if (strNode.equals("content")) {
String src = xml.getAttributeValue(null, "src");
if (src != null) {
setStartUrl(src);
}
}
}
public void handleEndTag(XmlResourceParser xml) {
String strNode = xml.getName();
if (strNode.equals("feature")) {
pluginEntries.add(new PluginEntry(service, pluginClass, onload));
service = "";
pluginClass = "";
insideFeature = false;
onload = false;
}
}
private void setStartUrl(String src) {
Pattern schemeRegex = Pattern.compile("^[a-z-]+://");
Matcher matcher = schemeRegex.matcher(src);

View File

@@ -18,28 +18,32 @@
*/
package org.apache.cordova;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.json.JSONArray;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.LOG;
import org.json.JSONException;
import org.json.JSONObject;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Display;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -87,12 +91,7 @@ public class CordovaActivity extends Activity implements CordovaInterface {
// The webview for our app
protected CordovaWebView appView;
@Deprecated // unused.
protected CordovaWebViewClient webViewClient;
@Deprecated // Will be removed. Use findViewById() to retrieve views.
protected LinearLayout root;
protected ProgressDialog spinnerDialog = null;
private final ExecutorService threadPool = Executors.newCachedThreadPool();
private static int ACTIVITY_STARTING = 0;
@@ -101,8 +100,7 @@ public class CordovaActivity extends Activity implements CordovaInterface {
private int activityState = 0; // 0=starting, 1=running (after 1st resume), 2=shutting down
// Plugin to call when activity result is received
protected int activityResultRequestCode;
protected CordovaPlugin activityResultCallback;
protected CordovaPlugin activityResultCallback = null;
protected boolean activityResultKeepRunning;
/*
@@ -110,10 +108,8 @@ public class CordovaActivity extends Activity implements CordovaInterface {
*/
// Draw a splash screen using an image located in the drawable resource directory.
@Deprecated // Use "SplashScreen" preference instead.
// This is not the same as calling super.loadSplashscreen(url)
protected int splashscreen = 0;
@Deprecated // Use "SplashScreenDelay" preference instead.
protected int splashscreenTime = -1;
// LoadUrl timeout value in msec (default of 20 sec)
protected int loadUrlTimeoutValue = 20000;
@@ -127,69 +123,9 @@ public class CordovaActivity extends Activity implements CordovaInterface {
// Read from config.xml:
protected CordovaPreferences preferences;
protected Whitelist internalWhitelist;
protected Whitelist externalWhitelist;
protected String launchUrl;
protected ArrayList<PluginEntry> pluginEntries;
/**
* Sets the authentication token.
*
* @param authenticationToken
* @param host
* @param realm
*/
public void setAuthenticationToken(AuthenticationToken authenticationToken, String host, String realm) {
if (this.appView != null && this.appView.viewClient != null) {
this.appView.viewClient.setAuthenticationToken(authenticationToken, host, realm);
}
}
/**
* Removes the authentication token.
*
* @param host
* @param realm
*
* @return the authentication token or null if did not exist
*/
public AuthenticationToken removeAuthenticationToken(String host, String realm) {
if (this.appView != null && this.appView.viewClient != null) {
return this.appView.viewClient.removeAuthenticationToken(host, realm);
}
return null;
}
/**
* Gets the authentication token.
*
* In order it tries:
* 1- host + realm
* 2- host
* 3- realm
* 4- no host, no realm
*
* @param host
* @param realm
*
* @return the authentication token
*/
public AuthenticationToken getAuthenticationToken(String host, String realm) {
if (this.appView != null && this.appView.viewClient != null) {
return this.appView.viewClient.getAuthenticationToken(host, realm);
}
return null;
}
/**
* Clear all authentication tokens.
*/
public void clearAuthenticationTokens() {
if (this.appView != null && this.appView.viewClient != null) {
this.appView.viewClient.clearAuthenticationTokens();
}
}
/**
* Called when the activity is first created.
*/
@@ -225,6 +161,20 @@ public class CordovaActivity extends Activity implements CordovaInterface {
initCallbackClass = savedInstanceState.getString("callbackClass");
}
}
protected void init() {
appView = makeWebView();
// TODO: Have the views set this themselves.
if (preferences.getBoolean("DisallowOverscroll", false)) {
appView.getView().setOverScrollMode(View.OVER_SCROLL_NEVER);
}
createViews();
// TODO: Make this a preference (CB-6153)
// Setup the hardware volume controls to handle volume control
setVolumeControlStream(AudioManager.STREAM_MUSIC);
}
@SuppressWarnings("deprecation")
protected void loadConfig() {
@@ -232,8 +182,7 @@ public class CordovaActivity extends Activity implements CordovaInterface {
parser.parse(this);
preferences = parser.getPreferences();
preferences.setPreferencesBundle(getIntent().getExtras());
internalWhitelist = parser.getInternalWhitelist();
externalWhitelist = parser.getExternalWhitelist();
preferences.copyIntoIntentExtras(this);
launchUrl = parser.getLaunchUrl();
pluginEntries = parser.getPluginEntries();
Config.parser = parser;
@@ -242,32 +191,31 @@ public class CordovaActivity extends Activity implements CordovaInterface {
@SuppressWarnings("deprecation")
protected void createViews() {
// This builds the view. We could probably get away with NOT having a LinearLayout, but I like having a bucket!
LOG.d(TAG, "CordovaActivity.createViews()");
Display display = getWindowManager().getDefaultDisplay();
int width = display.getWidth();
int height = display.getHeight();
root = new LinearLayoutSoftKeyboardDetect(this, width, height);
LinearLayoutSoftKeyboardDetect root = new LinearLayoutSoftKeyboardDetect(this, width, height);
root.setOrientation(LinearLayout.VERTICAL);
root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT, 0.0F));
appView.setId(100);
appView.setLayoutParams(new LinearLayout.LayoutParams(
appView.getView().setId(100);
appView.getView().setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
1.0F));
// Add web view but make it invisible while loading URL
appView.getView().setVisibility(View.INVISIBLE);
// need to remove appView from any existing parent before invoking root.addView(appView)
ViewParent parent = appView.getParent();
ViewParent parent = appView.getView().getParent();
if ((parent != null) && (parent != root)) {
LOG.d(TAG, "removing appView from existing parent");
ViewGroup parentGroup = (ViewGroup) parent;
parentGroup.removeView(appView);
parentGroup.removeView(appView.getView());
}
root.addView((View) appView);
root.addView(appView.getView());
setContentView(root);
int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK);
@@ -288,82 +236,68 @@ public class CordovaActivity extends Activity implements CordovaInterface {
* require a more specialized web view.
*/
protected CordovaWebView makeWebView() {
return new CordovaWebView(CordovaActivity.this);
}
/**
* Construct the client for the default web view object.
*
* This is intended to be overridable by subclasses of CordovaIntent which
* require a more specialized web view.
*
* @param webView the default constructed web view object
*/
protected CordovaWebViewClient makeWebViewClient(CordovaWebView webView) {
return webView.makeWebViewClient(this);
}
/**
* Construct the chrome client for the default web view object.
*
* This is intended to be overridable by subclasses of CordovaIntent which
* require a more specialized web view.
*
* @param webView the default constructed web view object
*/
protected CordovaChromeClient makeChromeClient(CordovaWebView webView) {
return webView.makeWebChromeClient(this);
}
public void init() {
this.init(appView, null, null);
}
@SuppressLint("NewApi")
@Deprecated // Call init() instead and override makeWebView() to customize.
public void init(CordovaWebView webView, CordovaWebViewClient webViewClient, CordovaChromeClient webChromeClient) {
LOG.d(TAG, "CordovaActivity.init()");
if (splashscreenTime >= 0) {
preferences.set("SplashScreenDelay", splashscreenTime);
String r = preferences.getString("webView", null);
CordovaWebView ret = null;
if (r != null) {
try {
Class<?> webViewClass = Class.forName(r);
Constructor<?> constructor = webViewClass.getConstructor(Context.class);
ret = (CordovaWebView) constructor.newInstance((Context)this);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
if (splashscreen != 0) {
preferences.set("SplashDrawableId", splashscreen);
}
appView = webView != null ? webView : makeWebView();
// TODO: Have the views set this themselves.
if (preferences.getBoolean("DisallowOverscroll", false)) {
appView.setOverScrollMode(View.OVER_SCROLL_NEVER);
}
createViews();
// Init plugins only after creating views
if (appView.pluginManager == null) {
appView.init(this, webViewClient != null ? webViewClient : makeWebViewClient(appView),
webChromeClient != null ? webChromeClient : makeChromeClient(appView),
pluginEntries, internalWhitelist, externalWhitelist, preferences);
}
// Wire the hardware volume controls to control media if desired.
String volumePref = preferences.getString("DefaultVolumeStream", "");
if ("media".equals(volumePref.toLowerCase(Locale.ENGLISH))) {
setVolumeControlStream(AudioManager.STREAM_MUSIC);
if (ret == null) {
// If all else fails, return a default WebView
ret = new AndroidWebView(this);
}
ret.init(this, pluginEntries, preferences);
return ret;
}
/**
* Load the url into the webview.
*/
public void loadUrl(String url) {
public void loadUrl(String url, int splashscreenTime) {
if (appView == null) {
init();
}
String splash = preferences.getString("SplashScreen", null);
if(splashscreenTime > 0 && splash != null)
{
this.splashscreen = getResources().getIdentifier(splash, "drawable", getClass().getPackage().getName());;
if(this.splashscreen != 0)
{
this.showSplashScreen(splashscreenTime);
}
}
// If keepRunning
this.keepRunning = preferences.getBoolean("KeepRunning", true);
appView.loadUrlIntoView(url, true);
//Check if the view is attached to anything
if(appView.getView().getParent() != null)
{
// Then load the spinner
this.loadSpinner();
}
//Load the correct splashscreen
if(this.splashscreen != 0)
{
appView.getPluginManager().postMessage("splashscreen", "show");
}
this.appView.loadUrlIntoView(url, true);
}
/**
@@ -373,139 +307,44 @@ public class CordovaActivity extends Activity implements CordovaInterface {
* @param url
* @param time The number of ms to wait before loading webview
*/
@Deprecated // Use loadUrl(String url) instead.
public void loadUrl(final String url, int time) {
this.splashscreenTime = time;
this.loadUrl(url);
}
@Deprecated
public void cancelLoadUrl() {
}
/**
* Clear the resource cache.
*/
@Deprecated // Call method on appView directly.
public void clearCache() {
public void loadUrl(final String url) {
if (appView == null) {
init();
}
this.appView.clearCache(true);
this.loadUrl(url, preferences.getInteger("SplashScreenDelay", 3000));
}
/**
* Clear web history in this web view.
/*
* Load the spinner
*/
@Deprecated // Call method on appView directly.
public void clearHistory() {
this.appView.clearHistory();
}
void loadSpinner() {
/**
* Go to previous page in history. (We manage our own history)
*
* @return true if we went back, false if we are already at top
*/
@Deprecated // Call method on appView directly.
public boolean backHistory() {
if (this.appView != null) {
return appView.backHistory();
// If loadingDialog property, then show the App loading dialog for first page of app
String loading = null;
if ((this.appView == null) || !this.appView.canGoBack()) {
loading = preferences.getString("LoadingDialog", null);
}
return false;
}
else {
loading = preferences.getString("LoadingPageDialog", null);
}
if (loading != null) {
/**
* Get boolean property for activity.
*/
@Deprecated // Call method on preferences directly.
public boolean getBooleanProperty(String name, boolean defaultValue) {
return preferences.getBoolean(name, defaultValue);
}
String title = "";
String message = "Loading Application...";
/**
* Get int property for activity.
*/
@Deprecated // Call method on preferences directly.
public int getIntegerProperty(String name, int defaultValue) {
return preferences.getInteger(name, defaultValue);
}
/**
* Get string property for activity.
*/
@Deprecated // Call method on preferences directly.
public String getStringProperty(String name, String defaultValue) {
return preferences.getString(name, defaultValue);
}
/**
* Get double property for activity.
*/
@Deprecated // Call method on preferences directly.
public double getDoubleProperty(String name, double defaultValue) {
return preferences.getDouble(name, defaultValue);
}
/**
* Set boolean property on activity.
* This method has been deprecated in 3.0 and will be removed at a future
* time. Please use config.xml instead.
*
* @param name
* @param value
* @deprecated
*/
@Deprecated
public void setBooleanProperty(String name, boolean value) {
Log.d(TAG, "Setting boolean properties in CordovaActivity will be deprecated in 3.0 on July 2013, please use config.xml");
this.getIntent().putExtra(name.toLowerCase(), value);
}
/**
* Set int property on activity.
* This method has been deprecated in 3.0 and will be removed at a future
* time. Please use config.xml instead.
*
* @param name
* @param value
* @deprecated
*/
@Deprecated
public void setIntegerProperty(String name, int value) {
Log.d(TAG, "Setting integer properties in CordovaActivity will be deprecated in 3.0 on July 2013, please use config.xml");
this.getIntent().putExtra(name.toLowerCase(), value);
}
/**
* Set string property on activity.
* This method has been deprecated in 3.0 and will be removed at a future
* time. Please use config.xml instead.
*
* @param name
* @param value
* @deprecated
*/
@Deprecated
public void setStringProperty(String name, String value) {
Log.d(TAG, "Setting string properties in CordovaActivity will be deprecated in 3.0 on July 2013, please use config.xml");
this.getIntent().putExtra(name.toLowerCase(), value);
}
/**
* Set double property on activity.
* This method has been deprecated in 3.0 and will be removed at a future
* time. Please use config.xml instead.
*
* @param name
* @param value
* @deprecated
*/
@Deprecated
public void setDoubleProperty(String name, double value) {
Log.d(TAG, "Setting double properties in CordovaActivity will be deprecated in 3.0 on July 2013, please use config.xml");
this.getIntent().putExtra(name.toLowerCase(), value);
if (loading.length() > 0) {
int comma = loading.indexOf(',');
if (comma > 0) {
title = loading.substring(0, comma);
message = loading.substring(comma + 1);
}
else {
title = "";
message = loading;
}
}
this.spinnerStart(title, message);
}
}
/**
@@ -529,6 +368,9 @@ public class CordovaActivity extends Activity implements CordovaInterface {
{
this.appView.handlePause(this.keepRunning);
}
// hide the splash screen to avoid leaking a window
this.removeSplashScreen();
}
/**
@@ -583,6 +425,9 @@ public class CordovaActivity extends Activity implements CordovaInterface {
LOG.d(TAG, "CordovaActivity.onDestroy()");
super.onDestroy();
// hide the splash screen to avoid leaking a window
this.removeSplashScreen();
if (this.appView != null) {
appView.handleDestroy();
}
@@ -591,67 +436,45 @@ public class CordovaActivity extends Activity implements CordovaInterface {
}
}
/**
* Send a message to all plugins.
*/
public void postMessage(String id, Object data) {
if (this.appView != null) {
this.appView.postMessage(id, data);
}
}
/**
* @deprecated
* Add services to res/xml/plugins.xml instead.
*
* Add a class that implements a service.
*/
@Deprecated
public void addService(String serviceType, String className) {
if (this.appView != null && this.appView.pluginManager != null) {
this.appView.pluginManager.addService(serviceType, className);
}
}
/**
* Send JavaScript statement back to JavaScript.
* (This is a convenience method)
*
* @param statement
*/
@Deprecated // Call method on appView directly.
public void sendJavascript(String statement) {
if (this.appView != null) {
this.appView.bridge.getMessageQueue().addJavaScript(statement);
}
}
/**
* Show the spinner. Must be called from the UI thread.
*
* @param title Title of the dialog
* @param message The message of the dialog
*/
@Deprecated // Call this directly on SplashScreen plugin instead.
public void spinnerStart(final String title, final String message) {
JSONArray args = new JSONArray();
args.put(title);
args.put(message);
doSplashScreenAction("spinnerStart", args);
if (this.spinnerDialog != null) {
this.spinnerDialog.dismiss();
this.spinnerDialog = null;
}
final CordovaActivity me = this;
this.spinnerDialog = ProgressDialog.show(CordovaActivity.this, title, message, true, true,
new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
me.spinnerDialog = null;
}
});
}
/**
* Stop spinner - Must be called from UI thread
*/
@Deprecated // Call this directly on SplashScreen plugin instead.
public void spinnerStop() {
doSplashScreenAction("spinnerStop", null);
if (this.spinnerDialog != null && this.spinnerDialog.isShowing()) {
this.spinnerDialog.dismiss();
this.spinnerDialog = null;
}
}
/**
* End this activity by calling finish for activity
*/
public void endActivity() {
finish();
}
@Override
public void finish() {
this.activityState = ACTIVITY_EXITING;
super.finish();
}
@@ -666,7 +489,7 @@ public class CordovaActivity extends Activity implements CordovaInterface {
* @param requestCode The request code that is passed to callback to identify the activity
*/
public void startActivityForResult(CordovaPlugin command, Intent intent, int requestCode) {
setActivityResultCallback(command);
this.activityResultCallback = command;
this.activityResultKeepRunning = this.keepRunning;
// If multitasking turned on, then disable it for activities that return results
@@ -674,19 +497,8 @@ public class CordovaActivity extends Activity implements CordovaInterface {
this.keepRunning = false;
}
try {
startActivityForResult(intent, requestCode);
} catch (RuntimeException e) { // E.g.: ActivityNotFoundException
activityResultCallback = null;
throw e;
}
}
@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
// Capture requestCode here so that it is captured in the setActivityResultCallback() case.
activityResultRequestCode = requestCode;
super.startActivityForResult(intent, requestCode, options);
// Start activity
super.startActivityForResult(intent, requestCode);
}
/**
@@ -696,34 +508,32 @@ public class CordovaActivity extends Activity implements CordovaInterface {
* @param requestCode The request code originally supplied to startActivityForResult(),
* allowing you to identify who this result came from.
* @param resultCode The integer result code returned by the child activity through its setResult().
* @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
* @param data An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
LOG.d(TAG, "Incoming Result. Request code = " + requestCode);
LOG.d(TAG, "Incoming Result");
super.onActivityResult(requestCode, resultCode, intent);
Log.d(TAG, "Request code = " + requestCode);
if (appView != null && requestCode == AndroidChromeClient.FILECHOOSER_RESULTCODE) {
Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
appView.onFilePickerResult(result);
}
CordovaPlugin callback = this.activityResultCallback;
if(callback == null && initCallbackClass != null) {
// The application was restarted, but had defined an initial callback
// before being shut down.
callback = appView.pluginManager.getPlugin(initCallbackClass);
//this.activityResultCallback = appView.pluginManager.getPlugin(initCallbackClass);
this.activityResultCallback = appView.getPluginManager().getPlugin(initCallbackClass);
callback = this.activityResultCallback;
}
initCallbackClass = null;
activityResultCallback = null;
if (callback != null) {
if(callback != null) {
LOG.d(TAG, "We have a callback to send this result to");
callback.onActivityResult(requestCode, resultCode, intent);
} else {
LOG.w(TAG, "Got an activity result, but no plugin was registered to receive it.");
}
}
public void setActivityResultCallback(CordovaPlugin plugin) {
// Cancel any previously pending activity.
if (activityResultCallback != null) {
activityResultCallback.onActivityResult(activityResultRequestCode, Activity.RESULT_CANCELED, null);
}
this.activityResultCallback = plugin;
}
@@ -740,11 +550,16 @@ public class CordovaActivity extends Activity implements CordovaInterface {
// If errorUrl specified, then load it
final String errorUrl = preferences.getString("errorUrl", null);
if ((errorUrl != null) && (errorUrl.startsWith("file://") || internalWhitelist.isUrlWhiteListed(errorUrl)) && (!failingUrl.equals(errorUrl))) {
CordovaUriHelper helper = new CordovaUriHelper(this, appView);
if ((errorUrl != null) &&
(!failingUrl.equals(errorUrl)) &&
(appView != null && helper.shouldAllowNavigation(errorUrl))
) {
// Load URL on UI thread
me.runOnUiThread(new Runnable() {
public void run() {
// Stop "app loading" spinner if showing
me.spinnerStop();
me.appView.showWebPage(errorUrl, false, true, null);
}
});
@@ -755,7 +570,7 @@ public class CordovaActivity extends Activity implements CordovaInterface {
me.runOnUiThread(new Runnable() {
public void run() {
if (exit) {
me.appView.setVisibility(View.GONE);
me.appView.getView().setVisibility(View.GONE);
me.displayError("Application Error", description + " (" + failingUrl + ")", "OK", exit);
}
}
@@ -793,121 +608,91 @@ public class CordovaActivity extends Activity implements CordovaInterface {
});
}
/**
* Determine if URL is in approved list of URLs to load.
*/
@Deprecated // Use whitelist object directly.
public boolean isUrlWhiteListed(String url) {
return internalWhitelist.isUrlWhiteListed(url);
}
/*
* Hook in Cordova for menu plugins
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
this.postMessage("onCreateOptionsMenu", menu);
if (appView != null) {
appView.getPluginManager().postMessage("onCreateOptionsMenu", menu);
}
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
this.postMessage("onPrepareOptionsMenu", menu);
if (appView != null) {
appView.getPluginManager().postMessage("onPrepareOptionsMenu", menu);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
this.postMessage("onOptionsItemSelected", item);
if (appView != null) {
appView.getPluginManager().postMessage("onOptionsItemSelected", item);
}
return true;
}
/**
* Get Activity context.
*/
@Deprecated
public Context getContext() {
LOG.d(TAG, "This will be deprecated December 2012");
return this;
}
/**
* Load the specified URL in the Cordova webview or a new browser instance.
*
* NOTE: If openExternal is false, only URLs listed in whitelist can be loaded.
*
* @param url The url to load.
* @param openExternal Load url in browser instead of Cordova webview.
* @param clearHistory Clear the history stack, so new page becomes top of history
* @param params Parameters for new app
*/
@Deprecated // Call method on appView directly.
public void showWebPage(String url, boolean openExternal, boolean clearHistory, HashMap<String, Object> params) {
if (this.appView != null) {
appView.showWebPage(url, openExternal, clearHistory, params);
}
}
private void doSplashScreenAction(String action, JSONArray args) {
CordovaPlugin p = appView.pluginManager.getPlugin("org.apache.cordova.splashscreeninternal");
if (p != null) {
args = args == null ? new JSONArray() : args;
try {
p.execute(action, args, null);
} catch (JSONException e) {
e.printStackTrace();
}
}
}
protected Dialog splashDialog;
/**
* Removes the Dialog that displays the splash screen
*/
@Deprecated
public void removeSplashScreen() {
doSplashScreenAction("hide", null);
if (splashDialog != null && splashDialog.isShowing()) {
splashDialog.dismiss();
splashDialog = null;
}
}
/**
* Shows the splash screen over the full Activity
*/
@SuppressWarnings("deprecation")
@Deprecated
protected void showSplashScreen(final int time) {
preferences.set("SplashScreenDelay", time);
doSplashScreenAction("show", null);
final CordovaActivity that = this;
Runnable runnable = new Runnable() {
public void run() {
// Get reference to display
Display display = getWindowManager().getDefaultDisplay();
// Create the layout for the dialog
LinearLayout root = new LinearLayout(that.getActivity());
root.setMinimumHeight(display.getHeight());
root.setMinimumWidth(display.getWidth());
root.setOrientation(LinearLayout.VERTICAL);
root.setBackgroundColor(preferences.getInteger("backgroundColor", Color.BLACK));
root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT, 0.0F));
root.setBackgroundResource(that.splashscreen);
// Create and show the dialog
splashDialog = new Dialog(that, android.R.style.Theme_Translucent_NoTitleBar);
// check to see if the splash screen should be full screen
if ((getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_FULLSCREEN)
== WindowManager.LayoutParams.FLAG_FULLSCREEN) {
splashDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
splashDialog.setContentView(root);
splashDialog.setCancelable(false);
splashDialog.show();
// Set Runnable to remove splash screen just in case
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
public void run() {
removeSplashScreen();
}
}, time);
}
};
this.runOnUiThread(runnable);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event)
{
if (appView != null && (appView.isCustomViewShowing() || appView.getFocusedChild() != null ) &&
(keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU)) {
return appView.onKeyUp(keyCode, event);
} else {
return super.onKeyUp(keyCode, event);
}
}
/*
* Android 2.x needs to be able to check where the cursor is. Android 4.x does not
*
* (non-Javadoc)
* @see android.app.Activity#onKeyDown(int, android.view.KeyEvent)
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
//Determine if the focus is on the current view or not
if (appView != null && appView.getFocusedChild() != null && (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU)) {
return appView.onKeyDown(keyCode, event);
}
else
return super.onKeyDown(keyCode, event);
}
/**
* Called when a message is sent to plugin.
*
@@ -920,7 +705,28 @@ public class CordovaActivity extends Activity implements CordovaInterface {
LOG.d(TAG, "onMessage(" + id + "," + data + ")");
}
if ("onReceivedError".equals(id)) {
if ("splashscreen".equals(id)) {
if ("hide".equals(data.toString())) {
this.removeSplashScreen();
}
else {
// If the splash dialog is showing don't try to show it again
if (this.splashDialog == null || !this.splashDialog.isShowing()) {
String splashResource = preferences.getString("SplashScreen", null);
if (splashResource != null) {
splashscreen = getResources().getIdentifier(splashResource, "drawable", getClass().getPackage().getName());
}
this.showSplashScreen(preferences.getInteger("SplashScreenDelay", 3000));
}
}
}
else if ("spinner".equals(id)) {
if ("stop".equals(data.toString())) {
this.spinnerStop();
this.appView.getView().setVisibility(View.VISIBLE);
}
}
else if ("onReceivedError".equals(id)) {
JSONObject d = (JSONObject) data;
try {
this.onReceivedError(d.getInt("errorCode"), d.getString("description"), d.getString("url"));

View File

@@ -37,12 +37,12 @@ public class CordovaBridge {
private NativeToJsMessageQueue jsMessageQueue;
private volatile int expectedBridgeSecret = -1; // written by UI thread, read by JS thread.
private String loadedUrl;
private String appContentUrlPrefix;
protected CordovaUriHelper helper;
public CordovaBridge(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue, String packageName) {
public CordovaBridge(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue, CordovaUriHelper helper) {
this.pluginManager = pluginManager;
this.jsMessageQueue = jsMessageQueue;
this.appContentUrlPrefix = "content://" + packageName + ".";
this.helper = helper;
}
public String jsExec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
@@ -99,8 +99,6 @@ public class CordovaBridge {
}
// Bridge secret wrong and bridge not due to it being from the previous page.
if (expectedBridgeSecret < 0 || bridgeSecret != expectedBridgeSecret) {
Log.e(LOG_TAG, "Bridge access attempt with wrong secret token, possibly from malicious code. Disabling exec() bridge!");
clearBridgeSecret();
throw new IllegalAccessException();
}
return true;
@@ -167,11 +165,9 @@ public class CordovaBridge {
}
else if (defaultValue != null && defaultValue.startsWith("gap_init:")) {
// Protect against random iframes being able to talk through the bridge.
// Trust only file URLs and the start URL's domain.
// The extra origin.startsWith("http") is to protect against iframes with data: having "" as origin.
if (origin.startsWith("file:") ||
origin.startsWith(this.appContentUrlPrefix) ||
(origin.startsWith("http") && loadedUrl.startsWith(origin))) {
// Trust only file URLs and pages which the app would have been allowed
// to navigate to anyway.
if (origin.startsWith("file:") || helper.shouldAllowNavigation(origin)) {
// Enable the bridge
int bridgeMode = Integer.parseInt(defaultValue.substring(9));
jsMessageQueue.setBridgeMode(bridgeMode);

View File

@@ -1,96 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import android.webkit.ClientCertRequest;
/**
* Implementation of the ICordovaClientCertRequest for Android WebView.
*/
public class CordovaClientCertRequest implements ICordovaClientCertRequest {
private final ClientCertRequest request;
public CordovaClientCertRequest(ClientCertRequest request) {
this.request = request;
}
/**
* Cancel this request
*/
public void cancel()
{
request.cancel();
}
/*
* Returns the host name of the server requesting the certificate.
*/
public String getHost()
{
return request.getHost();
}
/*
* Returns the acceptable types of asymmetric keys (can be null).
*/
public String[] getKeyTypes()
{
return request.getKeyTypes();
}
/*
* Returns the port number of the server requesting the certificate.
*/
public int getPort()
{
return request.getPort();
}
/*
* Returns the acceptable certificate issuers for the certificate matching the private key (can be null).
*/
public Principal[] getPrincipals()
{
return request.getPrincipals();
}
/*
* Ignore the request for now. Do not remember user's choice.
*/
public void ignore()
{
request.ignore();
}
/*
* Proceed with the specified private key and client certificate chain. Remember the user's positive choice and use it for future requests.
*
* @param privateKey The privateKey
* @param chain The certificate chain
*/
public void proceed(PrivateKey privateKey, X509Certificate[] chain)
{
request.proceed(privateKey, chain);
}
}

View File

@@ -1,51 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import android.webkit.HttpAuthHandler;
/**
* Specifies interface for HTTP auth handler object which is used to handle auth requests and
* specifying user credentials.
*/
public class CordovaHttpAuthHandler implements ICordovaHttpAuthHandler {
private final HttpAuthHandler handler;
public CordovaHttpAuthHandler(HttpAuthHandler handler) {
this.handler = handler;
}
/**
* Instructs the WebView to cancel the authentication request.
*/
public void cancel () {
this.handler.cancel();
}
/**
* Instructs the WebView to proceed with the authentication with the given credentials.
*
* @param username
* @param password
*/
public void proceed (String username, String password) {
this.handler.proceed(username, password);
}
}

View File

@@ -32,8 +32,6 @@ import android.net.Uri;
* Plugins must extend this class and override one of the execute methods.
*/
public class CordovaPlugin {
@Deprecated // This is never set.
public String id;
public CordovaWebView webView;
public CordovaInterface cordova;
protected CordovaPreferences preferences;
@@ -164,19 +162,67 @@ public class CordovaPlugin {
* Called when an activity you launched exits, giving you the requestCode you started it with,
* the resultCode it returned, and any additional data from it.
*
* @param requestCode The request code originally supplied to startActivityForResult(),
* allowing you to identify who this result came from.
* @param resultCode The integer result code returned by the child activity through its setResult().
* @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
* @param requestCode The request code originally supplied to startActivityForResult(),
* allowing you to identify who this result came from.
* @param resultCode The integer result code returned by the child activity through its setResult().
* @param intent An Intent, which can return result data to the caller (various data can be
* attached to Intent "extras").
*/
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
}
/**
* Hook for blocking the loading of external resources.
*
* This will be called when the WebView's shouldInterceptRequest wants to
* know whether to open a connection to an external resource. Return false
* to block the request: if any plugin returns false, Cordova will block
* the request. If all plugins return null, the default policy will be
* enforced. If at least one plugin returns true, and no plugins return
* false, then the request will proceed.
*
* Note that this only affects resource requests which are routed through
* WebViewClient.shouldInterceptRequest, such as XMLHttpRequest requests and
* img tag loads. WebSockets and media requests (such as <video> and <audio>
* tags) are not affected by this method. Use CSP headers to control access
* to such resources.
*/
public Boolean shouldAllowRequest(String url) {
return null;
}
/**
* Hook for blocking navigation by the Cordova WebView.
*
* This will be called when the WebView's needs to know whether to navigate
* to a new page. Return false to block the navigation: if any plugin
* returns false, Cordova will block the navigation. If all plugins return
* null, the default policy will be enforced. It at least one plugin returns
* true, and no plugins return false, then the navigation will proceed.
*/
public Boolean shouldAllowNavigation(String url) {
return null;
}
/**
* Hook for blocking the launching of Intents by the Cordova application.
*
* This will be called when the WebView will not navigate to a page, but
* could launch an intent to handle the URL. Return false to block this: if
* any plugin returns false, Cordova will block the navigation. If all
* plugins return null, the default policy will be enforced. If at least one
* plugin returns true, and no plugins return false, then the URL will be
* opened.
*/
public Boolean shouldOpenExternalUrl(String url) {
return null;
}
/**
* By specifying a <url-filter> in config.xml you can map a URL (using startsWith atm) to this method.
*
* @param url The URL that is trying to be loaded in the Cordova webview.
* @return Return true to prevent the URL from loading. Default is false.
* @param url The URL that is trying to be loaded in the Cordova webview.
* @return Return true to prevent the URL from loading. Default is false.
*/
public boolean onOverrideUrlLoading(String url) {
return false;
@@ -198,34 +244,4 @@ public class CordovaPlugin {
*/
public void onReset() {
}
/**
* Called when the system received an HTTP authentication request. Plugin can use
* the supplied HttpAuthHandler to process this auth challenge.
*
* @param view The WebView that is initiating the callback
* @param handler The HttpAuthHandler used to set the WebView's response
* @param host The host requiring authentication
* @param realm The realm for which authentication is required
*
* @return Returns True if plugin will resolve this auth challenge, otherwise False
*
*/
public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
return false;
}
/**
* Called when he system received an SSL client certificate request. Plugin can use
* the supplied ClientCertRequest to process this certificate challenge.
*
* @param view The WebView that is initiating the callback
* @param request The client certificate request
*
* @return Returns True if plugin will resolve this auth challenge, otherwise False
*
*/
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
return false;
}
}

View File

@@ -61,6 +61,13 @@ public class CordovaPreferences {
String value = prefs.get(name);
if (value != null) {
return Boolean.parseBoolean(value);
} else if (preferencesBundleExtras != null) {
Object bundleValue = preferencesBundleExtras.get(name);
if (bundleValue instanceof String) {
return "true".equals(bundleValue);
}
// Gives a nice warning if type is wrong.
return preferencesBundleExtras.getBoolean(name, defaultValue);
}
return defaultValue;
}
@@ -71,6 +78,13 @@ public class CordovaPreferences {
if (value != null) {
// Use Integer.decode() can't handle it if the highest bit is set.
return (int)(long)Long.decode(value);
} else if (preferencesBundleExtras != null) {
Object bundleValue = preferencesBundleExtras.get(name);
if (bundleValue instanceof String) {
return Integer.valueOf((String)bundleValue);
}
// Gives a nice warning if type is wrong.
return preferencesBundleExtras.getInt(name, defaultValue);
}
return defaultValue;
}
@@ -80,7 +94,14 @@ public class CordovaPreferences {
String value = prefs.get(name);
if (value != null) {
return Double.valueOf(value);
}
} else if (preferencesBundleExtras != null) {
Object bundleValue = preferencesBundleExtras.get(name);
if (bundleValue instanceof String) {
return Double.valueOf((String)bundleValue);
}
// Gives a nice warning if type is wrong.
return preferencesBundleExtras.getDouble(name, defaultValue);
}
return defaultValue;
}
@@ -89,8 +110,66 @@ public class CordovaPreferences {
String value = prefs.get(name);
if (value != null) {
return value;
}
} else if (preferencesBundleExtras != null && !"errorurl".equals(name)) {
Object bundleValue = preferencesBundleExtras.get(name);
if (bundleValue != null) {
return bundleValue.toString();
}
}
return defaultValue;
}
// Plugins should not rely on values within the intent since this does not work
// for apps with multiple webviews. Instead, they should retrieve prefs from the
// Config object associated with their webview.
public void copyIntoIntentExtras(Activity action) {
for (String name : prefs.keySet()) {
String value = prefs.get(name);
if (value == null) {
continue;
}
if (name.equals("loglevel")) {
LOG.setLogLevel(value);
} else if (name.equals("splashscreen")) {
// Note: We should probably pass in the classname for the variable splash on splashscreen!
int resource = action.getResources().getIdentifier(value, "drawable", action.getClass().getPackage().getName());
action.getIntent().putExtra(name, resource);
}
else if(name.equals("backgroundcolor")) {
int asInt = (int)(long)Long.decode(value);
action.getIntent().putExtra(name, asInt);
}
else if(name.equals("loadurltimeoutvalue")) {
int asInt = Integer.decode(value);
action.getIntent().putExtra(name, asInt);
}
else if(name.equals("splashscreendelay")) {
int asInt = Integer.decode(value);
action.getIntent().putExtra(name, asInt);
}
else if(name.equals("keeprunning"))
{
boolean asBool = Boolean.parseBoolean(value);
action.getIntent().putExtra(name, asBool);
}
else if(name.equals("inappbrowserstorageenabled"))
{
boolean asBool = Boolean.parseBoolean(value);
action.getIntent().putExtra(name, asBool);
}
else if(name.equals("disallowoverscroll"))
{
boolean asBool = Boolean.parseBoolean(value);
action.getIntent().putExtra(name, asBool);
}
else
{
action.getIntent().putExtra(name, value);
}
}
// In the normal case, the intent extras are null until the first call to putExtra().
if (preferencesBundleExtras == null) {
preferencesBundleExtras = action.getIntent().getExtras();
}
}
}

View File

@@ -84,7 +84,7 @@ public class CordovaResourceApi {
// Creating this is light-weight.
private static OkHttpClient httpClient = new OkHttpClient();
static Thread jsThread;
public static Thread jsThread;
private final AssetManager assetManager;
private final ContentResolver contentResolver;

View File

@@ -23,49 +23,108 @@ import android.annotation.TargetApi;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.webkit.WebView;
class CordovaUriHelper {
public class CordovaUriHelper {
private static final String TAG = "CordovaUriHelper";
private CordovaWebView appView;
private CordovaInterface cordova;
CordovaUriHelper(CordovaInterface cdv, CordovaWebView webView)
public CordovaUriHelper(CordovaInterface cdv, CordovaWebView webView)
{
appView = webView;
cordova = cdv;
}
/**
* Determine whether the webview should be allowed to navigate to a given URL.
*
* This method implements the default whitelist policy when no plugins override
* shouldAllowNavigation
*/
public boolean shouldAllowNavigation(String url) {
Boolean pluginManagerAllowsNavigation = this.appView.getPluginManager().shouldAllowNavigation(url);
if (pluginManagerAllowsNavigation == null) {
// Default policy:
// Internal urls on file:// or data:// that do not contain "/app_webview/" are allowed for navigation
if(url.startsWith("file://") || url.startsWith("data:"))
{
//This directory on WebKit/Blink based webviews contains SQLite databases!
//DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING!
return !url.contains("/app_webview/");
}
return false;
}
return pluginManagerAllowsNavigation;
}
/**
* Determine whether the webview should be allowed to launch an intent for a given URL.
*
* This method implements the default whitelist policy when no plugins override
* shouldOpenExternalUrl
*/
public boolean shouldOpenExternalUrl(String url) {
Boolean pluginManagerAllowsExternalUrl = this.appView.getPluginManager().shouldOpenExternalUrl(url);
if (pluginManagerAllowsExternalUrl == null) {
// Default policy:
// External URLs are not allowed
return false;
}
return pluginManagerAllowsExternalUrl;
}
/**
* Determine whether the webview should be allowed to request a resource from a given URL.
*
* This method implements the default whitelist policy when no plugins override
* shouldAllowRequest
*/
public boolean shouldAllowRequest(String url) {
Boolean pluginManagerAllowsRequest = this.appView.getPluginManager().shouldAllowRequest(url);
if (pluginManagerAllowsRequest == null) {
// Default policy:
// Internal urls on file:// or data:// that do not contain "/app_webview/" are allowed for navigation
if(url.startsWith("file://") || url.startsWith("data:"))
{
//This directory on WebKit/Blink based webviews contains SQLite databases!
//DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING!
return !url.contains("/app_webview/");
}
return false;
}
return pluginManagerAllowsRequest;
}
/**
* Give the host application a chance to take over the control when a new url
* is about to be loaded in the current WebView.
*
* This method implements the default whitelist policy when no plugins override
* the whitelist methods:
* Internal urls on file:// or data:// that do not contain "app_webview" are allowed for navigation
* External urls are not allowed.
*
* @param view The WebView that is initiating the callback.
* @param url The url to be loaded.
* @return true to override, false for default behavior
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
boolean shouldOverrideUrlLoading(WebView view, String url) {
public boolean shouldOverrideUrlLoading(String url) {
// Give plugins the chance to handle the url
if (this.appView.pluginManager.onOverrideUrlLoading(url)) {
// Do nothing other than what the plugins wanted.
// If any returned true, then the request was handled.
return true;
}
else if(url.startsWith("file://") | url.startsWith("data:"))
{
//This directory on WebKit/Blink based webviews contains SQLite databases!
//DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING!
return url.contains("app_webview");
}
else if (appView.getWhitelist().isUrlWhiteListed(url)) {
if (shouldAllowNavigation(url)) {
// Allow internal navigation
return false;
}
else if (appView.getExternalWhitelist().isUrlWhiteListed(url))
{
if (shouldOpenExternalUrl(url)) {
// Do nothing other than what the plugins wanted.
// If any returned false, then the request was either blocked
// completely, or handled out-of-band by the plugin. If they all
// returned true, then we should open the URL here.
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
@@ -77,10 +136,11 @@ class CordovaUriHelper {
this.cordova.getActivity().startActivity(intent);
return true;
} catch (android.content.ActivityNotFoundException e) {
LOG.e(TAG, "Error loading url " + url, e);
Log.e(TAG, "Error loading url " + url, e);
}
return true;
}
// Intercept the request and do nothing with it -- block it
// Block by default
return true;
}
}

950
framework/src/org/apache/cordova/CordovaWebView.java Executable file → Normal file
View File

@@ -1,504 +1,47 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebBackForwardList;
import android.webkit.WebHistoryItem;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebSettings.LayoutAlgorithm;
import android.webkit.WebViewClient;
import android.webkit.CookieManager;
import android.widget.FrameLayout;
import android.webkit.WebChromeClient.CustomViewCallback;
/*
* This class is our web view.
*
* @see <a href="http://developer.android.com/guide/webapps/webview.html">WebView guide</a>
* @see <a href="http://developer.android.com/reference/android/webkit/WebView.html">WebView</a>
*/
public class CordovaWebView extends WebView {
public interface CordovaWebView {
public static final String CORDOVA_VERSION = "4.0.0-dev";
public static final String TAG = "CordovaWebView";
public static final String CORDOVA_VERSION = "3.7.2";
void init(CordovaInterface cordova, List<PluginEntry> pluginEntries,
CordovaPreferences preferences);
private HashSet<Integer> boundKeyCodes = new HashSet<Integer>();
View getView();
public PluginManager pluginManager;
private boolean paused;
void loadUrlIntoView(String url, boolean recreatePlugins);
private BroadcastReceiver receiver;
void stopLoading();
boolean canGoBack();
/** Activities and other important classes **/
private CordovaInterface cordova;
CordovaWebViewClient viewClient;
private CordovaChromeClient chromeClient;
void clearCache(boolean b);
// Flag to track that a loadUrl timeout occurred
int loadUrlTimeout = 0;
void clearHistory();
private long lastMenuEventTime = 0;
boolean backHistory();
CordovaBridge bridge;
void handlePause(boolean keepRunning);
/** custom view created by the browser (a video player for example) */
private View mCustomView;
private WebChromeClient.CustomViewCallback mCustomViewCallback;
void onNewIntent(Intent intent);
private CordovaResourceApi resourceApi;
private Whitelist internalWhitelist;
private Whitelist externalWhitelist;
void handleResume(boolean keepRunning, boolean activityResultKeepRunning);
// The URL passed to loadUrl(), not necessarily the URL of the current page.
String loadedUrl;
private CordovaPreferences preferences;
private App appPlugin;
void handleDestroy();
class ActivityResult {
int request;
int result;
Intent incoming;
public ActivityResult(int req, int res, Intent intent) {
request = req;
result = res;
incoming = intent;
}
}
static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
Gravity.CENTER);
public CordovaWebView(Context context) {
this(context, null);
}
public CordovaWebView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Deprecated
public CordovaWebView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@TargetApi(11)
@Deprecated
public CordovaWebView(Context context, AttributeSet attrs, int defStyle, boolean privateBrowsing) {
super(context, attrs, defStyle, privateBrowsing);
}
// Use two-phase init so that the control will work with XML layouts.
public void init(CordovaInterface cordova, CordovaWebViewClient webViewClient, CordovaChromeClient webChromeClient,
List<PluginEntry> pluginEntries, Whitelist internalWhitelist, Whitelist externalWhitelist,
CordovaPreferences preferences) {
if (this.cordova != null) {
throw new IllegalStateException();
}
this.cordova = cordova;
this.viewClient = webViewClient;
this.chromeClient = webChromeClient;
this.internalWhitelist = internalWhitelist;
this.externalWhitelist = externalWhitelist;
this.preferences = preferences;
super.setWebChromeClient(webChromeClient);
super.setWebViewClient(webViewClient);
pluginManager = new PluginManager(this, this.cordova, pluginEntries);
bridge = new CordovaBridge(pluginManager, new NativeToJsMessageQueue(this, cordova), this.cordova.getActivity().getPackageName());
resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);
pluginManager.addService(App.PLUGIN_NAME, "org.apache.cordova.App");
// This will be removed in 4.0.x in favour of the plugin not being bundled.
pluginManager.addService(new PluginEntry("SplashScreenInternal", "org.apache.cordova.SplashScreenInternal", true));
pluginManager.init();
initWebViewSettings();
exposeJsInterface();
}
@SuppressWarnings("deprecation")
private void initIfNecessary() {
if (pluginManager == null) {
Log.w(TAG, "CordovaWebView.init() was not called. This will soon be required.");
// Before the refactor to a two-phase init, the Context needed to implement CordovaInterface.
CordovaInterface cdv = (CordovaInterface)getContext();
if (!Config.isInitialized()) {
Config.init(cdv.getActivity());
}
init(cdv, makeWebViewClient(cdv), makeWebChromeClient(cdv), Config.getPluginEntries(), Config.getWhitelist(), Config.getExternalWhitelist(), Config.getPreferences());
}
}
@SuppressLint("SetJavaScriptEnabled")
@SuppressWarnings("deprecation")
private void initWebViewSettings() {
this.setInitialScale(0);
this.setVerticalScrollBarEnabled(false);
// TODO: The Activity is the one that should call requestFocus().
if (shouldRequestFocusOnInit()) {
this.requestFocusFromTouch();
}
// Enable JavaScript
WebSettings settings = this.getSettings();
settings.setJavaScriptEnabled(true);
settings.setJavaScriptCanOpenWindowsAutomatically(true);
settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
// Enable third-party cookies if on Lolipop. TODO: Make this configurable
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
{
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptThirdPartyCookies(this, true);
}
// Set the nav dump for HTC 2.x devices (disabling for ICS, deprecated entirely for Jellybean 4.2)
try {
Method gingerbread_getMethod = WebSettings.class.getMethod("setNavDump", new Class[] { boolean.class });
String manufacturer = android.os.Build.MANUFACTURER;
Log.d(TAG, "CordovaWebView is running on device made by: " + manufacturer);
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB &&
android.os.Build.MANUFACTURER.contains("HTC"))
{
gingerbread_getMethod.invoke(settings, true);
}
} catch (NoSuchMethodException e) {
Log.d(TAG, "We are on a modern version of Android, we will deprecate HTC 2.3 devices in 2.8");
} catch (IllegalArgumentException e) {
Log.d(TAG, "Doing the NavDump failed with bad arguments");
} catch (IllegalAccessException e) {
Log.d(TAG, "This should never happen: IllegalAccessException means this isn't Android anymore");
} catch (InvocationTargetException e) {
Log.d(TAG, "This should never happen: InvocationTargetException means this isn't Android anymore.");
}
//We don't save any form data in the application
settings.setSaveFormData(false);
settings.setSavePassword(false);
// Jellybean rightfully tried to lock this down. Too bad they didn't give us a whitelist
// while we do this
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
Level16Apis.enableUniversalAccess(settings);
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
Level17Apis.setMediaPlaybackRequiresUserGesture(settings, false);
}
// Enable database
// We keep this disabled because we use or shim to get around DOM_EXCEPTION_ERROR_16
String databasePath = getContext().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
settings.setDatabaseEnabled(true);
settings.setDatabasePath(databasePath);
//Determine whether we're in debug or release mode, and turn on Debugging!
ApplicationInfo appInfo = getContext().getApplicationContext().getApplicationInfo();
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 &&
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
enableRemoteDebugging();
}
settings.setGeolocationDatabasePath(databasePath);
// Enable DOM storage
settings.setDomStorageEnabled(true);
// Enable built-in geolocation
settings.setGeolocationEnabled(true);
// Enable AppCache
// Fix for CB-2282
settings.setAppCacheMaxSize(5 * 1048576);
settings.setAppCachePath(databasePath);
settings.setAppCacheEnabled(true);
// Fix for CB-1405
// Google issue 4641
settings.getUserAgentString();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
if (this.receiver == null) {
this.receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
getSettings().getUserAgentString();
}
};
getContext().registerReceiver(this.receiver, intentFilter);
}
// end CB-1405
}
@TargetApi(Build.VERSION_CODES.KITKAT)
private void enableRemoteDebugging() {
try {
WebView.setWebContentsDebuggingEnabled(true);
} catch (IllegalArgumentException e) {
Log.d(TAG, "You have one job! To turn on Remote Web Debugging! YOU HAVE FAILED! ");
e.printStackTrace();
}
}
public CordovaChromeClient makeWebChromeClient(CordovaInterface cordova) {
return new CordovaChromeClient(cordova, this);
}
public CordovaWebViewClient makeWebViewClient(CordovaInterface cordova) {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return new CordovaWebViewClient(cordova, this);
}
return new IceCreamCordovaWebViewClient(cordova, this);
}
/**
* Override this method to decide whether or not you need to request the
* focus when your application start
*
* @return true unless this method is overriden to return a different value
*/
protected boolean shouldRequestFocusOnInit() {
return true;
}
private void exposeJsInterface() {
if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)) {
Log.i(TAG, "Disabled addJavascriptInterface() bridge since Android version is old.");
// Bug being that Java Strings do not get converted to JS strings automatically.
// This isn't hard to work-around on the JS side, but it's easier to just
// use the prompt bridge instead.
return;
}
this.addJavascriptInterface(new ExposedJsApi(bridge), "_cordovaNative");
}
@Override
public void setWebViewClient(WebViewClient client) {
this.viewClient = (CordovaWebViewClient)client;
super.setWebViewClient(client);
}
@Override
public void setWebChromeClient(WebChromeClient client) {
this.chromeClient = (CordovaChromeClient)client;
super.setWebChromeClient(client);
}
public CordovaChromeClient getWebChromeClient() {
return this.chromeClient;
}
public Whitelist getWhitelist() {
return this.internalWhitelist;
}
public Whitelist getExternalWhitelist() {
return this.externalWhitelist;
}
/**
* Load the url into the webview.
*
* @param url
*/
@Override
public void loadUrl(String url) {
if (url.equals("about:blank") || url.startsWith("javascript:")) {
this.loadUrlNow(url);
}
else {
this.loadUrlIntoView(url);
}
}
/**
* Load the url into the webview after waiting for period of time.
* This is used to display the splashscreen for certain amount of time.
*
* @param url
* @param time The number of ms to wait before loading webview
*/
@Deprecated
public void loadUrl(final String url, int time) {
if(url == null)
{
this.loadUrlIntoView(Config.getStartUrl());
}
else
{
this.loadUrlIntoView(url);
}
}
public void loadUrlIntoView(final String url) {
loadUrlIntoView(url, true);
}
/**
* Load the url into the webview.
*
* @param url
*/
public void loadUrlIntoView(final String url, boolean recreatePlugins) {
LOG.d(TAG, ">>> loadUrl(" + url + ")");
initIfNecessary();
if (recreatePlugins) {
// Don't re-initialize on first load.
if (loadedUrl != null) {
this.pluginManager.init();
}
this.loadedUrl = url;
}
// Create a timeout timer for loadUrl
final CordovaWebView me = this;
final int currentLoadUrlTimeout = me.loadUrlTimeout;
final int loadUrlTimeoutValue = Integer.parseInt(this.getProperty("LoadUrlTimeoutValue", "20000"));
// Timeout error method
final Runnable loadError = new Runnable() {
public void run() {
me.stopLoading();
LOG.e(TAG, "CordovaWebView: TIMEOUT ERROR!");
if (viewClient != null) {
viewClient.onReceivedError(me, -6, "The connection to the server was unsuccessful.", url);
}
}
};
// Timeout timer method
final Runnable timeoutCheck = new Runnable() {
public void run() {
try {
synchronized (this) {
wait(loadUrlTimeoutValue);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
// If timeout, then stop loading and handle error
if (me.loadUrlTimeout == currentLoadUrlTimeout) {
me.cordova.getActivity().runOnUiThread(loadError);
}
}
};
// Load url
this.cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
cordova.getThreadPool().execute(timeoutCheck);
me.loadUrlNow(url);
}
});
}
/**
* Load URL in webview.
*
* @param url
*/
void loadUrlNow(String url) {
if (LOG.isLoggable(LOG.DEBUG) && !url.startsWith("javascript:")) {
LOG.d(TAG, ">>> loadUrlNow()");
}
if (url.startsWith("file://") || url.startsWith("javascript:") || url.startsWith("about:") || internalWhitelist.isUrlWhiteListed(url)) {
super.loadUrl(url);
}
}
/**
* Load the url into the webview after waiting for period of time.
* This is used to display the splashscreen for certain amount of time.
*
* @param url
* @param time The number of ms to wait before loading webview
*/
public void loadUrlIntoView(final String url, final int time) {
// If not first page of app, then load immediately
// Add support for browser history if we use it.
if ((url.startsWith("javascript:")) || this.canGoBack()) {
}
// If first page, then show splashscreen
else {
LOG.d(TAG, "loadUrlIntoView(%s, %d)", url, time);
}
// Load url
this.loadUrlIntoView(url);
}
@Override
public void stopLoading() {
viewClient.isCurrentlyLoading = false;
super.stopLoading();
}
public void onScrollChanged(int l, int t, int oldl, int oldt)
{
super.onScrollChanged(l, t, oldl, oldt);
//We should post a message that the scroll changed
ScrollEvent myEvent = new ScrollEvent(l, t, oldl, oldt, this);
this.postMessage("onScrollChanged", myEvent);
}
/**
* Send JavaScript statement back to JavaScript.
* (This is a convenience method)
*
* @param statement
* Deprecated (https://issues.apache.org/jira/browse/CB-6851)
* Instead of executing snippets of JS, you should use the exec bridge
* to create a Java->JS communication channel.
@@ -518,454 +61,35 @@ public class CordovaWebView extends WebView {
* savedCallbackContext.sendPluginResult(dataResult);
*/
@Deprecated
public void sendJavascript(String statement) {
this.bridge.getMessageQueue().addJavaScript(statement);
}
void sendJavascript(String statememt);
/**
* Send a plugin result back to JavaScript.
* (This is a convenience method)
*
* @param result
* @param callbackId
*/
public void sendPluginResult(PluginResult result, String callbackId) {
this.bridge.getMessageQueue().addPluginResult(result, callbackId);
}
void showWebPage(String errorUrl, boolean b, boolean c, HashMap<String, Object> params);
/**
* Send a message to all plugins.
*
* @param id The message id
* @param data The message data
*/
public void postMessage(String id, Object data) {
if (this.pluginManager != null) {
this.pluginManager.postMessage(id, data);
}
}
boolean isCustomViewShowing();
void showCustomView(View view, CustomViewCallback callback);
/**
* Go to previous page in history. (We manage our own history)
*
* @return true if we went back, false if we are already at top
*/
public boolean backHistory() {
// Check webview first to see if there is a history
// This is needed to support curPage#diffLink, since they are added to appView's history, but not our history url array (JQMobile behavior)
if (super.canGoBack()) {
super.goBack();
return true;
}
return false;
}
void hideCustomView();
CordovaResourceApi getResourceApi();
/**
* Load the specified URL in the Cordova webview or a new browser instance.
*
* NOTE: If openExternal is false, only URLs listed in whitelist can be loaded.
*
* @param url The url to load.
* @param openExternal Load url in browser instead of Cordova webview.
* @param clearHistory Clear the history stack, so new page becomes top of history
* @param params Parameters for new app
*/
public void showWebPage(String url, boolean openExternal, boolean clearHistory, HashMap<String, Object> params) {
LOG.d(TAG, "showWebPage(%s, %b, %b, HashMap", url, openExternal, clearHistory);
void setButtonPlumbedToJs(int keyCode, boolean override);
boolean isButtonPlumbedToJs(int keyCode);
// If clearing history
if (clearHistory) {
this.clearHistory();
}
void sendPluginResult(PluginResult cr, String callbackId);
// If loading into our webview
if (!openExternal) {
PluginManager getPluginManager();
// Make sure url is in whitelist
if (url.startsWith("file://") || internalWhitelist.isUrlWhiteListed(url)) {
// TODO: What about params?
// Load new URL
this.loadUrl(url);
return;
}
// Load in default viewer if not
LOG.w(TAG, "showWebPage: Cannot load URL into webview since it is not in white list. Loading into browser instead. (URL=" + url + ")");
}
try {
// Omitting the MIME type for file: URLs causes "No Activity found to handle Intent".
// Adding the MIME type to http: URLs causes them to not be handled by the downloader.
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.parse(url);
if ("file".equals(uri.getScheme())) {
intent.setDataAndType(uri, resourceApi.getMimeType(uri));
} else {
intent.setData(uri);
}
cordova.getActivity().startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
LOG.e(TAG, "Error loading url " + url, e);
}
}
/**
* Get string property for activity.
*
* @param name
* @param defaultValue
* @return the String value for the named property
*/
public String getProperty(String name, String defaultValue) {
Bundle bundle = this.cordova.getActivity().getIntent().getExtras();
if (bundle == null) {
return defaultValue;
}
name = name.toLowerCase(Locale.getDefault());
Object p = bundle.get(name);
if (p == null) {
return defaultValue;
}
return p.toString();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
if(boundKeyCodes.contains(keyCode))
{
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
sendJavascriptEvent("volumedownbutton");
return true;
}
// If volumeup key
else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
sendJavascriptEvent("volumeupbutton");
return true;
}
else
{
return super.onKeyDown(keyCode, event);
}
}
else if(keyCode == KeyEvent.KEYCODE_BACK)
{
return !(this.startOfHistory()) || isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK);
}
else if(keyCode == KeyEvent.KEYCODE_MENU)
{
//How did we get here? Is there a childView?
View childView = this.getFocusedChild();
if(childView != null)
{
//Make sure we close the keyboard if it's present
InputMethodManager imm = (InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(childView.getWindowToken(), 0);
cordova.getActivity().openOptionsMenu();
return true;
} else {
return super.onKeyDown(keyCode, event);
}
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event)
{
// If back key
if (keyCode == KeyEvent.KEYCODE_BACK) {
// A custom view is currently displayed (e.g. playing a video)
if(mCustomView != null) {
this.hideCustomView();
return true;
} else {
// The webview is currently displayed
// If back key is bound, then send event to JavaScript
if (isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK)) {
sendJavascriptEvent("backbutton");
return true;
} else {
// If not bound
// Go to previous page in webview if it is possible to go back
if (this.backHistory()) {
return true;
}
// If not, then invoke default behavior
}
}
}
// Legacy
else if (keyCode == KeyEvent.KEYCODE_MENU) {
if (this.lastMenuEventTime < event.getEventTime()) {
sendJavascriptEvent("menubutton");
}
this.lastMenuEventTime = event.getEventTime();
return super.onKeyUp(keyCode, event);
}
// If search key
else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
sendJavascriptEvent("searchbutton");
return true;
}
//Does webkit change this behavior?
return super.onKeyUp(keyCode, event);
}
private void sendJavascriptEvent(String event) {
if (appPlugin == null) {
appPlugin = (App)this.pluginManager.getPlugin(App.PLUGIN_NAME);
}
if (appPlugin == null) {
LOG.w(TAG, "Unable to fire event without existing plugin");
return;
}
appPlugin.fireJavascriptEvent(event);
}
public void setButtonPlumbedToJs(int keyCode, boolean override) {
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_BACK:
// TODO: Why are search and menu buttons handled separately?
if (override) {
boundKeyCodes.add(keyCode);
} else {
boundKeyCodes.remove(keyCode);
}
return;
default:
throw new IllegalArgumentException("Unsupported keycode: " + keyCode);
}
}
@Deprecated // Use setButtonPlumbedToJs() instead.
public void bindButton(boolean override)
{
setButtonPlumbedToJs(KeyEvent.KEYCODE_BACK, override);
}
@Deprecated // Use setButtonPlumbedToJs() instead.
public void bindButton(String button, boolean override) {
if (button.compareTo("volumeup")==0) {
setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_UP, override);
}
else if (button.compareTo("volumedown")==0) {
setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_DOWN, override);
}
}
@Deprecated // Use setButtonPlumbedToJs() instead.
public void bindButton(int keyCode, boolean keyDown, boolean override) {
setButtonPlumbedToJs(keyCode, override);
}
@Deprecated // Use isButtonPlumbedToJs
public boolean isBackButtonBound()
{
return isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK);
}
public boolean isButtonPlumbedToJs(int keyCode)
{
return boundKeyCodes.contains(keyCode);
}
public void handlePause(boolean keepRunning)
{
LOG.d(TAG, "Handle the pause");
// Send pause event to JavaScript
sendJavascriptEvent("pause");
// Forward to plugins
if (this.pluginManager != null) {
this.pluginManager.onPause(keepRunning);
}
// If app doesn't want to run in background
if (!keepRunning) {
// Pause JavaScript timers (including setInterval)
this.pauseTimers();
}
paused = true;
}
CordovaPreferences getPreferences();
public void handleResume(boolean keepRunning, boolean activityResultKeepRunning)
{
sendJavascriptEvent("resume");
void onFilePickerResult(Uri uri);
// Forward to plugins
if (this.pluginManager != null) {
this.pluginManager.onResume(keepRunning);
}
// Resume JavaScript timers (including setInterval)
this.resumeTimers();
paused = false;
}
void setNetworkAvailable(boolean online);
public void handleDestroy()
{
// Cancel pending timeout timer.
loadUrlTimeout++;
String getUrl();
// Load blank page so that JavaScript onunload is called
this.loadUrl("about:blank");
//Remove last AlertDialog
this.chromeClient.destroyLastDialog();
// Forward to plugins
if (this.pluginManager != null) {
this.pluginManager.onDestroy();
}
// unregister the receiver
if (this.receiver != null) {
try {
getContext().unregisterReceiver(this.receiver);
} catch (Exception e) {
Log.e(TAG, "Error unregistering configuration receiver: " + e.getMessage(), e);
}
}
}
public void onNewIntent(Intent intent)
{
//Forward to plugins
if (this.pluginManager != null) {
this.pluginManager.onNewIntent(intent);
}
}
public boolean isPaused()
{
return paused;
}
@Deprecated // This never did anything.
public boolean hadKeyEvent() {
return false;
}
// Wrapping these functions in their own class prevents warnings in adb like:
// VFY: unable to resolve virtual method 285: Landroid/webkit/WebSettings;.setAllowUniversalAccessFromFileURLs
@TargetApi(16)
private static final class Level16Apis {
static void enableUniversalAccess(WebSettings settings) {
settings.setAllowUniversalAccessFromFileURLs(true);
}
}
@TargetApi(17)
private static final class Level17Apis {
static void setMediaPlaybackRequiresUserGesture(WebSettings settings, boolean value) {
settings.setMediaPlaybackRequiresUserGesture(value);
}
}
public void printBackForwardList() {
WebBackForwardList currentList = this.copyBackForwardList();
int currentSize = currentList.getSize();
for(int i = 0; i < currentSize; ++i)
{
WebHistoryItem item = currentList.getItemAtIndex(i);
String url = item.getUrl();
LOG.d(TAG, "The URL at index: " + Integer.toString(i) + " is " + url );
}
}
//Can Go Back is BROKEN!
public boolean startOfHistory()
{
WebBackForwardList currentList = this.copyBackForwardList();
WebHistoryItem item = currentList.getItemAtIndex(0);
if( item!=null){ // Null-fence in case they haven't called loadUrl yet (CB-2458)
String url = item.getUrl();
String currentUrl = this.getUrl();
LOG.d(TAG, "The current URL is: " + currentUrl);
LOG.d(TAG, "The URL at item 0 is: " + url);
return currentUrl.equals(url);
}
return false;
}
public void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
Log.d(TAG, "showing Custom View");
// if a view already exists then immediately terminate the new one
if (mCustomView != null) {
callback.onCustomViewHidden();
return;
}
// Store the view and its callback for later (to kill it properly)
mCustomView = view;
mCustomViewCallback = callback;
// Add the custom view to its container.
ViewGroup parent = (ViewGroup) this.getParent();
parent.addView(view, COVER_SCREEN_GRAVITY_CENTER);
// Hide the content view.
this.setVisibility(View.GONE);
// Finally show the custom view container.
parent.setVisibility(View.VISIBLE);
parent.bringToFront();
}
public void hideCustomView() {
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
Log.d(TAG, "Hiding Custom View");
if (mCustomView == null) return;
// Hide the custom view.
mCustomView.setVisibility(View.GONE);
// Remove the custom view from its container.
ViewGroup parent = (ViewGroup) this.getParent();
parent.removeView(mCustomView);
mCustomView = null;
mCustomViewCallback.onCustomViewHidden();
// Show the content view.
this.setVisibility(View.VISIBLE);
}
/**
* if the video overlay is showing then we need to know
* as it effects back button handling
*
* @return true if custom view is showing
*/
public boolean isCustomViewShowing() {
return mCustomView != null;
}
public WebBackForwardList restoreState(Bundle savedInstanceState)
{
WebBackForwardList myList = super.restoreState(savedInstanceState);
Log.d(TAG, "WebView restoration crew now restoring!");
//Initialize the plugin manager once more
this.pluginManager.init();
return myList;
}
@Deprecated // This never did anything
public void storeResult(int requestCode, int resultCode, Intent intent) {
}
public CordovaResourceApi getResourceApi() {
return resourceApi;
}
public CordovaPreferences getPreferences() {
return preferences;
}
// TODO: Work on deleting these by removing refs from plugins.
Context getContext();
void loadUrl(String url);
Object postMessage(String id, Object data);
}

View File

@@ -39,21 +39,10 @@ import java.util.HashMap;
/**
* This class exposes methods in Cordova that can be called from JavaScript.
*/
public class App extends CordovaPlugin {
public class CoreAndroid extends CordovaPlugin {
public static final String PLUGIN_NAME = "App";
protected static final String TAG = "CordovaApp";
private BroadcastReceiver telephonyReceiver;
private CallbackContext messageChannel;
/**
* Send an event to be fired on the Javascript side.
*
* @param action The name of the event to be fired
*/
public void fireJavascriptEvent(String action) {
sendEventMessage(action);
}
/**
* Sets the context of the Command. This can then be used to do things like
@@ -86,7 +75,7 @@ public class App extends CordovaPlugin {
// indicative of what this actually does (shows the webview).
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
webView.postMessage("spinner", "stop");
webView.getPluginManager().postMessage("spinner", "stop");
}
});
}
@@ -111,11 +100,6 @@ public class App extends CordovaPlugin {
else if (action.equals("exitApp")) {
this.exitApp();
}
else if (action.equals("messageChannel")) {
messageChannel = callbackContext;
return true;
}
callbackContext.sendPluginResult(new PluginResult(status, result));
return true;
} catch (JSONException e) {
@@ -263,9 +247,9 @@ public class App extends CordovaPlugin {
* Exit the Android application.
*/
public void exitApp() {
this.webView.postMessage("exit", null);
this.webView.getPluginManager().postMessage("exit", null);
}
/**
* Listen for telephony events: RINGING, OFFHOOK and IDLE
@@ -287,15 +271,15 @@ public class App extends CordovaPlugin {
String extraData = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
if (extraData.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
LOG.i(TAG, "Telephone RINGING");
webView.postMessage("telephone", "ringing");
webView.getPluginManager().postMessage("telephone", "ringing");
}
else if (extraData.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) {
LOG.i(TAG, "Telephone OFFHOOK");
webView.postMessage("telephone", "offhook");
webView.getPluginManager().postMessage("telephone", "offhook");
}
else if (extraData.equals(TelephonyManager.EXTRA_STATE_IDLE)) {
LOG.i(TAG, "Telephone IDLE");
webView.postMessage("telephone", "idle");
webView.getPluginManager().postMessage("telephone", "idle");
}
}
}
@@ -303,21 +287,7 @@ public class App extends CordovaPlugin {
};
// Register the receiver
webView.getContext().registerReceiver(this.telephonyReceiver, intentFilter);
}
private void sendEventMessage(String action) {
JSONObject obj = new JSONObject();
try {
obj.put("action", action);
} catch (JSONException e) {
LOG.e(TAG, "Failed to create event message", e);
}
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, obj);
pluginResult.setKeepCallback(true);
if (messageChannel != null) {
messageChannel.sendPluginResult(pluginResult);
}
this.cordova.getActivity().registerReceiver(this.telephonyReceiver, intentFilter);
}
/*
@@ -326,6 +296,6 @@ public class App extends CordovaPlugin {
*/
public void onDestroy()
{
webView.getContext().unregisterReceiver(this.telephonyReceiver);
this.cordova.getActivity().unregisterReceiver(this.telephonyReceiver);
}
}

View File

@@ -1,162 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.io.File;
import android.content.Context;
import android.os.Environment;
import android.os.StatFs;
/**
* This class provides file directory utilities.
* All file operations are performed on the SD card.
*
* It is used by the FileUtils class.
*/
@Deprecated // Deprecated in 3.1. To be removed in 4.0.
public class DirectoryManager {
@SuppressWarnings("unused")
private static final String LOG_TAG = "DirectoryManager";
/**
* Determine if a file or directory exists.
* @param name The name of the file to check.
* @return T=exists, F=not found
*/
public static boolean testFileExists(String name) {
boolean status;
// If SD card exists
if ((testSaveLocationExists()) && (!name.equals(""))) {
File path = Environment.getExternalStorageDirectory();
File newPath = constructFilePaths(path.toString(), name);
status = newPath.exists();
}
// If no SD card
else {
status = false;
}
return status;
}
/**
* Get the free disk space
*
* @return Size in KB or -1 if not available
*/
public static long getFreeDiskSpace(boolean checkInternal) {
String status = Environment.getExternalStorageState();
long freeSpace = 0;
// If SD card exists
if (status.equals(Environment.MEDIA_MOUNTED)) {
freeSpace = freeSpaceCalculation(Environment.getExternalStorageDirectory().getPath());
}
else if (checkInternal) {
freeSpace = freeSpaceCalculation("/");
}
// If no SD card and we haven't been asked to check the internal directory then return -1
else {
return -1;
}
return freeSpace;
}
/**
* Given a path return the number of free KB
*
* @param path to the file system
* @return free space in KB
*/
private static long freeSpaceCalculation(String path) {
StatFs stat = new StatFs(path);
long blockSize = stat.getBlockSize();
long availableBlocks = stat.getAvailableBlocks();
return availableBlocks * blockSize / 1024;
}
/**
* Determine if SD card exists.
*
* @return T=exists, F=not found
*/
public static boolean testSaveLocationExists() {
String sDCardStatus = Environment.getExternalStorageState();
boolean status;
// If SD card is mounted
if (sDCardStatus.equals(Environment.MEDIA_MOUNTED)) {
status = true;
}
// If no SD card
else {
status = false;
}
return status;
}
/**
* Create a new file object from two file paths.
*
* @param file1 Base file path
* @param file2 Remaining file path
* @return File object
*/
private static File constructFilePaths (String file1, String file2) {
File newPath;
if (file2.startsWith(file1)) {
newPath = new File(file2);
}
else {
newPath = new File(file1 + "/" + file2);
}
return newPath;
}
/**
* Determine if we can use the SD Card to store the temporary file. If not then use
* the internal cache directory.
*
* @return the absolute path of where to store the file
*/
public static String getTempDirectoryPath(Context ctx) {
File cache = null;
// SD Card Mounted
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
cache = new File(Environment.getExternalStorageDirectory().getAbsolutePath() +
"/Android/data/" + ctx.getPackageName() + "/cache/");
}
// Use internal storage
else {
cache = ctx.getCacheDir();
}
// Create the cache directory if it doesn't exist
if (!cache.exists()) {
cache.mkdirs();
}
return cache.getAbsolutePath();
}
}

View File

@@ -1,34 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
/**
* This used to be the class that should be extended by application
* developers, but everything has been moved to CordovaActivity. So
* you should extend CordovaActivity instead of DroidGap. This class
* will be removed at a future time.
*
* @see CordovaActivity
* @deprecated
*/
@Deprecated
public class DroidGap extends CordovaActivity {
}

View File

@@ -1,186 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.io.IOException;
import android.media.ExifInterface;
@Deprecated // Deprecated in 3.1. To be removed in 4.0.
public class ExifHelper {
private String aperture = null;
private String datetime = null;
private String exposureTime = null;
private String flash = null;
private String focalLength = null;
private String gpsAltitude = null;
private String gpsAltitudeRef = null;
private String gpsDateStamp = null;
private String gpsLatitude = null;
private String gpsLatitudeRef = null;
private String gpsLongitude = null;
private String gpsLongitudeRef = null;
private String gpsProcessingMethod = null;
private String gpsTimestamp = null;
private String iso = null;
private String make = null;
private String model = null;
private String orientation = null;
private String whiteBalance = null;
private ExifInterface inFile = null;
private ExifInterface outFile = null;
/**
* The file before it is compressed
*
* @param filePath
* @throws IOException
*/
public void createInFile(String filePath) throws IOException {
this.inFile = new ExifInterface(filePath);
}
/**
* The file after it has been compressed
*
* @param filePath
* @throws IOException
*/
public void createOutFile(String filePath) throws IOException {
this.outFile = new ExifInterface(filePath);
}
/**
* Reads all the EXIF data from the input file.
*/
public void readExifData() {
this.aperture = inFile.getAttribute(ExifInterface.TAG_APERTURE);
this.datetime = inFile.getAttribute(ExifInterface.TAG_DATETIME);
this.exposureTime = inFile.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
this.flash = inFile.getAttribute(ExifInterface.TAG_FLASH);
this.focalLength = inFile.getAttribute(ExifInterface.TAG_FOCAL_LENGTH);
this.gpsAltitude = inFile.getAttribute(ExifInterface.TAG_GPS_ALTITUDE);
this.gpsAltitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF);
this.gpsDateStamp = inFile.getAttribute(ExifInterface.TAG_GPS_DATESTAMP);
this.gpsLatitude = inFile.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
this.gpsLatitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
this.gpsLongitude = inFile.getAttribute(ExifInterface.TAG_GPS_LONGITUDE);
this.gpsLongitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
this.gpsProcessingMethod = inFile.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD);
this.gpsTimestamp = inFile.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP);
this.iso = inFile.getAttribute(ExifInterface.TAG_ISO);
this.make = inFile.getAttribute(ExifInterface.TAG_MAKE);
this.model = inFile.getAttribute(ExifInterface.TAG_MODEL);
this.orientation = inFile.getAttribute(ExifInterface.TAG_ORIENTATION);
this.whiteBalance = inFile.getAttribute(ExifInterface.TAG_WHITE_BALANCE);
}
/**
* Writes the previously stored EXIF data to the output file.
*
* @throws IOException
*/
public void writeExifData() throws IOException {
// Don't try to write to a null file
if (this.outFile == null) {
return;
}
if (this.aperture != null) {
this.outFile.setAttribute(ExifInterface.TAG_APERTURE, this.aperture);
}
if (this.datetime != null) {
this.outFile.setAttribute(ExifInterface.TAG_DATETIME, this.datetime);
}
if (this.exposureTime != null) {
this.outFile.setAttribute(ExifInterface.TAG_EXPOSURE_TIME, this.exposureTime);
}
if (this.flash != null) {
this.outFile.setAttribute(ExifInterface.TAG_FLASH, this.flash);
}
if (this.focalLength != null) {
this.outFile.setAttribute(ExifInterface.TAG_FOCAL_LENGTH, this.focalLength);
}
if (this.gpsAltitude != null) {
this.outFile.setAttribute(ExifInterface.TAG_GPS_ALTITUDE, this.gpsAltitude);
}
if (this.gpsAltitudeRef != null) {
this.outFile.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF, this.gpsAltitudeRef);
}
if (this.gpsDateStamp != null) {
this.outFile.setAttribute(ExifInterface.TAG_GPS_DATESTAMP, this.gpsDateStamp);
}
if (this.gpsLatitude != null) {
this.outFile.setAttribute(ExifInterface.TAG_GPS_LATITUDE, this.gpsLatitude);
}
if (this.gpsLatitudeRef != null) {
this.outFile.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, this.gpsLatitudeRef);
}
if (this.gpsLongitude != null) {
this.outFile.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, this.gpsLongitude);
}
if (this.gpsLongitudeRef != null) {
this.outFile.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, this.gpsLongitudeRef);
}
if (this.gpsProcessingMethod != null) {
this.outFile.setAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD, this.gpsProcessingMethod);
}
if (this.gpsTimestamp != null) {
this.outFile.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, this.gpsTimestamp);
}
if (this.iso != null) {
this.outFile.setAttribute(ExifInterface.TAG_ISO, this.iso);
}
if (this.make != null) {
this.outFile.setAttribute(ExifInterface.TAG_MAKE, this.make);
}
if (this.model != null) {
this.outFile.setAttribute(ExifInterface.TAG_MODEL, this.model);
}
if (this.orientation != null) {
this.outFile.setAttribute(ExifInterface.TAG_ORIENTATION, this.orientation);
}
if (this.whiteBalance != null) {
this.outFile.setAttribute(ExifInterface.TAG_WHITE_BALANCE, this.whiteBalance);
}
this.outFile.saveAttributes();
}
public int getOrientation() {
int o = Integer.parseInt(this.orientation);
if (o == ExifInterface.ORIENTATION_NORMAL) {
return 0;
} else if (o == ExifInterface.ORIENTATION_ROTATE_90) {
return 90;
} else if (o == ExifInterface.ORIENTATION_ROTATE_180) {
return 180;
} else if (o == ExifInterface.ORIENTATION_ROTATE_270) {
return 270;
} else {
return 0;
}
}
public void resetOrientation() {
this.orientation = "" + ExifInterface.ORIENTATION_NORMAL;
}
}

52
framework/src/org/apache/cordova/ExposedJsApi.java Executable file → Normal file
View File

@@ -1,52 +1,12 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import android.webkit.JavascriptInterface;
import org.json.JSONException;
/**
* Contains APIs that the JS can call. All functions in here should also have
* an equivalent entry in CordovaChromeClient.java, and be added to
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
/*
* Any exposed Javascript API MUST implement these three things!
*/
/* package */ class ExposedJsApi {
private CordovaBridge bridge;
public ExposedJsApi(CordovaBridge bridge) {
this.bridge = bridge;
}
@JavascriptInterface
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
return bridge.jsExec(bridgeSecret, service, action, callbackId, arguments);
}
@JavascriptInterface
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
bridge.jsSetNativeToJsBridgeMode(bridgeSecret, value);
}
@JavascriptInterface
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
return bridge.jsRetrieveJsMessages(bridgeSecret, fromOnlineEvent);
}
public interface ExposedJsApi {
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException;
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException;
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException;
}

View File

@@ -1,163 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import android.database.Cursor;
import android.net.Uri;
import android.webkit.MimeTypeMap;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.LOG;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Locale;
@Deprecated // Deprecated in 3.1. To be removed in 4.0.
public class FileHelper {
private static final String LOG_TAG = "FileUtils";
private static final String _DATA = "_data";
/**
* Returns the real path of the given URI string.
* If the given URI string represents a content:// URI, the real path is retrieved from the media store.
*
* @param uriString the URI string of the audio/image/video
* @param cordova the current application context
* @return the full path to the file
*/
@SuppressWarnings("deprecation")
public static String getRealPath(String uriString, CordovaInterface cordova) {
String realPath = null;
if (uriString.startsWith("content://")) {
String[] proj = { _DATA };
Cursor cursor = cordova.getActivity().managedQuery(Uri.parse(uriString), proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(_DATA);
cursor.moveToFirst();
realPath = cursor.getString(column_index);
if (realPath == null) {
LOG.e(LOG_TAG, "Could get real path for URI string %s", uriString);
}
} else if (uriString.startsWith("file://")) {
realPath = uriString.substring(7);
if (realPath.startsWith("/android_asset/")) {
LOG.e(LOG_TAG, "Cannot get real path for URI string %s because it is a file:///android_asset/ URI.", uriString);
realPath = null;
}
} else {
realPath = uriString;
}
return realPath;
}
/**
* Returns the real path of the given URI.
* If the given URI is a content:// URI, the real path is retrieved from the media store.
*
* @param uri the URI of the audio/image/video
* @param cordova the current application context
* @return the full path to the file
*/
public static String getRealPath(Uri uri, CordovaInterface cordova) {
return FileHelper.getRealPath(uri.toString(), cordova);
}
/**
* Returns an input stream based on given URI string.
*
* @param uriString the URI string from which to obtain the input stream
* @param cordova the current application context
* @return an input stream into the data at the given URI or null if given an invalid URI string
* @throws IOException
*/
public static InputStream getInputStreamFromUriString(String uriString, CordovaInterface cordova) throws IOException {
if (uriString.startsWith("content")) {
Uri uri = Uri.parse(uriString);
return cordova.getActivity().getContentResolver().openInputStream(uri);
} else if (uriString.startsWith("file://")) {
int question = uriString.indexOf("?");
if (question > -1) {
uriString = uriString.substring(0,question);
}
if (uriString.startsWith("file:///android_asset/")) {
Uri uri = Uri.parse(uriString);
String relativePath = uri.getPath().substring(15);
return cordova.getActivity().getAssets().open(relativePath);
} else {
return new FileInputStream(getRealPath(uriString, cordova));
}
} else {
return new FileInputStream(getRealPath(uriString, cordova));
}
}
/**
* Removes the "file://" prefix from the given URI string, if applicable.
* If the given URI string doesn't have a "file://" prefix, it is returned unchanged.
*
* @param uriString the URI string to operate on
* @return a path without the "file://" prefix
*/
public static String stripFileProtocol(String uriString) {
if (uriString.startsWith("file://")) {
uriString = uriString.substring(7);
}
return uriString;
}
public static String getMimeTypeForExtension(String path) {
String extension = path;
int lastDot = extension.lastIndexOf('.');
if (lastDot != -1) {
extension = extension.substring(lastDot + 1);
}
// Convert the URI string to lower case to ensure compatibility with MimeTypeMap (see CB-2185).
extension = extension.toLowerCase(Locale.getDefault());
if (extension.equals("3ga")) {
return "audio/3gpp";
}
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
/**
* Returns the mime type of the data specified by the given URI string.
*
* @param uriString the URI string of the data
* @return the mime type of the specified data
*/
public static String getMimeType(String uriString, CordovaInterface cordova) {
String mimeType = null;
Uri uri = Uri.parse(uriString);
if (uriString.startsWith("content://")) {
mimeType = cordova.getActivity().getContentResolver().getType(uri);
} else {
mimeType = getMimeTypeForExtension(uri.getPath());
}
return mimeType;
}
}

View File

@@ -1,66 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
/**
* Specifies interface for handling certificate requests.
*/
public interface ICordovaClientCertRequest {
/**
* Cancel this request
*/
public void cancel();
/*
* Returns the host name of the server requesting the certificate.
*/
public String getHost();
/*
* Returns the acceptable types of asymmetric keys (can be null).
*/
public String[] getKeyTypes();
/*
* Returns the port number of the server requesting the certificate.
*/
public int getPort();
/*
* Returns the acceptable certificate issuers for the certificate matching the private key (can be null).
*/
public Principal[] getPrincipals();
/*
* Ignore the request for now. Do not remember user's choice.
*/
public void ignore();
/*
* Proceed with the specified private key and client certificate chain. Remember the user's positive choice and use it for future requests.
*
* @param privateKey The privateKey
* @param chain The certificate chain
*/
public void proceed(PrivateKey privateKey, X509Certificate[] chain);
}

View File

@@ -1,38 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
/**
* Specifies interface for HTTP auth handler object which is used to handle auth requests and
* specifying user credentials.
*/
public interface ICordovaHttpAuthHandler {
/**
* Instructs the WebView to cancel the authentication request.
*/
public void cancel ();
/**
* Instructs the WebView to proceed with the authentication with the given credentials.
*
* @param username The user name
* @param password The password
*/
public void proceed (String username, String password);
}

View File

@@ -32,16 +32,11 @@ import android.webkit.WebResourceResponse;
import android.webkit.WebView;
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
public class IceCreamCordovaWebViewClient extends AndroidWebViewClient {
private static final String TAG = "IceCreamCordovaWebViewClient";
private CordovaUriHelper helper;
public IceCreamCordovaWebViewClient(CordovaInterface cordova) {
super(cordova);
}
public IceCreamCordovaWebViewClient(CordovaInterface cordova, CordovaWebView view) {
public IceCreamCordovaWebViewClient(CordovaInterface cordova, AndroidWebView view) {
super(cordova, view);
}
@@ -50,7 +45,7 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
try {
// Check the against the whitelist and lock out access to the WebView directory
// Changing this will cause problems for your application
if (isUrlHarmful(url)) {
if (!helper.shouldAllowRequest(url)) {
LOG.w(TAG, "URL blocked by whitelist: " + url);
// Results in a 404.
return new WebResourceResponse("text/plain", "UTF-8", null);
@@ -76,11 +71,6 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
}
}
private boolean isUrlHarmful(String url) {
return ((url.startsWith("http:") || url.startsWith("https:")) && !appView.getWhitelist().isUrlWhiteListed(url))
|| url.contains("app_webview");
}
private static boolean needsKitKatContentUrlFix(Uri uri) {
return android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && "content".equals(uri.getScheme());
}

View File

@@ -1,43 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
@Deprecated // Deprecated in 3.1. To be removed in 4.0.
public class JSONUtils {
public static List<String> toStringList(JSONArray array) throws JSONException {
if(array == null) {
return null;
}
else {
List<String> list = new ArrayList<String>();
for (int i = 0; i < array.length(); i++) {
list.add(array.get(i).toString());
}
return list;
}
}
}

View File

@@ -18,7 +18,10 @@
*/
package org.apache.cordova;
import org.apache.cordova.LOG;
import android.content.Context;
//import android.view.View.MeasureSpec;
import android.widget.LinearLayout;
/**
@@ -33,7 +36,6 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout {
private int screenWidth = 0;
private int screenHeight = 0;
private CordovaActivity app = null;
private App appPlugin = null;
public LinearLayoutSoftKeyboardDetect(Context context, int width, int height) {
super(context);
@@ -48,7 +50,7 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout {
* gets smaller fire a show keyboard event and when height gets bigger fire
* a hide keyboard event.
*
* Note: We are using the core App plugin to send events over the bridge to Javascript
* Note: We are using app.postMessage so that this is more compatible with the API
*
* @param widthMeasureSpec
* @param heightMeasureSpec
@@ -85,12 +87,14 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout {
// If the height as gotten bigger then we will assume the soft keyboard has
// gone away.
else if (height > oldHeight) {
sendEvent("hidekeyboard");
if (app != null)
app.appView.sendJavascript("cordova.fireDocumentEvent('hidekeyboard');");
}
// If the height as gotten smaller then we will assume the soft keyboard has
// If the height as gotten smaller then we will assume the soft keyboard has
// been displayed.
else if (height < oldHeight) {
sendEvent("showkeyboard");
if (app != null)
app.appView.sendJavascript("cordova.fireDocumentEvent('showkeyboard');");
}
// Update the old height for the next event
@@ -98,15 +102,4 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout {
oldWidth = width;
}
private void sendEvent(String event) {
if (appPlugin == null) {
appPlugin = (App)app.appView.pluginManager.getPlugin(App.PLUGIN_NAME);
}
if (appPlugin == null) {
LOG.w(TAG, "Unable to fire event without existing plugin");
return;
}
appPlugin.fireJavascriptEvent(event);
}
}

View File

@@ -85,7 +85,7 @@ public class NativeToJsMessageQueue {
registeredListeners[3] = new PrivateApiBridgeMode();
reset();
}
public boolean isBridgeEnabled() {
return activeBridgeMode != null;
}
@@ -299,7 +299,7 @@ public class NativeToJsMessageQueue {
public void run() {
String js = popAndEncodeAsJs();
if (js != null) {
webView.loadUrlNow("javascript:" + js);
webView.loadUrlIntoView("javascript:" + js, false);
}
}
};

View File

@@ -18,43 +18,38 @@
*/
package org.apache.cordova;
import java.util.List;
import org.apache.cordova.CordovaPlugin;
/**
* This class represents a service entry object.
*/
public class PluginEntry {
public final class PluginEntry {
/**
* The name of the service that this plugin implements
*/
public String service;
public final String service;
/**
* The plugin class name that implements the service.
*/
public String pluginClass;
public final String pluginClass;
/**
* The pre-instantiated plugin to use for this entry.
*/
public CordovaPlugin plugin;
public final CordovaPlugin plugin;
/**
* Flag that indicates the plugin object should be created when PluginManager is initialized.
*/
public boolean onload;
private List<String> urlFilters;
public final boolean onload;
/**
* Constructs with a CordovaPlugin already instantiated.
*/
public PluginEntry(String service, CordovaPlugin plugin) {
this(service, plugin.getClass().getName(), true, plugin, null);
this(service, plugin.getClass().getName(), true, plugin);
}
/**
@@ -63,27 +58,13 @@ public class PluginEntry {
* @param onload Create plugin object when HTML page is loaded
*/
public PluginEntry(String service, String pluginClass, boolean onload) {
this(service, pluginClass, onload, null, null);
}
@Deprecated // urlFilters are going away
public PluginEntry(String service, String pluginClass, boolean onload, List<String> urlFilters) {
this.service = service;
this.pluginClass = pluginClass;
this.onload = onload;
this.urlFilters = urlFilters;
plugin = null;
this(service, pluginClass, onload, null);
}
private PluginEntry(String service, String pluginClass, boolean onload, CordovaPlugin plugin, List<String> urlFilters) {
private PluginEntry(String service, String pluginClass, boolean onload, CordovaPlugin plugin) {
this.service = service;
this.pluginClass = pluginClass;
this.onload = onload;
this.urlFilters = urlFilters;
this.plugin = plugin;
}
public List<String> getUrlFilters() {
return urlFilters;
}
}

View File

@@ -18,8 +18,8 @@
*/
package org.apache.cordova;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CallbackContext;
@@ -51,31 +51,21 @@ public class PluginManager {
private final CordovaInterface ctx;
private final CordovaWebView app;
// Stores mapping of Plugin Name -> <url-filter> values.
// Using <url-filter> is deprecated.
protected HashMap<String, List<String>> urlMap = new HashMap<String, List<String>>();
@Deprecated
PluginManager(CordovaWebView cordovaWebView, CordovaInterface cordova) {
this(cordovaWebView, cordova, null);
}
PluginManager(CordovaWebView cordovaWebView, CordovaInterface cordova, List<PluginEntry> pluginEntries) {
public PluginManager(CordovaWebView cordovaWebView, CordovaInterface cordova, Collection<PluginEntry> pluginEntries) {
this.ctx = cordova;
this.app = cordovaWebView;
if (pluginEntries == null) {
ConfigXmlParser parser = new ConfigXmlParser();
parser.parse(ctx.getActivity());
pluginEntries = parser.getPluginEntries();
}
setPluginEntries(pluginEntries);
}
public void setPluginEntries(List<PluginEntry> pluginEntries) {
public Collection<PluginEntry> getPluginEntries() {
return entryMap.values();
}
public void setPluginEntries(Collection<PluginEntry> pluginEntries) {
this.onPause(false);
this.onDestroy();
pluginMap.clear();
urlMap.clear();
entryMap.clear();
for (PluginEntry entry : pluginEntries) {
addService(entry);
}
@@ -92,30 +82,13 @@ public class PluginManager {
this.startupPlugins();
}
@Deprecated
public void loadPlugins() {
}
/**
* Delete all plugin objects.
*/
@Deprecated // Should not be exposed as public.
public void clearPluginObjects() {
pluginMap.clear();
}
/**
* Create plugins objects that have onload set.
*/
@Deprecated // Should not be exposed as public.
public void startupPlugins() {
private void startupPlugins() {
for (PluginEntry entry : entryMap.values()) {
// Add a null entry to for each non-startup plugin to avoid ConcurrentModificationException
// When iterating plugins.
if (entry.onload) {
getPlugin(entry.service);
} else {
pluginMap.put(entry.service, null);
}
}
}
@@ -167,11 +140,6 @@ public class PluginManager {
}
}
@Deprecated
public void exec(String service, String action, String callbackId, String jsonArgs, boolean async) {
exec(service, action, callbackId, jsonArgs);
}
/**
* Get the plugin object that implements the service.
* If the plugin object does not already exist, then create it.
@@ -218,15 +186,10 @@ public class PluginManager {
*/
public void addService(PluginEntry entry) {
this.entryMap.put(entry.service, entry);
List<String> urlFilters = entry.getUrlFilters();
if (urlFilters != null) {
urlMap.put(entry.service, urlFilters);
}
if (entry.plugin != null) {
entry.plugin.privateInitialize(ctx, app, app.getPreferences());
pluginMap.put(entry.service, entry.plugin);
}
}
/**
@@ -236,52 +199,10 @@ public class PluginManager {
*/
public void onPause(boolean multitasking) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onPause(multitasking);
}
plugin.onPause(multitasking);
}
}
/**
* Called when the system received an HTTP authentication request. Plugins can use
* the supplied HttpAuthHandler to process this auth challenge.
*
* @param view The WebView that is initiating the callback
* @param handler The HttpAuthHandler used to set the WebView's response
* @param host The host requiring authentication
* @param realm The realm for which authentication is required
*
* @return Returns True if there is a plugin which will resolve this auth challenge, otherwise False
*
*/
public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null && plugin.onReceivedHttpAuthRequest(view, handler, host, realm)) {
return true;
}
}
return false;
}
/**
* Called when he system received an SSL client certificate request. Plugin can use
* the supplied ClientCertRequest to process this certificate challenge.
*
* @param view The WebView that is initiating the callback
* @param request The client certificate request
*
* @return Returns True if plugin will resolve this auth challenge, otherwise False
*
*/
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null && plugin.onReceivedClientCertRequest(view, request)) {
return true;
}
}
return false;
}
/**
* Called when the activity will start interacting with the user.
*
@@ -289,9 +210,7 @@ public class PluginManager {
*/
public void onResume(boolean multitasking) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onResume(multitasking);
}
plugin.onResume(multitasking);
}
}
@@ -300,9 +219,7 @@ public class PluginManager {
*/
public void onDestroy() {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onDestroy();
}
plugin.onDestroy();
}
}
@@ -319,11 +236,9 @@ public class PluginManager {
return obj;
}
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
obj = plugin.onMessage(id, data);
if (obj != null) {
return obj;
}
obj = plugin.onMessage(id, data);
if (obj != null) {
return obj;
}
}
return null;
@@ -334,10 +249,112 @@ public class PluginManager {
*/
public void onNewIntent(Intent intent) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
plugin.onNewIntent(intent);
}
}
/**
* Called when the webview is going to request an external resource.
*
* This delegates to the installed plugins, which must all return true for
* this method to return true.
*
* @param url The URL that is being requested.
* @return Tri-State:
* null: All plugins returned null (the default). This
* indicates that the default policy should be
* followed.
* true: All plugins returned true (allow the resource
* to load)
* false: At least one plugin returned false (block the
* resource)
*/
public Boolean shouldAllowRequest(String url) {
Boolean anyResponded = null;
for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null) {
plugin.onNewIntent(intent);
Boolean result = plugin.shouldAllowRequest(url);
if (result != null) {
anyResponded = true;
if (!result) {
return false;
}
}
}
}
// This will be true if all plugins allow the request, or null if no plugins override the method
return anyResponded;
}
/**
* Called when the webview is going to change the URL of the loaded content.
*
* This delegates to the installed plugins, which must all return true for
* this method to return true. A true result will allow the new page to load;
* a false result will prevent the page from loading.
*
* @param url The URL that is being requested.
* @return Tri-State:
* null: All plugins returned null (the default). This
* indicates that the default policy should be
* followed.
* true: All plugins returned true (allow the navigation)
* false: At least one plugin returned false (block the
* navigation)
*/
public Boolean shouldAllowNavigation(String url) {
Boolean anyResponded = null;
for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null) {
Boolean result = plugin.shouldAllowNavigation(url);
if (result != null) {
anyResponded = true;
if (!result) {
return false;
}
}
}
}
// This will be true if all plugins allow the request, or null if no plugins override the method
return anyResponded;
}
/**
* Called when the webview is going not going to navigate, but may launch
* an Intent for an URL.
*
* This delegates to the installed plugins, which must all return true for
* this method to return true. A true result will allow the URL to launch;
* a false result will prevent the URL from loading.
*
* @param url The URL that is being requested.
* @return Tri-State:
* null: All plugins returned null (the default). This
* indicates that the default policy should be
* followed.
* true: All plugins returned true (allow the URL to
* launch an intent)
* false: At least one plugin returned false (block the
* intent)
*/
public Boolean shouldOpenExternalUrl(String url) {
Boolean anyResponded = null;
for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null) {
Boolean result = plugin.shouldOpenExternalUrl(url);
if (result != null) {
anyResponded = true;
if (!result) {
return false;
}
}
}
}
// This will be true if all plugins allow the request, or null if no plugins override the method
return anyResponded;
}
/**
@@ -352,18 +369,9 @@ public class PluginManager {
// that they are loaded before this function is called (either by setting
// the onload <param> or by making an exec() call to them)
for (PluginEntry entry : this.entryMap.values()) {
List<String> urlFilters = urlMap.get(entry.service);
if (urlFilters != null) {
for (String s : urlFilters) {
if (url.startsWith(s)) {
return getPlugin(entry.service).onOverrideUrlLoading(url);
}
}
} else {
CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null && plugin.onOverrideUrlLoading(url)) {
return true;
}
CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null && plugin.onOverrideUrlLoading(url)) {
return true;
}
}
return false;
@@ -374,19 +382,15 @@ public class PluginManager {
*/
public void onReset() {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onReset();
}
plugin.onReset();
}
}
Uri remapUri(Uri uri) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
Uri ret = plugin.remapUri(uri);
if (ret != null) {
return ret;
}
Uri ret = plugin.remapUri(uri);
if (ret != null) {
return ret;
}
}
return null;

View File

@@ -43,7 +43,7 @@ public class ScrollEvent {
* @param view
*/
ScrollEvent(int nx, int ny, int x, int y, View view)
public ScrollEvent(int nx, int ny, int x, int y, View view)
{
l = x; y = t; nl = nx; nt = ny;
targetView = view;

View File

@@ -1,257 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Color;
import android.os.Handler;
import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.LinearLayout;
import org.json.JSONArray;
import org.json.JSONException;
// This file is a copy of SplashScreen.java from cordova-plugin-splashscreen, and is required only
// for pre-4.0 Cordova as a transition path to it being extracted into the plugin.
public class SplashScreenInternal extends CordovaPlugin {
private static final String LOG_TAG = "SplashScreenInternal";
private static Dialog splashDialog;
private static ProgressDialog spinnerDialog;
private static boolean firstShow = true;
@Override
protected void pluginInitialize() {
if (!firstShow) {
return;
}
// Make WebView invisible while loading URL
webView.setVisibility(View.INVISIBLE);
int drawableId = preferences.getInteger("SplashDrawableId", 0);
if (drawableId == 0) {
String splashResource = preferences.getString("SplashScreen", null);
if (splashResource != null) {
drawableId = cordova.getActivity().getResources().getIdentifier(splashResource, "drawable", cordova.getActivity().getClass().getPackage().getName());
if (drawableId == 0) {
drawableId = cordova.getActivity().getResources().getIdentifier(splashResource, "drawable", cordova.getActivity().getPackageName());
}
preferences.set("SplashDrawableId", drawableId);
}
}
firstShow = false;
loadSpinner();
showSplashScreen(true);
}
@Override
public void onPause(boolean multitasking) {
// hide the splash screen to avoid leaking a window
this.removeSplashScreen();
}
@Override
public void onDestroy() {
// hide the splash screen to avoid leaking a window
this.removeSplashScreen();
firstShow = true;
}
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (action.equals("hide")) {
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
webView.postMessage("splashscreen", "hide");
}
});
} else if (action.equals("show")) {
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
webView.postMessage("splashscreen", "show");
}
});
} else if (action.equals("spinnerStart")) {
final String title = args.getString(0);
final String message = args.getString(1);
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
spinnerStart(title, message);
}
});
} else {
return false;
}
callbackContext.success();
return true;
}
@Override
public Object onMessage(String id, Object data) {
if ("splashscreen".equals(id)) {
if ("hide".equals(data.toString())) {
this.removeSplashScreen();
} else {
this.showSplashScreen(false);
}
} else if ("spinner".equals(id)) {
if ("stop".equals(data.toString())) {
this.spinnerStop();
webView.setVisibility(View.VISIBLE);
}
} else if ("onReceivedError".equals(id)) {
spinnerStop();
}
return null;
}
private void removeSplashScreen() {
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
if (splashDialog != null && splashDialog.isShowing()) {
splashDialog.dismiss();
splashDialog = null;
}
}
});
}
/**
* Shows the splash screen over the full Activity
*/
@SuppressWarnings("deprecation")
private void showSplashScreen(final boolean hideAfterDelay) {
final int splashscreenTime = preferences.getInteger("SplashScreenDelay", 3000);
final int drawableId = preferences.getInteger("SplashDrawableId", 0);
// If the splash dialog is showing don't try to show it again
if (this.splashDialog != null && splashDialog.isShowing()) {
return;
}
if (drawableId == 0 || (splashscreenTime <= 0 && hideAfterDelay)) {
return;
}
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
// Get reference to display
Display display = cordova.getActivity().getWindowManager().getDefaultDisplay();
Context context = webView.getContext();
// Create the layout for the dialog
LinearLayout root = new LinearLayout(context);
root.setMinimumHeight(display.getHeight());
root.setMinimumWidth(display.getWidth());
root.setOrientation(LinearLayout.VERTICAL);
// TODO: Use the background color of the webview's parent instead of using the
// preference.
root.setBackgroundColor(preferences.getInteger("backgroundColor", Color.BLACK));
root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT, 0.0F));
root.setBackgroundResource(drawableId);
// Create and show the dialog
splashDialog = new Dialog(context, android.R.style.Theme_Translucent_NoTitleBar);
// check to see if the splash screen should be full screen
if ((cordova.getActivity().getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_FULLSCREEN)
== WindowManager.LayoutParams.FLAG_FULLSCREEN) {
splashDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
splashDialog.setContentView(root);
splashDialog.setCancelable(false);
splashDialog.show();
// Set Runnable to remove splash screen just in case
if (hideAfterDelay) {
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
public void run() {
removeSplashScreen();
}
}, splashscreenTime);
}
}
});
}
/*
* Load the spinner
*/
private void loadSpinner() {
// If loadingDialog property, then show the App loading dialog for first page of app
String loading = null;
if (webView.canGoBack()) {
loading = preferences.getString("LoadingDialog", null);
}
else {
loading = preferences.getString("LoadingPageDialog", null);
}
if (loading != null) {
String title = "";
String message = "Loading Application...";
if (loading.length() > 0) {
int comma = loading.indexOf(',');
if (comma > 0) {
title = loading.substring(0, comma);
message = loading.substring(comma + 1);
}
else {
title = "";
message = loading;
}
}
spinnerStart(title, message);
}
}
private void spinnerStart(final String title, final String message) {
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
spinnerStop();
spinnerDialog = ProgressDialog.show(webView.getContext(), title, message, true, true,
new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
spinnerDialog = null;
}
});
}
});
}
private void spinnerStop() {
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
if (spinnerDialog != null && spinnerDialog.isShowing()) {
spinnerDialog.dismiss();
spinnerDialog = null;
}
}
});
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "cordova-android",
"version": "3.7.2",
"version": "4.0.0-dev",
"description": "cordova-android release",
"main": "bin/create",
"repository": {
@@ -20,7 +20,8 @@
"license": "Apache version 2.0",
"dependencies": {
"q": "^0.9.0",
"shelljs": "^0.2.6"
"shelljs": "^0.2.6",
"which": "^1.0.5"
},
"devDependencies": {
"jasmine-node": "~1",

View File

@@ -0,0 +1,8 @@
<html>
<head>
<title>OH NOES!</title>
</head>
<body>
<h1>Things went terribly wrong!</h1>
</body>
</html>

View File

@@ -22,7 +22,7 @@
android:layout_height="fill_parent"
android:orientation="vertical" >
<org.apache.cordova.CordovaWebView
<org.apache.cordova.AndroidWebView
android:id="@+id/cordovaWebView"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />

View File

@@ -22,12 +22,12 @@ package org.apache.cordova.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.cordova.AndroidChromeClient;
import org.apache.cordova.AndroidWebViewClient;
import org.apache.cordova.Config;
import org.apache.cordova.CordovaChromeClient;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebViewClient;
import org.apache.cordova.test.R;
import android.app.Activity;
@@ -51,8 +51,7 @@ public class CordovaWebViewTestActivity extends Activity implements CordovaInter
Config.init(this);
cordovaWebView = (CordovaWebView) findViewById(R.id.cordovaWebView);
cordovaWebView.init(this, new CordovaWebViewClient(this, cordovaWebView), new CordovaChromeClient(this, cordovaWebView),
Config.getPluginEntries(), Config.getWhitelist(), Config.getExternalWhitelist(), Config.getPreferences());
cordovaWebView.init(this, Config.getPluginEntries(), Config.getPreferences());
cordovaWebView.loadUrl("file:///android_asset/www/index.html");

View File

@@ -1,46 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.test;
import android.os.Bundle;
import org.apache.cordova.*;
public class basicauth extends CordovaActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.init();
// LogCat: onReceivedHttpAuthRequest(browserspy.dk:80,BrowserSpy.dk - HTTP Password Test)
AuthenticationToken token = new AuthenticationToken();
token.setUserName("test");
token.setPassword("test");
// classic webview includes port in hostname, Chromium webview does not. Handle both here.
// BTW, the realm is optional.
setAuthenticationToken(token, "browserspy.dk:80", "BrowserSpy.dk - HTTP Password Test");
setAuthenticationToken(token, "browserspy.dk", "BrowserSpy.dk - HTTP Password Test");
// Add web site to whitelist
Config.getWhitelist().addWhiteListEntry("http://browserspy.dk/*", true);
// Load test
super.loadUrl("file:///android_asset/www/basicauth/index.html");
}
}

View File

@@ -27,6 +27,7 @@ import org.apache.cordova.test.backbuttonmultipage;
import android.test.ActivityInstrumentationTestCase2;
import android.test.UiThreadTest;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.BaseInputConnection;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
@@ -174,7 +175,7 @@ public class BackButtonMultiPageTest extends ActivityInstrumentationTestCase2<ba
{
String url = testView.getUrl();
assertTrue(url.endsWith("sample3.html"));
BaseInputConnection viewConnection = new BaseInputConnection(testView, true);
BaseInputConnection viewConnection = new BaseInputConnection((View) testView, true);
KeyEvent backDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
KeyEvent backUp = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
viewConnection.sendKeyEvent(backDown);
@@ -187,7 +188,7 @@ public class BackButtonMultiPageTest extends ActivityInstrumentationTestCase2<ba
{
String url = testView.getUrl();
assertTrue(url.endsWith("sample2.html"));
BaseInputConnection viewConnection = new BaseInputConnection(testView, true);
BaseInputConnection viewConnection = new BaseInputConnection((View) testView, true);
KeyEvent backDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
KeyEvent backUp = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
viewConnection.sendKeyEvent(backDown);

View File

@@ -59,9 +59,9 @@ public class CordovaActivityTest extends ActivityInstrumentationTestCase2<MainTe
}
public void testForCordovaView() {
public void testForAndroidWebView() {
String className = testView.getClass().getSimpleName();
assertTrue(className.equals("CordovaWebView"));
assertTrue(className.equals("AndroidWebView"));
}
public void testForLinearLayout() {

View File

@@ -65,7 +65,7 @@ public class CordovaResourceApiTest extends ActivityInstrumentationTestCase2<Cor
cordovaWebView = activity.cordovaWebView;
resourceApi = cordovaWebView.getResourceApi();
resourceApi.setThreadCheckingEnabled(false);
cordovaWebView.pluginManager.addService(new PluginEntry("CordovaResourceApiTestPlugin1", new CordovaPlugin() {
cordovaWebView.getPluginManager().addService(new PluginEntry("CordovaResourceApiTestPlugin1", new CordovaPlugin() {
@Override
public Uri remapUri(Uri uri) {
if (uri.getQuery() != null && uri.getQuery().contains("pluginRewrite")) {

View File

@@ -49,11 +49,11 @@ public class CordovaTest extends
assertNotNull(testView);
}
public void testForCordovaView() {
public void testForAndroidWebView() {
//Sleep for no reason!!!!
sleep();
sleep();
String className = testView.getClass().getSimpleName();
assertTrue(className.equals("CordovaWebView"));
assertTrue(className.equals("AndroidWebView"));
}
/*

View File

@@ -0,0 +1,66 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.test.junit;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginManager;
import org.apache.cordova.test.CordovaWebViewTestActivity;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.test.ActivityInstrumentationTestCase2;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
public class GapClientTest extends ActivityInstrumentationTestCase2<CordovaWebViewTestActivity> {
private CordovaWebViewTestActivity testActivity;
private FrameLayout containerView;
private LinearLayout innerContainer;
private View testView;
private String rString;
public GapClientTest() {
super("org.apache.cordova.test.activities",CordovaWebViewTestActivity.class);
}
protected void setUp() throws Exception{
super.setUp();
testActivity = this.getActivity();
containerView = (FrameLayout) testActivity.findViewById(android.R.id.content);
innerContainer = (LinearLayout) containerView.getChildAt(0);
testView = innerContainer.getChildAt(0);
}
public void testPreconditions(){
assertNotNull(innerContainer);
assertNotNull(testView);
}
public void testForAndroidWebView() {
String className = testView.getClass().getSimpleName();
assertTrue(className.equals("AndroidWebView"));
}
}

View File

@@ -1,32 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.test.junit;
import org.apache.cordova.test.menus;
import android.test.ActivityInstrumentationTestCase2;
public class MenuTest extends ActivityInstrumentationTestCase2<menus> {
public MenuTest() {
super("org.apache.cordova.test", menus.class);
}
}

View File

@@ -34,7 +34,7 @@ public class menus extends CordovaActivity {
super.onCreate(savedInstanceState);
// need the title to be shown (config.xml) for the options menu to be visible
super.init();
super.registerForContextMenu(super.appView);
super.registerForContextMenu(super.appView.getView());
super.loadUrl("file:///android_asset/www/menus/index.html");
}

View File

@@ -32,16 +32,16 @@ public class userwebview extends MainTestActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
testViewClient = new TestViewClient(this, appView);
testChromeClient = new TestChromeClient(this, appView);
testViewClient = new TestViewClient(this, ((AndroidWebView)appView));
testChromeClient = new TestChromeClient(this, ((AndroidWebView)appView));
super.init();
appView.setWebViewClient(testViewClient);
appView.setWebChromeClient(testChromeClient);
((AndroidWebView)appView).setWebViewClient(testViewClient);
((AndroidWebView)appView).setWebChromeClient(testChromeClient);
super.loadUrl("file:///android_asset/www/userwebview/index.html");
}
public class TestChromeClient extends CordovaChromeClient {
public TestChromeClient(CordovaInterface ctx, CordovaWebView app) {
public class TestChromeClient extends AndroidChromeClient {
public TestChromeClient(CordovaInterface ctx, AndroidWebView app) {
super(ctx, app);
LOG.d("userwebview", "TestChromeClient()");
}
@@ -57,8 +57,8 @@ public class userwebview extends MainTestActivity {
/**
* This class can be used to override the GapViewClient and receive notification of webview events.
*/
public class TestViewClient extends CordovaWebViewClient {
public TestViewClient(CordovaInterface ctx, CordovaWebView app) {
public class TestViewClient extends AndroidWebViewClient {
public TestViewClient(CordovaInterface ctx, AndroidWebView app) {
super(ctx, app);
LOG.d("userwebview", "TestViewClient()");
}

View File

@@ -22,23 +22,22 @@ import android.os.Bundle;
import android.webkit.WebView;
import org.apache.cordova.*;
import org.apache.cordova.LOG;
public class whitelist extends MainTestActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.init();
appView.setWebViewClient(new TestViewClient(this, appView));
((AndroidWebView)appView).setWebViewClient(new TestViewClient(this, ((AndroidWebView)appView)));
super.loadUrl("file:///android_asset/www/whitelist/index.html");
}
/**
* This class can be used to override the GapViewClient and receive notification of webview events.
*/
public class TestViewClient extends CordovaWebViewClient {
public class TestViewClient extends AndroidWebViewClient {
public TestViewClient(CordovaInterface ctx, CordovaWebView app) {
public TestViewClient(CordovaInterface ctx, AndroidWebView app) {
super(ctx, app);
}