Compare commits

...

148 Commits

Author SHA1 Message Date
Steve Gill
d16482d292 CB-12519 updated incorrect version in RELEASENOTES 2017-03-02 16:06:51 -08:00
Steve Gill
72502444c9 CB-12519 Updated version and RELEASENOTES.md for release 2.4.0 2017-03-01 16:53:54 -08:00
Alexander Sorokin
4fc25154f3 CB-12501 (Android) Appium tests don't use XPath selectors anymore 2017-02-23 20:01:59 +03:00
Alexander Sorokin
7f616d16f1 CB-12469 (ios) Appium tests can now run on iOS 10 2017-02-20 13:16:53 +03:00
Joe Bowser
dfbca19a7a Close #170 2017-02-02 12:48:05 -08:00
Joe Bowser
86e546f868 Close #169 2017-02-02 12:45:57 -08:00
Joe Bowser
f2ca5ed79f Close #143 2017-02-02 12:43:42 -08:00
Joe Bowser
ab7e02f0b8 Close #158 2017-02-02 12:42:27 -08:00
Joe Bowser
899f6d8059 Close #192 2017-02-02 12:40:21 -08:00
Joe Bowser
aa8a5945dd Close #220 2017-02-02 12:38:10 -08:00
Joe Bowser
c06480f4e3 Close #239 2017-02-02 12:37:01 -08:00
Joe Bowser
5ca4d8b082 Close #248 2017-02-02 12:34:57 -08:00
Joe Bowser
bba8283d98 CB-12005: Changing the getOrientation method to return the defined enumerated EXIF instead of orientation in degrees for Consistency
This closes #252
2017-02-02 12:10:42 -08:00
Joe Bowser
c27725ce66 Close #243 2017-01-31 15:34:39 -08:00
Julio César
415412cfef Closing merged pull request: close #251 2017-01-31 01:15:37 +01:00
Julio César
5ebda25164 Closing invalid pull request: close #247 2017-01-31 01:12:01 +01:00
Sergey Zolotarev
d29c767f07 CB-12368: Fix permission check on Android
The plugin was checking whether camera permission was granted but then
actually requested permission for external storage.

Surprisingly enough this fixed CB-12368.
2017-01-30 23:52:54 +01:00
daserge
0ad5bdd9ff CB-12353 Corrected merges usage in plugin.xml 2017-01-20 15:35:30 +03:00
Nikita Matrosov
d6bd9ae3b3 CB-12369: Add plugin typings from DefinitelyTyped
This closes #250
2017-01-20 10:58:49 +03:00
Alexander Sorokin
81f9433606 CB-12363 Added build badges for iOS 9.3 and 10.0 2017-01-18 13:27:26 +03:00
filmaj
05594c4646 CB-12312: [Appium] [Android] A few changes to the tests:
- updated comments on how to run the tests. extra comments around functionality at certain points in the automation.
 - stub of a resolution checker on test startup - still need to figure out acceptable values.
 - moved session shutdown to an afterAll clause.
 - changed resolution determiner from using webview-based values to using the native windows dimensions - this helps as the webview values may be scaled down intentionally by manufacturers (via changing devicePixelRatio). furthermore, since the screen dimension automation is used purely for native UI automation, better to use the dimensions reported by the native context rather than the web context.
 - when finding elements by XPath, use multiple calls to avoid a Windows emulator + Android bug. Made this pattern consistent in the entire test.
2017-01-03 06:36:36 -08:00
Shazron Abdullah
a364e79482 CB-12236 - Fixed RELEASENOTES for cordova-plugin-camera 2016-12-11 12:41:08 -08:00
Alexander Sorokin
42fc8e0bcd CB-12230 Removed Windows 8.1 build badges 2016-12-09 14:22:12 +03:00
Shazron Abdullah
ee5537694a CB-12224 Incremented plugin version. 2016-12-07 16:55:06 -08:00
Shazron Abdullah
9eba35e2f6 CB-12224 Updated version and RELEASENOTES.md for release 2.3.1 2016-12-07 16:39:43 -08:00
Shazron Abdullah
8b83171ee2 Fix missing license headers. 2016-12-07 16:19:39 -08:00
Vladimir Kotikov
c9e6a9a38a CB-12086 Regenerate README.md from template 2016-11-14 10:01:22 +03:00
Chris Rae
cc48945f37 Added NSPhotoLibraryUsageDescription parameter to example install command
Fixing some usages of NSPhotoLibraryUsageDescriptionentry

 This closes #240
2016-11-13 23:36:38 +01:00
Joe Bowser
8b3410bcc6 Close #124: I can crop fine with Photos. We should not have adopted Crop, since it makes no sense on Android. 2016-11-09 10:05:33 -08:00
Simon MacDonald
485a11e0f4 Updating compat dependency to 1.1.0 or better 2016-11-07 21:40:38 -05:00
Joe Bowser
2d47a26271 Close #199. We save photos to a shared Pictures directory, similar to the behaviour of the Twitter application 2016-11-07 16:23:16 -08:00
Joe Bowser
2d2352f695 Close #201. Running out of memory shouldn't be graceful. 2016-11-07 16:21:17 -08:00
Joe Bowser
2f003d2b49 Close #228. We don't require these permissions on Camera, since we use intents. 2016-11-07 16:19:00 -08:00
Joe Bowser
3a90bb7d55 Close #241 2016-11-07 16:11:45 -08:00
Joe Bowser
b13cbdeb16 Merge branch 'nougat_camera'
This closes #235
2016-11-07 15:59:20 -08:00
Joe Bowser
ee192d94b4 Bumping the CI 2016-11-07 15:49:52 -08:00
Joe Bowser
d9eb83bcb9 CB-11625: Forgot to add CordovaUri.java to plugin.xml 2016-10-27 13:38:46 -07:00
Joe Bowser
84f96c1067 CB-11625: Files Provider does not work with Android 4.4.4 or lower, and I have no idea why. Working around with CordovaUri 2016-10-27 13:37:03 -07:00
Joe Bowser
61064ae3ed CB-11625 (Android) : Make this work with previous versions of Cordova via cordova-plugin-compat 2016-10-21 15:12:34 -07:00
Shazron Abdullah
9ec8aea073 CB-11917 - Remove pull request template checklist item: "iCLA has been submitted…"
This closes #237
2016-10-04 21:39:29 -07:00
Julio César
9db952e161 Closing invalid pull request: close #98 2016-09-24 00:47:37 +02:00
Steve Gill
06d609cfa4 CB-11832 Incremented plugin version. 2016-09-09 16:08:02 -07:00
Steve Gill
cc1076d3cb CB-11832 Updated version and RELEASENOTES.md for release 2.3.0 2016-09-08 23:38:52 -07:00
Vladimir Kotikov
48d4213b2d CB-11795 Add 'protective' entry to cordovaDependencies
The entry is required to protect end-users from fetching edge versions of the plugin by incompatible version of cordova. This also assumes that we will not introduce any regressions in compatibility w/ cordova in minor and patch releases. On every major release we will need to add similar entry with _next_ major version.

 This closes #234
2016-09-08 11:40:37 +03:00
Jacques de Villiers
9b566d3f0b CB-11661: (ios) Add mandatory iOS 10 privacy description
Add support for photoalbum description

 This closes #236
2016-09-06 23:22:58 +02:00
Joe Bowser
b63a0d83e0 Merging API 24 code with master including large refactor 2016-09-01 15:11:33 -07:00
Vladimir Kotikov
9fe94479e2 CB-11661: (ios) Add mandatory iOS 10 privacy description
This commit adds corresponding change to README template so changes from a3af38a won't be lost
2016-09-01 16:08:20 +03:00
Steve Gill
2f89666db7 Revert "close #229"
This reverts commit fa58e83fca.
2016-08-30 11:51:59 -07:00
swbradshaw
f8682b9162 Removed unneeded file
This closes #197
2016-08-29 10:46:14 -07:00
swbradshaw
0ed6406864 Merge remote-tracking branch 'refs/remotes/apache/master'
Rebase from Master
2016-08-26 20:30:35 -04:00
Steve Gill
fa58e83fca close #229 2016-08-25 16:20:16 -07:00
mhartington
a3af38ad5b CB-11661: (ios) Add mandatory iOS 10 privacy description 2016-08-25 16:17:28 -07:00
Tyler Pham
c98607c613 CB-11714: (windows) added more explicit content-type when converting to target data on canvas
This closes #232
2016-08-25 10:00:41 +03:00
Vladimir Kotikov
b89645c749 CB-11295 Add WP8.1 quirk when choosing image from photoalbum 2016-08-23 16:18:13 +03:00
Vladimir Kotikov
1beeafb6e8 CB-10067 Update PictureSourceType JSDoc to reflect README update 2016-08-23 16:18:00 +03:00
Vladimir Kotikov
7813ad9bef CB-9070 Update CameraPopoverHandle docs to reflect README update 2016-08-23 16:16:41 +03:00
Simon MacDonald
6e19147b09 Plugin uses Android Log class and not Cordova LOG class 2016-08-22 15:52:52 -04:00
Alexander Sorokin
9f159d757a CB-11631 Appium tests: A working fix for a flaky "selection canceled" failure 2016-08-17 17:35:23 +03:00
Alexander Sorokin
62d1b01e81 CB-11709 Tests should use resolveLocalFileSystemURL() instead of deprecated resolveFileSystemURI() 2016-08-16 19:19:22 +03:00
Alexander Sorokin
92d67d990d Closing invalid PR
This closes #219
2016-08-16 14:29:26 +03:00
Alexander Sorokin
0accbf560b CB-11695 Increased session creation timeout for Appium tests 2016-08-16 14:03:09 +03:00
Alexander Sorokin
e588907ac7 CB-11631 Appium tests: A fix for a flaky "selection cancelled" failure 2016-08-13 16:58:33 +03:00
Joe Bowser
3ed3d887ca BuildConfig from test project crept in source code thanks to Android Studio, removing 2016-08-04 11:35:39 -07:00
Joe Bowser
f010394af8 WTF. Directory, not file. Not sure why I did that 2016-08-04 11:16:29 -07:00
Joe Bowser
00e0a7dc46 CB-11625: Managed to get Content Providers to work with a weird mix of Content Providers and non-Content Providers 2016-08-03 14:43:11 -07:00
Alexander Sorokin
fee72c7c04 CB-11656 (Android) Appium tests: Fixed side menu opening on some more resolutions 2016-08-02 18:11:19 +03:00
Alexander Sorokin
5807458a1d CB-11656 (Android) Appium tests: Fixed side menu opening on certain resolutions 2016-08-02 13:05:12 +03:00
Julio César
c13e9f327b CB-11376: (ios): fix CameraUsesGeolocation error
If it's not a camera picture don't use geolocation

 This closes #226
2016-08-02 00:16:38 +02:00
Julio César
7e8fe0bae9 CB-10067: (ios) clarifications on PictureSourceType
This closes #227
2016-08-01 00:27:07 +02:00
Julio César
3f8c53f7f3 CB-11410: (ios) fix cameraPopoverHandle.setPosition
This closes #224
2016-07-31 18:25:03 +02:00
Julio César
4e439d85c3 CB-9070. (ios) Fixed CameraPopoverHandle documentation
This closes #225
2016-07-31 18:20:50 +02:00
Joe Bowser
b62fdf50f7 Adding provider_paths.xml so this works 2016-07-29 13:33:51 -07:00
Joe Bowser
744d72a33b Partially modified plugin.xml 2016-07-29 09:07:03 -07:00
Joe Bowser
3d26986bfd CB-11625: Working on fix to API 24 no longer allowing File URIs to be passed across intents 2016-07-27 14:06:07 -07:00
Vladimir Kotikov
fed798e6c7 CB-11447 Respect output format when retrieving images from gallery 2016-07-26 15:59:52 +03:00
Vladimir Kotikov
2027d69606 CB-11447 Resolve iOS tests failures due to iOS quirks
* Skip image type verification if source is gallery
  and destination is native uri
* Update docs t oclarify how camera works on iOS in
  some edge cases
2016-07-26 15:59:49 +03:00
Alexander Sorokin
7129fb2c12 CB-11553 Pend failing Appium tests on Sauce Labs for the time being (reverted from commit b695717240) 2016-07-12 12:28:58 +03:00
Alexander Sorokin
b695717240 CB-11553 Pend failing Appium tests on Sauce Labs for the time being 2016-07-11 16:16:52 +03:00
Alexander Sorokin
acff98058f CB-11498 [Android] Appium tests should not fail when there is no camera 2016-06-28 18:49:59 +03:00
Vladimir Kotikov
8a7326969f Add badges for paramedic builds on Jenkins 2016-06-10 11:15:20 +03:00
Alexander Sorokin
a05f169984 CB-11296: Appium: Better element clicking and session error handling 2016-05-19 19:05:28 +03:00
Alexander Sorokin
dcc81bfbe1 CB-11232 Appium tests: fixed element tapping on iOS 9 2016-05-18 12:02:44 +03:00
Alexander Sorokin
278b527702 CB-11183 Appium tests: Added image verification 2016-05-18 11:48:52 +03:00
Raghav Katyal
d7ca7edf88 Closing stale PRs. This closes #114, closes #104 2016-05-17 14:10:36 -07:00
Raghav Katyal
f283502545 Closing stale PRs. This closes #85, closes #93 2016-05-17 11:38:59 -07:00
Mikejo5001
a831e15a91 fixed some bad formatting that hid HTML tags and added link to sample
This closes #207
2016-05-17 10:58:26 -07:00
Nikhil Khandelwal
3586ea58a2 Closing stale PRs. Close #167 2016-05-16 12:51:33 -07:00
Nikhil Khandelwal
cc840b6cef Closing stale PRs. Close #147 2016-05-16 12:48:57 -07:00
Julio César
0115458ce8 Set android quality default value to 50 on the java code
Default value is set to 80 on the java code, but doc says that default
value is 50.

I’m changing it just for making code clearer, but default value is set
to 50 to all platforms in Camera.js if no value is passed
2016-05-16 19:43:01 +02:00
Richard Knoll
2eef096861 Moving message in PR template to a comment 2016-05-13 12:15:11 -07:00
Nikhil Khandelwal
cf35b1bb2a Add pull request template. This closes #213 2016-05-13 12:00:06 -07:00
Keith M
39bff2f41d CB-11228 browser: Add classes for styling purposes
This closes #212
2016-05-13 11:58:11 -07:00
Keith M
7551778e13 CB-10139 browser: Respect target width and height
This closes #210
2016-05-13 11:56:43 -07:00
swbradshaw
e3a431cbeb Reverted indenting done by Android Studio 2016-05-12 21:25:36 -04:00
swbradshaw
832d6e3bea Adding missing function 2016-05-12 13:19:35 -04:00
Nikhil Khandelwal
2a7469e065 Stale PRs: closes #149, closes #148, closes #155, closes #107, closes #71, closes #55, closes #14 2016-05-09 17:00:51 -07:00
Keith M
af98d57417 CB-11227 browser: Fix incorrect mime type
This closes #211
2016-05-09 14:09:30 -07:00
Alexander Sorokin
57b177f3fb CB-11162 Appium tests: retry spec on failure 2016-04-28 10:45:49 +03:00
swbradshaw
f2b4eeded0 CB-4078: Fix for orientation/scaling on Android 4.4+ devices
The only way to get rotation for photos in library (Gallery, File
System, Google Drive,etc) is to first create a temporary file from the
provider. Only then can we determine the orientation and scale the
bitmap correctly. By doing it in a central place, it eliminates reading
the inputstream repetitively in the plugin.
2016-04-27 22:27:37 -04:00
Steve Gill
d4a55f20ec CB-11165 removed peer dependency 2016-04-27 16:22:18 -07:00
Alexander Sorokin
68e18a97d1 CB-11147 Appium tests: generate descriptive spec names 2016-04-26 10:32:08 +03:00
Dmitry Blotsky
bfaef0ff25 CB-10996 Adding front matter to README.md 2016-04-22 19:43:08 -07:00
Alexander Sorokin
0ba547cd3c CB-11128 Appum tests: Fixed some of the flaky failures 2016-04-21 16:14:58 +03:00
Mikejo5001
6d058fe9e7 CB-11003: Added Sample section to the Camera plugin README
This closes #203
2016-04-15 15:44:38 -07:00
Steve Gill
daf5fa48dd CB-11091 Incremented plugin version. 2016-04-15 13:45:27 -07:00
Steve Gill
d124e03cb9 Updated version and RELEASENOTES.md for release 2.2.0 2016-04-15 13:08:26 -07:00
Omar Mefire
4202fff7ac CB-10873
- Avoid crash due to usage of uninitialized variable when writing geolocation data to image destination
    - Properly handle 'CameraUsesGeolocation' option by properly setting geolocation data in EXIF header in all cases

 This closes #205
2016-04-13 15:49:51 -07:00
Omar Mefire
def399fe51 CB-10873 Avoid crash due to usage of uninitialized variable when writing geolocation data to image destination 2016-04-13 15:49:50 -07:00
Alexander Sorokin
82c9f4524a CB-11073 Appium tests stability improvements 2016-04-13 12:42:51 +03:00
Simon MacDonald
a9c18710f2 Replace PermissionHelper.java with cordova-plugin-compat 2016-04-05 12:17:18 -04:00
Raghav Katyal
624ddd5ced Making focus handler work only for windows 10 phone 2016-03-23 18:33:09 -07:00
daserge
fb871d40e2 CB-10865 Run ios native tests on Travis
Updated cordova-ios tests dependency version to latest published
Changed the tests to reflect the current scaling behavior
Fixed a typo in manual test img.onloadend -> img.onload
2016-03-21 12:54:07 +03:00
riknoll
0cd962466d CB-10120 android: Fixing use of constants and PermissionHelper
This closes #179
2016-03-14 17:56:52 -07:00
ochakov
c12206ebc8 CB-10120 android: Fix missing CAMERA permission for Android M
According to the PR conversation, when android.permission.CAMERA
is not set in the package, there is no need to ask for the
camera permission. Also, checking now camera and storage
permissions separately, so if only one of them is missing, the
other one will be requested and not both.

Rebased by MatthewBooth and riknoll

This closes #142, closes #174
2016-03-14 17:03:26 -07:00
Richard Knoll
826aca3524 CB-10756: Adding sterner warnings about DATA_URL
This closes #193
2016-03-14 10:39:20 -07:00
Julio César
76c129c95e CB-10460 getRealPath return null in some cases
Now there is only a function to get the real path on API 11 and above.
2016-03-11 20:43:08 +01:00
Carlos Santana
fac7a53383 clean RELEASENOTES for bold font 2016-03-10 13:49:49 -05:00
Alexander Sorokin
1348d2e138 Appium tests: tweaking some timeouts and default screenshot path 2016-03-10 13:27:34 +03:00
Carlos Santana
c5f5a46e3e CB-10820 Incremented plugin version. 2016-03-09 22:34:17 -05:00
Carlos Santana
37dd471d0e CB-10820 Updated version and RELEASENOTES.md for release 2.1.1 2016-03-09 22:11:36 -05:00
Richard Knoll
a19c75253a CB-10825 android: Always request READ permission for gallery source
This closes #191
2016-03-09 17:56:20 -08:00
Carlos Santana
c20e031d42 added apache license header to appium files 2016-03-08 22:21:47 -05:00
Dmitry Blotsky
68a1150939 CB-10720 Fixed spelling, capitalization, and other small issues. 2016-03-03 15:21:40 -08:00
Raghav Katyal
9c906b2ab7 CB-10414: Adding focus handler to resume video when user comes back on leaving the app while preview was running 2016-03-02 14:15:01 -08:00
Alexander Sorokin
56b9469313 Appium tests: adjust swipe distance on Android 2016-03-02 17:39:59 +03:00
Alexander Sorokin
d51e23ad7b CB-10750 Appium tests: fail fast if session is irrecoverable 2016-03-02 13:23:51 +03:00
Raghav Katyal
2cd2528d1c Adding missing semi colon 2016-02-29 10:30:40 -08:00
Raghav Katyal
5f7f4f3e55 Adding focus handler to make sure filepicker gets launched when app is active 2016-02-26 14:15:27 -08:00
Miguel Revetria
20dcaf2cb3 CB-10128: [iOS] Fixed how checks access authorization to camera & library. This closes #146 2016-02-24 16:10:20 -08:00
daserge
b16c5234d5 CB-10636 Add JSHint for plugins 2016-02-24 17:14:58 +03:00
Alexander Sorokin
c1948fc0d4 CB-10639 Appium tests: Added some timeouts,
Taking a screenshot on failure,
Retry taking a picture up to 3 times,
Try to restart the Appium session if it's lost
2016-02-24 16:44:29 +03:00
Dmitry Blotsky
f792aaacc3 CB-10552 Replacing images in README.md. 2016-02-22 12:06:29 -08:00
Julio César
16636d18f2 Added a lot of more cases to get the real path
Added a lot of more cases to get the real path

This closes #175
2016-02-22 11:54:22 -08:00
Julio César
019346d188 Fix for CB-10625
getDocumentId was crashing in some cases.
Now, in case it crashes, it will use the original uri to query.
2016-02-22 11:23:53 -08:00
Alexander Sorokin
61b77951e1 CB-10619 Appium tests: Properly switch to webview 2016-02-16 16:28:43 +03:00
Alexander Sorokin
a060fb36f3 CB-10397 Added Appium tests 2016-02-12 14:13:46 +03:00
Sarangan Rajamanickam
77653183dd CB-10576: MobileSpec can't get results for Windows-Store 8.1 Builds
Fixing a minor syntax issue
2016-02-09 16:17:17 -08:00
t1st3
df734a522c chore: edit package.json license to match SPDX id
See [NPM package.json spec for licenses](https://docs.npmjs.com/files/package.json#license) and [SPDX license IDs](https://spdx.org/licenses/)

X-ref: https://github.com/apache/cordova-plugin-device/pull/48
2016-02-06 11:16:08 +01:00
Raghav Katyal
654286d373 CB-10539: Commenting out the verySmallQvga maxResolution option 2016-02-05 15:03:19 -08:00
Raghav Katyal
76ad059c9c CB-10541: Changing default maxResoltion to be highestAvailable for CameraCaptureUI 2016-02-04 12:14:16 -08:00
Laurent Deketelaere
5b38453262 CB-10113 Browser - Layer camera UI on top of all!
Adds CSS style {position: 'relative', z-index: 2147483647} (2147483647 is the highest possible z-index) on DOM appended elements.

This closes #134
2016-02-02 17:02:15 +10:00
Tim Barham
e48a7e5c5c CB-10502 Fix camera plugin exception in Chrome when click capture.
The MediaStream.stop() method has been deprecated as of Chrome 47. We were using it, and it was generating an exception.

If stop() method is not found, instead stop each individual track (the new way of doing it).
2016-02-02 16:31:39 +10:00
Raghav Katyal
06fcbf05a2 Adding comments 2016-01-21 14:11:59 -08:00
PerfectionCSGO
9a9081b0d4 Camera tapping fix 2016-01-21 14:07:51 -08:00
Steve Gill
6f7ce333cc CB-10368 Incremented plugin version. 2016-01-15 16:58:32 -08:00
Steve Gill
0f32b78c82 CB-10368 Updated version and RELEASENOTES.md for release 2.1.0 2016-01-15 16:35:05 -08:00
Steve Gill
eb009471ab added .ratignore 2016-01-15 15:09:14 -08:00
riknoll
1d32ea46f0 CB-10319 android: Adding reflective helper methods for permission requests 2016-01-12 17:42:29 -08:00
31 changed files with 3342 additions and 574 deletions

22
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,22 @@
<!--
Please make sure the checklist boxes are all checked before submitting the PR. The checklist
is intended as a quick reference, for complete details please see our Contributor Guidelines:
http://cordova.apache.org/contribute/contribute_guidelines.html
Thanks!
-->
### Platforms affected
### What does this PR do?
### What testing has been done on this change?
### Checklist
- [ ] [Reported an issue](http://cordova.apache.org/contribute/issues.html) in the JIRA database
- [ ] Commit message follows the format: "CB-3232: (android) Fix bug with resolving file paths", where CB-xxxx is the JIRA ID & "android" is the platform affected.
- [ ] Added automated test coverage as appropriate for this change.

16
.jshintrc Normal file
View File

@@ -0,0 +1,16 @@
{
"browser": true
, "devel": true
, "bitwise": true
, "undef": true
, "trailing": true
, "quotmark": false
, "indent": 4
, "unused": "vars"
, "latedef": "nofunc"
, "globals": {
"module": false,
"exports": false,
"require": false
}
}

1
.ratignore Normal file
View File

@@ -0,0 +1 @@
TEMPLATE.md

8
.travis.yml Normal file
View File

@@ -0,0 +1,8 @@
language: objective-c
sudo: false
node_js:
- "4.2"
env:
- TEST_DIR=.
- TEST_DIR=./tests/ios
script: cd $TEST_DIR && npm install && npm test

385
README.md
View File

@@ -1,3 +1,7 @@
---
title: Camera
description: Take pictures with the device camera.
---
<!---
# license: Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
@@ -17,6 +21,10 @@
# under the License.
-->
|Android|iOS| Windows 8.1 Store | Windows 8.1 Phone | Windows 10 Store | Travis CI |
|:-:|:-:|:-:|:-:|:-:|:-:|
|[![Build Status](http://cordova-ci.cloudapp.net:8080/buildStatus/icon?job=cordova-periodic-build/PLATFORM=android,PLUGIN=cordova-plugin-camera)](http://cordova-ci.cloudapp.net:8080/job/cordova-periodic-build/PLATFORM=android,PLUGIN=cordova-plugin-camera/)|[![Build Status](http://cordova-ci.cloudapp.net:8080/buildStatus/icon?job=cordova-periodic-build/PLATFORM=ios,PLUGIN=cordova-plugin-camera)](http://cordova-ci.cloudapp.net:8080/job/cordova-periodic-build/PLATFORM=ios,PLUGIN=cordova-plugin-camera/)|[![Build Status](http://cordova-ci.cloudapp.net:8080/buildStatus/icon?job=cordova-periodic-build/PLATFORM=windows-8.1-store,PLUGIN=cordova-plugin-camera)](http://cordova-ci.cloudapp.net:8080/job/cordova-periodic-build/PLATFORM=windows-8.1-store,PLUGIN=cordova-plugin-camera/)|[![Build Status](http://cordova-ci.cloudapp.net:8080/buildStatus/icon?job=cordova-periodic-build/PLATFORM=windows-8.1-phone,PLUGIN=cordova-plugin-camera)](http://cordova-ci.cloudapp.net:8080/job/cordova-periodic-build/PLATFORM=windows-8.1-phone,PLUGIN=cordova-plugin-camera/)|[![Build Status](http://cordova-ci.cloudapp.net:8080/buildStatus/icon?job=cordova-periodic-build/PLATFORM=windows-10-store,PLUGIN=cordova-plugin-camera)](http://cordova-ci.cloudapp.net:8080/job/cordova-periodic-build/PLATFORM=windows-10-store,PLUGIN=cordova-plugin-camera/)|[![Build Status](https://travis-ci.org/apache/cordova-plugin-camera.svg?branch=master)](https://travis-ci.org/apache/cordova-plugin-camera)
# cordova-plugin-camera
This plugin defines a global `navigator.camera` object, which provides an API for taking pictures and for choosing images from
@@ -65,26 +73,47 @@ In order for your changes to be accepted, you need to sign and submit an Apache
Documentation consists of template and API docs produced from the plugin JS code and should be regenerated before each commit (done automatically via [husky](https://github.com/typicode/husky), running `npm run gen-docs` script as a `precommit` hook - see `package.json` for details).
### iOS Quirks
Since iOS 10 it's mandatory to add a `NSCameraUsageDescription` and `NSPhotoLibraryUsageDescription` in the info.plist.
- `NSCameraUsageDescription` describes the reason that the app accesses the users camera.
- `NSPhotoLibraryUsageDescription` describes the reason the app accesses the user's photo library.
When the system prompts the user to allow access, this string is displayed as part of the dialog box.
To add this entry you can pass the following variables on plugin install.
- `CAMERA_USAGE_DESCRIPTION` for `NSCameraUsageDescription`
- `PHOTOLIBRARY_USAGE_DESCRIPTION` for `NSPhotoLibraryUsageDescription`
Example:
cordova plugin add cordova-plugin-camera --variable CAMERA_USAGE_DESCRIPTION="your usage message" --variable PHOTOLIBRARY_USAGE_DESCRIPTION="your usage message"
If you don't pass the variable, the plugin will add an empty string as value.
---
# API Reference
# API Reference <a name="reference"></a>
* [camera](#module_camera)
* [.getPicture(successCallback, errorCallback, options)](#module_camera.getPicture)
* [.cleanup()](#module_camera.cleanup)
* [.onError](#module_camera.onError) : <code>function</code>
* [.onSuccess](#module_camera.onSuccess) : <code>function</code>
* [.CameraOptions](#module_camera.CameraOptions) : <code>Object</code>
* [.getPicture(successCallback, errorCallback, options)](#module_camera.getPicture)
* [.cleanup()](#module_camera.cleanup)
* [.onError](#module_camera.onError) : <code>function</code>
* [.onSuccess](#module_camera.onSuccess) : <code>function</code>
* [.CameraOptions](#module_camera.CameraOptions) : <code>Object</code>
* [Camera](#module_Camera)
* [.DestinationType](#module_Camera.DestinationType) : <code>enum</code>
* [.EncodingType](#module_Camera.EncodingType) : <code>enum</code>
* [.MediaType](#module_Camera.MediaType) : <code>enum</code>
* [.PictureSourceType](#module_Camera.PictureSourceType) : <code>enum</code>
* [.PopoverArrowDirection](#module_Camera.PopoverArrowDirection) : <code>enum</code>
* [.Direction](#module_Camera.Direction) : <code>enum</code>
* [.DestinationType](#module_Camera.DestinationType) : <code>enum</code>
* [.EncodingType](#module_Camera.EncodingType) : <code>enum</code>
* [.MediaType](#module_Camera.MediaType) : <code>enum</code>
* [.PictureSourceType](#module_Camera.PictureSourceType) : <code>enum</code>
* [.PopoverArrowDirection](#module_Camera.PopoverArrowDirection) : <code>enum</code>
* [.Direction](#module_Camera.Direction) : <code>enum</code>
* [CameraPopoverHandle](#module_CameraPopoverHandle)
* [CameraPopoverOptions](#module_CameraPopoverOptions)
@@ -92,40 +121,36 @@ Documentation consists of template and API docs produced from the plugin JS code
---
<a name="module_camera"></a>
## camera
<a name="module_camera.getPicture"></a>
### camera.getPicture(successCallback, errorCallback, options)
Takes a photo using the camera, or retrieves a photo from the device's
image gallery. The image is passed to the success callback as a
base64-encoded `String`, or as the URI for the image file.
Base64-encoded `String`, or as the URI for the image file.
The `camera.getPicture` function opens the device's default camera
application that allows users to snap pictures by default - this behavior occurs,
when `Camera.sourceType` equals [`Camera.PictureSourceType.CAMERA`](#module_Camera.PictureSourceType).
when `Camera.sourceType` equals [`Camera.PictureSourceType.CAMERA`](#module_Camera.PictureSourceType).
Once the user snaps the photo, the camera application closes and the application is restored.
If `Camera.sourceType` is `Camera.PictureSourceType.PHOTOLIBRARY` or
`Camera.PictureSourceType.SAVEDPHOTOALBUM`, then a dialog displays
that allows users to select an existing image. The
`camera.getPicture` function returns a [`CameraPopoverHandle`](#module_CameraPopoverHandle) object,
which can be used to reposition the image selection dialog, for
example, when the device orientation changes.
that allows users to select an existing image.
The return value is sent to the [`cameraSuccess`](#module_camera.onSuccess) callback function, in
one of the following formats, depending on the specified
`cameraOptions`:
- A `String` containing the base64-encoded photo image.
- A `String` containing the Base64-encoded photo image.
- A `String` representing the image file location on local storage (default).
You can do whatever you want with the encoded image or URI, for
example:
- Render the image in an `<img>` tag, as in the example below
- Save the data locally (`LocalStorage`, [Lawnchair](http://brianleroux.github.com/lawnchair/), etc.)
- Post the data to a remote server
__NOTE__: Photo resolution on newer devices is quite good. Photos
@@ -136,11 +161,17 @@ than `DATA_URL`.
__Supported Platforms__
![](doc/img/android-success.png) ![](doc/img/blackberry-success.png) ![](doc/img/browser-success.png) ![](doc/img/firefox-success.png) ![](doc/img/fireos-success.png) ![](doc/img/ios-success.png) ![](doc/img/windows-success.png) ![](doc/img/wp8-success.png) ![](doc/img/ubuntu-success.png)
- Android
- BlackBerry
- Browser
- Firefox
- FireOS
- iOS
- Windows
- WP8
- Ubuntu
* [More examples](#camera-getPicture-examples)
* [Quirks](#camera-getPicture-quirks)
More examples [here](#camera-getPicture-examples). Quirks [here](#camera-getPicture-quirks).
**Kind**: static method of <code>[camera](#module_camera)</code>
@@ -155,6 +186,7 @@ __Supported Platforms__
navigator.camera.getPicture(cameraSuccess, cameraError, cameraOptions);
```
<a name="module_camera.cleanup"></a>
### camera.cleanup()
Removes intermediate image files that are kept in temporary storage
after calling [`camera.getPicture`](#module_camera.getPicture). Applies only when the value of
@@ -163,7 +195,7 @@ after calling [`camera.getPicture`](#module_camera.getPicture). Applies only whe
__Supported Platforms__
![](doc/img/android-fail.png) ![](doc/img/blackberry-fail.png) ![](doc/img/browser-fail.png) ![](doc/img/firefox-fail.png) ![](doc/img/fireos-fail.png) ![](doc/img/ios-success.png) ![](doc/img/windows-fail.png) ![](doc/img/wp8-fail.png) ![](doc/img/ubuntu-fail.png)
- iOS
**Kind**: static method of <code>[camera](#module_camera)</code>
**Example**
@@ -179,6 +211,7 @@ function onFail(message) {
}
```
<a name="module_camera.onError"></a>
### camera.onError : <code>function</code>
Callback function that provides an error message.
@@ -189,6 +222,7 @@ Callback function that provides an error message.
| message | <code>string</code> | The message is provided by the device's native code. |
<a name="module_camera.onSuccess"></a>
### camera.onSuccess : <code>function</code>
Callback function that provides the image data.
@@ -208,6 +242,7 @@ function cameraCallback(imageData) {
}
```
<a name="module_camera.CameraOptions"></a>
### camera.CameraOptions : <code>Object</code>
Optional parameters to customize the camera settings.
* [Quirks](#CameraOptions-quirks)
@@ -233,19 +268,28 @@ Optional parameters to customize the camera settings.
---
<a name="module_Camera"></a>
## Camera
<a name="module_Camera.DestinationType"></a>
### Camera.DestinationType : <code>enum</code>
Defines the output format of `Camera.getPicture` call.
_Note:_ On iOS passing `DestinationType.NATIVE_URI` along with
`PictureSourceType.PHOTOLIBRARY` or `PictureSourceType.SAVEDPHOTOALBUM` will
disable any image modifications (resize, quality change, cropping, etc.) due
to implementation specific.
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
**Properties**
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| DATA_URL | <code>number</code> | <code>0</code> | Return base64 encoded string |
| DATA_URL | <code>number</code> | <code>0</code> | Return base64 encoded string. DATA_URL can be very memory intensive and cause app crashes or out of memory errors. Use FILE_URI or NATIVE_URI if possible |
| FILE_URI | <code>number</code> | <code>1</code> | Return file uri (content://media/external/images/media/2 for Android) |
| NATIVE_URI | <code>number</code> | <code>2</code> | Return native uri (eg. asset-library://... for iOS) |
<a name="module_Camera.EncodingType"></a>
### Camera.EncodingType : <code>enum</code>
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
**Properties**
@@ -256,6 +300,7 @@ Optional parameters to customize the camera settings.
| PNG | <code>number</code> | <code>1</code> | Return PNG encoded image |
<a name="module_Camera.MediaType"></a>
### Camera.MediaType : <code>enum</code>
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
**Properties**
@@ -267,17 +312,24 @@ Optional parameters to customize the camera settings.
| ALLMEDIA | <code>number</code> | <code>2</code> | Allow selection from all media types |
<a name="module_Camera.PictureSourceType"></a>
### Camera.PictureSourceType : <code>enum</code>
Defines the output format of `Camera.getPicture` call.
_Note:_ On iOS passing `PictureSourceType.PHOTOLIBRARY` or `PictureSourceType.SAVEDPHOTOALBUM`
along with `DestinationType.NATIVE_URI` will disable any image modifications (resize, quality
change, cropping, etc.) due to implementation specific.
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
**Properties**
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| PHOTOLIBRARY | <code>number</code> | <code>0</code> | Choose image from picture library (same as SAVEDPHOTOALBUM for Android) |
| PHOTOLIBRARY | <code>number</code> | <code>0</code> | Choose image from the device's photo library (same as SAVEDPHOTOALBUM for Android) |
| CAMERA | <code>number</code> | <code>1</code> | Take picture from camera |
| SAVEDPHOTOALBUM | <code>number</code> | <code>2</code> | Choose image from picture library (same as PHOTOLIBRARY for Android) |
| SAVEDPHOTOALBUM | <code>number</code> | <code>2</code> | Choose image only from the device's Camera Roll album (same as PHOTOLIBRARY for Android) |
<a name="module_Camera.PopoverArrowDirection"></a>
### Camera.PopoverArrowDirection : <code>enum</code>
Matches iOS UIPopoverArrowDirection constants to specify arrow location on popover.
@@ -293,6 +345,7 @@ Matches iOS UIPopoverArrowDirection constants to specify arrow location on popov
| ARROW_ANY | <code>number</code> | <code>15</code> |
<a name="module_Camera.Direction"></a>
### Camera.Direction : <code>enum</code>
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
**Properties**
@@ -305,6 +358,7 @@ Matches iOS UIPopoverArrowDirection constants to specify arrow location on popov
---
<a name="module_CameraPopoverOptions"></a>
## CameraPopoverOptions
iOS-only parameters that specify the anchor element location and arrow
direction of the popover when selecting images from an iPad's library
@@ -326,17 +380,18 @@ location.
---
<a name="module_CameraPopoverHandle"></a>
## CameraPopoverHandle
A handle to an image picker popover.
__Supported Platforms__
![](doc/img/android-fail.png) ![](doc/img/blackberry-fail.png) ![](doc/img/browser-fail.png) ![](doc/img/firefox-fail.png) ![](doc/img/fireos-fail.png) ![](doc/img/ios-success.png) ![](doc/img/windows-fail.png) ![](doc/img/wp8-fail.png) ![](doc/img/ubuntu-fail.png)
- iOS
**Example**
```js
var cameraPopoverHandle = navigator.camera.getPicture(onSuccess, onFail,
{
navigator.camera.getPicture(onSuccess, onFail,
{
destinationType: Camera.DestinationType.FILE_URI,
sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
popoverOptions: new CameraPopoverOptions(300, 300, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY)
@@ -344,6 +399,7 @@ var cameraPopoverHandle = navigator.camera.getPicture(onSuccess, onFail,
// Reposition the popover if the orientation changes.
window.onorientationchange = function() {
var cameraPopoverHandle = new CameraPopoverHandle();
var cameraPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY);
cameraPopoverHandle.setPosition(cameraPopoverOptions);
}
@@ -355,21 +411,6 @@ window.onorientationchange = function() {
#### Example <a name="camera-getPicture-examples"></a>
Take a photo and retrieve it as a base64-encoded image:
navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
destinationType: Camera.DestinationType.DATA_URL
});
function onSuccess(imageData) {
var image = document.getElementById('myImage');
image.src = "data:image/jpeg;base64," + imageData;
}
function onFail(message) {
alert('Failed because: ' + message);
}
Take a photo and retrieve the image's file location:
navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
@@ -384,6 +425,27 @@ Take a photo and retrieve the image's file location:
alert('Failed because: ' + message);
}
Take a photo and retrieve it as a Base64-encoded image:
/**
* Warning: Using DATA_URL is not recommended! The DATA_URL destination
* type is very memory intensive, even with a low quality setting. Using it
* can result in out of memory errors and application crashes. Use FILE_URI
* or NATIVE_URI instead.
*/
navigator.camera.getPicture(onSuccess, onFail, { quality: 25,
destinationType: Camera.DestinationType.DATA_URL
});
function onSuccess(imageData) {
var image = document.getElementById('myImage');
image.src = "data:image/jpeg;base64," + imageData;
}
function onFail(message) {
alert('Failed because: ' + message);
}
#### Preferences (iOS)
- __CameraUsesGeolocation__ (boolean, defaults to false). For capturing JPEGs, set to true to get geolocation data in the EXIF header. This will trigger a request for geolocation permissions if set to true.
@@ -394,14 +456,14 @@ Take a photo and retrieve the image's file location:
Amazon Fire OS uses intents to launch the camera activity on the device to capture
images, and on phones with low memory, the Cordova activity may be killed. In this
scenario, the image may not appear when the cordova activity is restored.
scenario, the image may not appear when the Cordova activity is restored.
#### Android Quirks
Android uses intents to launch the camera activity on the device to capture
images, and on phones with low memory, the Cordova activity may be killed. In this
scenario, the result from the plugin call will be delivered via the resume event.
See [the Android Lifecycle guide](http://cordova.apache.org/docs/en/dev/guide/platforms/android/lifecycle.html)
See [the Android Lifecycle guide][android_lifecycle]
for more information. The `pendingResult.result` value will contain the value that
would be passed to the callbacks (either the URI/URL or an error message). Check
the `pendingResult.pluginStatus` to determine whether or not the call was
@@ -409,11 +471,11 @@ successful.
#### Browser Quirks
Can only return photos as base64-encoded image.
Can only return photos as Base64-encoded image.
#### Firefox OS Quirks
Camera plugin is currently implemented using [Web Activities](https://hacks.mozilla.org/2013/01/introducing-web-activities/).
Camera plugin is currently implemented using [Web Activities][web_activities].
#### iOS Quirks
@@ -431,6 +493,16 @@ displays:
Invoking the native camera application while the device is connected
via Zune does not work, and triggers an error callback.
#### Windows quirks
On Windows Phone 8.1 using `SAVEDPHOTOALBUM` or `PHOTOLIBRARY` as a source type causes application to suspend until file picker returns the selected image and
then restore with start page as defined in app's `config.xml`. In case when `camera.getPicture` was called from different page, this will lead to reloading
start page from scratch and success and error callbacks will never be called.
To avoid this we suggest using SPA pattern or call `camera.getPicture` only from your app's start page.
More information about Windows Phone 8.1 picker APIs is here: [How to continue your Windows Phone app after calling a file picker](https://msdn.microsoft.com/en-us/library/windows/apps/dn720490.aspx)
#### Tizen Quirks
Tizen only supports a `destinationType` of
@@ -496,6 +568,8 @@ Tizen only supports a `destinationType` of
- When using `destinationType.NATIVE_URI` and `sourceType.CAMERA`, photos are saved in the saved photo album regardless on the value of `saveToPhotoAlbum` parameter.
- When using `destinationType.NATIVE_URI` and `sourceType.PHOTOLIBRARY` or `sourceType.SAVEDPHOTOALBUM`, all editing options are ignored and link is returned to original picture.
#### Tizen Quirks
- options not supported
@@ -510,7 +584,210 @@ Tizen only supports a `destinationType` of
- Ignores the `cameraDirection` parameter.
- Ignores the `saveToPhotoAlbum` parameter. IMPORTANT: All images taken with the wp7/8 cordova camera API are always copied to the phone's camera roll. Depending on the user's settings, this could also mean the image is auto-uploaded to their OneDrive. This could potentially mean the image is available to a wider audience than your app intended. If this a blocker for your application, you will need to implement the CameraCaptureTask as documented on msdn : [http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh394006.aspx](http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh394006.aspx)
You may also comment or up-vote the related issue in the [issue tracker](https://issues.apache.org/jira/browse/CB-2083)
- Ignores the `saveToPhotoAlbum` parameter. IMPORTANT: All images taken with the WP8/8 Cordova camera API are always copied to the phone's camera roll. Depending on the user's settings, this could also mean the image is auto-uploaded to their OneDrive. This could potentially mean the image is available to a wider audience than your app intended. If this is a blocker for your application, you will need to implement the CameraCaptureTask as [documented on MSDN][msdn_wp8_docs]. You may also comment or up-vote the related issue in the [issue tracker][wp8_bug].
- Ignores the `mediaType` property of `cameraOptions` as the Windows Phone SDK does not provide a way to choose videos from PHOTOLIBRARY.
[android_lifecycle]: http://cordova.apache.org/docs/en/dev/guide/platforms/android/lifecycle.html
[web_activities]: https://hacks.mozilla.org/2013/01/introducing-web-activities/
[wp8_bug]: https://issues.apache.org/jira/browse/CB-2083
[msdn_wp8_docs]: http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh394006.aspx
## Sample: Take Pictures, Select Pictures from the Picture Library, and Get Thumbnails <a name="sample"></a>
The Camera plugin allows you to do things like open the device's Camera app and take a picture, or open the file picker and select one. The code snippets in this section demonstrate different tasks including:
* Open the Camera app and [take a Picture](#takePicture)
* Take a picture and [return thumbnails](#getThumbnails) (resized picture)
* Take a picture and [generate a FileEntry object](#convert)
* [Select a file](#selectFile) from the picture library
* Select a JPEG image and [return thumbnails](#getFileThumbnails) (resized image)
* Select an image and [generate a FileEntry object](#convert)
## Take a Picture <a name="takePicture"></a>
Before you can take a picture, you need to set some Camera plugin options to pass into the Camera plugin's `getPicture` function. Here is a common set of recommendations. In this example, you create the object that you will use for the Camera options, and set the `sourceType` dynamically to support both the Camera app and the file picker.
```js
function setOptions(srcType) {
var options = {
// Some common settings are 20, 50, and 100
quality: 50,
destinationType: Camera.DestinationType.FILE_URI,
// In this app, dynamically set the picture source, Camera or photo gallery
sourceType: srcType,
encodingType: Camera.EncodingType.JPEG,
mediaType: Camera.MediaType.PICTURE,
allowEdit: true,
correctOrientation: true //Corrects Android orientation quirks
}
return options;
}
```
Typically, you want to use a FILE_URI instead of a DATA_URL to avoid most memory issues. JPEG is the recommended encoding type for Android.
You take a picture by passing in the options object to `getPicture`, which takes a CameraOptions object as the third argument. When you call `setOptions`, pass `Camera.PictureSourceType.CAMERA` as the picture source.
```js
function openCamera(selection) {
var srcType = Camera.PictureSourceType.CAMERA;
var options = setOptions(srcType);
var func = createNewFileEntry;
navigator.camera.getPicture(function cameraSuccess(imageUri) {
displayImage(imageUri);
// You may choose to copy the picture, save it somewhere, or upload.
func(imageUri);
}, function cameraError(error) {
console.debug("Unable to obtain picture: " + error, "app");
}, options);
}
```
Once you take the picture, you can display it or do something else. In this example, call the app's `displayImage` function from the preceding code.
```js
function displayImage(imgUri) {
var elem = document.getElementById('imageFile');
elem.src = imgUri;
}
```
To display the image on some platforms, you might need to include the main part of the URI in the Content-Security-Policy `<meta>` element in index.html. For example, on Windows 10, you can include `ms-appdata:` in your `<meta>` element. Here is an example.
```html
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: ms-appdata: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *">
```
## Take a Picture and Return Thumbnails (Resize the Picture) <a name="getThumbnails"></a>
To get smaller images, you can return a resized image by passing both `targetHeight` and `targetWidth` values with your CameraOptions object. In this example, you resize the returned image to fit in a 100px by 100px box (the aspect ratio is maintained, so 100px is either the height or width, whichever is greater in the source).
```js
function openCamera(selection) {
var srcType = Camera.PictureSourceType.CAMERA;
var options = setOptions(srcType);
var func = createNewFileEntry;
if (selection == "camera-thmb") {
options.targetHeight = 100;
options.targetWidth = 100;
}
navigator.camera.getPicture(function cameraSuccess(imageUri) {
// Do something
}, function cameraError(error) {
console.debug("Unable to obtain picture: " + error, "app");
}, options);
}
```
## Select a File from the Picture Library <a name="selectFile"></a>
When selecting a file using the file picker, you also need to set the CameraOptions object. In this example, set the `sourceType` to `Camera.PictureSourceType.SAVEDPHOTOALBUM`. To open the file picker, call `getPicture` just as you did in the previous example, passing in the success and error callbacks along with CameraOptions object.
```js
function openFilePicker(selection) {
var srcType = Camera.PictureSourceType.SAVEDPHOTOALBUM;
var options = setOptions(srcType);
var func = createNewFileEntry;
navigator.camera.getPicture(function cameraSuccess(imageUri) {
// Do something
}, function cameraError(error) {
console.debug("Unable to obtain picture: " + error, "app");
}, options);
}
```
## Select an Image and Return Thumbnails (resized images) <a name="getFileThumbnails"></a>
Resizing a file selected with the file picker works just like resizing using the Camera app; set the `targetHeight` and `targetWidth` options.
```js
function openFilePicker(selection) {
var srcType = Camera.PictureSourceType.SAVEDPHOTOALBUM;
var options = setOptions(srcType);
var func = createNewFileEntry;
if (selection == "picker-thmb") {
// To downscale a selected image,
// Camera.EncodingType (e.g., JPEG) must match the selected image type.
options.targetHeight = 100;
options.targetWidth = 100;
}
navigator.camera.getPicture(function cameraSuccess(imageUri) {
// Do something with image
}, function cameraError(error) {
console.debug("Unable to obtain picture: " + error, "app");
}, options);
}
```
## Take a picture and get a FileEntry Object <a name="convert"></a>
If you want to do something like copy the image to another location, or upload it somewhere using the FileTransfer plugin, you need to get a FileEntry object for the returned picture. To do that, call `window.resolveLocalFileSystemURL` on the file URI returned by the Camera app. If you need to use a FileEntry object, set the `destinationType` to `Camera.DestinationType.FILE_URI` in your CameraOptions object (this is also the default value).
>*Note* You need the [File plugin](https://www.npmjs.com/package/cordova-plugin-file) to call `window.resolveLocalFileSystemURL`.
Here is the call to `window.resolveLocalFileSystemURL`. The image URI is passed to this function from the success callback of `getPicture`. The success handler of `resolveLocalFileSystemURL` receives the FileEntry object.
```js
function getFileEntry(imgUri) {
window.resolveLocalFileSystemURL(imgUri, function success(fileEntry) {
// Do something with the FileEntry object, like write to it, upload it, etc.
// writeFile(fileEntry, imgUri);
console.log("got file: " + fileEntry.fullPath);
// displayFileData(fileEntry.nativeURL, "Native URL");
}, function () {
// If don't get the FileEntry (which may happen when testing
// on some emulators), copy to a new FileEntry.
createNewFileEntry(imgUri);
});
}
```
In the example shown in the preceding code, you call the app's `createNewFileEntry` function if you don't get a valid FileEntry object. The image URI returned from the Camera app should result in a valid FileEntry, but platform behavior on some emulators may be different for files returned from the file picker.
>*Note* To see an example of writing to a FileEntry, see the [File plugin README](https://www.npmjs.com/package/cordova-plugin-file).
The code shown here creates a file in your app's cache (in sandboxed storage) named `tempFile.jpeg`. With the new FileEntry object, you can copy the image to the file or do something else like upload it.
```js
function createNewFileEntry(imgUri) {
window.resolveLocalFileSystemURL(cordova.file.cacheDirectory, function success(dirEntry) {
// JPEG file
dirEntry.getFile("tempFile.jpeg", { create: true, exclusive: false }, function (fileEntry) {
// Do something with it, like write to it, upload it, etc.
// writeFile(fileEntry, imgUri);
console.log("got file: " + fileEntry.fullPath);
// displayFileData(fileEntry.fullPath, "File copied to");
}, onErrorCreateFile);
}, onErrorResolveUrl);
}
```

View File

@@ -20,6 +20,117 @@
-->
# Release Notes
### 2.4.0 (Feb 28, 2017)
* [CB-12501](https://issues.apache.org/jira/browse/CB-12501) **Android**: Appium tests don't use `XPath` selectors anymore
* [CB-12469](https://issues.apache.org/jira/browse/CB-12469) Appium tests can now run on **iOS 10**
* [CB-12005](https://issues.apache.org/jira/browse/CB-12005) Changing the `getOrientation` method to return the defined enumerated `EXIF` instead of orientation in degrees for Consistency
* [CB-12368](https://issues.apache.org/jira/browse/CB-12368) Fix permission check on **Android**
* [CB-12353](https://issues.apache.org/jira/browse/CB-12353) Corrected merges usage in `plugin.xml`
* [CB-12369](https://issues.apache.org/jira/browse/CB-12369) Add plugin typings from `DefinitelyTyped`
* [CB-12363](https://issues.apache.org/jira/browse/CB-12363) Added build badges for **iOS 9.3** and **iOS 10.0**
* [CB-12312](https://issues.apache.org/jira/browse/CB-12312) [Appium] [Android] A few changes to the tests: - updated comments on how to run the tests. extra comments around functionality at certain points in the automation. - stub of a resolution checker on test startup - still need to figure out acceptable values. - moved session shutdown to an afterAll clause. - changed resolution determiner from using webview-based values to using the native windows dimensions - this helps as the webview values may be scaled down intentionally by manufacturers (via changing devicePixelRatio). furthermore, since the screen dimension automation is used purely for native UI automation, better to use the dimensions reported by the native context rather than the web context. - when finding elements by XPath, use multiple calls to avoid a Windows emulator + Android bug. Made this pattern consistent in the entire test.
* [CB-12236](https://issues.apache.org/jira/browse/CB-12236) - Fixed RELEASENOTES for cordova-plugin-camera
* [CB-12230](https://issues.apache.org/jira/browse/CB-12230) Removed Windows 8.1 build badges
### 2.3.1 (Dec 07, 2016)
* [CB-12224](https://issues.apache.org/jira/browse/CB-12224) Updated version and RELEASENOTES.md for release 2.3.1
* Fix missing license headers.
* [CB-12086](https://issues.apache.org/jira/browse/CB-12086) Regenerate README.md from template
* Added NSPhotoLibraryUsageDescription parameter to example install command Fixing some usages of NSPhotoLibraryUsageDescriptionentry
* Updating compat dependency to 1.1.0 or better
* [CB-11625](https://issues.apache.org/jira/browse/CB-11625) Forgot to add CordovaUri.java to plugin.xml
* [CB-11625](https://issues.apache.org/jira/browse/CB-11625) Files Provider does not work with Android 4.4.4 or lower, and I have no idea why. Working around with CordovaUri
* [CB-11625](https://issues.apache.org/jira/browse/CB-11625) (Android) : Make this work with previous versions of Cordova via cordova-plugin-compat
* BuildConfig from test project crept in source code thanks to Android Studio, removing
* [CB-11625](https://issues.apache.org/jira/browse/CB-11625) Managed to get Content Providers to work with a weird mix of Content Providers and non-Content Providers
* [CB-11625](https://issues.apache.org/jira/browse/CB-11625) Working on fix to API 24 no longer allowing File URIs to be passed across intents
* [CB-11917](https://issues.apache.org/jira/browse/CB-11917) - Remove pull request template checklist item: "iCLA has been submitted…"
* [CB-11832](https://issues.apache.org/jira/browse/CB-11832) Incremented plugin version.
### 2.3.0 (Sep 08, 2016)
* [CB-11795](https://issues.apache.org/jira/browse/CB-11795) Add 'protective' entry to cordovaDependencies
* [CB-11661](https://issues.apache.org/jira/browse/CB-11661) Add mandatory **iOS 10** privacy description
* [CB-11714](https://issues.apache.org/jira/browse/CB-11714) **windows** added more explicit content-type when converting to target data on canvas
* [CB-11295](https://issues.apache.org/jira/browse/CB-11295) Add **WP8.1** quirk when choosing image from `photoalbum`
* [CB-10067](https://issues.apache.org/jira/browse/CB-10067) Update `PictureSourceType` JSDoc to reflect `README` update
* [CB-9070](https://issues.apache.org/jira/browse/CB-9070) Update `CameraPopoverHandle` docs to reflect `README` update
* Plugin uses `Android Log class` and not `Cordova LOG class`
* [CB-11631](https://issues.apache.org/jira/browse/CB-11631) Appium tests: A working fix for a flaky `selection canceled` failure
* [CB-11709](https://issues.apache.org/jira/browse/CB-11709) Tests should use `resolveLocalFileSystemURL()` instead of deprecated `resolveFileSystemURI()`
* [CB-11695](https://issues.apache.org/jira/browse/CB-11695) Increased session creation timeout for Appium tests
* [CB-11656](https://issues.apache.org/jira/browse/CB-11656) (**Android**) Appium tests: Fixed side menu opening on some more resolutions
* [CB-11376](https://issues.apache.org/jira/browse/CB-11376) (**ios**): fix `CameraUsesGeolocation` error
* [CB-10067](https://issues.apache.org/jira/browse/CB-10067) (**ios**) clarifications on `PictureSourceType`
* [CB-11410](https://issues.apache.org/jira/browse/CB-11410) (**ios**) fix `cameraPopoverHandle.setPosition`
* [CB-9070](https://issues.apache.org/jira/browse/CB-9070) (**ios**) Fixed `CameraPopoverHandle` documentation
* [CB-11447](https://issues.apache.org/jira/browse/CB-11447) Respect output format when retrieving images from gallery
* [CB-11447](https://issues.apache.org/jira/browse/CB-11447) Resolve **iOS** tests failures due to **iOS** quirks
* [CB-11553](https://issues.apache.org/jira/browse/CB-11553) Pend failing Appium tests on Sauce Labs for the time being (reverted from commit b69571724035f41642f3ee612c5b66e1f0c4386c)
* [CB-11553](https://issues.apache.org/jira/browse/CB-11553) Pend failing Appium tests on Sauce Labs for the time being
* [CB-11498](https://issues.apache.org/jira/browse/CB-11498) [**Android**] Appium tests should not fail when there is no camera
* Add badges for paramedic builds on Jenkins
* [CB-11296](https://issues.apache.org/jira/browse/CB-11296) Appium: Better element clicking and session error handling
* [CB-11232](https://issues.apache.org/jira/browse/CB-11232) Appium tests: fixed element tapping on **iOS 9**
* [CB-11183](https://issues.apache.org/jira/browse/CB-11183) Appium tests: Added image verification
* fixed some bad formatting that hid `HTML` tags and added link to sample
* Set **android** quality default value to 50 on the java code
* Moving message in PR template to a comment
* Add pull request template. This closes #213
* [CB-11228](https://issues.apache.org/jira/browse/CB-11228) **browser**: Add classes for styling purposes
* [CB-10139](https://issues.apache.org/jira/browse/CB-10139) **browser**: Respect target width and height
* [CB-11227](https://issues.apache.org/jira/browse/CB-11227) **browser**: Fix incorrect `mime type`
* [CB-11162](https://issues.apache.org/jira/browse/CB-11162) Appium tests: retry spec on failure
* [CB-4078](https://issues.apache.org/jira/browse/CB-4078) Fix for `orientation/scaling` on **Android 4.4+** devices
* [CB-11165](https://issues.apache.org/jira/browse/CB-11165) removed peer dependency
* [CB-11147](https://issues.apache.org/jira/browse/CB-11147) Appium tests: generate descriptive spec names
* [CB-10996](https://issues.apache.org/jira/browse/CB-10996) Adding front matter to `README.md`
* [CB-11128](https://issues.apache.org/jira/browse/CB-11128) Appum tests: Fixed some of the flaky failures
* [CB-11003](https://issues.apache.org/jira/browse/CB-11003) Added Sample section to the Camera plugin README
### 2.2.0 (Apr 15, 2016)
* [CB-10873](https://issues.apache.org/jira/browse/CB-10873) Avoid crash due to usage of uninitialized variable when writing geolocation data to image destination. Properly handle 'CameraUsesGeolocation' option by properly setting geolocation data in EXIF header in all cases
* [CB-11073](https://issues.apache.org/jira/browse/CB-11073) Appium tests stability improvements
* Replace `PermissionHelper.java` with `cordova-plugin-compat`
* Making focus handler work only for **windows 10** phone
* [CB-10865](https://issues.apache.org/jira/browse/CB-10865) Run **ios** native tests on **Travis**
* [CB-10120](https://issues.apache.org/jira/browse/CB-10120) Fixing use of constants and `PermissionHelper`
* [CB-10120](https://issues.apache.org/jira/browse/CB-10120) Fix missing CAMERA permission for **Android M**
* [CB-10756](https://issues.apache.org/jira/browse/CB-10756) Adding sterner warnings about `DATA_URL`
* [CB-10460](https://issues.apache.org/jira/browse/CB-10460) `getRealPath` return null in some cases
### 2.1.1 (Mar 09, 2016)
* [CB-10825](https://issues.apache.org/jira/browse/CB-10825) **Android** should request READ permission for gallery source
* added apache license header to appium files
* [CB-10720](https://issues.apache.org/jira/browse/CB-10720) Fixed spelling, capitalization, and other small issues.
* [CB-10414](https://issues.apache.org/jira/browse/CB-10414) Adding focus handler to resume video when user comes back on leaving the app while preview was running
* Appium tests: adjust swipe distance on **Android**
* [CB-10750](https://issues.apache.org/jira/browse/CB-10750) Appium tests: fail fast if session is irrecoverable
* Adding missing semi colon
* Adding focus handler to make sure filepicker gets launched when app is active on **Windows**
* [CB-10128](https://issues.apache.org/jira/browse/CB-10128) **iOS** Fixed how checks access authorization to camera & library. This closes #146
* [CB-10636](https://issues.apache.org/jira/browse/CB-10636) Add JSHint for plugins
* [CB-10639](https://issues.apache.org/jira/browse/CB-10639) Appium tests: Added some timeouts, Taking a screenshot on failure, Retry taking a picture up to 3 times, Try to restart the Appium session if it's lost
* [CB-10552](https://issues.apache.org/jira/browse/CB-10552) Replacing images in README.md.
* Added a lot of more cases to get the real path on **Android**
* [CB-10625](https://issues.apache.org/jira/browse/CB-10625) **Android** getPicture fails when getting a photo from the Photo Library - Google Photos
* [CB-10619](https://issues.apache.org/jira/browse/CB-10619) Appium tests: Properly switch to webview on **Android**
* [CB-10397](https://issues.apache.org/jira/browse/CB-10397) Added Appium tests
* [CB-10576](https://issues.apache.org/jira/browse/CB-10576) MobileSpec can't get results for **Windows**-Store 8.1 Builds
* chore: edit package.json license to match SPDX id
* [CB-10539](https://issues.apache.org/jira/browse/CB-10539) Commenting out the verySmallQvga maxResolution option on **Windows**
* [CB-10541](https://issues.apache.org/jira/browse/CB-10541) Changing default maxResoltion to be highestAvailable for CameraCaptureUI on **Windows**
* [CB-10113](https://issues.apache.org/jira/browse/CB-10113) **Browse** - Layer camera UI on top of all!
* [CB-10502](https://issues.apache.org/jira/browse/CB-10502) **Browser** - Fix camera plugin exception in Chrome when click capture.
* Adding comments
* Camera tapping fix on **Windows**
### 2.1.0 (Jan 15, 2016)
* added `.ratignore`
* [CB-10319](https://issues.apache.org/jira/browse/CB-10319) **Android** Adding reflective helper methods for permission requests
* [CB-9189](https://issues.apache.org/jira/browse/CB-9189) **Android** Implementing `save/restore` API to handle Activity destruction
* [CB-10241](https://issues.apache.org/jira/browse/CB-10241) App Crash cause by Camera Plugin **iOS 7**
* [CB-8940](https://issues.apache.org/jira/browse/CB-8940) Setting `z-index` values to maximum for UI buttons.
### 2.0.0 (Nov 18, 2015)
* [CB-10035](https://issues.apache.org/jira/browse/CB-10035) Updated `RELEASENOTES` to be newest to oldest
* [CB-8863](https://issues.apache.org/jira/browse/CB-8863) correct block usage for `async` calls

View File

@@ -0,0 +1,628 @@
/*jshint node: true, jasmine: true */
/*
*
* 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.
*
*/
// these tests are meant to be executed by Cordova ParaMedic Appium runner
// you can find it here: https://github.com/apache/cordova-paramedic/
// it is not necessary to do a full CI setup to run these tests
// Run:
// node cordova-paramedic/main.js --platform android --plugin cordova-plugin-camera --skipMainTests --target <emulator name>
// Please note only Android 5.1 and 4.4 are supported at this point.
'use strict';
var wdHelper = global.WD_HELPER;
var screenshotHelper = global.SCREENSHOT_HELPER;
var wd = wdHelper.getWD();
var cameraConstants = require('../../www/CameraConstants');
var cameraHelper = require('../helpers/cameraHelper');
var MINUTE = 60 * 1000;
var BACK_BUTTON = 4;
var DEFAULT_SCREEN_WIDTH = 360;
var DEFAULT_SCREEN_HEIGHT = 567;
var DEFAULT_WEBVIEW_CONTEXT = 'WEBVIEW';
var PROMISE_PREFIX = 'appium_camera_promise_';
var CONTEXT_NATIVE_APP = 'NATIVE_APP';
describe('Camera tests Android.', function () {
var driver;
// the name of webview context, it will be changed to match needed context if there are named ones:
var webviewContext = DEFAULT_WEBVIEW_CONTEXT;
// this indicates that the device library has the test picture:
var isTestPictureSaved = false;
// we need to know the screen width and height to properly click on an image in the gallery:
var screenWidth = DEFAULT_SCREEN_WIDTH;
var screenHeight = DEFAULT_SCREEN_HEIGHT;
// promise count to use in promise ID
var promiseCount = 0;
// determine if Appium session is created successfully
var appiumSessionStarted = false;
// determine if camera is present on the device/emulator
var cameraAvailable = false;
// determine if emulator is within a range of acceptable resolutions able to run these tests
var isResolutionBad = true;
// a path to the image we add to the gallery before test run
var fillerImagePath;
function getNextPromiseId() {
promiseCount += 1;
return getCurrentPromiseId();
}
function getCurrentPromiseId() {
return PROMISE_PREFIX + promiseCount;
}
function gracefullyFail(error) {
fail(error);
return driver
.quit()
.then(function () {
return getDriver();
});
}
// combinines specified options in all possible variations
// you can add more options to test more scenarios
function generateOptions() {
var sourceTypes = [
cameraConstants.PictureSourceType.CAMERA,
cameraConstants.PictureSourceType.PHOTOLIBRARY
];
var destinationTypes = cameraConstants.DestinationType;
var encodingTypes = cameraConstants.EncodingType;
var allowEditOptions = [ true, false ];
var correctOrientationOptions = [ true, false ];
return cameraHelper.generateSpecs(sourceTypes, destinationTypes, encodingTypes, allowEditOptions, correctOrientationOptions);
}
// invokes Camera.getPicture() with the specified options
// and goes through all UI interactions unless 'skipUiInteractions' is true
function getPicture(options, skipUiInteractions) {
var promiseId = getNextPromiseId();
if (!options) {
options = {};
}
return driver
.context(webviewContext)
.execute(cameraHelper.getPicture, [options, promiseId])
.context(CONTEXT_NATIVE_APP)
.then(function () {
if (skipUiInteractions) {
return;
}
// selecting a picture from gallery
if (options.hasOwnProperty('sourceType') &&
(options.sourceType === cameraConstants.PictureSourceType.PHOTOLIBRARY ||
options.sourceType === cameraConstants.PictureSourceType.SAVEDPHOTOALBUM)) {
var tapTile = new wd.TouchAction();
var swipeRight = new wd.TouchAction();
tapTile
.tap({
x: Math.round(screenWidth / 4),
y: Math.round(screenHeight / 4)
});
swipeRight
.press({x: 10, y: Math.round(screenHeight / 4)})
.wait(300)
.moveTo({x: Math.round(screenWidth - (screenWidth / 8)), y: 0})
.wait(1500)
.release()
.wait(1000);
if (options.allowEdit) {
return driver
// always wait before performing touchAction
.sleep(7000)
.performTouchAction(tapTile);
}
return driver
.waitForElementByAndroidUIAutomator('new UiSelector().text("Gallery");', 20000)
.fail(function () {
// If the Gallery button is not present, swipe right to reveal the Gallery button!
return driver
.performTouchAction(swipeRight)
.waitForElementByAndroidUIAutomator('new UiSelector().text("Gallery");', 20000)
})
.click()
// always wait before performing touchAction
.sleep(7000)
.performTouchAction(tapTile);
}
// taking a picture from camera
return driver
.waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*shutter.*")', MINUTE / 2)
.click()
.waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*done.*")', MINUTE / 2)
.click();
})
.then(function () {
if (skipUiInteractions) {
return;
}
if (options.allowEdit) {
return driver
.waitForElementByAndroidUIAutomator('new UiSelector().text("Save")', MINUTE)
.click();
}
})
.fail(function (failure) {
throw failure;
});
}
// checks if the picture was successfully taken
// if shouldLoad is falsy, ensures that the error callback was called
function checkPicture(shouldLoad, options) {
if (!options) {
options = {};
}
return driver
.context(webviewContext)
.setAsyncScriptTimeout(MINUTE / 2)
.executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId(), options])
.then(function (result) {
if (shouldLoad) {
if (result !== 'OK') {
fail(result);
}
} else if (result.indexOf('ERROR') === -1) {
throw 'Unexpected success callback with result: ' + result;
}
});
}
// deletes the latest image from the gallery
function deleteImage() {
var holdTile = new wd.TouchAction();
holdTile
.press({x: Math.round(screenWidth / 4), y: Math.round(screenHeight / 5)})
.wait(1000)
.release();
return driver
// always wait before performing touchAction
.sleep(7000)
.performTouchAction(holdTile)
.elementByAndroidUIAutomator('new UiSelector().text("Delete")')
.then(function (element) {
return element
.click()
.elementByAndroidUIAutomator('new UiSelector().text("OK")')
.click();
}, function () {
// couldn't find Delete menu item. Possibly there is no image.
return driver;
});
}
function getDriver() {
driver = wdHelper.getDriver('Android');
return driver.getWebviewContext()
.then(function(context) {
webviewContext = context;
return driver.context(webviewContext);
})
.waitForDeviceReady()
.injectLibraries()
.deleteFillerImage(fillerImagePath)
.then(function () {
fillerImagePath = null;
})
.addFillerImage()
.then(function (result) {
if (result && result.indexOf('ERROR:') === 0) {
throw new Error(result);
} else {
fillerImagePath = result;
}
});
}
function recreateSession() {
return driver
.quit()
.finally(function () {
return getDriver();
});
}
function tryRunSpec(spec) {
return driver
.then(spec)
.fail(function () {
return recreateSession()
.then(spec)
.fail(function() {
return recreateSession()
.then(spec);
});
})
.fail(gracefullyFail);
}
// produces a generic spec function which
// takes a picture with specified options
// and then verifies it
function generateSpec(options) {
return function () {
return driver
.then(function () {
return getPicture(options);
})
.then(function () {
return checkPicture(true, options);
});
};
}
function checkSession(done, skipResolutionCheck) {
if (!appiumSessionStarted) {
fail('Failed to start a session ' + (lastFailureReason ? lastFailureReason : ''));
done();
}
if (!skipResolutionCheck && isResolutionBad) {
fail('The resolution of this target device is not within the appropriate range of width: blah-blah and height: bleh-bleh. The target\'s current resolution is: ' + isResolutionBad);
}
}
function checkCamera(pending) {
if (!cameraAvailable) {
pending('This test requires a functioning camera on the Android device/emulator, and this test suite\'s functional camera test failed on your target environment.');
}
}
afterAll(function (done) {
checkSession(done);
driver
.quit()
.done(done);
}, MINUTE);
it('camera.ui.util configuring driver and starting a session', function (done) {
getDriver()
.then(function () {
appiumSessionStarted = true;
}, fail)
.done(done);
}, 10 * MINUTE);
it('camera.ui.util determine screen dimensions', function (done) {
checkSession(done, /*skipResolutionCheck?*/ true); // skip the resolution check here since we are about to find out in this spec!
driver
.context(CONTEXT_NATIVE_APP)
.getWindowSize()
.then(function (size) {
screenWidth = Number(size.width);
screenHeight = Number(size.height);
isResolutionBad = false;
/*
TODO: what are acceptable resolution values?
need to check what the emulators used in CI return.
and also what local device definitions work and dont
*/
})
.done(done);
}, MINUTE);
it('camera.ui.util determine camera availability', function (done) {
checkSession(done);
var opts = {
sourceType: cameraConstants.PictureSourceType.CAMERA,
saveToPhotoAlbum: false
};
return driver
.then(function () {
return getPicture(opts);
})
.then(function () {
cameraAvailable = true;
}, function () {
return recreateSession();
})
.done(done);
}, 5 * MINUTE);
describe('Specs.', function () {
// getPicture() with saveToPhotoLibrary = true
it('camera.ui.spec.1 Saving a picture to the photo library', function (done) {
checkSession(done);
checkCamera(pending);
var spec = generateSpec({
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
saveToPhotoAlbum: true
});
tryRunSpec(spec)
.then(function () {
isTestPictureSaved = true;
})
.done(done);
}, 10 * MINUTE);
// getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY
it('camera.ui.spec.2 Selecting only videos', function (done) {
checkSession(done);
var spec = function () {
var options = { sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
mediaType: cameraConstants.MediaType.VIDEO };
return driver
.then(function () {
return getPicture(options, true);
})
.context(CONTEXT_NATIVE_APP)
.then(function () {
// try to find "Gallery" menu item
// if there's none, the gallery should be already opened
return driver
.waitForElementByAndroidUIAutomator('new UiSelector().text("Gallery")', 20000)
.then(function (element) {
return element.click();
}, function () {
return driver;
});
})
.then(function () {
// if the gallery is opened on the videos page,
// there should be a "Choose video" caption
return driver
.elementByAndroidUIAutomator('new UiSelector().text("Choose video")')
.fail(function () {
throw 'Couldn\'t find "Choose video" element.';
});
})
.deviceKeyEvent(BACK_BUTTON)
.elementByAndroidUIAutomator('new UiSelector().text("Gallery")')
.deviceKeyEvent(BACK_BUTTON)
.finally(function () {
return driver
.elementById('action_bar_title')
.then(function () {
// success means we're still in native app
return driver
.deviceKeyEvent(BACK_BUTTON)
// give native app some time to close
.sleep(2000)
// try again! because every ~30th build
// on Sauce Labs this backbutton doesn't work
.elementById('action_bar_title')
.then(function () {
// success means we're still in native app
return driver
.deviceKeyEvent(BACK_BUTTON);
}, function () {
// error means we're already in webview
return driver;
});
}, function () {
// error means we're already in webview
return driver;
});
});
};
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
// getPicture(), then dismiss
// wait for the error callback to be called
it('camera.ui.spec.3 Dismissing the camera', function (done) {
checkSession(done);
checkCamera(pending);
var spec = function () {
var options = {
quality: 50,
allowEdit: true,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.FILE_URI
};
return driver
.then(function () {
return getPicture(options, true);
})
.context(CONTEXT_NATIVE_APP)
.waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*cancel.*")', MINUTE / 2)
.click()
.then(function () {
return checkPicture(false);
});
};
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
// getPicture(), then take picture but dismiss the edit
// wait for the error callback to be called
it('camera.ui.spec.4 Dismissing the edit', function (done) {
checkSession(done);
checkCamera(pending);
var spec = function () {
var options = {
quality: 50,
allowEdit: true,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.FILE_URI
};
return driver
.then(function () {
return getPicture(options, true);
})
.waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*shutter.*")', MINUTE / 2)
.click()
.waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*done.*")', MINUTE / 2)
.click()
.waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*discard.*")', MINUTE / 2)
.click()
.then(function () {
return checkPicture(false);
});
};
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.5 Verifying target image size, sourceType=CAMERA', function (done) {
checkSession(done);
checkCamera(pending);
var spec = generateSpec({
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
});
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.6 Verifying target image size, sourceType=PHOTOLIBRARY', function (done) {
checkSession(done);
var spec = generateSpec({
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
});
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.7 Verifying target image size, sourceType=CAMERA, DestinationType=NATIVE_URI', function (done) {
checkSession(done);
checkCamera(pending);
var spec = generateSpec({
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.NATIVE_URI,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
});
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.8 Verifying target image size, sourceType=PHOTOLIBRARY, DestinationType=NATIVE_URI', function (done) {
checkSession(done);
var spec = generateSpec({
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
destinationType: cameraConstants.DestinationType.NATIVE_URI,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
});
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.9 Verifying target image size, sourceType=CAMERA, DestinationType=NATIVE_URI, quality=100', function (done) {
checkSession(done);
checkCamera(pending);
var spec = generateSpec({
quality: 100,
allowEdit: true,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.NATIVE_URI,
saveToPhotoAlbum: false,
targetWidth: 305,
targetHeight: 305
});
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.10 Verifying target image size, sourceType=PHOTOLIBRARY, DestinationType=NATIVE_URI, quality=100', function (done) {
checkSession(done);
var spec = generateSpec({
quality: 100,
allowEdit: true,
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
destinationType: cameraConstants.DestinationType.NATIVE_URI,
saveToPhotoAlbum: false,
targetWidth: 305,
targetHeight: 305
});
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
// combine various options for getPicture()
generateOptions().forEach(function (spec) {
it('camera.ui.spec.11.' + spec.id + ' Combining options. ' + spec.description, function (done) {
checkSession(done);
if (spec.options.sourceType == cameraConstants.PictureSourceType.CAMERA) {
checkCamera(pending);
}
var s = generateSpec(spec.options);
tryRunSpec(s).done(done);
}, 10 * MINUTE);
});
it('camera.ui.util Delete filler picture from device library', function (done) {
driver
.context(webviewContext)
.deleteFillerImage(fillerImagePath)
.done(done);
}, MINUTE);
it('camera.ui.util Delete taken picture from device library', function (done) {
checkSession(done);
if (!isTestPictureSaved) {
// couldn't save test picture earlier, so nothing to delete here
done();
return;
}
// delete exactly one latest picture
// this should be the picture we've taken in the first spec
driver
.context(CONTEXT_NATIVE_APP)
.deviceKeyEvent(BACK_BUTTON)
.sleep(1000)
.deviceKeyEvent(BACK_BUTTON)
.sleep(1000)
.deviceKeyEvent(BACK_BUTTON)
.elementById('Apps')
.click()
.elementByAndroidUIAutomator('new UiSelector().text("Gallery")')
.click()
.elementByAndroidUIAutomator('new UiSelector().textContains("Pictures")')
.click()
.then(deleteImage)
.deviceKeyEvent(BACK_BUTTON)
.sleep(1000)
.deviceKeyEvent(BACK_BUTTON)
.sleep(1000)
.deviceKeyEvent(BACK_BUTTON)
.fail(fail)
.finally(done);
}, 3 * MINUTE);
});
});

View File

@@ -0,0 +1,305 @@
/*jshint node: true */
/* global Q, resolveLocalFileSystemURL, Camera, cordova */
/*
*
* 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.
*
*/
'use strict';
var cameraConstants = require('../../www/CameraConstants');
function findKeyByValue(set, value) {
for (var k in set) {
if (set.hasOwnProperty(k)) {
if (set[k] == value) {
return k;
}
}
}
return undefined;
}
function getDescription(spec) {
var desc = '';
desc += 'sourceType: ' + findKeyByValue(cameraConstants.PictureSourceType, spec.options.sourceType);
desc += ', destinationType: ' + findKeyByValue(cameraConstants.DestinationType, spec.options.destinationType);
desc += ', encodingType: ' + findKeyByValue(cameraConstants.EncodingType, spec.options.encodingType);
desc += ', allowEdit: ' + spec.options.allowEdit.toString();
desc += ', correctOrientation: ' + spec.options.correctOrientation.toString();
return desc;
}
module.exports.generateSpecs = function (sourceTypes, destinationTypes, encodingTypes, allowEditOptions, correctOrientationOptions) {
var destinationType,
sourceType,
encodingType,
allowEdit,
correctOrientation,
specs = [],
id = 1;
for (destinationType in destinationTypes) {
if (destinationTypes.hasOwnProperty(destinationType)) {
for (sourceType in sourceTypes) {
if (sourceTypes.hasOwnProperty(sourceType)) {
for (encodingType in encodingTypes) {
if (encodingTypes.hasOwnProperty(encodingType)) {
for (allowEdit in allowEditOptions) {
if (allowEditOptions.hasOwnProperty(allowEdit)) {
for (correctOrientation in correctOrientationOptions) {
// if taking picture from photolibrary, don't vary 'correctOrientation' option
if ((sourceTypes[sourceType] === cameraConstants.PictureSourceType.PHOTOLIBRARY ||
sourceTypes[sourceType] === cameraConstants.PictureSourceType.SAVEDPHOTOALBUM) &&
correctOrientation === true) { continue; }
var spec = {
'id': id++,
'options': {
'destinationType': destinationTypes[destinationType],
'sourceType': sourceTypes[sourceType],
'encodingType': encodingTypes[encodingType],
'allowEdit': allowEditOptions[allowEdit],
'saveToPhotoAlbum': false,
'correctOrientation': correctOrientationOptions[correctOrientation]
}
};
spec.description = getDescription(spec);
specs.push(spec);
}
}
}
}
}
}
}
}
}
return specs;
};
// calls getPicture() and saves the result in promise
// note that this function is executed in the context of tested app
// and not in the context of tests
module.exports.getPicture = function (opts, pid) {
if (navigator._appiumPromises[pid - 1]) {
navigator._appiumPromises[pid - 1] = null;
}
navigator._appiumPromises[pid] = Q.defer();
navigator.camera.getPicture(function (result) {
navigator._appiumPromises[pid].resolve(result);
}, function (err) {
navigator._appiumPromises[pid].reject(err);
}, opts);
};
// verifies taken picture when the promise is resolved,
// calls a callback with 'OK' if everything is good,
// calls a callback with 'ERROR: <error message>' if something is wrong
// note that this function is executed in the context of tested app
// and not in the context of tests
module.exports.checkPicture = function (pid, options, cb) {
var isIos = cordova.platformId === "ios";
var isAndroid = cordova.platformId === "android";
// skip image type check if it's unmodified on Android:
// https://github.com/apache/cordova-plugin-camera/#android-quirks-1
var skipFileTypeCheckAndroid = isAndroid && options.quality === 100 &&
!options.targetWidth && !options.targetHeight &&
!options.correctOrientation;
// Skip image type check if destination is NATIVE_URI and source - device's photoalbum
// https://github.com/apache/cordova-plugin-camera/#ios-quirks-1
var skipFileTypeCheckiOS = isIos && options.destinationType === Camera.DestinationType.NATIVE_URI &&
(options.sourceType === Camera.PictureSourceType.PHOTOLIBRARY ||
options.sourceType === Camera.PictureSourceType.SAVEDPHOTOALBUM);
var skipFileTypeCheck = skipFileTypeCheckAndroid || skipFileTypeCheckiOS;
var desiredType = 'JPEG';
var mimeType = 'image/jpeg';
if (options.encodingType === Camera.EncodingType.PNG) {
desiredType = 'PNG';
mimeType = 'image/png';
}
function errorCallback(msg) {
if (msg.hasOwnProperty('message')) {
msg = msg.message;
}
cb('ERROR: ' + msg);
}
// verifies the image we get from plugin
function verifyResult(result) {
if (result.length === 0) {
errorCallback('The result is empty.');
return;
} else if (isIos && options.destinationType === Camera.DestinationType.NATIVE_URI && result.indexOf('assets-library:') !== 0) {
errorCallback('Expected "' + result.substring(0, 150) + '"to start with "assets-library:"');
return;
} else if (isIos && options.destinationType === Camera.DestinationType.FILE_URI && result.indexOf('file:') !== 0) {
errorCallback('Expected "' + result.substring(0, 150) + '"to start with "file:"');
return;
}
try {
window.atob(result);
// if we got here it is a base64 string (DATA_URL)
result = "data:" + mimeType + ";base64," + result;
} catch (e) {
// not DATA_URL
if (options.destinationType === Camera.DestinationType.DATA_URL) {
errorCallback('Expected ' + result.substring(0, 150) + 'not to be DATA_URL');
return;
}
}
try {
if (result.indexOf('file:') === 0 ||
result.indexOf('content:') === 0 ||
result.indexOf('assets-library:') === 0) {
if (!window.resolveLocalFileSystemURL) {
errorCallback('Cannot read file. Please install cordova-plugin-file to fix this.');
return;
}
resolveLocalFileSystemURL(result, function (entry) {
if (skipFileTypeCheck) {
displayFile(entry);
} else {
verifyFile(entry);
}
});
} else {
displayImage(result);
}
} catch (e) {
errorCallback(e);
}
}
// verifies that the file type matches the requested type
function verifyFile(entry) {
try {
var reader = new FileReader();
reader.onloadend = function(e) {
var arr = (new Uint8Array(e.target.result)).subarray(0, 4);
var header = '';
for(var i = 0; i < arr.length; i++) {
header += arr[i].toString(16);
}
var actualType = 'unknown';
switch (header) {
case "89504e47":
actualType = 'PNG';
break;
case 'ffd8ffe0':
case 'ffd8ffe1':
case 'ffd8ffe2':
actualType = 'JPEG';
break;
}
if (actualType === desiredType) {
displayFile(entry);
} else {
errorCallback('File type mismatch. Expected ' + desiredType + ', got ' + actualType);
}
};
reader.onerror = function (e) {
errorCallback(e);
};
entry.file(function (file) {
reader.readAsArrayBuffer(file);
}, function (e) {
errorCallback(e);
});
} catch (e) {
errorCallback(e);
}
}
// reads the file, then displays the image
function displayFile(entry) {
function onFileReceived(file) {
var reader = new FileReader();
reader.onerror = function (e) {
errorCallback(e);
};
reader.onloadend = function (evt) {
displayImage(evt.target.result);
};
reader.readAsDataURL(file);
}
entry.file(onFileReceived, function (e) {
errorCallback(e);
});
}
function displayImage(image) {
try {
var imgEl = document.getElementById('camera_test_image');
if (!imgEl) {
imgEl = document.createElement('img');
imgEl.id = 'camera_test_image';
document.body.appendChild(imgEl);
}
var timedOut = false;
var loadTimeout = setTimeout(function () {
timedOut = true;
imgEl.src = '';
errorCallback('The image did not load: ' + image.substring(0, 150));
}, 10000);
var done = function (status) {
if (!timedOut) {
clearTimeout(loadTimeout);
imgEl.src = '';
cb(status);
}
};
imgEl.onload = function () {
try {
// aspect ratio is preserved so only one dimension should match
if ((typeof options.targetWidth === 'number' && imgEl.naturalWidth !== options.targetWidth) &&
(typeof options.targetHeight === 'number' && imgEl.naturalHeight !== options.targetHeight))
{
done('ERROR: Wrong image size: ' + imgEl.naturalWidth + 'x' + imgEl.naturalHeight +
'. Requested size: ' + options.targetWidth + 'x' + options.targetHeight);
} else {
done('OK');
}
} catch (e) {
errorCallback(e);
}
};
imgEl.src = image;
} catch (e) {
errorCallback(e);
}
}
navigator._appiumPromises[pid].promise
.then(function (result) {
verifyResult(result);
})
.fail(function (e) {
errorCallback(e);
});
};

View File

@@ -0,0 +1,489 @@
/*jshint node: true, jasmine: true */
/*
*
* 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.
*
*/
// these tests are meant to be executed by Cordova Paramedic test runner
// you can find it here: https://github.com/apache/cordova-paramedic/
// it is not necessary to do a full CI setup to run these tests
// just run "node cordova-paramedic/main.js --platform ios --plugin cordova-plugin-camera"
'use strict';
var wdHelper = global.WD_HELPER;
var screenshotHelper = global.SCREENSHOT_HELPER;
var isDevice = global.DEVICE;
var cameraConstants = require('../../www/CameraConstants');
var cameraHelper = require('../helpers/cameraHelper');
var MINUTE = 60 * 1000;
var DEFAULT_WEBVIEW_CONTEXT = 'WEBVIEW_1';
var PROMISE_PREFIX = 'appium_camera_promise_';
var CONTEXT_NATIVE_APP = 'NATIVE_APP';
describe('Camera tests iOS.', function () {
var driver;
var webviewContext = DEFAULT_WEBVIEW_CONTEXT;
// promise count to use in promise ID
var promiseCount = 0;
// going to set this to false if session is created successfully
var failedToStart = true;
// points out which UI automation to use
var isXCUI = false;
// spec counter to restart the session
var specsRun = 0;
function getNextPromiseId() {
promiseCount += 1;
return getCurrentPromiseId();
}
function getCurrentPromiseId() {
return PROMISE_PREFIX + promiseCount;
}
function gracefullyFail(error) {
fail(error);
return driver
.quit()
.then(function () {
return getDriver();
});
}
// generates test specs by combining all the specified options
// you can add more options to test more scenarios
function generateOptions() {
var sourceTypes = cameraConstants.PictureSourceType;
var destinationTypes = cameraConstants.DestinationType;
var encodingTypes = cameraConstants.EncodingType;
var allowEditOptions = [ true, false ];
var correctOrientationOptions = [ true, false ];
return cameraHelper.generateSpecs(sourceTypes, destinationTypes, encodingTypes, allowEditOptions, correctOrientationOptions);
}
function usePicture() {
return driver
.elementByXPath('//*[@label="Use"]')
.click()
.fail(function () {
if (isXCUI) {
return driver
.waitForElementByAccessibilityId('Choose', MINUTE / 3)
.click();
}
// For some reason "Choose" element is not clickable by standard Appium methods on iOS <= 9
return wdHelper.tapElementByXPath('//UIAButton[@label="Choose"]', driver);
});
}
function clickPhoto() {
if (isXCUI) {
// iOS >=10
return driver
.context(CONTEXT_NATIVE_APP)
.elementsByXPath('//XCUIElementTypeCell')
.then(function(photos) {
if (photos.length == 0) {
return driver
.sleep(0) // driver.source is not a function o.O
.source()
.then(function (src) {
console.log(src);
gracefullyFail('Couldn\'t find an image to click');
});
}
// intentionally clicking the second photo here
// the first one is not clickable for some reason
return photos[1].click();
});
}
// iOS <10
return driver
.elementByXPath('//UIACollectionCell')
.click();
}
function getPicture(options, cancelCamera, skipUiInteractions) {
var promiseId = getNextPromiseId();
if (!options) {
options = {};
}
return driver
.context(webviewContext)
.execute(cameraHelper.getPicture, [options, promiseId])
.context(CONTEXT_NATIVE_APP)
.then(function () {
if (skipUiInteractions) {
return;
}
if (options.hasOwnProperty('sourceType') && options.sourceType === cameraConstants.PictureSourceType.PHOTOLIBRARY) {
return driver
.waitForElementByAccessibilityId('Camera Roll', MINUTE / 2)
.click()
.then(function () {
return clickPhoto();
})
.then(function () {
if (!options.allowEdit) {
return driver;
}
return usePicture();
});
}
if (options.hasOwnProperty('sourceType') && options.sourceType === cameraConstants.PictureSourceType.SAVEDPHOTOALBUM) {
return clickPhoto()
.then(function () {
if (!options.allowEdit) {
return driver;
}
return usePicture();
});
}
if (cancelCamera) {
return driver
.waitForElementByAccessibilityId('Cancel', MINUTE / 2)
.click();
}
return driver
.waitForElementByAccessibilityId('Take Picture', MINUTE / 2)
.click()
.waitForElementByAccessibilityId('Use Photo', MINUTE / 2)
.click();
})
.fail(fail);
}
// checks if the picture was successfully taken
// if shouldLoad is falsy, ensures that the error callback was called
function checkPicture(shouldLoad, options) {
if (!options) {
options = {};
}
return driver
.context(webviewContext)
.setAsyncScriptTimeout(MINUTE / 2)
.executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId(), options])
.then(function (result) {
if (shouldLoad) {
if (result !== 'OK') {
fail(result);
}
} else if (result.indexOf('ERROR') === -1) {
throw 'Unexpected success callback with result: ' + result;
}
});
}
// takes a picture with the specified options
// and then verifies it
function runSpec(options, done, pending) {
if (options.sourceType === cameraConstants.PictureSourceType.CAMERA && !isDevice) {
pending('Camera is not available on iOS simulator');
}
checkSession(done);
specsRun += 1;
return driver
.then(function () {
return getPicture(options);
})
.then(function () {
return checkPicture(true, options);
})
.fail(gracefullyFail);
}
function getDriver() {
failedToStart = true;
driver = wdHelper.getDriver('iOS');
return wdHelper.getWebviewContext(driver)
.then(function(context) {
webviewContext = context;
return driver.context(webviewContext);
})
.then(function () {
return wdHelper.waitForDeviceReady(driver);
})
.then(function () {
return wdHelper.injectLibraries(driver);
})
.sessionCapabilities()
.then(function (caps) {
var platformVersion = parseFloat(caps.platformVersion);
isXCUI = platformVersion >= 10.0;
})
.then(function () {
var options = {
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
};
return driver
.then(function () { return getPicture(options, false, true); })
.context(CONTEXT_NATIVE_APP)
.acceptAlert()
.then(function alertDismissed() {
// TODO: once we move to only XCUITest-based (which is force on you in either iOS 10+ or Xcode 8+)
// UI tests, we will have to:
// a) remove use of autoAcceptAlerts appium capability since it no longer functions in XCUITest
// b) can remove this entire then() clause, as we do not need to explicitly handle the acceptAlert
// failure callback, since we will be guaranteed to hit the permission dialog on startup.
}, function noAlert() {
// in case the contacts permission alert never showed up: no problem, don't freak out.
// This can happen if:
// a) The application-under-test already had photos permissions granted to it
// b) Appium's autoAcceptAlerts capability is provided (and functioning)
})
.elementByAccessibilityId('Cancel', 10000)
.click();
})
.then(function () {
failedToStart = false;
});
}
function checkSession(done) {
if (failedToStart) {
fail('Failed to start a session');
done();
}
}
it('camera.ui.util configure driver and start a session', function (done) {
getDriver()
.fail(fail)
.done(done);
}, 15 * MINUTE);
describe('Specs.', function () {
afterEach(function (done) {
if (specsRun >= 15) {
specsRun = 0;
// we need to restart the session regularly because for some reason
// when running against iOS 10 simulator on SauceLabs,
// Appium cannot handle more than ~20 specs at one session
// the error would be as follows:
// "Could not proxy command to remote server. Original error: Error: connect ECONNREFUSED 127.0.0.1:8100"
checkSession(done);
return driver
.quit()
.then(function () {
return getDriver();
})
.done(done);
} else {
done();
}
}, 15 * MINUTE);
// getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY
it('camera.ui.spec.1 Selecting only videos', function (done) {
checkSession(done);
specsRun += 1;
var options = { sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
mediaType: cameraConstants.MediaType.VIDEO };
driver
// skip ui unteractions
.then(function () { return getPicture(options, false, true); })
.waitForElementByXPath('//*[contains(@label,"Videos")]', MINUTE / 2)
.elementByAccessibilityId('Cancel')
.click()
.fail(gracefullyFail)
.done(done);
}, 7 * MINUTE);
// getPicture(), then dismiss
// wait for the error callback to be called
it('camera.ui.spec.2 Dismissing the camera', function (done) {
checkSession(done);
if (!isDevice) {
pending('Camera is not available on iOS simulator');
}
specsRun += 1;
var options = { sourceType: cameraConstants.PictureSourceType.CAMERA,
saveToPhotoAlbum: false };
driver
.then(function () {
return getPicture(options, true);
})
.then(function () {
return checkPicture(false);
})
.fail(gracefullyFail)
.done(done);
}, 7 * MINUTE);
it('camera.ui.spec.3 Verifying target image size, sourceType=CAMERA', function (done) {
var options = {
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
};
runSpec(options, done, pending).done(done);
}, 7 * MINUTE);
it('camera.ui.spec.4 Verifying target image size, sourceType=SAVEDPHOTOALBUM', function (done) {
var options = {
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
};
runSpec(options, done, pending).done(done);
}, 7 * MINUTE);
it('camera.ui.spec.5 Verifying target image size, sourceType=PHOTOLIBRARY', function (done) {
var options = {
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
};
runSpec(options, done, pending).done(done);
}, 7 * MINUTE);
it('camera.ui.spec.6 Verifying target image size, sourceType=CAMERA, destinationType=FILE_URL', function (done) {
// remove this line if you don't mind the tests leaving a photo saved on device
pending('Cannot prevent iOS from saving the picture to photo library');
var options = {
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.FILE_URL,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
};
runSpec(options, done, pending).done(done);
}, 7 * MINUTE);
it('camera.ui.spec.7 Verifying target image size, sourceType=SAVEDPHOTOALBUM, destinationType=FILE_URL', function (done) {
var options = {
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
destinationType: cameraConstants.DestinationType.FILE_URL,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
};
runSpec(options, done, pending).done(done);
}, 7 * MINUTE);
it('camera.ui.spec.8 Verifying target image size, sourceType=PHOTOLIBRARY, destinationType=FILE_URL', function (done) {
var options = {
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
destinationType: cameraConstants.DestinationType.FILE_URL,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
};
runSpec(options, done, pending).done(done);
}, 7 * MINUTE);
it('camera.ui.spec.9 Verifying target image size, sourceType=CAMERA, destinationType=FILE_URL, quality=100', function (done) {
// remove this line if you don't mind the tests leaving a photo saved on device
pending('Cannot prevent iOS from saving the picture to photo library');
var options = {
quality: 100,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.FILE_URL,
saveToPhotoAlbum: false,
targetWidth: 305,
targetHeight: 305
};
runSpec(options, done, pending).done(done);
}, 7 * MINUTE);
it('camera.ui.spec.10 Verifying target image size, sourceType=SAVEDPHOTOALBUM, destinationType=FILE_URL, quality=100', function (done) {
var options = {
quality: 100,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
destinationType: cameraConstants.DestinationType.FILE_URL,
saveToPhotoAlbum: false,
targetWidth: 305,
targetHeight: 305
};
runSpec(options, done, pending).done(done);
}, 7 * MINUTE);
it('camera.ui.spec.11 Verifying target image size, sourceType=PHOTOLIBRARY, destinationType=FILE_URL, quality=100', function (done) {
var options = {
quality: 100,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
destinationType: cameraConstants.DestinationType.FILE_URL,
saveToPhotoAlbum: false,
targetWidth: 305,
targetHeight: 305
};
runSpec(options, done, pending).done(done);
}, 7 * MINUTE);
// combine various options for getPicture()
generateOptions().forEach(function (spec) {
it('camera.ui.spec.12.' + spec.id + ' Combining options. ' + spec.description, function (done) {
// remove this check if you don't mind the tests leaving a photo saved on device
if (spec.options.sourceType === cameraConstants.PictureSourceType.CAMERA &&
spec.options.destinationType === cameraConstants.DestinationType.NATIVE_URI) {
pending('Skipping: cannot prevent iOS from saving the picture to photo library and cannot delete it. ' +
'For more info, see iOS quirks here: https://github.com/apache/cordova-plugin-camera#ios-quirks-1');
}
runSpec(spec.options, done, pending).done(done);
}, 7 * MINUTE);
});
});
it('camera.ui.util Destroy the session', function (done) {
checkSession(done);
driver
.quit()
.done(done);
}, 5 * MINUTE);
});

View File

@@ -1,5 +1,13 @@
---
title: Camera
description: Take pictures with the device camera.
---
{{>cdv-license~}}
|Android|iOS| Windows 8.1 Store | Windows 8.1 Phone | Windows 10 Store | Travis CI |
|:-:|:-:|:-:|:-:|:-:|:-:|
|[![Build Status](http://cordova-ci.cloudapp.net:8080/buildStatus/icon?job=cordova-periodic-build/PLATFORM=android,PLUGIN=cordova-plugin-camera)](http://cordova-ci.cloudapp.net:8080/job/cordova-periodic-build/PLATFORM=android,PLUGIN=cordova-plugin-camera/)|[![Build Status](http://cordova-ci.cloudapp.net:8080/buildStatus/icon?job=cordova-periodic-build/PLATFORM=ios,PLUGIN=cordova-plugin-camera)](http://cordova-ci.cloudapp.net:8080/job/cordova-periodic-build/PLATFORM=ios,PLUGIN=cordova-plugin-camera/)|[![Build Status](http://cordova-ci.cloudapp.net:8080/buildStatus/icon?job=cordova-periodic-build/PLATFORM=windows-8.1-store,PLUGIN=cordova-plugin-camera)](http://cordova-ci.cloudapp.net:8080/job/cordova-periodic-build/PLATFORM=windows-8.1-store,PLUGIN=cordova-plugin-camera/)|[![Build Status](http://cordova-ci.cloudapp.net:8080/buildStatus/icon?job=cordova-periodic-build/PLATFORM=windows-8.1-phone,PLUGIN=cordova-plugin-camera)](http://cordova-ci.cloudapp.net:8080/job/cordova-periodic-build/PLATFORM=windows-8.1-phone,PLUGIN=cordova-plugin-camera/)|[![Build Status](http://cordova-ci.cloudapp.net:8080/buildStatus/icon?job=cordova-periodic-build/PLATFORM=windows-10-store,PLUGIN=cordova-plugin-camera)](http://cordova-ci.cloudapp.net:8080/job/cordova-periodic-build/PLATFORM=windows-10-store,PLUGIN=cordova-plugin-camera/)|[![Build Status](https://travis-ci.org/apache/cordova-plugin-camera.svg?branch=master)](https://travis-ci.org/apache/cordova-plugin-camera)
# cordova-plugin-camera
This plugin defines a global `navigator.camera` object, which provides an API for taking pictures and for choosing images from
@@ -7,9 +15,30 @@ the system's image library.
{{>cdv-header device-ready-warning-obj='navigator.camera' npmName='cordova-plugin-camera' cprName='org.apache.cordova.camera' pluginName='Plugin Camera' repoUrl='https://github.com/apache/cordova-plugin-camera' }}
### iOS Quirks
Since iOS 10 it's mandatory to add a `NSCameraUsageDescription` and `NSPhotoLibraryUsageDescription` in the info.plist.
- `NSCameraUsageDescription` describes the reason that the app accesses the users camera.
- `NSPhotoLibraryUsageDescription` describes the reason the app accesses the user's photo library.
When the system prompts the user to allow access, this string is displayed as part of the dialog box.
To add this entry you can pass the following variables on plugin install.
- `CAMERA_USAGE_DESCRIPTION` for `NSCameraUsageDescription`
- `PHOTOLIBRARY_USAGE_DESCRIPTION` for `NSPhotoLibraryUsageDescription`
Example:
cordova plugin add cordova-plugin-camera --variable CAMERA_USAGE_DESCRIPTION="your usage message" --variable PHOTOLIBRARY_USAGE_DESCRIPTION="your usage message"
If you don't pass the variable, the plugin will add an empty string as value.
---
# API Reference
# API Reference <a name="reference"></a>
{{#orphans~}}
{{>member-index}}
@@ -32,21 +61,6 @@ the system's image library.
#### Example <a name="camera-getPicture-examples"></a>
Take a photo and retrieve it as a base64-encoded image:
navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
destinationType: Camera.DestinationType.DATA_URL
});
function onSuccess(imageData) {
var image = document.getElementById('myImage');
image.src = "data:image/jpeg;base64," + imageData;
}
function onFail(message) {
alert('Failed because: ' + message);
}
Take a photo and retrieve the image's file location:
navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
@@ -61,6 +75,27 @@ Take a photo and retrieve the image's file location:
alert('Failed because: ' + message);
}
Take a photo and retrieve it as a Base64-encoded image:
/**
* Warning: Using DATA_URL is not recommended! The DATA_URL destination
* type is very memory intensive, even with a low quality setting. Using it
* can result in out of memory errors and application crashes. Use FILE_URI
* or NATIVE_URI instead.
*/
navigator.camera.getPicture(onSuccess, onFail, { quality: 25,
destinationType: Camera.DestinationType.DATA_URL
});
function onSuccess(imageData) {
var image = document.getElementById('myImage');
image.src = "data:image/jpeg;base64," + imageData;
}
function onFail(message) {
alert('Failed because: ' + message);
}
#### Preferences (iOS)
- __CameraUsesGeolocation__ (boolean, defaults to false). For capturing JPEGs, set to true to get geolocation data in the EXIF header. This will trigger a request for geolocation permissions if set to true.
@@ -71,14 +106,14 @@ Take a photo and retrieve the image's file location:
Amazon Fire OS uses intents to launch the camera activity on the device to capture
images, and on phones with low memory, the Cordova activity may be killed. In this
scenario, the image may not appear when the cordova activity is restored.
scenario, the image may not appear when the Cordova activity is restored.
#### Android Quirks
Android uses intents to launch the camera activity on the device to capture
images, and on phones with low memory, the Cordova activity may be killed. In this
scenario, the result from the plugin call will be delivered via the resume event.
See [the Android Lifecycle guide](http://cordova.apache.org/docs/en/dev/guide/platforms/android/lifecycle.html)
See [the Android Lifecycle guide][android_lifecycle]
for more information. The `pendingResult.result` value will contain the value that
would be passed to the callbacks (either the URI/URL or an error message). Check
the `pendingResult.pluginStatus` to determine whether or not the call was
@@ -86,11 +121,11 @@ successful.
#### Browser Quirks
Can only return photos as base64-encoded image.
Can only return photos as Base64-encoded image.
#### Firefox OS Quirks
Camera plugin is currently implemented using [Web Activities](https://hacks.mozilla.org/2013/01/introducing-web-activities/).
Camera plugin is currently implemented using [Web Activities][web_activities].
#### iOS Quirks
@@ -108,6 +143,16 @@ displays:
Invoking the native camera application while the device is connected
via Zune does not work, and triggers an error callback.
#### Windows quirks
On Windows Phone 8.1 using `SAVEDPHOTOALBUM` or `PHOTOLIBRARY` as a source type causes application to suspend until file picker returns the selected image and
then restore with start page as defined in app's `config.xml`. In case when `camera.getPicture` was called from different page, this will lead to reloading
start page from scratch and success and error callbacks will never be called.
To avoid this we suggest using SPA pattern or call `camera.getPicture` only from your app's start page.
More information about Windows Phone 8.1 picker APIs is here: [How to continue your Windows Phone app after calling a file picker](https://msdn.microsoft.com/en-us/library/windows/apps/dn720490.aspx)
#### Tizen Quirks
Tizen only supports a `destinationType` of
@@ -173,6 +218,8 @@ Tizen only supports a `destinationType` of
- When using `destinationType.NATIVE_URI` and `sourceType.CAMERA`, photos are saved in the saved photo album regardless on the value of `saveToPhotoAlbum` parameter.
- When using `destinationType.NATIVE_URI` and `sourceType.PHOTOLIBRARY` or `sourceType.SAVEDPHOTOALBUM`, all editing options are ignored and link is returned to original picture.
#### Tizen Quirks
- options not supported
@@ -187,7 +234,210 @@ Tizen only supports a `destinationType` of
- Ignores the `cameraDirection` parameter.
- Ignores the `saveToPhotoAlbum` parameter. IMPORTANT: All images taken with the wp7/8 cordova camera API are always copied to the phone's camera roll. Depending on the user's settings, this could also mean the image is auto-uploaded to their OneDrive. This could potentially mean the image is available to a wider audience than your app intended. If this a blocker for your application, you will need to implement the CameraCaptureTask as documented on msdn : [http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh394006.aspx](http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh394006.aspx)
You may also comment or up-vote the related issue in the [issue tracker](https://issues.apache.org/jira/browse/CB-2083)
- Ignores the `saveToPhotoAlbum` parameter. IMPORTANT: All images taken with the WP8/8 Cordova camera API are always copied to the phone's camera roll. Depending on the user's settings, this could also mean the image is auto-uploaded to their OneDrive. This could potentially mean the image is available to a wider audience than your app intended. If this is a blocker for your application, you will need to implement the CameraCaptureTask as [documented on MSDN][msdn_wp8_docs]. You may also comment or up-vote the related issue in the [issue tracker][wp8_bug].
- Ignores the `mediaType` property of `cameraOptions` as the Windows Phone SDK does not provide a way to choose videos from PHOTOLIBRARY.
[android_lifecycle]: http://cordova.apache.org/docs/en/dev/guide/platforms/android/lifecycle.html
[web_activities]: https://hacks.mozilla.org/2013/01/introducing-web-activities/
[wp8_bug]: https://issues.apache.org/jira/browse/CB-2083
[msdn_wp8_docs]: http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh394006.aspx
## Sample: Take Pictures, Select Pictures from the Picture Library, and Get Thumbnails <a name="sample"></a>
The Camera plugin allows you to do things like open the device's Camera app and take a picture, or open the file picker and select one. The code snippets in this section demonstrate different tasks including:
* Open the Camera app and [take a Picture](#takePicture)
* Take a picture and [return thumbnails](#getThumbnails) (resized picture)
* Take a picture and [generate a FileEntry object](#convert)
* [Select a file](#selectFile) from the picture library
* Select a JPEG image and [return thumbnails](#getFileThumbnails) (resized image)
* Select an image and [generate a FileEntry object](#convert)
## Take a Picture <a name="takePicture"></a>
Before you can take a picture, you need to set some Camera plugin options to pass into the Camera plugin's `getPicture` function. Here is a common set of recommendations. In this example, you create the object that you will use for the Camera options, and set the `sourceType` dynamically to support both the Camera app and the file picker.
```js
function setOptions(srcType) {
var options = {
// Some common settings are 20, 50, and 100
quality: 50,
destinationType: Camera.DestinationType.FILE_URI,
// In this app, dynamically set the picture source, Camera or photo gallery
sourceType: srcType,
encodingType: Camera.EncodingType.JPEG,
mediaType: Camera.MediaType.PICTURE,
allowEdit: true,
correctOrientation: true //Corrects Android orientation quirks
}
return options;
}
```
Typically, you want to use a FILE_URI instead of a DATA_URL to avoid most memory issues. JPEG is the recommended encoding type for Android.
You take a picture by passing in the options object to `getPicture`, which takes a CameraOptions object as the third argument. When you call `setOptions`, pass `Camera.PictureSourceType.CAMERA` as the picture source.
```js
function openCamera(selection) {
var srcType = Camera.PictureSourceType.CAMERA;
var options = setOptions(srcType);
var func = createNewFileEntry;
navigator.camera.getPicture(function cameraSuccess(imageUri) {
displayImage(imageUri);
// You may choose to copy the picture, save it somewhere, or upload.
func(imageUri);
}, function cameraError(error) {
console.debug("Unable to obtain picture: " + error, "app");
}, options);
}
```
Once you take the picture, you can display it or do something else. In this example, call the app's `displayImage` function from the preceding code.
```js
function displayImage(imgUri) {
var elem = document.getElementById('imageFile');
elem.src = imgUri;
}
```
To display the image on some platforms, you might need to include the main part of the URI in the Content-Security-Policy `<meta>` element in index.html. For example, on Windows 10, you can include `ms-appdata:` in your `<meta>` element. Here is an example.
```html
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: ms-appdata: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *">
```
## Take a Picture and Return Thumbnails (Resize the Picture) <a name="getThumbnails"></a>
To get smaller images, you can return a resized image by passing both `targetHeight` and `targetWidth` values with your CameraOptions object. In this example, you resize the returned image to fit in a 100px by 100px box (the aspect ratio is maintained, so 100px is either the height or width, whichever is greater in the source).
```js
function openCamera(selection) {
var srcType = Camera.PictureSourceType.CAMERA;
var options = setOptions(srcType);
var func = createNewFileEntry;
if (selection == "camera-thmb") {
options.targetHeight = 100;
options.targetWidth = 100;
}
navigator.camera.getPicture(function cameraSuccess(imageUri) {
// Do something
}, function cameraError(error) {
console.debug("Unable to obtain picture: " + error, "app");
}, options);
}
```
## Select a File from the Picture Library <a name="selectFile"></a>
When selecting a file using the file picker, you also need to set the CameraOptions object. In this example, set the `sourceType` to `Camera.PictureSourceType.SAVEDPHOTOALBUM`. To open the file picker, call `getPicture` just as you did in the previous example, passing in the success and error callbacks along with CameraOptions object.
```js
function openFilePicker(selection) {
var srcType = Camera.PictureSourceType.SAVEDPHOTOALBUM;
var options = setOptions(srcType);
var func = createNewFileEntry;
navigator.camera.getPicture(function cameraSuccess(imageUri) {
// Do something
}, function cameraError(error) {
console.debug("Unable to obtain picture: " + error, "app");
}, options);
}
```
## Select an Image and Return Thumbnails (resized images) <a name="getFileThumbnails"></a>
Resizing a file selected with the file picker works just like resizing using the Camera app; set the `targetHeight` and `targetWidth` options.
```js
function openFilePicker(selection) {
var srcType = Camera.PictureSourceType.SAVEDPHOTOALBUM;
var options = setOptions(srcType);
var func = createNewFileEntry;
if (selection == "picker-thmb") {
// To downscale a selected image,
// Camera.EncodingType (e.g., JPEG) must match the selected image type.
options.targetHeight = 100;
options.targetWidth = 100;
}
navigator.camera.getPicture(function cameraSuccess(imageUri) {
// Do something with image
}, function cameraError(error) {
console.debug("Unable to obtain picture: " + error, "app");
}, options);
}
```
## Take a picture and get a FileEntry Object <a name="convert"></a>
If you want to do something like copy the image to another location, or upload it somewhere using the FileTransfer plugin, you need to get a FileEntry object for the returned picture. To do that, call `window.resolveLocalFileSystemURL` on the file URI returned by the Camera app. If you need to use a FileEntry object, set the `destinationType` to `Camera.DestinationType.FILE_URI` in your CameraOptions object (this is also the default value).
>*Note* You need the [File plugin](https://www.npmjs.com/package/cordova-plugin-file) to call `window.resolveLocalFileSystemURL`.
Here is the call to `window.resolveLocalFileSystemURL`. The image URI is passed to this function from the success callback of `getPicture`. The success handler of `resolveLocalFileSystemURL` receives the FileEntry object.
```js
function getFileEntry(imgUri) {
window.resolveLocalFileSystemURL(imgUri, function success(fileEntry) {
// Do something with the FileEntry object, like write to it, upload it, etc.
// writeFile(fileEntry, imgUri);
console.log("got file: " + fileEntry.fullPath);
// displayFileData(fileEntry.nativeURL, "Native URL");
}, function () {
// If don't get the FileEntry (which may happen when testing
// on some emulators), copy to a new FileEntry.
createNewFileEntry(imgUri);
});
}
```
In the example shown in the preceding code, you call the app's `createNewFileEntry` function if you don't get a valid FileEntry object. The image URI returned from the Camera app should result in a valid FileEntry, but platform behavior on some emulators may be different for files returned from the file picker.
>*Note* To see an example of writing to a FileEntry, see the [File plugin README](https://www.npmjs.com/package/cordova-plugin-file).
The code shown here creates a file in your app's cache (in sandboxed storage) named `tempFile.jpeg`. With the new FileEntry object, you can copy the image to the file or do something else like upload it.
```js
function createNewFileEntry(imgUri) {
window.resolveLocalFileSystemURL(cordova.file.cacheDirectory, function success(dirEntry) {
// JPEG file
dirEntry.getFile("tempFile.jpeg", { create: true, exclusive: false }, function (fileEntry) {
// Do something with it, like write to it, upload it, etc.
// writeFile(fileEntry, imgUri);
console.log("got file: " + fileEntry.fullPath);
// displayFileData(fileEntry.fullPath, "File copied to");
}, onErrorCreateFile);
}, onErrorResolveUrl);
}
```

View File

@@ -1,7 +1,8 @@
{
"name": "cordova-plugin-camera",
"version": "2.0.1-dev",
"version": "2.4.0",
"description": "Cordova Camera Plugin",
"types": "./types/index.d.ts",
"cordova": {
"id": "cordova-plugin-camera",
"platforms": [
@@ -38,18 +39,25 @@
"cordova-browser",
"cordova-windows"
],
"peerDependencies": {
"cordova-plugin-file": ">=2.0.0"
},
"scripts": {
"precommit": "npm run gen-docs && git add README.md",
"gen-docs": "jsdoc2md --template \"jsdoc2md/TEMPLATE.md\" \"www/**/*.js\" --plugin \"dmd-plugin-cordova-plugin\" > README.md"
"gen-docs": "jsdoc2md --template \"jsdoc2md/TEMPLATE.md\" \"www/**/*.js\" --plugin \"dmd-plugin-cordova-plugin\" > README.md",
"test": "npm run jshint",
"jshint": "node node_modules/jshint/bin/jshint www && node node_modules/jshint/bin/jshint src && node node_modules/jshint/bin/jshint tests"
},
"author": "Apache Software Foundation",
"license": "Apache 2.0",
"license": "Apache-2.0",
"engines": {
"cordovaDependencies": {
"3.0.0": {
"cordova": ">100"
}
}
},
"devDependencies": {
"dmd-plugin-cordova-plugin": "^0.1.0",
"husky": "^0.10.1",
"jsdoc-to-markdown": "^1.2.0"
"jsdoc-to-markdown": "^1.2.0",
"jshint": "^2.6.0"
}
}

View File

@@ -22,7 +22,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:rim="http://www.blackberry.com/ns/widgets"
id="cordova-plugin-camera"
version="2.0.1-dev">
version="2.4.0">
<name>Camera</name>
<description>Cordova Camera Plugin</description>
<license>Apache 2.0</license>
@@ -30,9 +30,7 @@
<repo>https://git-wip-us.apache.org/repos/asf/cordova-plugin-camera.git</repo>
<issue>https://issues.apache.org/jira/browse/CB/component/12320645</issue>
<engines>
<engine name="cordova-android" version=">=5.0.0-dev" />
</engines>
<dependency id="cordova-plugin-compat" version="^1.1.0" />
<js-module src="www/CameraConstants.js" name="Camera">
<clobbers target="Camera" />
@@ -54,12 +52,12 @@
<feature name="Camera">
<param name="firefoxos-package" value="Camera" />
</feature>
</config-file>
</config-file>
<js-module src="src/firefoxos/CameraProxy.js" name="CameraProxy">
<runs />
</js-module>
</platform>
</platform>
<!-- android -->
<platform name="android">
@@ -71,16 +69,31 @@
<config-file target="AndroidManifest.xml" parent="/*">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</config-file>
<config-file target="AndroidManifest.xml" parent="application">
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true" >
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
</config-file>
<source-file src="src/android/CameraLauncher.java" target-dir="src/org/apache/cordova/camera" />
<source-file src="src/android/CordovaUri.java" target-dir="src/org/apache/cordova/camera" />
<source-file src="src/android/FileHelper.java" target-dir="src/org/apache/cordova/camera" />
<source-file src="src/android/ExifHelper.java" target-dir="src/org/apache/cordova/camera" />
<source-file src="src/android/xml/provider_paths.xml" target-dir="res/xml" />
<js-module src="www/CameraPopoverHandle.js" name="CameraPopoverHandle">
<clobbers target="CameraPopoverHandle" />
</js-module>
</js-module>
</platform>
<framework src="com.android.support:support-v4:24.1.1+" />
</platform>
<!-- amazon-fireos -->
<platform name="amazon-fireos">
@@ -102,7 +115,7 @@
</js-module>
</platform>
<!-- ubuntu -->
<platform name="ubuntu">
<config-file target="config.xml" parent="/*">
@@ -151,11 +164,21 @@
<framework src="MobileCoreServices.framework" />
<framework src="CoreGraphics.framework" />
<framework src="AVFoundation.framework" />
<preference name="CAMERA_USAGE_DESCRIPTION" default=" " />
<config-file target="*-Info.plist" parent="NSCameraUsageDescription">
<string>$CAMERA_USAGE_DESCRIPTION</string>
</config-file>
<preference name="PHOTOLIBRARY_USAGE_DESCRIPTION" default=" " />
<config-file target="*-Info.plist" parent="NSPhotoLibraryUsageDescription">
<string>$PHOTOLIBRARY_USAGE_DESCRIPTION</string>
</config-file>
<config-file target="*-Info.plist" parent="NSLocationWhenInUseUsageDescription">
<string></string>
</config-file>
</platform>
<!-- blackberry10 -->
@@ -226,7 +249,7 @@
<clobbers target="CameraPopoverHandle" />
</js-module>
<js-module src="src/windows/CameraProxy.js" name="CameraProxy">
<merges target="" />
<runs />
</js-module>
</platform>
@@ -253,10 +276,8 @@
<clobbers target="CameraPopoverHandle" />
</js-module>
<js-module src="src/windows/CameraProxy.js" name="CameraProxy">
<merges target="" />
<runs />
</js-module>
</platform>
</plugin>

File diff suppressed because it is too large Load Diff

104
src/android/CordovaUri.java Normal file
View File

@@ -0,0 +1,104 @@
/*
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.camera;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.support.v4.content.FileProvider;
import java.io.File;
/*
* This class exists because Andorid FilesProvider doesn't work on Android 4.4.4 and below and throws
* weird errors. I'm not sure why writing to shared cache directories is somehow verboten, but it is
* and this error is irritating for a Compatibility library to have.
*
*/
public class CordovaUri {
private Uri androidUri;
private String fileName;
private Uri fileUri;
/*
* We always expect a FileProvider string to be passed in for the file that we create
*
*/
CordovaUri (Uri inputUri)
{
//Determine whether the file is a content or file URI
if(inputUri.getScheme().equals("content"))
{
androidUri = inputUri;
fileName = getFileNameFromUri(androidUri);
fileUri = Uri.parse("file://" + fileName);
}
else
{
fileUri = inputUri;
fileName = FileHelper.stripFileProtocol(inputUri.toString());
}
}
public Uri getFileUri()
{
return fileUri;
}
public String getFilePath()
{
return fileName;
}
/*
* This only gets called by takePicture
*/
public Uri getCorrectUri()
{
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
return androidUri;
else
return fileUri;
}
/*
* This is dirty, but it does the job.
*
* Since the FilesProvider doesn't really provide you a way of getting a URL from the file,
* and since we actually need the Camera to create the file for us most of the time, we don't
* actually write the file, just generate the location based on a timestamp, we need to get it
* back from the Intent.
*
* However, the FilesProvider preserves the path, so we can at least write to it from here, since
* we own the context in this case.
*/
private String getFileNameFromUri(Uri uri) {
String fullUri = uri.toString();
String partial_path = fullUri.split("external_files")[1];
File external_storage = Environment.getExternalStorageDirectory();
String path = external_storage.getAbsolutePath() + partial_path;
return path;
}
}

View File

@@ -17,11 +17,13 @@
package org.apache.cordova.camera;
import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.content.CursorLoader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.webkit.MimeTypeMap;
@@ -53,13 +55,9 @@ public class FileHelper {
if (Build.VERSION.SDK_INT < 11)
realPath = FileHelper.getRealPathFromURI_BelowAPI11(cordova.getActivity(), uri);
// SDK >= 11 && SDK < 19
else if (Build.VERSION.SDK_INT < 19)
realPath = FileHelper.getRealPathFromURI_API11to18(cordova.getActivity(), uri);
// SDK > 19 (Android 4.4)
// SDK >= 11
else
realPath = FileHelper.getRealPathFromURI_API19(cordova.getActivity(), uri);
realPath = FileHelper.getRealPathFromURI_API11_And_Above(cordova.getActivity(), uri);
return realPath;
}
@@ -77,53 +75,71 @@ public class FileHelper {
}
@SuppressLint("NewApi")
public static String getRealPathFromURI_API19(Context context, Uri uri) {
String filePath = "";
try {
String wholeID = DocumentsContract.getDocumentId(uri);
public static String getRealPathFromURI_API11_And_Above(final Context context, final Uri uri) {
// Split at colon, use second item in the array
String id = wholeID.indexOf(":") > -1 ? wholeID.split(":")[1] : wholeID.indexOf(";") > -1 ? wholeID
.split(";")[1] : wholeID;
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
String[] column = { MediaStore.Images.Media.DATA };
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
// where id is equal to
String sel = MediaStore.Images.Media._ID + "=?";
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
Cursor cursor = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, column,
sel, new String[] { id }, null);
int columnIndex = cursor.getColumnIndex(column[0]);
if (cursor.moveToFirst()) {
filePath = cursor.getString(columnIndex);
// TODO handle non-primary volumes
}
cursor.close();
} catch (Exception e) {
filePath = "";
}
return filePath;
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
@SuppressLint("NewApi")
public static String getRealPathFromURI_API11to18(Context context, Uri contentUri) {
String[] proj = { MediaStore.Images.Media.DATA };
String result = null;
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
try {
CursorLoader cursorLoader = new CursorLoader(context, contentUri, proj, null, null, null);
Cursor cursor = cursorLoader.loadInBackground();
if (cursor != null) {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
result = cursor.getString(column_index);
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[] {
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
} catch (Exception e) {
result = null;
}
return result;
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(uri))
return uri.getLastPathSegment();
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
public static String getRealPathFromURI_BelowAPI11(Context context, Uri contentUri) {
@@ -228,4 +244,76 @@ public class FileHelper {
return mimeType;
}
/**
* Get the value of the data column for this Uri. This is useful for
* MediaStore Uris, and other file-based ContentProviders.
*
* @param context The context.
* @param uri The Uri to query.
* @param selection (Optional) Filter used in the query.
* @param selectionArgs (Optional) Selection arguments used in the query.
* @return The value of the _data column, which is typically a file path.
* @author paulburke
*/
public static String getDataColumn(Context context, Uri uri, String selection,
String[] selectionArgs) {
Cursor cursor = null;
final String column = "_data";
final String[] projection = {
column
};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst()) {
final int column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
} catch (Exception e) {
return null;
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
* @author paulburke
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
* @author paulburke
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
* @author paulburke
*/
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
public static boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}
}

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>

View File

@@ -18,6 +18,9 @@
* under the License.
*
*/
/* globals qnx, FileError, PluginResult */
var PictureSourceType = {
PHOTOLIBRARY : 0, // Choose image from picture library (same as SAVEDPHOTOALBUM for Android)
CAMERA : 1, // Take picture from camera
@@ -40,7 +43,7 @@ window.qnx.webplatform.getApplication().invocation.queryTargets(
},
function (error, targets) {
invokeAvailable = !error && targets && targets instanceof Array &&
targets.filter(function (t) { return t.default === 'sys.camera.card' }).length > 0;
targets.filter(function (t) { return t.default === 'sys.camera.card'; }).length > 0;
}
);
@@ -85,7 +88,7 @@ function showCameraDialog (done, cancel, fail) {
//create unique name for saved file (same pattern as BB10 camera app)
function imgName() {
var date = new Date(),
pad = function (n) { return n < 10 ? '0' + n : n };
pad = function (n) { return n < 10 ? '0' + n : n; };
return 'IMG_' + date.getFullYear() + pad(date.getMonth() + 1) + pad(date.getDate()) + '_' +
pad(date.getHours()) + pad(date.getMinutes()) + pad(date.getSeconds()) + '.png';
}
@@ -145,7 +148,7 @@ function encodeBase64(filePath, callback) {
default:
msg += "Unknown Error";
break;
};
}
// set it back to original value
window.qnx.webplatform.getController().setFileSystemSandbox = sandbox;

View File

@@ -19,17 +19,20 @@
*
*/
var HIGHEST_POSSIBLE_Z_INDEX = 2147483647;
function takePicture(success, error, opts) {
if (opts && opts[2] === 1) {
capture(success, error);
capture(success, error, opts);
} else {
var input = document.createElement('input');
input.style.position = 'relative';
input.style.zIndex = HIGHEST_POSSIBLE_Z_INDEX;
input.className = 'cordova-camera-select';
input.type = 'file';
input.name = 'files[]';
input.onchange = function(inputEvent) {
var canvas = document.createElement('canvas');
var reader = new FileReader();
reader.onload = function(readerEvent) {
input.parentNode.removeChild(input);
@@ -37,7 +40,7 @@ function takePicture(success, error, opts) {
var imageData = readerEvent.target.result;
return success(imageData.substr(imageData.indexOf(',') + 1));
}
};
reader.readAsDataURL(inputEvent.target.files[0]);
};
@@ -46,32 +49,51 @@ function takePicture(success, error, opts) {
}
}
function capture(success, errorCallback) {
function capture(success, errorCallback, opts) {
var localMediaStream;
var targetWidth = opts[3];
var targetHeight = opts[4];
targetWidth = targetWidth == -1?320:targetWidth;
targetHeight = targetHeight == -1?240:targetHeight;
var video = document.createElement('video');
var button = document.createElement('button');
var parent = document.createElement('div');
parent.style.position = 'relative';
parent.style.zIndex = HIGHEST_POSSIBLE_Z_INDEX;
parent.className = 'cordova-camera-capture';
parent.appendChild(video);
parent.appendChild(button);
video.width = 320;
video.height = 240;
video.width = targetWidth;
video.height = targetHeight;
button.innerHTML = 'Capture!';
button.onclick = function() {
// create a canvas and capture a frame from video stream
var canvas = document.createElement('canvas');
canvas.getContext('2d').drawImage(video, 0, 0, 320, 240);
canvas.width = targetWidth;
canvas.height = targetHeight;
canvas.getContext('2d').drawImage(video, 0, 0, targetWidth, targetHeight);
// convert image stored in canvas to base64 encoded image
var imageData = canvas.toDataURL('img/png');
var imageData = canvas.toDataURL('image/png');
imageData = imageData.replace('data:image/png;base64,', '');
// stop video stream, remove video and button
localMediaStream.stop();
video.parentNode.removeChild(video);
button.parentNode.removeChild(button);
// stop video stream, remove video and button.
// Note that MediaStream.stop() is deprecated as of Chrome 47.
if (localMediaStream.stop) {
localMediaStream.stop();
} else {
localMediaStream.getTracks().forEach(function (track) {
track.stop();
});
}
parent.parentNode.removeChild(parent);
return success(imageData);
}
};
navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
@@ -83,9 +105,8 @@ function capture(success, errorCallback) {
video.src = window.URL.createObjectURL(localMediaStream);
video.play();
document.body.appendChild(video);
document.body.appendChild(button);
}
document.body.appendChild(parent);
};
if (navigator.getUserMedia) {
navigator.getUserMedia({video: true, audio: true}, successCallback, errorCallback);

View File

@@ -19,6 +19,8 @@
*
*/
/* globals MozActivity */
function takePicture(success, error, opts) {
var pick = new MozActivity({
name: "pick",
@@ -32,7 +34,7 @@ function takePicture(success, error, opts) {
pick.onsuccess = function() {
// image is returned as Blob in this.result.blob
// we need to call success with url or base64 encoded image
if (opts && opts.destinationType == 0) {
if (opts && opts.destinationType === 0) {
// TODO: base64
return;
}

View File

@@ -240,9 +240,13 @@ static NSString* toBase64(NSData* data) {
- (void)repositionPopover:(CDVInvokedUrlCommand*)command
{
NSDictionary* options = [command argumentAtIndex:0 withDefault:nil];
if (([[self pickerController] pickerPopoverController] != nil) && [[[self pickerController] pickerPopoverController] isPopoverVisible]) {
[self displayPopover:options];
[[[self pickerController] pickerPopoverController] dismissPopoverAnimated:NO];
NSDictionary* options = [command argumentAtIndex:0 withDefault:nil];
[self displayPopover:options];
}
}
- (NSInteger)integerValueForKey:(NSDictionary*)dict key:(NSString*)key defaultValue:(NSInteger)defaultValue
@@ -358,24 +362,24 @@ static NSString* toBase64(NSData* data) {
// use image unedited as requested , don't resize
data = UIImageJPEGRepresentation(image, 1.0);
} else {
if (options.usesGeolocation) {
NSDictionary* controllerMetadata = [info objectForKey:@"UIImagePickerControllerMediaMetadata"];
if (controllerMetadata) {
self.data = data;
self.metadata = [[NSMutableDictionary alloc] init];
NSMutableDictionary* EXIFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyExifDictionary]mutableCopy];
if (EXIFDictionary) {
[self.metadata setObject:EXIFDictionary forKey:(NSString*)kCGImagePropertyExifDictionary];
}
if (IsAtLeastiOSVersion(@"8.0")) {
[[self locationManager] performSelector:NSSelectorFromString(@"requestWhenInUseAuthorization") withObject:nil afterDelay:0];
}
[[self locationManager] startUpdatingLocation];
data = UIImageJPEGRepresentation(image, [options.quality floatValue] / 100.0f);
}
if (options.usesGeolocation) {
NSDictionary* controllerMetadata = [info objectForKey:@"UIImagePickerControllerMediaMetadata"];
if (controllerMetadata) {
self.data = data;
self.metadata = [[NSMutableDictionary alloc] init];
NSMutableDictionary* EXIFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyExifDictionary]mutableCopy];
if (EXIFDictionary) {
[self.metadata setObject:EXIFDictionary forKey:(NSString*)kCGImagePropertyExifDictionary];
}
} else {
data = UIImageJPEGRepresentation(image, [options.quality floatValue] / 100.0f);
if (IsAtLeastiOSVersion(@"8.0")) {
[[self locationManager] performSelector:NSSelectorFromString(@"requestWhenInUseAuthorization") withObject:nil afterDelay:0];
}
[[self locationManager] startUpdatingLocation];
}
}
}
@@ -520,9 +524,11 @@ static NSString* toBase64(NSData* data) {
NSString* mediaType = [info objectForKey:UIImagePickerControllerMediaType];
if ([mediaType isEqualToString:(NSString*)kUTTypeImage]) {
[weakSelf resultForImage:cameraPicker.pictureOptions info:info completion:^(CDVPluginResult* res) {
[weakSelf.commandDelegate sendPluginResult:res callbackId:cameraPicker.callbackId];
weakSelf.hasPendingOperation = NO;
weakSelf.pickerController = nil;
if (![self usesGeolocation] || picker.sourceType != UIImagePickerControllerSourceTypeCamera) {
[weakSelf.commandDelegate sendPluginResult:res callbackId:cameraPicker.callbackId];
weakSelf.hasPendingOperation = NO;
weakSelf.pickerController = nil;
}
}];
}
else {
@@ -558,11 +564,14 @@ static NSString* toBase64(NSData* data) {
dispatch_block_t invoke = ^ (void) {
CDVPluginResult* result;
if ([ALAssetsLibrary authorizationStatus] == ALAuthorizationStatusAuthorized) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no image selected"];
} else {
if (picker.sourceType == UIImagePickerControllerSourceTypeCamera && [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] != ALAuthorizationStatusAuthorized) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"has no access to camera"];
} else if (picker.sourceType != UIImagePickerControllerSourceTypeCamera && [ALAssetsLibrary authorizationStatus] != ALAuthorizationStatusAuthorized) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"has no access to assets"];
} else {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no image selected"];
}
[weakSelf.commandDelegate sendPluginResult:result callbackId:cameraPicker.callbackId];

View File

@@ -79,10 +79,14 @@ var HIGHEST_POSSIBLE_Z_INDEX = 2147483647;
// Resize method
function resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType) {
var tempPhotoFileName = "";
var targetContentType = "";
if (encodingType == Camera.EncodingType.PNG) {
tempPhotoFileName = "camera_cordova_temp_return.png";
targetContentType = "image/png";
} else {
tempPhotoFileName = "camera_cordova_temp_return.jpg";
targetContentType = "image/jpeg";
}
var storageFolder = getAppData().localFolder;
@@ -108,7 +112,7 @@ function resizeImage(successCallback, errorCallback, file, targetWidth, targetHe
canvas.getContext("2d").drawImage(this, 0, 0, imageWidth, imageHeight);
var fileContent = canvas.toDataURL(file.contentType).split(',')[1];
var fileContent = canvas.toDataURL(targetContentType).split(',')[1];
var storageFolder = getAppData().localFolder;
@@ -325,14 +329,14 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
CaptureNS = Windows.Media.Capture,
sensor = null;
var createCameraUI = function () {
function createCameraUI() {
// create style for take and cancel buttons
var buttonStyle = "width:45%;padding: 10px 16px;font-size: 18px;line-height: 1.3333333;color: #333;background-color: #fff;border-color: #ccc; border: 1px solid transparent;border-radius: 6px; display: block; margin: 20px; z-index: 1000;border-color: #adadad;";
// Create fullscreen preview
// z-order style element for capturePreview and cameraCancelButton elts
// is necessary to avoid overriding by another page elements, -1 sometimes is not enough
capturePreview = document.createElement("video");
capturePreview = document.createElement("video");
capturePreview.style.cssText = "position: fixed; left: 0; top: 0; width: 100%; height: 100%; z-index: " + (HIGHEST_POSSIBLE_Z_INDEX - 1) + ";";
// Create capture button
@@ -349,14 +353,24 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
captureSettings = new CaptureNS.MediaCaptureInitializationSettings();
captureSettings.streamingCaptureMode = CaptureNS.StreamingCaptureMode.video;
};
}
var startCameraPreview = function () {
function continueVideoOnFocus() {
// if preview is defined it would be stuck, play it
if (capturePreview) {
capturePreview.play();
}
}
function startCameraPreview() {
// Search for available camera devices
// This is necessary to detect which camera (front or back) we should use
var DeviceEnum = Windows.Devices.Enumeration;
var expectedPanel = cameraDirection === 1 ? DeviceEnum.Panel.front : DeviceEnum.Panel.back;
// Add focus event handler to capture the event when user suspends the app and comes back while the preview is on
window.addEventListener("focus", continueVideoOnFocus);
DeviceEnum.DeviceInformation.findAllAsync(DeviceEnum.DeviceClass.videoCapture).then(function (devices) {
if (devices.length <= 0) {
destroyCameraPreview();
@@ -381,11 +395,17 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
if (FocusControl.supported === true) {
capturePreview.addEventListener('click', function () {
// Make sure function isn't called again before previous focus is completed
if (this.getAttribute('clicked') === '1') {
return false;
} else {
this.setAttribute('clicked', '1');
}
var preset = Windows.Media.Devices.FocusPreset.autoNormal;
var parent = this;
FocusControl.setPresetAsync(preset).done(function () {
// set the clicked attribute back to '0' to allow focus again
parent.setAttribute('clicked', '0');
});
});
}
@@ -437,9 +457,9 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
destroyCameraPreview();
errorCallback('Camera intitialization error ' + err);
});
};
}
var destroyCameraPreview = function () {
function destroyCameraPreview() {
// If sensor is available, remove event listener
if (sensor !== null) {
sensor.removeEventListener('orientationchanged', onOrientationChange);
@@ -453,6 +473,9 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
cameraCaptureButton.removeEventListener('click', onCameraCaptureButtonClick);
cameraCancelButton.removeEventListener('click', onCameraCancelButtonClick);
// Remove the focus event handler
window.removeEventListener("focus", continueVideoOnFocus);
// Remove elements
[capturePreview, cameraCaptureButton, cameraCancelButton].forEach(function (elem) {
if (elem /* && elem in document.body.childNodes */) {
@@ -465,9 +488,9 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
capture.stopRecordAsync();
capture = null;
}
};
}
var captureAction = function () {
function captureAction() {
var encodingProperties,
fileName,
@@ -529,9 +552,9 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
destroyCameraPreview();
errorCallback(err);
});
};
}
var getAspectRatios = function (capture) {
function getAspectRatios(capture) {
var videoDeviceController = capture.videoDeviceController;
var photoAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.photo).map(function (element) {
return (element.width / element.height).toFixed(1);
@@ -558,9 +581,9 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
return Object.keys(aspectObj).filter(function (k) {
return aspectObj[k] === 3;
});
};
}
var setAspectRatio = function (capture, aspect) {
function setAspectRatio(capture, aspect) {
// Max photo resolution with desired aspect ratio
var videoDeviceController = capture.videoDeviceController;
var photoResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.photo)
@@ -596,12 +619,12 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
.then(function () {
return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.videoRecord, videoRecordResolution);
});
};
}
/**
* When Capture button is clicked, try to capture a picture and return
*/
var onCameraCaptureButtonClick = function() {
function onCameraCaptureButtonClick() {
// Make sure user can't click more than once
if (this.getAttribute('clicked') === '1') {
return false;
@@ -609,12 +632,12 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
this.setAttribute('clicked', '1');
}
captureAction();
};
}
/**
* When Cancel button is clicked, destroy camera preview and return with error callback
*/
var onCameraCancelButtonClick = function() {
function onCameraCancelButtonClick() {
// Make sure user can't click more than once
if (this.getAttribute('clicked') === '1') {
return false;
@@ -623,15 +646,15 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
}
destroyCameraPreview();
errorCallback('no image selected');
};
}
/**
* When the phone orientation change, get the event and change camera preview rotation
* @param {Object} e - SimpleOrientationSensorOrientationChangedEventArgs
*/
var onOrientationChange = function (e) {
function onOrientationChange(e) {
setPreviewRotation(e.orientation);
};
}
/**
* Converts SimpleOrientation to a VideoRotation to remove difference between camera sensor orientation
@@ -639,7 +662,7 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
* @param {number} orientation - Windows.Devices.Sensors.SimpleOrientation
* @return {number} - Windows.Media.Capture.VideoRotation
*/
var orientationToRotation = function (orientation) {
function orientationToRotation(orientation) {
// VideoRotation enumerable and BitmapRotation enumerable have the same values
// https://msdn.microsoft.com/en-us/library/windows/apps/windows.media.capture.videorotation.aspx
// https://msdn.microsoft.com/en-us/library/windows/apps/windows.graphics.imaging.bitmaprotation.aspx
@@ -663,15 +686,15 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
// Falling back to portrait default
return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
}
};
}
/**
* Rotates the current MediaCapture's video
* @param {number} orientation - Windows.Devices.Sensors.SimpleOrientation
*/
var setPreviewRotation = function(orientation) {
function setPreviewRotation(orientation) {
capture.setPreviewRotation(orientationToRotation(orientation));
};
}
try {
createCameraUI();
@@ -704,9 +727,14 @@ function takePictureFromCameraWindows(successCallback, errorCallback, args) {
var UIMaxRes = WMCapture.CameraCaptureUIMaxPhotoResolution;
var totalPixels = targetWidth * targetHeight;
if (totalPixels <= 320 * 240) {
if (targetWidth == -1 && targetHeight == -1) {
maxRes = UIMaxRes.highestAvailable;
}
// Temp fix for CB-10539
/*else if (totalPixels <= 320 * 240) {
maxRes = UIMaxRes.verySmallQvga;
} else if (totalPixels <= 640 * 480) {
}*/
else if (totalPixels <= 640 * 480) {
maxRes = UIMaxRes.smallVga;
} else if (totalPixels <= 1024 * 768) {
maxRes = UIMaxRes.mediumXga;
@@ -720,21 +748,48 @@ function takePictureFromCameraWindows(successCallback, errorCallback, args) {
cameraCaptureUI.photoSettings.maxResolution = maxRes;
cameraCaptureUI.captureFileAsync(WMCapture.CameraCaptureUIMode.photo).done(function(picture) {
if (!picture) {
errorCallback("User didn't capture a photo.");
return;
}
var cameraPicture;
savePhoto(picture, {
// define focus handler for windows phone 10.0
var savePhotoOnFocus = function () {
window.removeEventListener("focus", savePhotoOnFocus);
// call only when the app is in focus again
savePhoto(cameraPicture, {
destinationType: destinationType,
targetHeight: targetHeight,
targetWidth: targetWidth,
encodingType: encodingType,
saveToPhotoAlbum: saveToPhotoAlbum
}, successCallback, errorCallback);
}, function() {
};
// if windows phone 10, add and delete focus eventHandler to capture the focus back from cameraUI to app
if (navigator.appVersion.indexOf('Windows Phone 10.0') >= 0) {
window.addEventListener("focus", savePhotoOnFocus);
}
cameraCaptureUI.captureFileAsync(WMCapture.CameraCaptureUIMode.photo).done(function (picture) {
if (!picture) {
errorCallback("User didn't capture a photo.");
// Remove the focus handler if present
window.removeEventListener("focus", savePhotoOnFocus);
return;
}
cameraPicture = picture;
// If not windows 10, call savePhoto() now. If windows 10, wait for the app to be in focus again
if (navigator.appVersion.indexOf('Windows Phone 10.0') < 0) {
savePhoto(cameraPicture, {
destinationType: destinationType,
targetHeight: targetHeight,
targetWidth: targetWidth,
encodingType: encodingType,
saveToPhotoAlbum: saveToPhotoAlbum
}, successCallback, errorCallback);
}
}, function () {
errorCallback("Fail to capture a photo.");
window.removeEventListener("focus", savePhotoOnFocus);
});
}

View File

@@ -21,9 +21,6 @@
#import <XCTest/XCTest.h>
#import "CDVCamera.h"
#import "UIImage+CropScaleOrientation.h"
#import <Cordova/NSArray+Comparisons.h>
#import <Cordova/NSData+Base64.h>
#import <Cordova/NSDictionary+Extensions.h>
#import <MobileCoreServices/UTCoreTypes.h>
@@ -291,12 +288,14 @@
// test 640x480
targetSize = CGSizeMake(640, 480);
targetSize = CGSizeMake(480, 640);
targetImage = [sourceImagePortrait imageByScalingNotCroppingForSize:targetSize];
XCTAssertEqual(targetImage.size.width, targetSize.width);
XCTAssertEqual(targetImage.size.height, targetSize.height);
targetSize = CGSizeMake(640, 480);
targetImage = [sourceImageLandscape imageByScalingNotCroppingForSize:targetSize];
XCTAssertEqual(targetImage.size.width, targetSize.width);
XCTAssertEqual(targetImage.size.height, targetSize.height);
@@ -304,24 +303,28 @@
// test 800x600
targetSize = CGSizeMake(800, 600);
targetSize = CGSizeMake(600, 800);
targetImage = [sourceImagePortrait imageByScalingNotCroppingForSize:targetSize];
XCTAssertEqual(targetImage.size.width, targetSize.width);
XCTAssertEqual(targetImage.size.height, targetSize.height);
targetSize = CGSizeMake(800, 600);
targetImage = [sourceImageLandscape imageByScalingNotCroppingForSize:targetSize];
XCTAssertEqual(targetImage.size.width, targetSize.width);
XCTAssertEqual(targetImage.size.height, targetSize.height);
// test 1024x768
targetSize = CGSizeMake(1024, 768);
targetSize = CGSizeMake(768, 1024);
targetImage = [sourceImagePortrait imageByScalingNotCroppingForSize:targetSize];
XCTAssertEqual(targetImage.size.width, targetSize.width);
XCTAssertEqual(targetImage.size.height, targetSize.height);
targetSize = CGSizeMake(1024, 768);
targetImage = [sourceImageLandscape imageByScalingNotCroppingForSize:targetSize];
XCTAssertEqual(targetImage.size.width, targetSize.width);
XCTAssertEqual(targetImage.size.height, targetSize.height);

View File

@@ -5,9 +5,9 @@
"author": "Apache Software Foundation",
"license": "Apache Version 2.0",
"dependencies": {
"cordova-ios": "^3.7.0"
"cordova-ios": "*"
},
"scripts": {
"test": "xcodebuild -scheme CordovaLib && xcodebuild test -scheme CDVCameraLibTests -destination 'platform=iOS Simulator,name=iPhone 5'"
}
}
}
}

View File

@@ -22,10 +22,10 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:rim="http://www.blackberry.com/ns/widgets"
id="cordova-plugin-camera-tests"
version="2.0.1-dev">
version="2.4.0">
<name>Cordova Camera Plugin Tests</name>
<license>Apache 2.0</license>
<dependency id="cordova-plugin-file" version=">=2.0.0" />
<js-module src="tests.js" name="tests">

View File

@@ -19,6 +19,9 @@
*
*/
/* globals Camera, resolveLocalFileSystemURL, FileEntry, CameraPopoverOptions, FileTransfer, FileUploadOptions, LocalFileSystem, MSApp */
/* jshint jasmine: true */
exports.defineAutoTests = function () {
describe('Camera (navigator.camera)', function () {
it("should exist", function () {
@@ -78,7 +81,6 @@ exports.defineAutoTests = function () {
/******************************************************************************/
exports.defineManualTests = function (contentEl, createActionButton) {
var platformId = cordova.require('cordova/platform').id;
var pictureUrl = null;
var fileObj = null;
var fileEntry = null;
@@ -94,11 +96,6 @@ exports.defineManualTests = function (contentEl, createActionButton) {
var camCorrectOrientationDefault = ['correctOrientation', false];
var camSaveToPhotoAlbumDefault = ['saveToPhotoAlbum', true];
var clearLog = function () {
var log = document.getElementById('info');
log.innerHTML = "";
}
function log(value) {
console.log(value);
document.getElementById('camera_status').textContent += (new Date() - pageStartTime) / 1000 + ': ' + value + '\n';
@@ -121,16 +118,19 @@ exports.defineManualTests = function (contentEl, createActionButton) {
url = "data:image/jpeg;base64," + url;
} catch (e) {
// not DATA_URL
log('URL: ' + url.slice(0, 100));
}
log('URL: "' + url.slice(0, 90) + '"');
pictureUrl = url;
var img = document.getElementById('camera_image');
var startTime = new Date();
img.src = url;
img.onloadend = function () {
img.onload = function () {
log('Img size: ' + img.naturalWidth + 'x' + img.naturalHeight);
log('Image tag load time: ' + (new Date() - startTime));
callback && callback();
if (callback) {
callback();
}
};
}
@@ -141,12 +141,13 @@ exports.defineManualTests = function (contentEl, createActionButton) {
function getPictureWin(data) {
setPicture(data);
// TODO: Fix resolveLocalFileSystemURI to work with native-uri.
if (pictureUrl.indexOf('file:') == 0 || pictureUrl.indexOf('content:') == 0 || pictureUrl.indexOf('ms-appdata:') === 0) {
resolveLocalFileSystemURI(data, function (e) {
if (pictureUrl.indexOf('file:') === 0 || pictureUrl.indexOf('content:') === 0 || pictureUrl.indexOf('ms-appdata:') === 0 || pictureUrl.indexOf('assets-library:') === 0) {
resolveLocalFileSystemURL(data, function (e) {
fileEntry = e;
logCallback('resolveLocalFileSystemURI()', true)(e.toURL());
}, logCallback('resolveLocalFileSystemURI()', false));
} else if (pictureUrl.indexOf('data:image/jpeg;base64') == 0) {
logCallback('resolveLocalFileSystemURL()', true)(e.toURL());
readFile();
}, logCallback('resolveLocalFileSystemURL()', false));
} else if (pictureUrl.indexOf('data:image/jpeg;base64') === 0) {
// do nothing
} else {
var path = pictureUrl.replace(/^file:\/\/(localhost)?/, '').replace(/%20/g, ' ');
@@ -164,13 +165,11 @@ exports.defineManualTests = function (contentEl, createActionButton) {
window.onorientationchange = function () {
var newPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, 0);
popoverHandle.setPosition(newPopoverOptions);
}
};
}
function uploadImage() {
var ft = new FileTransfer(),
uploadcomplete = 0,
progress = 0,
options = new FileUploadOptions();
options.fileKey = "photo";
options.fileName = 'test.jpg';
@@ -206,7 +205,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
img.style.display = "block";
img.src = evt.target.result;
log("FileReader.readAsDataURL success");
};
}
function onFileReceived(file) {
log('Got file: ' + JSON.stringify(file));
@@ -217,8 +216,10 @@ exports.defineManualTests = function (contentEl, createActionButton) {
log('FileReader.readAsDataURL() - length = ' + reader.result.length);
};
reader.onerror = logCallback('FileReader.readAsDataURL', false);
reader.onloadend = onFileReadAsDataURL;
reader.readAsDataURL(file);
};
}
// Test out onFileReceived when the file object was set via a native <input> elements.
if (fileObj) {
onFileReceived(fileObj);
@@ -226,13 +227,14 @@ exports.defineManualTests = function (contentEl, createActionButton) {
fileEntry.file(onFileReceived, logCallback('FileEntry.file', false));
}
}
function getFileInfo() {
// Test FileEntry API here.
fileEntry.getMetadata(logCallback('FileEntry.getMetadata', true), logCallback('FileEntry.getMetadata', false));
fileEntry.setMetadata(logCallback('FileEntry.setMetadata', true), logCallback('FileEntry.setMetadata', false), { "com.apple.MobileBackup": 1 });
fileEntry.getParent(logCallback('FileEntry.getParent', true), logCallback('FileEntry.getParent', false));
fileEntry.getParent(logCallback('FileEntry.getParent', true), logCallback('FileEntry.getParent', false));
};
}
/**
* Copy image from library using a NATIVE_URI destination type
@@ -249,7 +251,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
//cleanup
//rename moved file back to original name so other tests can reference image
resolveLocalFileSystemURI(destDirEntry.nativeURL+'moved_file.png', function(fileEntry) {
resolveLocalFileSystemURL(destDirEntry.nativeURL+'moved_file.png', function(fileEntry) {
fileEntry.moveTo(destDirEntry, origName, logCallback('FileEntry.moveTo', true), logCallback('FileEntry.moveTo', false));
console.log('Cleanup: successfully renamed file back to original name');
}, function () {
@@ -257,7 +259,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
});
//remove copied file
resolveLocalFileSystemURI(destDirEntry.nativeURL+'copied_file.png', function(fileEntry) {
resolveLocalFileSystemURL(destDirEntry.nativeURL+'copied_file.png', function(fileEntry) {
fileEntry.remove(logCallback('FileEntry.remove', true), logCallback('FileEntry.remove', false));
console.log('Cleanup: successfully removed copied file');
}, function () {
@@ -266,7 +268,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
};
window.requestFileSystem(LocalFileSystem.TEMPORARY, 0, onFileSystemReceived, null);
};
}
/**
* Write image to library using a NATIVE_URI destination type
@@ -287,7 +289,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
fileEntry.createWriter(onFileWriterReceived, logCallback('FileEntry.createWriter', false));
fileEntry.createWriter(onFileTruncateWriterReceived, null);
};
}
function displayImageUsingCanvas() {
var canvas = document.getElementById('canvas');
@@ -300,7 +302,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
canvas.height = h;
var context = canvas.getContext('2d');
context.drawImage(img, 0, 0, w, h);
};
}
/**
* Remove image from library using a NATIVE_URI destination type
@@ -308,7 +310,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
*/
function removeImage() {
fileEntry.remove(logCallback('FileEntry.remove', true), logCallback('FileEntry.remove', false));
};
}
function testInputTag(inputEl) {
clearStatus();
@@ -317,7 +319,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
window.setTimeout(function () {
testNativeFile2(inputEl);
}, 0);
};
}
function testNativeFile2(inputEl) {
if (!inputEl.value) {
@@ -347,24 +349,28 @@ exports.defineManualTests = function (contentEl, createActionButton) {
function extractOptions() {
var els = document.querySelectorAll('#image-options select');
var ret = {};
/*jshint -W084 */
for (var i = 0, el; el = els[i]; ++i) {
var value = el.value;
if (value === '') continue;
value = +value;
if (el.isBool) {
ret[el.getAttribute("name")] = !!+value;
ret[el.getAttribute("name")] = !!value;
} else {
ret[el.getAttribute("name")] = +value;
ret[el.getAttribute("name")] = value;
}
}
/*jshint +W084 */
return ret;
}
function createOptionsEl(name, values, selectionDefault) {
var openDiv = '<div style="display: inline-block">' + name + ': ';
var select = '<select name=' + name + '>';
var select = '<select name=' + name + ' id="' + name + '">';
var defaultOption = '';
if (selectionDefault == undefined) {
if (selectionDefault === undefined) {
defaultOption = '<option value="">default</option>';
}
@@ -460,7 +466,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
var elements = document.getElementsByClassName("testInputTag");
var listener = function (e) {
testInputTag(e.target);
}
};
for (var i = 0; i < elements.length; ++i) {
var item = elements[i];
item.addEventListener("change", listener, false);

174
types/index.d.ts vendored Normal file
View File

@@ -0,0 +1,174 @@
// Type definitions for Apache Cordova Camera plugin
// Project: https://github.com/apache/cordova-plugin-camera
// Definitions by: Microsoft Open Technologies Inc <http://msopentech.com>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
//
// Copyright (c) Microsoft Open Technologies Inc
// Licensed under the MIT license.
interface Navigator {
/**
* This plugin provides an API for taking pictures and for choosing images from the system's image library.
*/
camera: Camera;
}
/**
* This plugin provides an API for taking pictures and for choosing images from the system's image library.
*/
interface Camera {
/**
* Removes intermediate photos taken by the camera from temporary storage.
* @param onSuccess Success callback, that called when cleanup succeeds.
* @param onError Error callback, that get an error message.
*/
cleanup(
onSuccess: () => void,
onError: (message: string) => void): void;
/**
* Takes a photo using the camera, or retrieves a photo from the device's image gallery.
* @param cameraSuccess Success callback, that get the image
* as a base64-encoded String, or as the URI for the image file.
* @param cameraError Error callback, that get an error message.
* @param cameraOptions Optional parameters to customize the camera settings.
*/
getPicture(
cameraSuccess: (data: string) => void,
cameraError: (message: string) => void,
cameraOptions?: CameraOptions): void;
// Next will work only on iOS
//getPicture(
// cameraSuccess: (data: string) => void,
// cameraError: (message: string) => void,
// cameraOptions?: CameraOptions): CameraPopoverHandle;
}
interface CameraOptions {
/** Picture quality in range 0-100. Default is 50 */
quality?: number;
/**
* Choose the format of the return value.
* Defined in navigator.camera.DestinationType. Default is FILE_URI.
* DATA_URL : 0, Return image as base64-encoded string
* FILE_URI : 1, Return image file URI
* NATIVE_URI : 2 Return image native URI
* (e.g., assets-library:// on iOS or content:// on Android)
*/
destinationType?: number;
/**
* Set the source of the picture.
* Defined in navigator.camera.PictureSourceType. Default is CAMERA.
* PHOTOLIBRARY : 0,
* CAMERA : 1,
* SAVEDPHOTOALBUM : 2
*/
sourceType?: number;
/** Allow simple editing of image before selection. */
allowEdit?: boolean;
/**
* Choose the returned image file's encoding.
* Defined in navigator.camera.EncodingType. Default is JPEG
* JPEG : 0 Return JPEG encoded image
* PNG : 1 Return PNG encoded image
*/
encodingType?: number;
/**
* Width in pixels to scale image. Must be used with targetHeight.
* Aspect ratio remains constant.
*/
targetWidth?: number;
/**
* Height in pixels to scale image. Must be used with targetWidth.
* Aspect ratio remains constant.
*/
targetHeight?: number;
/**
* Set the type of media to select from. Only works when PictureSourceType
* is PHOTOLIBRARY or SAVEDPHOTOALBUM. Defined in nagivator.camera.MediaType
* PICTURE: 0 allow selection of still pictures only. DEFAULT.
* Will return format specified via DestinationType
* VIDEO: 1 allow selection of video only, WILL ALWAYS RETURN FILE_URI
* ALLMEDIA : 2 allow selection from all media types
*/
mediaType?: number;
/** Rotate the image to correct for the orientation of the device during capture. */
correctOrientation?: boolean;
/** Save the image to the photo album on the device after capture. */
saveToPhotoAlbum?: boolean;
/**
* Choose the camera to use (front- or back-facing).
* Defined in navigator.camera.Direction. Default is BACK.
* FRONT: 0
* BACK: 1
*/
cameraDirection?: number;
/** iOS-only options that specify popover location in iPad. Defined in CameraPopoverOptions. */
popoverOptions?: CameraPopoverOptions;
}
/**
* A handle to the popover dialog created by navigator.camera.getPicture. Used on iOS only.
*/
interface CameraPopoverHandle {
/**
* Set the position of the popover.
* @param popoverOptions the CameraPopoverOptions that specify the new position.
*/
setPosition(popoverOptions: CameraPopoverOptions): void;
}
/**
* iOS-only parameters that specify the anchor element location and arrow direction
* of the popover when selecting images from an iPad's library or album.
*/
interface CameraPopoverOptions {
x: number;
y: number;
width: number;
height: number;
/**
* Direction the arrow on the popover should point. Defined in Camera.PopoverArrowDirection
* Matches iOS UIPopoverArrowDirection constants.
* ARROW_UP : 1,
* ARROW_DOWN : 2,
* ARROW_LEFT : 4,
* ARROW_RIGHT : 8,
* ARROW_ANY : 15
*/
arrowDir : number;
}
declare var Camera: {
// Camera constants, defined in Camera plugin
DestinationType: {
DATA_URL: number;
FILE_URI: number;
NATIVE_URI: number
}
Direction: {
BACK: number;
FRONT: number;
}
EncodingType: {
JPEG: number;
PNG: number;
}
MediaType: {
PICTURE: number;
VIDEO: number;
ALLMEDIA: number;
}
PictureSourceType: {
PHOTOLIBRARY: number;
CAMERA: number;
SAVEDPHOTOALBUM: number;
}
// Used only on iOS
PopoverArrowDirection: {
ARROW_UP: number;
ARROW_DOWN: number;
ARROW_LEFT: number;
ARROW_RIGHT: number;
ARROW_ANY: number;
}
};

View File

@@ -25,7 +25,7 @@ var argscheck = require('cordova/argscheck'),
// XXX: commented out
//CameraPopoverHandle = require('./CameraPopoverHandle');
/**
/**
* @namespace navigator
*/
@@ -80,37 +80,31 @@ for (var key in Camera) {
/**
* @description Takes a photo using the camera, or retrieves a photo from the device's
* image gallery. The image is passed to the success callback as a
* base64-encoded `String`, or as the URI for the image file.
*
* Base64-encoded `String`, or as the URI for the image file.
*
* The `camera.getPicture` function opens the device's default camera
* application that allows users to snap pictures by default - this behavior occurs,
* when `Camera.sourceType` equals [`Camera.PictureSourceType.CAMERA`]{@link module:Camera.PictureSourceType}.
* when `Camera.sourceType` equals [`Camera.PictureSourceType.CAMERA`]{@link module:Camera.PictureSourceType}.
* Once the user snaps the photo, the camera application closes and the application is restored.
*
*
* If `Camera.sourceType` is `Camera.PictureSourceType.PHOTOLIBRARY` or
* `Camera.PictureSourceType.SAVEDPHOTOALBUM`, then a dialog displays
* that allows users to select an existing image. The
* `camera.getPicture` function returns a [`CameraPopoverHandle`]{@link module:CameraPopoverHandle} object,
* which can be used to reposition the image selection dialog, for
* example, when the device orientation changes.
*
* that allows users to select an existing image.
*
* The return value is sent to the [`cameraSuccess`]{@link module:camera.onSuccess} callback function, in
* one of the following formats, depending on the specified
* `cameraOptions`:
*
* - A `String` containing the base64-encoded photo image.
*
*
* - A `String` containing the Base64-encoded photo image.
* - A `String` representing the image file location on local storage (default).
*
*
* You can do whatever you want with the encoded image or URI, for
* example:
*
*
* - Render the image in an `<img>` tag, as in the example below
*
* - Save the data locally (`LocalStorage`, [Lawnchair](http://brianleroux.github.com/lawnchair/), etc.)
*
* - Post the data to a remote server
*
*
* __NOTE__: Photo resolution on newer devices is quite good. Photos
* selected from the device's gallery are not downscaled to a lower
* quality, even if a `quality` parameter is specified. To avoid common
@@ -119,11 +113,18 @@ for (var key in Camera) {
*
* __Supported Platforms__
*
* ![](doc/img/android-success.png) ![](doc/img/blackberry-success.png) ![](doc/img/browser-success.png) ![](doc/img/firefox-success.png) ![](doc/img/fireos-success.png) ![](doc/img/ios-success.png) ![](doc/img/windows-success.png) ![](doc/img/wp8-success.png) ![](doc/img/ubuntu-success.png)
* - Android
* - BlackBerry
* - Browser
* - Firefox
* - FireOS
* - iOS
* - Windows
* - WP8
* - Ubuntu
*
* * [More examples](#camera-getPicture-examples)
* More examples [here](#camera-getPicture-examples). Quirks [here](#camera-getPicture-quirks).
*
* * [Quirks](#camera-getPicture-quirks)
* @example
* navigator.camera.getPicture(cameraSuccess, cameraError, cameraOptions);
* @param {module:camera.onSuccess} successCallback
@@ -164,11 +165,11 @@ cameraExport.getPicture = function(successCallback, errorCallback, options) {
*
* __Supported Platforms__
*
* ![](doc/img/android-fail.png) ![](doc/img/blackberry-fail.png) ![](doc/img/browser-fail.png) ![](doc/img/firefox-fail.png) ![](doc/img/fireos-fail.png) ![](doc/img/ios-success.png) ![](doc/img/windows-fail.png) ![](doc/img/wp8-fail.png) ![](doc/img/ubuntu-fail.png)
* - iOS
*
* @example
* navigator.camera.cleanup(onSuccess, onFail);
*
*
* function onSuccess() {
* console.log("Camera cleanup success.")
* }

View File

@@ -24,10 +24,17 @@
*/
module.exports = {
/**
* @description
* Defines the output format of `Camera.getPicture` call.
* _Note:_ On iOS passing `DestinationType.NATIVE_URI` along with
* `PictureSourceType.PHOTOLIBRARY` or `PictureSourceType.SAVEDPHOTOALBUM` will
* disable any image modifications (resize, quality change, cropping, etc.) due
* to implementation specific.
*
* @enum {number}
*/
DestinationType:{
/** Return base64 encoded string */
/** Return base64 encoded string. DATA_URL can be very memory intensive and cause app crashes or out of memory errors. Use FILE_URI or NATIVE_URI if possible */
DATA_URL: 0,
/** Return file uri (content://media/external/images/media/2 for Android) */
FILE_URI: 1,
@@ -55,14 +62,20 @@ module.exports = {
ALLMEDIA : 2
},
/**
* @description
* Defines the output format of `Camera.getPicture` call.
* _Note:_ On iOS passing `PictureSourceType.PHOTOLIBRARY` or `PictureSourceType.SAVEDPHOTOALBUM`
* along with `DestinationType.NATIVE_URI` will disable any image modifications (resize, quality
* change, cropping, etc.) due to implementation specific.
*
* @enum {number}
*/
PictureSourceType:{
/** Choose image from picture library (same as SAVEDPHOTOALBUM for Android) */
/** Choose image from the device's photo library (same as SAVEDPHOTOALBUM for Android) */
PHOTOLIBRARY : 0,
/** Take picture from camera */
CAMERA : 1,
/** Choose image from picture library (same as PHOTOLIBRARY for Android) */
/** Choose image only from the device's Camera Roll album (same as PHOTOLIBRARY for Android) */
SAVEDPHOTOALBUM : 2
},
/**

View File

@@ -19,10 +19,8 @@
*
*/
var exec = require('cordova/exec');
/**
* @ignore in favour of ios' one
* @ignore in favour of iOS' one
* A handle to an image picker popover.
*/
var CameraPopoverHandle = function() {

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*
*/
*/
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('back').onclick = function () {

View File

@@ -21,7 +21,7 @@
var exec = require('cordova/exec');
/**
/**
* @namespace navigator
*/
@@ -30,25 +30,31 @@ var exec = require('cordova/exec');
*
* __Supported Platforms__
*
* ![](doc/img/android-fail.png) ![](doc/img/blackberry-fail.png) ![](doc/img/browser-fail.png) ![](doc/img/firefox-fail.png) ![](doc/img/fireos-fail.png) ![](doc/img/ios-success.png) ![](doc/img/windows-fail.png) ![](doc/img/wp8-fail.png) ![](doc/img/ubuntu-fail.png)
* - iOS
*
* @example
* var cameraPopoverHandle = navigator.camera.getPicture(onSuccess, onFail,
* {
* navigator.camera.getPicture(onSuccess, onFail,
* {
* destinationType: Camera.DestinationType.FILE_URI,
* sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
* popoverOptions: new CameraPopoverOptions(300, 300, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY)
* });
*
*
* // Reposition the popover if the orientation changes.
* window.onorientationchange = function() {
* var cameraPopoverHandle = new CameraPopoverHandle();
* var cameraPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY);
* cameraPopoverHandle.setPosition(cameraPopoverOptions);
* }
* @module CameraPopoverHandle
*/
var CameraPopoverHandle = function() {
/** Set the position of the popover.
/**
* Can be used to reposition the image selection dialog,
* for example, when the device orientation changes.
* @memberof CameraPopoverHandle
* @instance
* @method setPosition
* @param {module:CameraPopoverOptions} popoverOptions
*/
this.setPosition = function(popoverOptions) {