Compare commits

...

145 Commits

Author SHA1 Message Date
Steve Gill d456eeb711 CB-13542 Updated version and RELEASENOTES.md for release 3.0.0 (via coho) 2017-11-06 14:37:41 -08:00
Steve Gill f761934814 Merge pull request #290 from JoseExposito/master
CB-13421: (osx) Added macOS support
2017-11-03 22:21:09 -07:00
Steve Gill 43b3a911c2 Merge pull request #297 from jcesarmobile/CB-13515
CB-13515 (all): Add 'protective' entry to cordovaDependencies
2017-11-01 13:39:41 -07:00
Julio César 8f033f53db CB-13515 (all): Add 'protective' entry to cordovaDependencies 2017-11-01 12:04:34 +01:00
Julio César 51d665a7a1 CB-13332 (iOS): document NSPhotoLibraryAddUsageDescription
This closes #296
2017-10-21 17:27:52 +02:00
Julio César 9f480b10cb CB-13264 (iOS): Remove ios usage descriptions
This closes #295
2017-10-21 16:45:13 +02:00
Alexander Sorokin a6706aef5f CB-13473: (CI) Removed browser builds from AppVeyor 2017-10-20 10:11:26 +03:00
Alexander Sorokin fb213b2b38 CB-13472: (CI) Fixed Travis Android builds again 2017-10-20 08:58:12 +03:00
Julio César 3e24b15934 CB-13446: Sync template with previous doc changes
This closes #291
2017-10-19 18:38:08 -07:00
Kelvin Dart 2acda2d2f9 CB-13294 Remove cordova-plugin-compat
This closes #293
2017-10-19 18:28:14 -07:00
José Expósito 97d2014d62 Adds macOS support 2017-10-10 17:11:10 +02:00
Alexander Sorokin b2107e0818 CB-13299 (CI) Fix Android builds 2017-09-19 08:58:14 +03:00
Audrey So 16c4325fef CB-12895 : updated src files to use eslint format 2017-07-31 13:29:36 -07:00
Audrey So 80342b0ddd CB-12985 : setup eslint and removed jshint 2017-07-31 11:28:15 -07:00
Alexander Sorokin 55d419a36e CB-13028 (CI) Browser builds on Travis and AppVeyor 2017-07-25 14:50:55 +03:00
Alexander Sorokin 5574490bc3 CB-13002: (Android, iOS) Fix occasional Appium tests failures 2017-07-10 14:51:10 +03:00
Alexander Sorokin f1b0ccec56 CB-13000: (CI) Speed up android builds 2017-07-10 10:02:24 +03:00
Alexander Sorokin e69240f251 CB-12991: (CI) Updated CI badges 2017-07-06 13:58:47 +03:00
Alexander Sorokin 5b3a99a6db CB-12990 (iOS) Optimized Appium tests 2017-07-06 12:04:19 +03:00
José Luis Ballesteros del Val 3b8f64e330 CB-12964: (android) Fix of bug when Pictures folder did not exist.
If someone had removed Pictures folder in android, plugin failed trying to move there the new photo. This happened because plugin did not check the existence of this folder, and if not, it did not complete the folder tree.

This closes #273
2017-07-04 14:03:10 +03:00
Alexander Sorokin f667ba6179 CB-12982: (Android, iOS) Appium tests: try to create a session harder 2017-07-03 18:01:34 +03:00
Alexander Sorokin 77539558e6 CB-12935: (windows) Enable paramedic builds on AppVeyor 2017-06-30 09:30:25 +03:00
Alexander Sorokin cba99940a7 CB-12935: (android, ios) Run paramedic tests on Travis 2017-06-23 09:56:20 +03:00
Jacques de Villiers 7155b636d5 Incorrect "JIRA issue tracker" link in Readme 2017-06-19 14:37:15 +01:00
Shazron Abdullah 160ed2d03f CB-12912 - Incorrect 'Report Bugs' link in README
This closes #270
2017-06-14 01:59:09 -07:00
Sergii Stotskyi c3d7e55ad4 CB-12682: (ios, android): changes cancel error message to be consistent for ios android 2017-06-07 07:36:30 +03:00
Alexander Sorokin 1b218cd8a0 CB-12764: (android) Adapt Appium tests for Android 7 2017-06-02 15:02:10 +03:00
filmaj 51e5c02dcb CB-12847: fixed bugs entry in package.json. 2017-05-25 12:35:40 +02:00
filmaj 26d13b0245 CB-12847: added bugs entry to package.json. 2017-05-24 21:11:53 +02:00
filmaj ef617cc943 Close #265 2017-05-10 09:15:40 -07:00
filmaj 1422b0a4f2 Set VERSION to 2.4.2-dev (via coho) 2017-04-27 10:27:41 -07:00
filmaj ba9a803b69 CB-12736 Updated version and RELEASENOTES.md for release 2.4.1 2017-04-27 10:26:08 -07:00
Alexander Sorokin 9615620843 CB-12622: Updated build badges in README 2017-04-26 14:29:16 +03:00
Nikita Matrosov a33c35152e CB-12650: Fix manual test for uploading image 2017-04-25 15:25:20 +03:00
filmaj eb98015e8a Close #256 2017-04-24 16:40:08 -07:00
filmaj 3c48ea9c9c Close #258 2017-04-24 16:30:53 -07:00
Steve Gill 2286bb3bb2 CB-12685: added package.json to tests folder 2017-04-21 18:15:33 -07:00
Alexander Sorokin 9badea4c95 CB-12622: (android) Appium tests: Bust Android 6 and 7 permission dialogs 2017-04-20 14:58:03 +03:00
Alexander Sorokin 180f7b5510 CB-12618: (android) Appium tests: Handle native cling 2017-03-30 17:52:15 +03:00
Steve Gill d0b381aad8 CB-12519 updated incorrect version in RELEASENOTES 2017-03-02 16:06:15 -08:00
Steve Gill 926fbf0e8c Set VERSION to 2.4.1-dev (via coho) 2017-03-02 15:44:42 -08:00
Steve Gill e4ff41c07c CB-12519 Updated version and RELEASENOTES.md for release 2.4.0 2017-02-28 17:41:23 -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
33 changed files with 3572 additions and 1190 deletions
+28
View File
@@ -0,0 +1,28 @@
# appveyor file
# http://www.appveyor.com/docs/appveyor-yml
max_jobs: 1
shallow_clone: true
init:
- git config --global core.autocrlf true
image:
- Visual Studio 2017
environment:
nodejs_version: "4"
matrix:
- PLATFORM: windows-10-store
JUST_BUILD: --justBuild
install:
- npm cache clean -f
- node --version
- npm install -g cordova-paramedic@https://github.com/apache/cordova-paramedic.git
- npm install -g cordova
build: off
test_script:
- cordova-paramedic --config pr\%PLATFORM% --plugin . %JUST_BUILD%
+10
View File
@@ -0,0 +1,10 @@
root: true
extends: semistandard
rules:
indent:
- error
- 4
camelcase: off
padded-blocks: off
operator-linebreak: off
no-throw-literal: off
+22
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
View File
@@ -1,16 +0,0 @@
{
"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
}
}
+90 -6
View File
@@ -1,8 +1,92 @@
language: objective-c
sudo: false
node_js:
- "4.2"
addons:
jwt:
secure: QivPLlqTVvOo3TJeHxuBOfxU6lho1I0IxQ3b68yntkEQQJko6kzleXHfgjf0a8aw8m38E3+fxaBWF1bGyucGwOLDWY8Ddt2P2xg44zdXH5EXHd9oIqAgngIdzLvUtH3Db2TbQEtIGOkrnNR2STovjqB7vHGLASQrgs4oL7r32/s=
env:
- TEST_DIR=.
- TEST_DIR=./tests/ios
script: cd $TEST_DIR && npm install && npm test
global:
- SAUCE_USERNAME=snay
- TRAVIS_NODE_VERSION="4.2"
matrix:
include:
- env: TEST_DIR=.
language: objective-c
- env: TEST_DIR=./tests/ios
language: objective-c
- env: PLATFORM=browser-chrome
os: linux
language: node_js
node_js: '4.2'
- env: PLATFORM=browser-firefox
os: linux
language: node_js
node_js: '4.2'
- env: PLATFORM=browser-safari
os: linux
language: node_js
node_js: '4.2'
- env: PLATFORM=browser-edge
os: linux
language: node_js
node_js: '4.2'
- env: PLATFORM=ios-9.3
os: osx
osx_image: xcode7.3
language: node_js
node_js: "4.2"
- env: PLATFORM=ios-10.0
os: osx
osx_image: xcode7.3
language: node_js
node_js: "4.2"
- env: PLATFORM=android-4.4
os: linux
language: android
jdk: oraclejdk8
android:
components:
- tools
- extra-android-m2repository
- env: PLATFORM=android-5.1
os: linux
language: android
jdk: oraclejdk8
android:
components:
- tools
- extra-android-m2repository
- env: PLATFORM=android-6.0
os: linux
language: android
jdk: oraclejdk8
android:
components:
- tools
- extra-android-m2repository
- env: PLATFORM=android-7.0
os: linux
language: android
jdk: oraclejdk8
android:
components:
- tools
- extra-android-m2repository
before_install:
- rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install $TRAVIS_NODE_VERSION
- node --version
- if [[ "$PLATFORM" =~ android ]]; then gradle --version; fi
- if [[ "$PLATFORM" =~ ios ]]; then npm install -g ios-deploy; fi
- if [[ "$PLATFORM" =~ android ]]; then echo y | android update sdk -u --filter android-22,android-23,android-24,android-25,android-26;
fi
- git clone https://github.com/apache/cordova-paramedic /tmp/paramedic && pushd /tmp/paramedic
&& npm install && popd
- npm install -g cordova
install:
- npm install
script:
- if [[ "$TEST_DIR" != "" ]];
then cd $TEST_DIR && npm install && npm test;
else
node /tmp/paramedic/main.js --config pr/$PLATFORM --plugin $(pwd) --shouldUseSauce
--buildName travis-plugin-camera-$TRAVIS_JOB_NUMBER;
fi
+297 -15
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,7 +21,9 @@
# under the License.
-->
[![Build Status](https://travis-ci.org/apache/cordova-plugin-camera.svg?branch=master)](https://travis-ci.org/apache/cordova-plugin-camera)
|AppVeyor|Travis CI|
|:-:|:-:|
|[![Build status](https://ci.appveyor.com/api/projects/status/github/apache/cordova-plugin-camera?branch=master)](https://ci.appveyor.com/project/ApacheSoftwareFoundation/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
@@ -47,11 +53,11 @@ It is also possible to install via repo url directly ( unstable )
## How to Contribute
Contributors are welcome! And we need your contributions to keep the project moving forward. You can [report bugs](https://issues.apache.org/jira/issues/?jql=project%20%3D%20CB%20AND%20status%20in%20(Open%2C%20%22In%20Progress%22%2C%20Reopened)%20AND%20resolution%20%3D%20Unresolved%20AND%20component%20%3D%20%22Plugin%20Camera%22%20ORDER%20BY%20priority%20DESC%2C%20summary%20ASC%2C%20updatedDate%20DESC), improve the documentation, or [contribute code](https://github.com/apache/cordova-plugin-camera/pulls).
Contributors are welcome! And we need your contributions to keep the project moving forward. You can [report bugs](https://issues.apache.org/jira/issues/?jql=project%20%3D%20CB%20AND%20status%20in%20(Open%2C%20%22In%20Progress%22%2C%20Reopened)%20AND%20resolution%20%3D%20Unresolved%20AND%20component%20%3D%20%22cordova-plugin-camera%22%20ORDER%20BY%20priority%20DESC%2C%20summary%20ASC%2C%20updatedDate%20DESC), improve the documentation, or [contribute code](https://github.com/apache/cordova-plugin-camera/pulls).
There is a specific [contributor workflow](http://wiki.apache.org/cordova/ContributorWorkflow) we recommend. Start reading there. More information is available on [our wiki](http://wiki.apache.org/cordova).
:warning: **Found an issue?** File it on [JIRA issue tracker](https://issues.apache.org/jira/issues/?jql=project%20%3D%20CB%20AND%20status%20in%20(Open%2C%20%22In%20Progress%22%2C%20Reopened)%20AND%20resolution%20%3D%20Unresolved%20AND%20component%20%3D%20%22Plugin%20Camera%22%20ORDER%20BY%20priority%20DESC%2C%20summary%20ASC%2C%20updatedDate%20DESC).
:warning: **Found an issue?** File it on [JIRA issue tracker](https://issues.apache.org/jira/issues/?jql=project%20%3D%20CB%20AND%20status%20in%20(Open%2C%20%22In%20Progress%22%2C%20Reopened)%20AND%20resolution%20%3D%20Unresolved%20AND%20component%20%3D%20%22cordova-plugin-camera%22%20ORDER%20BY%20priority%20DESC%2C%20summary%20ASC%2C%20updatedDate%20DESC).
**Have a solution?** Send a [Pull Request](https://github.com/apache/cordova-plugin-camera/pulls).
@@ -67,9 +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 provide an usage description in the `info.plist` if trying to access privacy-sensitive data. When the system prompts the user to allow access, this usage description string will displayed as part of the permission dialog box, but if you didn't provide the usage description, the app will crash before showing the dialog. Also, Apple will reject apps that access private data but don't provide an usage description.
This plugins requires the following usage descriptions:
- `NSCameraUsageDescription` specifies the reason for your app to access the device's camera.
- `NSPhotoLibraryUsageDescription` specifies the reason for your app to access the user's photo library.
- `NSLocationWhenInUseUsageDescription` specifies the reason for your app to access the user's location information while your app is in use. (Set it if you have `CameraUsesGeolocation` preference set to `true`)
- `NSPhotoLibraryAddUsageDescription` specifies the reason for your app to get write-only access to the user's photo library
To add these entries into the `info.plist`, you can use the `edit-config` tag in the `config.xml` like this:
```
<edit-config target="NSCameraUsageDescription" file="*-Info.plist" mode="merge">
<string>need camera access to take pictures</string>
</edit-config>
```
```
<edit-config target="NSPhotoLibraryUsageDescription" file="*-Info.plist" mode="merge">
<string>need to photo library access to get pictures from there</string>
</edit-config>
```
```
<edit-config target="NSLocationWhenInUseUsageDescription" file="*-Info.plist" mode="merge">
<string>need location access to find things nearby</string>
</edit-config>
```
```
<edit-config target="NSPhotoLibraryAddUsageDescription" file="*-Info.plist" mode="merge">
<string>need to photo library access to save pictures there</string>
</edit-config>
```
---
# API Reference
# API Reference <a name="reference"></a>
* [camera](#module_camera)
@@ -94,8 +138,10 @@ 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
@@ -108,26 +154,20 @@ Once the user snaps the photo, the camera application closes and the application
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` 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
@@ -163,6 +203,7 @@ More examples [here](#camera-getPicture-examples). Quirks [here](#camera-getPict
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
@@ -187,6 +228,7 @@ function onFail(message) {
}
```
<a name="module_camera.onError"></a>
### camera.onError : <code>function</code>
Callback function that provides an error message.
@@ -197,6 +239,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.
@@ -216,6 +259,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)
@@ -241,9 +285,17 @@ 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**
@@ -254,6 +306,7 @@ Optional parameters to customize the camera settings.
| 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**
@@ -264,6 +317,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**
@@ -275,17 +329,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.
@@ -301,6 +362,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**
@@ -313,8 +375,15 @@ 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
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.
Note that the size of the popover may change to adjust to the
direction of the arrow and orientation of the screen. Make sure to
account for orientation changes when specifying the anchor element
location.
| Param | Type | Default | Description |
@@ -328,6 +397,7 @@ iOS-only parameters that specify the anchor element location and arrow direction
---
<a name="module_CameraPopoverHandle"></a>
## CameraPopoverHandle
A handle to an image picker popover.
@@ -337,7 +407,7 @@ __Supported Platforms__
**Example**
```js
__Supported Platforms__
navigator.camera.getPicture(onSuccess, onFail,
{
destinationType: Camera.DestinationType.FILE_URI,
sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
@@ -346,6 +416,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);
}
@@ -439,6 +510,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
@@ -504,6 +585,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
@@ -526,3 +609,202 @@ Tizen only supports a `destinationType` of
[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);
}
```
+105 -11
View File
@@ -20,16 +20,110 @@
-->
# Release Notes
### 3.0.0 (Nov 06, 2017)
* Added `cordova-OSX` support
* [CB-13515](https://issues.apache.org/jira/browse/CB-13515) (all): Add 'protective' entry to `cordovaDependencies`
* [CB-13332](https://issues.apache.org/jira/browse/CB-13332) (iOS): document `NSPhotoLibraryAddUsageDescription`
* [CB-13264](https://issues.apache.org/jira/browse/CB-13264) (iOS): Remove **iOS** usage descriptions
* [CB-13473](https://issues.apache.org/jira/browse/CB-13473) (CI) Removed **Browser** builds from AppVeyor
* [CB-13446](https://issues.apache.org/jira/browse/CB-13446) Sync template with previous doc changes
* [CB-13294](https://issues.apache.org/jira/browse/CB-13294) Removed `cordova-plugin-compat`
* [CB-13299](https://issues.apache.org/jira/browse/CB-13299) (CI) Fix **Android** builds
* [CB-12985](https://issues.apache.org/jira/browse/CB-12985) setup `eslint` and removed `jshint`
* [CB-13028](https://issues.apache.org/jira/browse/CB-13028) (CI) **Browser** builds on Travis and AppVeyor
* [CB-13002](https://issues.apache.org/jira/browse/CB-13002) (Android, **iOS**) Fix occasional Appium tests failures
* [CB-13000](https://issues.apache.org/jira/browse/CB-13000) (CI) Speed up **Android** builds
* [CB-12991](https://issues.apache.org/jira/browse/CB-12991) (CI) Updated CI badges
* [CB-12964](https://issues.apache.org/jira/browse/CB-12964) (android) Fix of bug when Pictures folder did not exist.
* [CB-12982](https://issues.apache.org/jira/browse/CB-12982) (Android, **iOS**) Appium tests: try to create a session harder
* [CB-12682](https://issues.apache.org/jira/browse/CB-12682) (ios, **Android**): changes cancel error message to be consistent for **iOS** **Android**
* [CB-12764](https://issues.apache.org/jira/browse/CB-12764) (android) Adapt Appium tests for **Android** 7
* [CB-12847](https://issues.apache.org/jira/browse/CB-12847) added `bugs` entry to `package.json`.
### 2.4.1 (Apr 27, 2017)
* [CB-12622](https://issues.apache.org/jira/browse/CB-12622) Updated build badges in `README`
* [CB-12650](https://issues.apache.org/jira/browse/CB-12650) Fix manual test for uploading image
* [CB-12685](https://issues.apache.org/jira/browse/CB-12685) added `package.json` to tests folder
* [CB-12622](https://issues.apache.org/jira/browse/CB-12622) (android) Appium tests: Bust **Android** 6 and 7 permission dialogs
* [CB-12618](https://issues.apache.org/jira/browse/CB-12618) (android) Appium tests: Handle native cling
### 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 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 Appium tests stability improvements
* [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 Run **ios** native tests on **Travis**
* CB-10120 Fixing use of constants and `PermissionHelper`
* CB-10120 Fix missing CAMERA permission for **Android M**
* CB-10756 Adding sterner warnings about `DATA_URL`
* CB-10460 `getRealPath` return null in some cases
* [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
@@ -59,10 +153,10 @@
### 2.1.0 (Jan 15, 2016)
* added `.ratignore`
* CB-10319 **Android** Adding reflective helper methods for permission requests
* CB-9189 **Android** Implementing `save/restore` API to handle Activity destruction
* CB-10241 App Crash cause by Camera Plugin **iOS 7**
* CB-8940 Setting `z-index` values to maximum for UI buttons.
* [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
+524 -184
View File
@@ -1,5 +1,3 @@
/*jshint node: true, jasmine: true */
/* global navigator, Q */
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
@@ -21,10 +19,12 @@
*
*/
// these tests are meant to be executed by Cordova Medic Appium runner
// you can find it here: https://github.com/apache/cordova-medic/
// 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
// just run "node cordova-medic/medic/medic.js appium --platform android --plugins cordova-plugin-camera"
// 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';
@@ -40,6 +40,7 @@ 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;
@@ -52,6 +53,25 @@ describe('Camera tests Android.', function () {
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;
var isAndroid7 = getIsAndroid7();
function getIsAndroid7() {
if (global.USE_SAUCE) {
return global.SAUCE_CAPS && (parseFloat(global.SAUCE_CAPS.platformVersion) >= 7);
} else {
// this is most likely null, meaning we cannot determine if it is Android 7 or not
// paramedic needs to be modified to receive and pass the platform version when testing locally
return global.PLATFORM_VERSION && (parseFloat(global.PLATFORM_VERSION) >= 7);
}
}
function getNextPromiseId() {
promiseCount += 1;
@@ -62,34 +82,28 @@ describe('Camera tests Android.', function () {
return PROMISE_PREFIX + promiseCount;
}
function saveScreenshotAndFail(error) {
function gracefullyFail(error) {
fail(error);
return screenshotHelper
.saveScreenshot(driver)
return driver
.quit()
.then(function () {
return getDriver();
});
}
// generates test specs by combining all the specified options
// combinines specified options in all possible variations
// you can add more options to test more scenarios
function generateSpecs() {
function generateOptions() {
var sourceTypes = [
cameraConstants.PictureSourceType.CAMERA,
cameraConstants.PictureSourceType.PHOTOLIBRARY
],
destinationTypes = cameraConstants.DestinationType,
encodingTypes = [
cameraConstants.EncodingType.JPEG,
cameraConstants.EncodingType.PNG
],
allowEditOptions = [
true,
false
];
var destinationTypes = cameraConstants.DestinationType;
var encodingTypes = cameraConstants.EncodingType;
var allowEditOptions = [ true, false ];
var correctOrientationOptions = [ true, false ];
return cameraHelper.generateSpecs(sourceTypes, destinationTypes, encodingTypes, allowEditOptions);
return cameraHelper.generateSpecs(sourceTypes, destinationTypes, encodingTypes, allowEditOptions, correctOrientationOptions);
}
// invokes Camera.getPicture() with the specified options
@@ -99,11 +113,21 @@ describe('Camera tests Android.', function () {
if (!options) {
options = {};
}
// assign default values
if (!options.hasOwnProperty('allowEdit')) {
options.allowEdit = true;
}
if (!options.hasOwnProperty('destinationType')) {
options.destinationType = cameraConstants.DestinationType.FILE_URI;
}
if (!options.hasOwnProperty('sourceType')) {
options.destinationType = cameraConstants.PictureSourceType.CAMERA;
}
return driver
.context(webviewContext)
.execute(cameraHelper.getPicture, [options, promiseId])
.context('NATIVE_APP')
.context(CONTEXT_NATIVE_APP)
.then(function () {
if (skipUiInteractions) {
return;
@@ -114,95 +138,115 @@ describe('Camera tests Android.', function () {
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 / 5)});
swipeRight.press({x: 10, y: 100})
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 / 2), y: 100})
.release();
.moveTo({x: Math.round(screenWidth - (screenWidth / 8)), y: 0})
.wait(1500)
.release()
.wait(1000);
if (options.allowEdit) {
return driver
.elementByXPath('//android.widget.TextView[@text="Gallery"]')
// 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)
.elementByXPath('//android.widget.TextView[@text="Gallery"]');
.waitForElementByAndroidUIAutomator('new UiSelector().text("Gallery");', 20000)
})
.then(function (element) {
return element
.click()
// we need to sleep here to give a sidebar some time to close
// if we don't sleep here, sometimes we would click on a sidebar
// in the next step
.sleep(3000);
}, function () {
// the gallery is already opened, just go on:
return driver;
})
// always wait before performing touchAction
.sleep(7000)
.performTouchAction(tapTile);
}
// taking a picture from camera
return driver
.waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]', MINUTE)
.waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*shutter.*")', MINUTE / 2)
.click()
.waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]', MINUTE)
.click();
.waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*done.*")', MINUTE / 2)
.click()
.then(function () {
if (isAndroid7 && options.allowEdit) {
return driver
.elementByAndroidUIAutomator('new UiSelector().text("Crop picture");', 20000)
.click()
.fail(function () {
// don't freak out just yet...
return driver;
})
.elementByAndroidUIAutomator('new UiSelector().text("JUST ONCE");', 20000)
.click()
.fail(function () {
// maybe someone's hit that "ALWAYS" button?
return driver;
});
}
return driver;
});
})
.then(function () {
if (skipUiInteractions) {
return;
}
if (options.allowEdit) {
var saveText = isAndroid7 ? 'SAVE' : 'Save';
return driver
.waitForElementByXPath('//*[contains(@resource-id,\'save\')]', MINUTE)
.waitForElementByAndroidUIAutomator('new UiSelector().text("' + saveText + '")', MINUTE)
.click();
}
})
.fail(fail);
.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) {
function checkPicture(shouldLoad, options) {
if (!options) {
options = {};
}
return driver
.context(webviewContext)
.setAsyncScriptTimeout(MINUTE)
.executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId()])
.setAsyncScriptTimeout(MINUTE / 2)
.executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId(), options, isAndroid7])
.then(function (result) {
if (shouldLoad) {
expect(result.length).toBeGreaterThan(0);
if (result.indexOf('ERROR') >= 0) {
return fail(result);
if (result !== 'OK') {
fail(result);
}
} else {
if (result.indexOf('ERROR') === -1) {
return fail('Unexpected success callback with result: ' + result);
}
expect(result.indexOf('ERROR')).toBe(0);
} else if (result.indexOf('ERROR') === -1) {
throw 'Unexpected success callback with result: ' + result;
}
});
}
function runCombinedSpec(spec) {
return driver
.then(function () {
return getPicture(spec.options);
})
.then(function () {
return checkPicture(true);
})
.fail(saveScreenshotAndFail);
}
// 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();
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)
.elementByXPath('//android.widget.TextView[@text="Delete"]')
.elementByAndroidUIAutomator('new UiSelector().text("Delete")')
.then(function (element) {
return element
.click()
.elementByXPath('//android.widget.Button[@text="OK"]')
.elementByAndroidUIAutomator('new UiSelector().text("OK")')
.click();
}, function () {
// couldn't find Delete menu item. Possibly there is no image.
@@ -212,96 +256,31 @@ describe('Camera tests Android.', function () {
function getDriver() {
driver = wdHelper.getDriver('Android');
return wdHelper.getWebviewContext(driver)
return driver.getWebviewContext()
.then(function(context) {
webviewContext = context;
return driver.context(webviewContext);
})
.waitForDeviceReady()
.injectLibraries()
.then(function () {
return wdHelper.waitForDeviceReady(driver);
})
.then(function () {
return wdHelper.injectLibraries(driver);
});
}
it('camera.ui.util configuring driver and starting a session', function (done) {
getDriver()
.fail(fail)
.done(done);
}, 5 * MINUTE);
it('camera.ui.util determine screen dimensions', function (done) {
return driver
.context(webviewContext)
.execute(function () {
return {
'width': window.innerWidth,
'height': window.innerHeight
};
}, [])
.then(function (size) {
screenWidth = Number(size.width);
screenHeight = Number(size.height);
})
.done(done);
}, MINUTE);
describe('Specs.', function () {
// getPicture() with saveToPhotoLibrary = true
it('camera.ui.spec.1 Saving the picture to photo library', function (done) {
var options = {
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
saveToPhotoAlbum: true
sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
};
driver
.then(function () {
return getPicture(options);
})
.then(function () {
isTestPictureSaved = true;
return checkPicture(true);
})
.fail(saveScreenshotAndFail)
.done(done);
}, 3 * MINUTE);
// getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY
it('camera.ui.spec.2 Selecting only videos', function (done) {
var options = { sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
mediaType: cameraConstants.MediaType.VIDEO };
driver
.then(function () {
return getPicture(options, true);
})
.context('NATIVE_APP')
.then(function () {
// try to find "Gallery" menu item
// if there's none, the gallery should be already opened
return driver
.elementByXPath('//android.widget.TextView[@text="Gallery"]')
.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
.elementByXPath('//*[@text="Choose video"]')
.fail(function () {
throw 'Couldn\'t find "Choose video" element.';
});
})
.then(function () { return getPicture(options, true); })
.context(CONTEXT_NATIVE_APP)
// case insensitive select, will be handy with Android 7 support
.elementByXPath('//android.widget.Button[translate(@text, "alow", "ALOW")="ALLOW"]')
.click()
.fail(function noAlert() { })
.deviceKeyEvent(BACK_BUTTON)
.elementByXPath('//android.widget.TextView[@text="Gallery"]')
.deviceKeyEvent(BACK_BUTTON)
.finally(function () {
return driver
.sleep(2000)
.elementById('action_bar_title')
.then(function () {
// success means we're still in native app
@@ -312,66 +291,422 @@ describe('Camera tests Android.', function () {
return driver;
});
})
.fail(saveScreenshotAndFail)
.then(function () {
// doing it inside a function because otherwise
// it would not hook up to the webviewContext var change
// in the first methods of this chain
return driver.context(webviewContext);
})
.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(options, pending) {
if (!cameraAvailable) {
pending('Skipping because this test requires a functioning camera on the Android device/emulator, and this test suite\'s functional camera test failed on your target environment.');
} else if (isAndroid7 && options.allowEdit) {
// TODO: Check if it is fixed some day
pending('Skipping because can\'t test with allowEdit=true on Android 7: getting unexpected "Camera cancelled" message.');
} else if (isAndroid7 && (options.sourceType !== cameraConstants.PictureSourceType.CAMERA)) {
pending('Skipping because can\'t click on the gallery tile on Android 7.');
}
}
afterAll(function (done) {
checkSession(done);
driver
.quit()
.done(done);
}, 3 * MINUTE);
}, MINUTE);
it('camera.ui.util configuring driver and starting a session', function (done) {
// retry up to 3 times
getDriver()
.fail(function () {
return getDriver()
.fail(function () {
return getDriver()
.fail(fail);
});
})
.then(function () {
appiumSessionStarted = true;
})
.done(done);
}, 30 * 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) {
var opts = {
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
saveToPhotoAlbum: true
};
checkSession(done);
checkCamera(opts, pending);
var spec = generateSpec(opts);
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" or "Select video" caption
var videoSelector = isAndroid7 ? 'new UiSelector().text("Select video")' : 'new UiSelector().text("Choose video")';
return driver
.elementByAndroidUIAutomator(videoSelector)
.fail(function () {
throw 'Couldn\'t find a "Choose/select 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) {
var options = { quality: 50,
var options = {
quality: 50,
allowEdit: true,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.FILE_URI };
driver
destinationType: cameraConstants.DestinationType.FILE_URI
};
checkSession(done);
checkCamera(options, pending);
var spec = function () {
return driver
.then(function () {
return getPicture(options, true);
})
.context("NATIVE_APP")
.waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'cancel\')]', MINUTE / 2)
.context(CONTEXT_NATIVE_APP)
.waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*cancel.*")', MINUTE / 2)
.click()
.then(function () {
return checkPicture(false);
})
.fail(saveScreenshotAndFail)
.done(done);
}, 3 * MINUTE);
});
};
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) {
var options = { quality: 50,
var options = {
quality: 50,
allowEdit: true,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.FILE_URI };
driver
destinationType: cameraConstants.DestinationType.FILE_URI
};
checkSession(done);
checkCamera(options, pending);
var spec = function () {
return driver
.then(function () {
return getPicture(options, true);
})
.context('NATIVE_APP')
.waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]', MINUTE / 2)
.waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*shutter.*")', MINUTE / 2)
.click()
.waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]', MINUTE / 2)
.click()
.waitForElementByXPath('//*[contains(@resource-id,\'discard\')]', MINUTE / 2)
.waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*done.*")', MINUTE / 2)
.click()
.then(function () {
return checkPicture(false);
if (isAndroid7 && options.allowEdit) {
return driver
.waitForElementByAndroidUIAutomator('new UiSelector().text("Crop picture");', 20000)
.click()
.waitForElementByAndroidUIAutomator('new UiSelector().text("JUST ONCE");', 20000)
.click()
.deviceKeyEvent(BACK_BUTTON);
}
return driver
.waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*discard.*")', MINUTE / 2)
.click();
})
.fail(saveScreenshotAndFail)
.done(done);
}, 3 * MINUTE);
.then(function () {
return checkPicture(false);
});
};
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.5 Verifying target image size, sourceType=CAMERA', function (done) {
var opts = {
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
};
checkSession(done);
checkCamera(opts, pending);
var spec = generateSpec(opts);
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.6 Verifying target image size, sourceType=PHOTOLIBRARY', function (done) {
var opts = {
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
};
checkSession(done);
checkCamera(opts, pending);
var spec = generateSpec(opts);
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.7 Verifying target image size, sourceType=CAMERA, DestinationType=NATIVE_URI', function (done) {
var opts = {
quality: 50,
allowEdit: true,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.NATIVE_URI,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
};
checkSession(done);
checkCamera(opts, pending);
var spec = generateSpec(opts);
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.8 Verifying target image size, sourceType=PHOTOLIBRARY, DestinationType=NATIVE_URI', function (done) {
var opts = {
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
destinationType: cameraConstants.DestinationType.NATIVE_URI,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
};
checkSession(done);
checkCamera(opts, pending);
var spec = generateSpec(opts);
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.9 Verifying target image size, sourceType=CAMERA, DestinationType=NATIVE_URI, quality=100', function (done) {
var opts = {
quality: 50,
allowEdit: true,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.NATIVE_URI,
saveToPhotoAlbum: false,
targetWidth: 305,
targetHeight: 305
};
checkSession(done);
checkCamera(opts, pending);
var spec = generateSpec(opts);
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.10 Verifying target image size, sourceType=PHOTOLIBRARY, DestinationType=NATIVE_URI, quality=100', function (done) {
var opts = {
quality: 100,
allowEdit: true,
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
destinationType: cameraConstants.DestinationType.NATIVE_URI,
saveToPhotoAlbum: false,
targetWidth: 305,
targetHeight: 305
};
checkSession(done);
checkCamera(opts, pending);
var spec = generateSpec(opts);
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
// combine various options for getPicture()
generateSpecs().forEach(function (spec) {
it('camera.ui.spec.5.' + spec.id + ' Combining options', function (done) {
runCombinedSpec(spec)
.done(done);
}, 3 * MINUTE);
generateOptions().forEach(function (spec) {
it('camera.ui.spec.11.' + spec.id + ' Combining options. ' + spec.description, function (done) {
checkSession(done);
checkCamera(spec.options, pending);
var s = generateSpec(spec.options);
tryRunSpec(s).done(done);
}, 10 * MINUTE);
});
it('camera.ui.util Delete filler picture from device library', function (done) {
if (isAndroid7 || global.USE_SAUCE) {
pending();
}
driver
.context(webviewContext)
.deleteFillerImage(fillerImagePath)
.done(done);
}, MINUTE);
it('camera.ui.util Delete test image from device library', function (done) {
it('camera.ui.util Delete taken picture from device library', function (done) {
if (isAndroid7 || global.USE_SAUCE) {
pending();
}
checkSession(done);
if (!isTestPictureSaved) {
// couldn't save test picture earlier, so nothing to delete here
done();
@@ -379,8 +714,8 @@ describe('Camera tests Android.', function () {
}
// delete exactly one latest picture
// this should be the picture we've taken in the first spec
return driver
.context('NATIVE_APP')
driver
.context(CONTEXT_NATIVE_APP)
.deviceKeyEvent(BACK_BUTTON)
.sleep(1000)
.deviceKeyEvent(BACK_BUTTON)
@@ -388,9 +723,18 @@ describe('Camera tests Android.', function () {
.deviceKeyEvent(BACK_BUTTON)
.elementById('Apps')
.click()
.elementByXPath('//android.widget.TextView[@text="Gallery"]')
.then(function () {
return driver
.elementByXPath('//android.widget.Button[@text="OK"]')
.click()
.elementByXPath('//android.widget.TextView[contains(@text,"Pictures")]')
.fail(function () {
// no cling is all right
// it is not a brand new emulator, then
});
})
.elementByAndroidUIAutomator('new UiSelector().text("Gallery")')
.click()
.elementByAndroidUIAutomator('new UiSelector().textContains("Pictures")')
.click()
.then(deleteImage)
.deviceKeyEvent(BACK_BUTTON)
@@ -403,9 +747,5 @@ describe('Camera tests Android.', function () {
}, 3 * MINUTE);
});
it('camera.ui.util Destroy the session', function (done) {
driver
.quit()
.done(done);
}, MINUTE);
});
+238 -32
View File
@@ -1,5 +1,4 @@
/*jshint node: true */
/* global Q */
/* global Q, resolveLocalFileSystemURL, Camera, cordova */
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
@@ -25,11 +24,35 @@
var cameraConstants = require('../../www/CameraConstants');
module.exports.generateSpecs = function (sourceTypes, destinationTypes, encodingTypes, allowEditOptions) {
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) {
@@ -40,9 +63,12 @@ module.exports.generateSpecs = function (sourceTypes, destinationTypes, encoding
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) {
specs.push({
if ((sourceTypes[sourceType] === cameraConstants.PictureSourceType.PHOTOLIBRARY ||
sourceTypes[sourceType] === cameraConstants.PictureSourceType.SAVEDPHOTOALBUM) &&
correctOrientation === true) { continue; }
var spec = {
'id': id++,
'options': {
'destinationType': destinationTypes[destinationType],
@@ -50,30 +76,11 @@ module.exports.generateSpecs = function (sourceTypes, destinationTypes, encoding
'encodingType': encodingTypes[encodingType],
'allowEdit': allowEditOptions[allowEdit],
'saveToPhotoAlbum': false,
'correctOrientation': correctOrientationOptions[correctOrientation]
}
});
} else {
specs.push({
'id': id++,
'options': {
'destinationType': destinationTypes[destinationType],
'sourceType': sourceTypes[sourceType],
'encodingType': encodingTypes[encodingType],
'correctOrientation': true,
'allowEdit': allowEditOptions[allowEdit],
'saveToPhotoAlbum': false,
}
}, {
'id': id++,
'options': {
'destinationType': destinationTypes[destinationType],
'sourceType': sourceTypes[sourceType],
'encodingType': encodingTypes[encodingType],
'correctOrientation': false,
'allowEdit': allowEditOptions[allowEdit],
'saveToPhotoAlbum': false,
}
});
};
spec.description = getDescription(spec);
specs.push(spec);
}
}
}
@@ -86,7 +93,13 @@ module.exports.generateSpecs = function (sourceTypes, destinationTypes, encoding
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);
@@ -95,11 +108,204 @@ module.exports.getPicture = function (opts, pid) {
}, opts);
};
module.exports.checkPicture = function (pid, cb) {
// 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, skipContentCheck, 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;
}
if (skipContentCheck) {
cb('OK');
return;
}
resolveLocalFileSystemURL(result, function (entry) {
if (skipFileTypeCheck) {
displayFile(entry);
} else {
verifyFile(entry);
}
}, function (err) {
errorCallback(err);
});
} 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) {
cb(result);
}, function (err) {
cb('ERROR: ' + err);
verifyResult(result);
})
.fail(function (e) {
errorCallback(e);
});
};
+332 -84
View File
@@ -1,5 +1,3 @@
/*jshint node: true, jasmine: true */
/* global navigator, Q */
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
@@ -21,16 +19,15 @@
*
*/
// these tests are meant to be executed by Cordova Medic Appium runner
// you can find it here: https://github.com/apache/cordova-medic/
// 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-medic/medic/medic.js appium --platform android --plugins cordova-plugin-camera"
// 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 wd = wdHelper.getWD();
var isDevice = global.DEVICE;
var cameraConstants = require('../../www/CameraConstants');
var cameraHelper = require('../helpers/cameraHelper');
@@ -38,12 +35,19 @@ 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;
@@ -54,10 +58,9 @@ describe('Camera tests iOS.', function () {
return PROMISE_PREFIX + promiseCount;
}
function saveScreenshotAndFail(error) {
function gracefullyFail(error) {
fail(error);
return screenshotHelper
.saveScreenshot(driver)
return driver
.quit()
.then(function () {
return getDriver();
@@ -66,90 +69,114 @@ describe('Camera tests iOS.', function () {
// generates test specs by combining all the specified options
// you can add more options to test more scenarios
function generateSpecs() {
var sourceTypes = [
cameraConstants.PictureSourceType.CAMERA,
cameraConstants.PictureSourceType.PHOTOLIBRARY
],
destinationTypes = cameraConstants.DestinationType,
encodingTypes = [
cameraConstants.EncodingType.JPEG,
cameraConstants.EncodingType.PNG
],
allowEditOptions = [
true,
false
];
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);
return cameraHelper.generateSpecs(sourceTypes, destinationTypes, encodingTypes, allowEditOptions, correctOrientationOptions);
}
function usePicture() {
function usePicture(allowEdit) {
return driver
.elementByXPath('//*[@label="Use"]')
.click()
.fail(function () {
return driver
// For some reason "Choose" element is not clickable by standard Appium methods
// So getting its position and tapping there using TouchAction
.elementByXPath('//UIAButton[@label="Choose"]')
.getLocation()
.then(function (loc) {
var tapChoose = new wd.TouchAction();
tapChoose.tap(loc);
return driver
.performTouchAction(tapChoose);
.sleep(10)
.then(function () {
if (isXCUI) {
return driver.waitForElementByAccessibilityId('Choose', MINUTE / 3).click();
} else {
if (allowEdit) {
return wdHelper.tapElementByXPath('//UIAButton[@label="Choose"]', driver);
}
return driver.elementByXPath('//*[@label="Use"]').click();
}
});
}
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 = {};
}
// assign defaults
if (!options.hasOwnProperty('allowEdit')) {
options.allowEdit = true;
}
if (!options.hasOwnProperty('destinationType')) {
options.destinationType = cameraConstants.DestinationType.FILE_URI;
}
if (!options.hasOwnProperty('sourceType')) {
options.destinationType = cameraConstants.PictureSourceType.CAMERA;
}
return driver
.context(webviewContext)
.execute(cameraHelper.getPicture, [options, promiseId])
.context('NATIVE_APP')
.context(CONTEXT_NATIVE_APP)
.then(function () {
if (skipUiInteractions) {
return;
}
if (options.hasOwnProperty('sourceType') && options.sourceType === cameraConstants.PictureSourceType.PHOTOLIBRARY) {
return driver
.waitForElementByXPath('//*[@label="Camera Roll"]', MINUTE / 2)
.click()
.elementByXPath('//UIACollectionCell')
.waitForElementByAccessibilityId('Camera Roll', MINUTE / 2)
.click()
.then(function () {
return clickPhoto();
})
.then(function () {
if (!options.allowEdit) {
return driver;
}
return usePicture();
return usePicture(options.allowEdit);
});
}
if (options.hasOwnProperty('sourceType') && options.sourceType === cameraConstants.PictureSourceType.SAVEDPHOTOALBUM) {
return driver
.waitForElementByXPath('//UIACollectionCell', MINUTE / 2)
.click()
return clickPhoto()
.then(function () {
if (!options.allowEdit) {
return driver;
}
return usePicture();
return usePicture(options.allowEdit);
});
}
if (cancelCamera) {
return driver
.waitForElementByXPath('//*[@label="Cancel"]', MINUTE / 2)
.waitForElementByAccessibilityId('Cancel', MINUTE / 2)
.click();
}
return driver
.waitForElementByXPath('//*[@label="Take Picture"]', MINUTE / 2)
.waitForElementByAccessibilityId('Take Picture', MINUTE / 2)
.click()
.elementByXPath('//*[@label="Use Photo"]')
.waitForElementByAccessibilityId('Use Photo', MINUTE / 2)
.click();
})
.fail(fail);
@@ -157,38 +184,45 @@ describe('Camera tests iOS.', function () {
// checks if the picture was successfully taken
// if shouldLoad is falsy, ensures that the error callback was called
function checkPicture(shouldLoad) {
function checkPicture(shouldLoad, options) {
if (!options) {
options = {};
}
return driver
.context(webviewContext)
.setAsyncScriptTimeout(MINUTE)
.executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId()])
.setAsyncScriptTimeout(MINUTE / 2)
.executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId(), options, false])
.then(function (result) {
if (shouldLoad) {
expect(result.length).toBeGreaterThan(0);
if (result.indexOf('ERROR') >= 0) {
return fail(result);
if (result !== 'OK') {
fail(result);
}
} else {
if (result.indexOf('ERROR') === -1) {
return fail('Unexpected success callback with result: ' + result);
}
expect(result.indexOf('ERROR')).toBe(0);
} else if (result.indexOf('ERROR') === -1) {
throw 'Unexpected success callback with result: ' + result;
}
});
}
function runCombinedSpec(spec) {
// 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(spec.options);
return getPicture(options);
})
.then(function () {
return checkPicture(true);
return checkPicture(true, options);
})
.fail(saveScreenshotAndFail);
.fail(gracefullyFail);
}
function getDriver() {
failedToStart = true;
driver = wdHelper.getDriver('iOS');
return wdHelper.getWebviewContext(driver)
.then(function(context) {
@@ -200,38 +234,120 @@ describe('Camera tests iOS.', function () {
})
.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) {
// retry up to 3 times
getDriver()
.fail(function () {
return getDriver()
.fail(function () {
return getDriver()
.fail(fail);
});
})
.fail(fail)
.finally(done);
}, 5 * MINUTE);
.done(done);
}, 30 * MINUTE);
describe('Specs.', function () {
afterEach(function (done) {
if (specsRun >= 19) {
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()
.fail(function () {
return getDriver()
.fail(function () {
return getDriver()
.fail(fail);
});
});
})
.done(done);
} else {
done();
}
}, 30 * 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)
.elementByXPath('//*[@label="Cancel"]')
.elementByAccessibilityId('Cancel')
.click()
.fail(saveScreenshotAndFail)
.fail(gracefullyFail)
.done(done);
}, 3 * MINUTE);
}, 7 * MINUTE);
// getPicture(), then dismiss
// wait for the error callback to be called
it('camera.ui.spec.2 Dismissing the camera', function (done) {
// camera is not available on the iOS simulator
checkSession(done);
if (!isDevice) {
pending();
pending('Camera is not available on iOS simulator');
}
var options = { sourceType: cameraConstants.PictureSourceType.CAMERA };
specsRun += 1;
var options = { sourceType: cameraConstants.PictureSourceType.CAMERA,
saveToPhotoAlbum: false };
driver
.then(function () {
return getPicture(options, true);
@@ -239,26 +355,158 @@ describe('Camera tests iOS.', function () {
.then(function () {
return checkPicture(false);
})
.fail(saveScreenshotAndFail)
.fail(gracefullyFail)
.done(done);
}, 3 * MINUTE);
}, 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()
generateSpecs().forEach(function (spec) {
it('camera.ui.spec.3.' + spec.id + ' Combining options', function (done) {
// camera is not available on iOS simulator
if (!isDevice && spec.options.sourceType === cameraConstants.PictureSourceType.CAMERA) {
pending();
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');
}
runCombinedSpec(spec).done(done);
}, 3 * MINUTE);
runSpec(spec.options, done, pending).done(done);
}, 7 * MINUTE);
});
});
it('camera.ui.util.4 Destroy the session', function (done) {
it('camera.ui.util Destroy the session', function (done) {
checkSession(done);
driver
.quit()
.done(done);
}, MINUTE);
}, 5 * MINUTE);
});
+258 -3
View File
@@ -1,17 +1,61 @@
---
title: Camera
description: Take pictures with the device camera.
---
{{>cdv-license~}}
[![Build Status](https://travis-ci.org/apache/cordova-plugin-camera.svg?branch=master)](https://travis-ci.org/apache/cordova-plugin-camera)
|AppVeyor|Travis CI|
|:-:|:-:|
|[![Build status](https://ci.appveyor.com/api/projects/status/github/apache/cordova-plugin-camera?branch=master)](https://ci.appveyor.com/project/ApacheSoftwareFoundation/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
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' }}
{{>cdv-header device-ready-warning-obj='navigator.camera' npmName='cordova-plugin-camera' cprName='org.apache.cordova.camera' pluginName='cordova-plugin-camera' repoUrl='https://github.com/apache/cordova-plugin-camera' }}
### iOS Quirks
Since iOS 10 it's mandatory to provide an usage description in the `info.plist` if trying to access privacy-sensitive data. When the system prompts the user to allow access, this usage description string will displayed as part of the permission dialog box, but if you didn't provide the usage description, the app will crash before showing the dialog. Also, Apple will reject apps that access private data but don't provide an usage description.
This plugins requires the following usage descriptions:
- `NSCameraUsageDescription` specifies the reason for your app to access the device's camera.
- `NSPhotoLibraryUsageDescription` specifies the reason for your app to access the user's photo library.
- `NSLocationWhenInUseUsageDescription` specifies the reason for your app to access the user's location information while your app is in use. (Set it if you have `CameraUsesGeolocation` preference set to `true`)
- `NSPhotoLibraryAddUsageDescription` specifies the reason for your app to get write-only access to the user's photo library
To add these entries into the `info.plist`, you can use the `edit-config` tag in the `config.xml` like this:
```
<edit-config target="NSCameraUsageDescription" file="*-Info.plist" mode="merge">
<string>need camera access to take pictures</string>
</edit-config>
```
```
<edit-config target="NSPhotoLibraryUsageDescription" file="*-Info.plist" mode="merge">
<string>need to photo library access to get pictures from there</string>
</edit-config>
```
```
<edit-config target="NSLocationWhenInUseUsageDescription" file="*-Info.plist" mode="merge">
<string>need location access to find things nearby</string>
</edit-config>
```
```
<edit-config target="NSPhotoLibraryAddUsageDescription" file="*-Info.plist" mode="merge">
<string>need to photo library access to save pictures there</string>
</edit-config>
```
---
# API Reference
# API Reference <a name="reference"></a>
{{#orphans~}}
{{>member-index}}
@@ -116,6 +160,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
@@ -181,6 +235,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
@@ -203,3 +259,202 @@ Tizen only supports a `destinationType` of
[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);
}
```
+29 -10
View File
@@ -1,7 +1,8 @@
{
"name": "cordova-plugin-camera",
"version": "2.2.0",
"version": "3.0.0",
"description": "Cordova Camera Plugin",
"types": "./types/index.d.ts",
"cordova": {
"id": "cordova-plugin-camera",
"platforms": [
@@ -15,13 +16,17 @@
"wp8",
"windows8",
"browser",
"windows"
"windows",
"osx"
]
},
"repository": {
"type": "git",
"url": "https://github.com/apache/cordova-plugin-camera"
},
"bugs": {
"url": "https://issues.apache.org/jira/browse/CB"
},
"keywords": [
"cordova",
"camera",
@@ -36,23 +41,37 @@
"cordova-wp8",
"cordova-windows8",
"cordova-browser",
"cordova-windows"
"cordova-windows",
"cordova-osx"
],
"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",
"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"
"test": "npm run eslint",
"eslint": "node node_modules/eslint/bin/eslint www && node node_modules/eslint/bin/eslint src && node node_modules/eslint/bin/eslint tests"
},
"author": "Apache Software Foundation",
"license": "Apache-2.0",
"engines": {
"cordovaDependencies": {
"3.0.0": {
"cordova-android": ">=6.3.0"
},
"4.0.0": {
"cordova": ">100"
}
}
},
"devDependencies": {
"dmd-plugin-cordova-plugin": "^0.1.0",
"eslint": "^4.3.0",
"eslint-config-semistandard": "^11.0.0",
"eslint-config-standard": "^10.2.1",
"eslint-plugin-import": "^2.3.0",
"eslint-plugin-node": "^5.0.0",
"eslint-plugin-promise": "^3.5.0",
"eslint-plugin-standard": "^3.0.1",
"husky": "^0.10.1",
"jsdoc-to-markdown": "^1.2.0",
"jshint": "^2.6.0"
"jsdoc-to-markdown": "^1.2.0"
}
}
+38 -8
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.2.0">
version="3.0.0">
<name>Camera</name>
<description>Cordova Camera Plugin</description>
<license>Apache 2.0</license>
@@ -30,7 +30,9 @@
<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>
<dependency id="cordova-plugin-compat" version="^1.0.0" />
<engines>
<engine name="cordova-android" version=">=6.3.0" />
</engines>
<js-module src="www/CameraConstants.js" name="Camera">
<clobbers target="Camera" />
@@ -69,15 +71,30 @@
<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>
<framework src="com.android.support:support-v4:24.1.1+" />
</platform>
<!-- amazon-fireos -->
@@ -150,10 +167,6 @@
<framework src="CoreGraphics.framework" />
<framework src="AVFoundation.framework" />
<config-file target="*-Info.plist" parent="NSLocationWhenInUseUsageDescription">
<string></string>
</config-file>
</platform>
<!-- blackberry10 -->
@@ -224,7 +237,7 @@
<clobbers target="CameraPopoverHandle" />
</js-module>
<js-module src="src/windows/CameraProxy.js" name="CameraProxy">
<merges target="" />
<runs />
</js-module>
</platform>
@@ -251,10 +264,27 @@
<clobbers target="CameraPopoverHandle" />
</js-module>
<js-module src="src/windows/CameraProxy.js" name="CameraProxy">
<merges target="" />
<runs />
</js-module>
</platform>
<!-- osx -->
<platform name="osx">
<config-file target="config.xml" parent="/*">
<feature name="Camera">
<param name="osx-package" value="CDVCamera"/>
</feature>
</config-file>
<js-module src="www/CameraPopoverHandle.js" name="CameraPopoverHandle">
<clobbers target="CameraPopoverHandle" />
</js-module>
<header-file src="src/osx/CDVCamera.h" />
<source-file src="src/osx/CDVCamera.m" />
<framework src="Quartz.framework" />
<framework src="AppKit.framework" />
</platform>
</plugin>
+270 -161
View File
@@ -26,10 +26,10 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.cordova.BuildHelper;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaResourceApi;
@@ -40,27 +40,31 @@ import org.json.JSONArray;
import org.json.JSONException;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.media.MediaScannerConnection;
import android.media.MediaScannerConnection.MediaScannerConnectionClient;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.support.v4.content.FileProvider;
import android.util.Base64;
import android.util.Log;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PermissionInfo;
/**
* This class launches the camera view, allows the user to take a picture, closes the camera view,
@@ -99,7 +103,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
private int mQuality; // Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
private int targetWidth; // desired width of the image
private int targetHeight; // desired height of the image
private Uri imageUri; // Uri of captured image
private CordovaUri imageUri; // Uri of captured image
private int encodingType; // Type of encoding to use
private int mediaType; // What type of media to retrieve
private int destType; // Source type (needs to be saved for the permission handling)
@@ -117,6 +121,8 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
private MediaScannerConnection conn; // Used to update gallery app with newly-written files
private Uri scanMe; // Uri of image to be added to content store
private Uri croppedUri;
private ExifHelper exifData; // Exif data from source
private String applicationId;
/**
@@ -129,6 +135,11 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
*/
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
this.callbackContext = callbackContext;
//Adding an API to CoreAndroid to get the BuildConfigValue
//This allows us to not make this a breaking change to embedding
this.applicationId = (String) BuildHelper.getBuildConfigValue(cordova.getActivity(), "APPLICATION_ID");
this.applicationId = preferences.getString("applicationId", this.applicationId);
if (action.equals("takePicture")) {
this.srcType = CAMERA;
@@ -138,7 +149,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
this.targetWidth = 0;
this.encodingType = JPEG;
this.mediaType = PICTURE;
this.mQuality = 80;
this.mQuality = 50;
//Take the values from the arguments if they're not already defined (this is tricky)
this.destType = args.getInt(1);
@@ -174,7 +185,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
else if ((this.srcType == PHOTOLIBRARY) || (this.srcType == SAVEDPHOTOALBUM)) {
// FIXME: Stop always requesting the permission
if(!PermissionHelper.hasPermission(this, permissions[0])) {
if(!PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
PermissionHelper.requestPermission(this, SAVE_TO_ALBUM_SEC, Manifest.permission.READ_EXTERNAL_STORAGE);
} else {
this.getImage(this.srcType, destType, encodingType);
@@ -230,8 +241,8 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
* or to display URI in an img tag
* img.src=result;
*
* @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
* @param returnType Set the type of image to return.
* @param encodingType Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
*/
public void callTakePicture(int returnType, int encodingType) {
boolean saveAlbumPermission = PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
@@ -281,8 +292,12 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// Specify file so that large image is captured and returned
File photo = createCaptureFile(encodingType);
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo));
this.imageUri = Uri.fromFile(photo);
this.imageUri = new CordovaUri(FileProvider.getUriForFile(cordova.getActivity(),
applicationId + ".provider",
photo));
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, imageUri.getCorrectUri());
//We can write to this URI, this will hopefully allow us to write files to get to the next step
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
if (this.cordova != null) {
// Let's check to make sure the camera is actually installed. (Legacy Nexus 7 code)
@@ -339,7 +354,6 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
/**
* Get image from photo library.
*
* @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
* @param srcType The album to get image from.
* @param returnType Set the type of image to return.
* @param encodingType
@@ -365,7 +379,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
}
File photo = createCaptureFile(encodingType);
File photo = createCaptureFile(JPEG);
croppedUri = Uri.fromFile(photo);
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, croppedUri);
} else {
@@ -391,6 +405,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
}
/**
* Brings up the UI to perform crop on passed image URI
*
@@ -404,6 +419,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// set crop properties
cropIntent.putExtra("crop", "true");
// indicate output X and Y
if (targetWidth > 0) {
cropIntent.putExtra("outputX", targetWidth);
@@ -417,8 +433,11 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
// create new file handle to get full resolution crop
croppedUri = Uri.fromFile(createCaptureFile(this.encodingType, System.currentTimeMillis() + ""));
cropIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
cropIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
cropIntent.putExtra("output", croppedUri);
// start the activity - we handle returning in onActivityResult
if (this.cordova != null) {
@@ -426,14 +445,14 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
cropIntent, CROP_CAMERA + destType);
}
} catch (ActivityNotFoundException anfe) {
Log.e(LOG_TAG, "Crop operation not supported on this device");
LOG.e(LOG_TAG, "Crop operation not supported on this device");
try {
processResultFromCamera(destType, cameraIntent);
}
catch (IOException e)
{
e.printStackTrace();
Log.e(LOG_TAG, "Unable to write to file");
LOG.e(LOG_TAG, "Unable to write to file");
}
}
}
@@ -449,9 +468,11 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// Create an ExifHelper to save the exif data that is lost during compression
ExifHelper exif = new ExifHelper();
String sourcePath = (this.allowEdit && this.croppedUri != null) ?
FileHelper.stripFileProtocol(this.croppedUri.toString()) :
FileHelper.stripFileProtocol(this.imageUri.toString());
this.imageUri.getFilePath();
if (this.encodingType == JPEG) {
try {
@@ -472,12 +493,13 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// in the gallery and the modified image is saved in the temporary
// directory
if (this.saveToPhotoAlbum) {
galleryUri = Uri.fromFile(new File(getPicutresPath()));
galleryUri = Uri.fromFile(new File(getPicturesPath()));
if (this.allowEdit && this.croppedUri != null) {
writeUncompressedImage(this.croppedUri, galleryUri);
writeUncompressedImage(croppedUri, galleryUri);
} else {
writeUncompressedImage(this.imageUri, galleryUri);
Uri imageUri = this.imageUri.getFileUri();
writeUncompressedImage(imageUri, galleryUri);
}
refreshGallery(galleryUri);
@@ -485,7 +507,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// If sending base64 image back
if (destType == DATA_URL) {
bitmap = getScaledBitmap(sourcePath);
bitmap = getScaledAndRotatedBitmap(sourcePath);
if (bitmap == null) {
// Try to get the bitmap from intent.
@@ -494,14 +516,11 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// Double-check the bitmap.
if (bitmap == null) {
Log.d(LOG_TAG, "I either have a null image path or bitmap");
LOG.d(LOG_TAG, "I either have a null image path or bitmap");
this.failPicture("Unable to create bitmap!");
return;
}
if (rotate != 0 && this.correctOrientation) {
bitmap = getRotatedBitmap(rotate, bitmap, exif);
}
this.processPicture(bitmap, this.encodingType);
@@ -524,27 +543,26 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
Uri uri = Uri.fromFile(createCaptureFile(this.encodingType, System.currentTimeMillis() + ""));
if (this.allowEdit && this.croppedUri != null) {
writeUncompressedImage(this.croppedUri, uri);
Uri croppedUri = Uri.fromFile(new File(getFileNameFromUri(this.croppedUri)));
writeUncompressedImage(croppedUri, uri);
} else {
writeUncompressedImage(this.imageUri, uri);
Uri imageUri = this.imageUri.getFileUri();
writeUncompressedImage(imageUri, uri);
}
this.callbackContext.success(uri.toString());
}
} else {
Uri uri = Uri.fromFile(createCaptureFile(this.encodingType, System.currentTimeMillis() + ""));
bitmap = getScaledBitmap(sourcePath);
bitmap = getScaledAndRotatedBitmap(sourcePath);
// Double-check the bitmap.
if (bitmap == null) {
Log.d(LOG_TAG, "I either have a null image path or bitmap");
LOG.d(LOG_TAG, "I either have a null image path or bitmap");
this.failPicture("Unable to create bitmap!");
return;
}
if (rotate != 0 && this.correctOrientation) {
bitmap = getRotatedBitmap(rotate, bitmap, exif);
}
// Add compressed version of captured image to returned media store Uri
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
@@ -559,6 +577,9 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
if (this.encodingType == JPEG) {
String exifPath;
exifPath = uri.getPath();
//We just finished rotating it by an arbitrary orientation, just make sure it's normal
if(rotate != ExifInterface.ORIENTATION_NORMAL)
exif.resetOrientation();
exif.createOutFile(exifPath);
exif.writeExifData();
}
@@ -571,29 +592,40 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
throw new IllegalStateException();
}
this.cleanup(FILE_URI, this.imageUri, galleryUri, bitmap);
this.cleanup(FILE_URI, this.imageUri.getFileUri(), galleryUri, bitmap);
bitmap = null;
}
private String getPicutresPath()
{
private String getPicturesPath() {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? ".jpg" : ".png");
File storageDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
storageDir.mkdirs();
String galleryPath = storageDir.getAbsolutePath() + "/" + imageFileName;
return galleryPath;
}
private void refreshGallery(Uri contentUri)
{
private void refreshGallery(Uri contentUri) {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
mediaScanIntent.setData(contentUri);
this.cordova.getActivity().sendBroadcast(mediaScanIntent);
}
/**
* Converts output image format int value to string value of mime type.
* @param outputFormat int Output format of camera API.
* Must be value of either JPEG or PNG constant
* @return String String value of mime type or empty string if mime type is not supported
*/
private String getMimetypeForFormat(int outputFormat) {
if (outputFormat == PNG) return "image/png";
if (outputFormat == JPEG) return "image/jpeg";
return "";
}
private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
private String outputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
// Some content: URIs do not map to file paths (e.g. picasa).
String realPath = FileHelper.getRealPath(uri, this.cordova);
@@ -602,6 +634,8 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
realPath.substring(realPath.lastIndexOf('/') + 1) :
"modified." + (this.encodingType == JPEG ? "jpg" : "png");
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
//String fileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? ".jpg" : ".png");
String modifiedPath = getTempDirectoryPath() + "/" + fileName;
OutputStream os = new FileOutputStream(modifiedPath);
@@ -612,18 +646,14 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
bitmap.compress(compressFormat, this.mQuality, os);
os.close();
if (realPath != null && this.encodingType == JPEG) {
// Create an ExifHelper to save the exif data that is lost during compression
ExifHelper exif = new ExifHelper();
if (exifData != null && this.encodingType == JPEG) {
try {
exif.createInFile(realPath);
exif.readExifData();
if (this.correctOrientation && this.orientationCorrected) {
exif.resetOrientation();
exifData.resetOrientation();
}
exif.createOutFile(modifiedPath);
exif.writeExifData();
exifData.createOutFile(modifiedPath);
exifData.writeExifData();
exifData = null;
} catch (IOException e) {
e.printStackTrace();
}
@@ -632,7 +662,6 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
}
/**
* Applies all needed transformation to the image received from the gallery.
*
@@ -652,7 +681,7 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
int rotate = 0;
String fileLocation = FileHelper.getRealPath(uri, this.cordova);
Log.d(LOG_TAG, "File locaton is: " + fileLocation);
LOG.d(LOG_TAG, "File locaton is: " + fileLocation);
// If you ask for video or all media type you will automatically get back a file URI
// and there will be no attempt to resize any returned data
@@ -660,47 +689,36 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
this.callbackContext.success(fileLocation);
}
else {
// This is a special case to just return the path as no scaling,
// rotating, nor compressing needs to be done
if (this.targetHeight == -1 && this.targetWidth == -1 &&
(destType == FILE_URI || destType == NATIVE_URI) && !this.correctOrientation) {
this.callbackContext.success(uri.toString());
} else {
String uriString = uri.toString();
// Get the path to the image. Makes loading so much easier.
String mimeType = FileHelper.getMimeType(uriString, this.cordova);
// This is a special case to just return the path as no scaling,
// rotating, nor compressing needs to be done
if (this.targetHeight == -1 && this.targetWidth == -1 &&
(destType == FILE_URI || destType == NATIVE_URI) && !this.correctOrientation &&
mimeType.equalsIgnoreCase(getMimetypeForFormat(encodingType)))
{
this.callbackContext.success(uriString);
} else {
// If we don't have a valid image so quit.
if (!("image/jpeg".equalsIgnoreCase(mimeType) || "image/png".equalsIgnoreCase(mimeType))) {
Log.d(LOG_TAG, "I either have a null image path or bitmap");
LOG.d(LOG_TAG, "I either have a null image path or bitmap");
this.failPicture("Unable to retrieve path to picture!");
return;
}
Bitmap bitmap = null;
try {
bitmap = getScaledBitmap(uriString);
bitmap = getScaledAndRotatedBitmap(uriString);
} catch (IOException e) {
e.printStackTrace();
}
if (bitmap == null) {
Log.d(LOG_TAG, "I either have a null image path or bitmap");
LOG.d(LOG_TAG, "I either have a null image path or bitmap");
this.failPicture("Unable to create bitmap!");
return;
}
if (this.correctOrientation) {
rotate = getImageOrientation(uri);
if (rotate != 0) {
Matrix matrix = new Matrix();
matrix.setRotate(rotate);
try {
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
this.orientationCorrected = true;
} catch (OutOfMemoryError oom) {
this.orientationCorrected = false;
}
}
}
// If sending base64 image back
if (destType == DATA_URL) {
this.processPicture(bitmap, this.encodingType);
@@ -710,9 +728,11 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
else if (destType == FILE_URI || destType == NATIVE_URI) {
// Did we modify the image?
if ( (this.targetHeight > 0 && this.targetWidth > 0) ||
(this.correctOrientation && this.orientationCorrected) ) {
(this.correctOrientation && this.orientationCorrected) ||
!mimeType.equalsIgnoreCase(getMimetypeForFormat(encodingType)))
{
try {
String modifiedPath = this.ouputModifiedBitmap(bitmap, uri);
String modifiedPath = this.outputModifiedBitmap(bitmap, uri);
// The modified image is cached by the app in order to get around this and not have to delete you
// application cache I'm adding the current system time to the end of the file url.
this.callbackContext.success("file://" + modifiedPath + "?" + System.currentTimeMillis());
@@ -721,8 +741,7 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
e.printStackTrace();
this.failPicture("Error retrieving image.");
}
}
else {
} else {
this.callbackContext.success(fileLocation);
}
}
@@ -760,12 +779,12 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
processResultFromCamera(destType, intent);
} catch (IOException e) {
e.printStackTrace();
Log.e(LOG_TAG, "Unable to write to file");
LOG.e(LOG_TAG, "Unable to write to file");
}
}// If cancelled
else if (resultCode == Activity.RESULT_CANCELED) {
this.failPicture("Camera cancelled.");
this.failPicture("No Image Selected");
}
// If something else
@@ -778,12 +797,12 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
// If image available
if (resultCode == Activity.RESULT_OK) {
try {
if(this.allowEdit)
{
Uri tmpFile = Uri.fromFile(createCaptureFile(this.encodingType));
if (this.allowEdit) {
Uri tmpFile = FileProvider.getUriForFile(cordova.getActivity(),
applicationId + ".provider",
createCaptureFile(this.encodingType));
performCrop(tmpFile, destType, intent);
}
else {
} else {
this.processResultFromCamera(destType, intent);
}
} catch (IOException e) {
@@ -794,7 +813,7 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
// If cancelled
else if (resultCode == Activity.RESULT_CANCELED) {
this.failPicture("Camera cancelled.");
this.failPicture("No Image Selected");
}
// If something else
@@ -812,79 +831,38 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
processResultFromGallery(finalDestType, i);
}
});
}
else if (resultCode == Activity.RESULT_CANCELED) {
this.failPicture("Selection cancelled.");
}
else {
} else if (resultCode == Activity.RESULT_CANCELED) {
this.failPicture("No Image Selected");
} else {
this.failPicture("Selection did not complete!");
}
}
}
private int getImageOrientation(Uri uri) {
int rotate = 0;
String[] cols = { MediaStore.Images.Media.ORIENTATION };
try {
Cursor cursor = cordova.getActivity().getContentResolver().query(uri,
cols, null, null, null);
if (cursor != null) {
cursor.moveToPosition(0);
rotate = cursor.getInt(0);
cursor.close();
}
} catch (Exception e) {
// You can get an IllegalArgumentException if ContentProvider doesn't support querying for orientation.
}
return rotate;
}
/**
* Figure out if the bitmap should be rotated. For instance if the picture was taken in
* portrait mode
*
* @param rotate
* @param bitmap
* @return rotated bitmap
*/
private Bitmap getRotatedBitmap(int rotate, Bitmap bitmap, ExifHelper exif) {
Matrix matrix = new Matrix();
if (rotate == 180) {
matrix.setRotate(rotate);
private int exifToDegrees(int exifOrientation) {
if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_90) {
return 90;
} else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_180) {
return 180;
} else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_270) {
return 270;
} else {
matrix.setRotate(rotate, (float) bitmap.getWidth() / 2, (float) bitmap.getHeight() / 2);
return 0;
}
try
{
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
exif.resetOrientation();
}
catch (OutOfMemoryError oom)
{
// You can run out of memory if the image is very large:
// http://simonmacdonald.blogspot.ca/2012/07/change-to-camera-code-in-phonegap-190.html
// If this happens, simply do not rotate the image and return it unmodified.
// If you do not catch the OutOfMemoryError, the Android app crashes.
}
return bitmap;
}
/**
* In the special case where the default width, height and quality are unchanged
* we just write the file out to disk saving the expensive Bitmap.compress function.
* Write an inputstream to local disk
*
* @param uri
* @param fis - The InputStream to write
* @param dest - Destination on disk to write to
* @throws FileNotFoundException
* @throws IOException
*/
private void writeUncompressedImage(Uri src, Uri dest) throws FileNotFoundException,
private void writeUncompressedImage(InputStream fis, Uri dest) throws FileNotFoundException,
IOException {
FileInputStream fis = null;
OutputStream os = null;
try {
fis = new FileInputStream(FileHelper.stripFileProtocol(src.toString()));
os = this.cordova.getActivity().getContentResolver().openOutputStream(dest);
byte[] buffer = new byte[4096];
int len;
@@ -909,6 +887,21 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
}
}
}
/**
* In the special case where the default width, height and quality are unchanged
* we just write the file out to disk saving the expensive Bitmap.compress function.
*
* @param src
* @throws FileNotFoundException
* @throws IOException
*/
private void writeUncompressedImage(Uri src, Uri dest) throws FileNotFoundException,
IOException {
FileInputStream fis = new FileInputStream(FileHelper.stripFileProtocol(src.toString()));
writeUncompressedImage(fis, dest);
}
/**
* Create entry in media store for image
@@ -934,15 +927,15 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
}
/**
* Return a scaled bitmap based on the target width and height
* Return a scaled and rotated bitmap based on the target width and height
*
* @param imagePath
* @param imageUrl
* @return
* @throws IOException
*/
private Bitmap getScaledBitmap(String imageUrl) throws IOException {
// If no new width or height were specified return the original bitmap
if (this.targetWidth <= 0 && this.targetHeight <= 0) {
private Bitmap getScaledAndRotatedBitmap(String imageUrl) throws IOException {
// If no new width or height were specified, and orientation is not needed return the original bitmap
if (this.targetWidth <= 0 && this.targetHeight <= 0 && !(this.correctOrientation)) {
InputStream fileStream = null;
Bitmap image = null;
try {
@@ -960,12 +953,61 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
return image;
}
/* Copy the inputstream to a temporary file on the device.
We then use this temporary file to determine the width/height/orientation.
This is the only way to determine the orientation of the photo coming from 3rd party providers (Google Drive, Dropbox,etc)
This also ensures we create a scaled bitmap with the correct orientation
We delete the temporary file once we are done
*/
File localFile = null;
Uri galleryUri = null;
int rotate = 0;
try {
InputStream fileStream = FileHelper.getInputStreamFromUriString(imageUrl, cordova);
if (fileStream != null) {
// Generate a temporary file
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String fileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? ".jpg" : ".png");
localFile = new File(getTempDirectoryPath() + fileName);
galleryUri = Uri.fromFile(localFile);
writeUncompressedImage(fileStream, galleryUri);
try {
String mimeType = FileHelper.getMimeType(imageUrl.toString(), cordova);
if ("image/jpeg".equalsIgnoreCase(mimeType)) {
// ExifInterface doesn't like the file:// prefix
String filePath = galleryUri.toString().replace("file://", "");
// read exifData of source
exifData = new ExifHelper();
exifData.createInFile(filePath);
// Use ExifInterface to pull rotation information
if (this.correctOrientation) {
ExifInterface exif = new ExifInterface(filePath);
rotate = exifToDegrees(exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED));
}
}
} catch (Exception oe) {
LOG.w(LOG_TAG,"Unable to read Exif data: "+ oe.toString());
rotate = 0;
}
}
}
catch (Exception e)
{
LOG.e(LOG_TAG,"Exception while getting input stream: "+ e.toString());
return null;
}
try {
// figure out the original width and height of the image
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
InputStream fileStream = null;
try {
fileStream = FileHelper.getInputStreamFromUriString(imageUrl, cordova);
fileStream = FileHelper.getInputStreamFromUriString(galleryUri.toString(), cordova);
BitmapFactory.decodeStream(fileStream, null, options);
} finally {
if (fileStream != null) {
@@ -977,21 +1019,40 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
}
}
//CB-2292: WTF? Why is the width null?
if(options.outWidth == 0 || options.outHeight == 0)
{
if (options.outWidth == 0 || options.outHeight == 0) {
return null;
}
// User didn't specify output dimensions, but they need orientation
if (this.targetWidth <= 0 && this.targetHeight <= 0) {
this.targetWidth = options.outWidth;
this.targetHeight = options.outHeight;
}
// Setup target width/height based on orientation
int rotatedWidth, rotatedHeight;
boolean rotated= false;
if (rotate == 90 || rotate == 270) {
rotatedWidth = options.outHeight;
rotatedHeight = options.outWidth;
rotated = true;
} else {
rotatedWidth = options.outWidth;
rotatedHeight = options.outHeight;
}
// determine the correct aspect ratio
int[] widthHeight = calculateAspectRatio(options.outWidth, options.outHeight);
int[] widthHeight = calculateAspectRatio(rotatedWidth, rotatedHeight);
// Load in the smallest bitmap possible that is closest to the size we want
options.inJustDecodeBounds = false;
options.inSampleSize = calculateSampleSize(options.outWidth, options.outHeight, this.targetWidth, this.targetHeight);
options.inSampleSize = calculateSampleSize(rotatedWidth, rotatedHeight, widthHeight[0], widthHeight[1]);
Bitmap unscaledBitmap = null;
try {
fileStream = FileHelper.getInputStreamFromUriString(imageUrl, cordova);
fileStream = FileHelper.getInputStreamFromUriString(galleryUri.toString(), cordova);
unscaledBitmap = BitmapFactory.decodeStream(fileStream, null, options);
} finally {
if (fileStream != null) {
@@ -1006,7 +1067,33 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
return null;
}
return Bitmap.createScaledBitmap(unscaledBitmap, widthHeight[0], widthHeight[1], true);
int scaledWidth = (!rotated) ? widthHeight[0] : widthHeight[1];
int scaledHeight = (!rotated) ? widthHeight[1] : widthHeight[0];
Bitmap scaledBitmap = Bitmap.createScaledBitmap(unscaledBitmap, scaledWidth, scaledHeight, true);
if (scaledBitmap != unscaledBitmap) {
unscaledBitmap.recycle();
unscaledBitmap = null;
}
if (this.correctOrientation && (rotate != 0)) {
Matrix matrix = new Matrix();
matrix.setRotate(rotate);
try {
scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true);
this.orientationCorrected = true;
} catch (OutOfMemoryError oom) {
this.orientationCorrected = false;
}
}
return scaledBitmap;
}
finally {
// delete the temporary copy
if (localFile != null) {
localFile.delete();
}
}
}
/**
@@ -1027,11 +1114,11 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
}
// Only the width was specified
else if (newWidth > 0 && newHeight <= 0) {
newHeight = (newWidth * origHeight) / origWidth;
newHeight = (int)((double)(newWidth / (double)origWidth) * origHeight);
}
// only the height was specified
else if (newWidth <= 0 && newHeight > 0) {
newWidth = (newHeight * origWidth) / origHeight;
newWidth = (int)((double)(newHeight / (double)origHeight) * origWidth);
}
// If the user specified both a positive width and height
// (potentially different aspect ratio) then the width or height is
@@ -1093,6 +1180,7 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
/**
* Cleans up after picture taking. Checking for duplicates and that kind of stuff.
*
* @param newImage
*/
private void cleanup(int imageType, Uri oldImage, Uri newImage, Bitmap bitmap) {
@@ -1144,6 +1232,7 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
/**
* Determine if we are storing the images in internal or external storage
*
* @return Uri
*/
private Uri whichContentStore() {
@@ -1214,18 +1303,14 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
public void onRequestPermissionResult(int requestCode, String[] permissions,
int[] grantResults) throws JSONException
{
for(int r:grantResults)
{
if(r == PackageManager.PERMISSION_DENIED)
{
int[] grantResults) throws JSONException {
for (int r : grantResults) {
if (r == PackageManager.PERMISSION_DENIED) {
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, PERMISSION_DENIED_ERROR));
return;
}
}
switch(requestCode)
{
switch (requestCode) {
case TAKE_PIC_SEC:
takePicture(this.destType, this.encodingType);
break;
@@ -1259,7 +1344,7 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
}
if (this.imageUri != null) {
state.putString("imageUri", this.imageUri.toString());
state.putString("imageUri", this.imageUri.getFileUri().toString());
}
return state;
@@ -1283,9 +1368,33 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
}
if (state.containsKey("imageUri")) {
this.imageUri = Uri.parse(state.getString("imageUri"));
//I have no idea what type of URI is being passed in
this.imageUri = new CordovaUri(Uri.parse(state.getString("imageUri")));
}
this.callbackContext = callbackContext;
}
/*
* 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;
}
}
+104
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;
}
}
+21
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>
+39 -39
View File
@@ -25,14 +25,14 @@ var PictureSourceType = {
PHOTOLIBRARY: 0, // Choose image from picture library (same as SAVEDPHOTOALBUM for Android)
CAMERA: 1, // Take picture from camera
SAVEDPHOTOALBUM: 2 // Choose image from picture library (same as PHOTOLIBRARY for Android)
},
DestinationType = {
};
var DestinationType = {
DATA_URL: 0, // Return base64 encoded string
FILE_URI: 1, // Return file uri (content://media/external/images/media/2 for Android)
NATIVE_URI: 2 // Return native uri (eg. asset-library://... for iOS)
},
savePath = window.qnx.webplatform.getApplication().getEnv("HOME").replace('/data', '') + '/shared/camera/',
invokeAvailable = true;
};
var savePath = window.qnx.webplatform.getApplication().getEnv('HOME').replace('/data', '') + '/shared/camera/';
var invokeAvailable = true;
// check for camera card - it isn't currently availble in work perimeter
window.qnx.webplatform.getApplication().invocation.queryTargets(
@@ -54,7 +54,7 @@ function showCameraDialog (done, cancel, fail) {
wv.allowQnxObject = true;
wv.allowRpc = true;
wv.zOrder = 1;
wv.setGeometry(0, 0, screen.width, screen.height);
wv.setGeometry(0, 0, screen.width, screen.height); /* eslint no-undef : 0 */
wv.backgroundColor = 0x00000000;
wv.active = true;
wv.visible = true;
@@ -71,7 +71,7 @@ function showCameraDialog (done, cancel, fail) {
} else {
saveImage(args[1], done, fail);
}
wv.un('JavaScriptCallback', arguments.callee);
wv.un('JavaScriptCallback', arguments.callee); /* eslint no-caller : 0 */
wv.visible = false;
wv.destroy();
qnx.webplatform.getApplication().unlockRotation();
@@ -87,19 +87,19 @@ 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; };
var date = new Date();
var 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';
}
// convert dataURI to Blob
function dataURItoBlob (dataURI) {
var byteString = atob(dataURI.split(',')[1]),
mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0],
arrayBuffer = new ArrayBuffer(byteString.length),
ia = new Uint8Array(arrayBuffer),
i;
var byteString = atob(dataURI.split(',')[1]);
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
var arrayBuffer = new ArrayBuffer(byteString.length);
var ia = new Uint8Array(arrayBuffer);
var i;
for (i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
@@ -124,37 +124,37 @@ function saveImage(data, success, fail) {
}
function encodeBase64 (filePath, callback) {
var sandbox = window.qnx.webplatform.getController().setFileSystemSandbox, // save original sandbox value
errorHandler = function (err) {
var msg = "An error occured: ";
var sandbox = window.qnx.webplatform.getController().setFileSystemSandbox; // save original sandbox value
var errorHandler = function (err) {
var msg = 'An error occured: ';
switch (err.code) {
case FileError.NOT_FOUND_ERR:
msg += "File or directory not found";
msg += 'File or directory not found';
break;
case FileError.NOT_READABLE_ERR:
msg += "File or directory not readable";
msg += 'File or directory not readable';
break;
case FileError.PATH_EXISTS_ERR:
msg += "File or directory already exists";
msg += 'File or directory already exists';
break;
case FileError.TYPE_MISMATCH_ERR:
msg += "Invalid file type";
msg += 'Invalid file type';
break;
default:
msg += "Unknown Error";
msg += 'Unknown Error';
break;
}
// set it back to original value
window.qnx.webplatform.getController().setFileSystemSandbox = sandbox;
callback(msg);
},
gotFile = function (fileEntry) {
};
var gotFile = function (fileEntry) {
fileEntry.file(function (file) {
var reader = new FileReader();
@@ -166,8 +166,8 @@ function encodeBase64(filePath, callback) {
reader.readAsDataURL(file);
}, errorHandler);
},
onInitFs = function (fs) {
};
var onInitFs = function (fs) {
window.qnx.webplatform.getController().setFileSystemSandbox = false;
fs.root.getFile(filePath, {create: false}, gotFile, errorHandler);
};
@@ -177,28 +177,28 @@ function encodeBase64(filePath, callback) {
module.exports = {
takePicture: function (success, fail, args, env) {
var destinationType = JSON.parse(decodeURIComponent(args[1])),
sourceType = JSON.parse(decodeURIComponent(args[2])),
result = new PluginResult(args, env),
done = function (data) {
var destinationType = JSON.parse(decodeURIComponent(args[1]));
var sourceType = JSON.parse(decodeURIComponent(args[2]));
var result = new PluginResult(args, env);
var done = function (data) {
if (destinationType === DestinationType.FILE_URI) {
data = "file://" + data;
data = 'file://' + data;
result.callbackOk(data, false);
} else {
encodeBase64(data, function (data) {
if (/^data:/.test(data)) {
data = data.slice(data.indexOf(",") + 1);
data = data.slice(data.indexOf(',') + 1);
result.callbackOk(data, false);
} else {
result.callbackError(data, false);
}
});
}
},
cancel = function (reason) {
};
var cancel = function (reason) {
result.callbackError(reason, false);
},
invoked = function (error) {
};
var invoked = function (error) {
if (error) {
result.callbackError(error, false);
}
@@ -207,7 +207,7 @@ module.exports = {
switch (sourceType) {
case PictureSourceType.CAMERA:
if (invokeAvailable) {
window.qnx.webplatform.getApplication().cards.camera.open("photo", done, cancel, invoked);
window.qnx.webplatform.getApplication().cards.camera.open('photo', done, cancel, invoked);
} else {
showCameraDialog(done, cancel, fail);
}
@@ -216,8 +216,8 @@ module.exports = {
case PictureSourceType.PHOTOLIBRARY:
case PictureSourceType.SAVEDPHOTOALBUM:
window.qnx.webplatform.getApplication().cards.filePicker.open({
mode: "Picker",
type: ["picture"]
mode: 'Picker',
type: ['picture']
}, done, cancel, invoked);
break;
}
+17 -8
View File
@@ -23,16 +23,17 @@ 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 reader = new FileReader();
var reader = new FileReader(); /* eslint no-undef : 0 */
reader.onload = function (readerEvent) {
input.parentNode.removeChild(input);
@@ -48,28 +49,36 @@ 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.
@@ -111,4 +120,4 @@ module.exports = {
cleanup: function () {}
};
require("cordova/exec/proxy").add("Camera",module.exports);
require('cordova/exec/proxy').add('Camera', module.exports);
+3 -3
View File
@@ -23,9 +23,9 @@
function takePicture (success, error, opts) {
var pick = new MozActivity({
name: "pick",
name: 'pick',
data: {
type: ["image/*"]
type: ['image/*']
}
});
@@ -50,4 +50,4 @@ module.exports = {
cleanup: function () {}
};
require("cordova/exec/proxy").add("Camera", module.exports);
require('cordova/exec/proxy').add('Camera', module.exports);
+8 -2
View File
@@ -240,10 +240,14 @@ 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 pickerController] pickerPopoverController] dismissPopoverAnimated:NO];
NSDictionary* options = [command argumentAtIndex:0 withDefault:nil];
[self displayPopover:options];
}
}
- (NSInteger)integerValueForKey:(NSDictionary*)dict key:(NSString*)key defaultValue:(NSInteger)defaultValue
{
@@ -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) {
if (![self usesGeolocation] || picker.sourceType != UIImagePickerControllerSourceTypeCamera) {
[weakSelf.commandDelegate sendPluginResult:res callbackId:cameraPicker.callbackId];
weakSelf.hasPendingOperation = NO;
weakSelf.pickerController = nil;
}
}];
}
else {
@@ -563,7 +569,7 @@ static NSString* toBase64(NSData* data) {
} 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"];
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No Image Selected"];
}
+81
View File
@@ -0,0 +1,81 @@
/*
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.
*/
#import <Quartz/Quartz.h>
#import <AppKit/AppKit.h>
#import <Cordova/CDVPlugin.h>
enum CDVDestinationType {
DestinationTypeDataUrl = 0,
DestinationTypeFileUri,
DestinationTypeNativeUri
};
typedef NSUInteger CDVDestinationType;
enum CDVSourceType {
SourceTypePhotoLibrary = 0,
SourceTypeCamera,
SourceTypePhotoAlbum
};
typedef NSUInteger CDVSourceType;
enum CDVEncodingType {
EncodingTypeJPEG = 0,
EncodingTypePNG
};
typedef NSUInteger CDVEncodingType;
enum CDVMediaType {
MediaTypePicture = 0,
MediaTypeVideo,
MediaTypeAll
};
typedef NSUInteger CDVMediaType;
// ======================================================================= //
@interface CDVPictureOptions : NSObject
@property (strong) NSNumber *quality;
@property (assign) CDVDestinationType destinationType;
@property (assign) CDVSourceType sourceType;
@property (assign) CGSize targetSize;
@property (assign) CDVEncodingType encodingType;
@property (assign) CDVMediaType mediaType;
@property (assign) BOOL allowsEditing;
@property (assign) BOOL correctOrientation;
@property (assign) BOOL saveToPhotoAlbum;
+ (instancetype) createFromTakePictureArguments:(CDVInvokedUrlCommand *)command;
@end
// ======================================================================= //
@interface CDVCamera : CDVPlugin
- (void)takePicture:(CDVInvokedUrlCommand *)command;
- (void)cleanup:(CDVInvokedUrlCommand *)command;
@end
+258
View File
@@ -0,0 +1,258 @@
/*
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.
*/
#import "CDVCamera.h"
@implementation CDVPictureOptions
+ (instancetype) createFromTakePictureArguments:(CDVInvokedUrlCommand*)command {
CDVPictureOptions *pictureOptions = [[CDVPictureOptions alloc] init];
pictureOptions.quality = [command argumentAtIndex:0 withDefault:@(50)];
pictureOptions.destinationType = [[command argumentAtIndex:1 withDefault:@(DestinationTypeFileUri)] unsignedIntegerValue];
pictureOptions.sourceType = [[command argumentAtIndex:2 withDefault:@(SourceTypeCamera)] unsignedIntegerValue];
NSNumber *targetWidth = [command argumentAtIndex:3 withDefault:nil];
NSNumber *targetHeight = [command argumentAtIndex:4 withDefault:nil];
pictureOptions.targetSize = CGSizeMake(0, 0);
if ((targetWidth != nil) && (targetHeight != nil)) {
pictureOptions.targetSize = CGSizeMake([targetWidth floatValue], [targetHeight floatValue]);
}
pictureOptions.encodingType = [[command argumentAtIndex:5 withDefault:@(EncodingTypeJPEG)] unsignedIntegerValue];
pictureOptions.mediaType = [[command argumentAtIndex:6 withDefault:@(MediaTypePicture)] unsignedIntegerValue];
pictureOptions.allowsEditing = [[command argumentAtIndex:7 withDefault:@(NO)] boolValue];
pictureOptions.correctOrientation = [[command argumentAtIndex:8 withDefault:@(NO)] boolValue];
pictureOptions.saveToPhotoAlbum = [[command argumentAtIndex:9 withDefault:@(NO)] boolValue];
return pictureOptions;
}
@end
// ======================================================================= //
@implementation CDVCamera
/*!
Static array that stores the temporary created files allowing to delete them when calling navigator.camera.cleanup(...)
*/
static NSMutableArray *cleanUpFiles;
+ (void)initialize {
cleanUpFiles = [NSMutableArray array];
}
- (void)takePicture:(CDVInvokedUrlCommand *)command {
CDVPictureOptions *pictureOptions = [CDVPictureOptions createFromTakePictureArguments:command];
if (pictureOptions.sourceType == SourceTypeCamera) {
[self takePictureFromCamera:command withOptions:pictureOptions];
} else {
[self takePictureFromFile:command withOptions:pictureOptions];
}
}
- (void)cleanup:(CDVInvokedUrlCommand*)command {
[self.commandDelegate runInBackground:^{
if (cleanUpFiles.count > 0) {
for (int i=0; i<cleanUpFiles.count; i++) {
NSString *path = [cleanUpFiles objectAtIndex:i];
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
}
[cleanUpFiles removeAllObjects];
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
}
}];
}
#pragma mark - Camera
/*!
Takes a picture from the iSight camera using the default OS dialog.
@see https://developer.apple.com/documentation/quartz/ikpicturetaker
*/
- (void)takePictureFromCamera:(CDVInvokedUrlCommand *)command withOptions:(CDVPictureOptions *)pictureOptions {
IKPictureTaker *pictureTaker = [IKPictureTaker pictureTaker];
[pictureTaker setValue:[NSNumber numberWithBool:YES] forKey:IKPictureTakerAllowsVideoCaptureKey];
[pictureTaker setValue:[NSNumber numberWithBool:NO] forKey:IKPictureTakerAllowsFileChoosingKey];
[pictureTaker setValue:[NSNumber numberWithBool:pictureOptions.allowsEditing] forKey:IKPictureTakerShowEffectsKey];
[pictureTaker setValue:[NSNumber numberWithBool:pictureOptions.allowsEditing] forKey:IKPictureTakerAllowsEditingKey];
NSDictionary *contextInfo = @{ @"command": command, @"pictureOptions" : pictureOptions};
[pictureTaker beginPictureTakerSheetForWindow:self.viewController.contentView.window withDelegate:self didEndSelector:@selector(pictureTakerDidEnd:returnCode:contextInfo:) contextInfo:(void *)CFBridgingRetain(contextInfo)];
}
- (void)pictureTakerDidEnd:(IKPictureTaker *)pictureTaker returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
if (returnCode == NSOKButton) {
NSDictionary *contextInfoDictionary = (NSDictionary *)CFBridgingRelease(contextInfo);
CDVInvokedUrlCommand *command = [contextInfoDictionary valueForKey:@"command"];
CDVPictureOptions *pictureOptions = [contextInfoDictionary valueForKey:@"pictureOptions"];
[self returnImage:pictureTaker.outputImage command:command options:pictureOptions];
}
}
#pragma mark - File
/*!
Allows to select an image or video using the system native dialog.
*/
- (void)takePictureFromFile:(CDVInvokedUrlCommand *)command withOptions:(CDVPictureOptions *)pictureOptions {
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
openPanel.canChooseFiles = YES;
openPanel.canChooseDirectories = NO;
openPanel.canCreateDirectories = YES;
openPanel.allowsMultipleSelection = NO;
NSMutableArray *allowedTypes = [NSMutableArray array];
if (pictureOptions.mediaType == MediaTypePicture || pictureOptions.mediaType == MediaTypeAll) {
[allowedTypes addObjectsFromArray:[NSImage imageTypes]];
}
if (pictureOptions.mediaType == MediaTypeVideo || pictureOptions.mediaType == MediaTypeAll) {
[allowedTypes addObjectsFromArray:@[(NSString *)kUTTypeMovie]];
}
[openPanel setAllowedFileTypes:allowedTypes];
[openPanel beginSheetModalForWindow:self.viewController.contentView.window completionHandler:^(NSInteger result) {
if (result == NSOKButton) {
NSURL *fileURL = [openPanel.URLs objectAtIndex:0];
if ([self fileIsImage:fileURL]) {
NSImage *image = [[NSImage alloc] initWithContentsOfFile:fileURL.path];
[self returnImage:image command:command options:pictureOptions];
} else {
if (pictureOptions.destinationType == DestinationTypeDataUrl) {
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Camera.DestinationType.DATA_URL is only available with image files"];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
} else {
[self returnUri:fileURL.path command:command options:pictureOptions];
}
}
}
}];
}
#pragma mark - Common
/*!
Returns to JavaScript a URI.
Called when Camera.DestinationType.FILE_URI or Camera.DestinationType.NATIVE_URI.
*/
- (void)returnUri:(NSString *)path command:(CDVInvokedUrlCommand *)command options:(CDVPictureOptions *)pictureOptions {
NSString *protocol = (pictureOptions.destinationType == DestinationTypeFileUri) ? @"file://" : @"";
NSString *uri = [NSString stringWithFormat:@"%@%@", protocol, path];
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:uri];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
}
/*!
Returns to JavaScript a base64 encoded image.
Called when Camera.DestinationType.DATA_URL.
*/
- (void)returnImage:(NSImage *)image command:(CDVInvokedUrlCommand *)command options:(CDVPictureOptions *)pictureOptions {
[self.commandDelegate runInBackground:^{
NSData *processedImageData = [self processImage:image options:pictureOptions];
if (pictureOptions.destinationType == DestinationTypeDataUrl) {
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[processedImageData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
} else {
NSString *tempFilePath = [self uniqueImageName:pictureOptions];
[processedImageData writeToFile:tempFilePath atomically:YES];
[cleanUpFiles addObject:tempFilePath];
[self returnUri:tempFilePath command:command options:pictureOptions];
}
}];
}
/*!
Top level method to apply the size and quality required changes to an image.
*/
- (NSData *)processImage:(NSImage *)image options:(CDVPictureOptions *)pictureOptions {
NSImage *sourceImage = image;
if (pictureOptions.targetSize.width > 0 && pictureOptions.targetSize.height > 0) {
sourceImage = [self resizeImage:sourceImage toSize:pictureOptions.targetSize];
}
CGImageRef cgRef = [sourceImage CGImageForProposedRect:NULL context:nil hints:nil];
NSBitmapImageRep *imageRepresentation = [[NSBitmapImageRep alloc] initWithCGImage:cgRef];
NSData *data = (pictureOptions.encodingType == EncodingTypeJPEG)
? [imageRepresentation representationUsingType:NSJPEGFileType properties:@{NSImageCompressionFactor: [NSNumber numberWithFloat:pictureOptions.quality.floatValue/100.f]}]
: [imageRepresentation representationUsingType:NSPNGFileType properties:@{NSImageCompressionFactor: @1.0}];
return data;
}
/*!
Auxiliar method to resize an image.
*/
- (NSImage *)resizeImage:(NSImage *)image toSize:(CGSize)newSize {
CGFloat aspectWidth = newSize.width / image.size.width;
CGFloat aspectHeight = newSize.height / image.size.height;
CGFloat aspectRatio = MIN(aspectWidth, aspectHeight);
CGSize scaledSize = NSMakeSize(image.size.width*aspectRatio, image.size.height*aspectRatio);
NSImage *smallImage = [[NSImage alloc] initWithSize: scaledSize];
[smallImage lockFocus];
[image setSize: scaledSize];
[[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
[image drawAtPoint:NSZeroPoint fromRect:CGRectMake(0, 0, scaledSize.width, scaledSize.height) operation:NSCompositeCopy fraction:1.0];
[smallImage unlockFocus];
return smallImage;
}
/*!
Auxiliar method to know if a given file is an image or not.
*/
- (BOOL)fileIsImage:(NSURL *)fileURL {
NSString *type;
BOOL isImage = NO;
if ([fileURL getResourceValue:&type forKey:NSURLTypeIdentifierKey error:nil]) {
isImage = [[NSImage imageTypes] containsObject:type];
}
return isImage;
}
/*!
Auxiliar method that generates an unique filename for an image in the temporary directory.
*/
- (NSString *)uniqueImageName:(CDVPictureOptions *)pictureOptions {
NSString *tempDir = NSTemporaryDirectory();
NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString] ;
NSString *extension = (pictureOptions.encodingType == EncodingTypeJPEG) ? @"jpeg" : @"png";
NSString *uniqueFileName = [NSString stringWithFormat:@"%@%@.%@", tempDir, guid, extension];
return uniqueFileName;
}
@end
+115 -126
View File
@@ -19,13 +19,10 @@
*
*/
/*jshint unused:true, undef:true, browser:true */
/* global Windows:true, URL:true, module:true, require:true, WinJS:true */
var Camera = require('./Camera');
var getAppData = function () {
return Windows.Storage.ApplicationData.current;
};
@@ -58,7 +55,7 @@ module.exports = {
takePicture: function (successCallback, errorCallback, args) {
var sourceType = args[2];
if (sourceType != Camera.PictureSourceType.CAMERA) {
if (sourceType !== Camera.PictureSourceType.CAMERA) {
takePictureFromFile(successCallback, errorCallback, args);
} else {
takePictureFromCamera(successCallback, errorCallback, args);
@@ -67,8 +64,8 @@ module.exports = {
};
// https://msdn.microsoft.com/en-us/library/windows/apps/ff462087(v=vs.105).aspx
var windowsVideoContainers = [".avi", ".flv", ".asx", ".asf", ".mov", ".mp4", ".mpg", ".rm", ".srt", ".swf", ".wmv", ".vob"];
var windowsPhoneVideoContainers = [".avi", ".3gp", ".3g2", ".wmv", ".3gp", ".3g2", ".mp4", ".m4v"];
var windowsVideoContainers = ['.avi', '.flv', '.asx', '.asf', '.mov', '.mp4', '.mpg', '.rm', '.srt', '.swf', '.wmv', '.vob'];
var windowsPhoneVideoContainers = ['.avi', '.3gp', '.3g2', '.wmv', '.3gp', '.3g2', '.mp4', '.m4v'];
// Default aspect ratio 1.78 (16:9 hd video standard)
var DEFAULT_ASPECT_RATIO = '1.8';
@@ -78,11 +75,15 @@ var HIGHEST_POSSIBLE_Z_INDEX = 2147483647;
// Resize method
function resizeImage (successCallback, errorCallback, file, targetWidth, targetHeight, encodingType) {
var tempPhotoFileName = "";
if (encodingType == Camera.EncodingType.PNG) {
tempPhotoFileName = "camera_cordova_temp_return.png";
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";
tempPhotoFileName = 'camera_cordova_temp_return.jpg';
targetContentType = 'image/jpeg';
}
var storageFolder = getAppData().localFolder;
@@ -92,8 +93,8 @@ function resizeImage(successCallback, errorCallback, file, targetWidth, targetHe
})
.then(function (buffer) {
var strBase64 = encodeToBase64String(buffer);
var imageData = "data:" + file.contentType + ";base64," + strBase64;
var image = new Image();
var imageData = 'data:' + file.contentType + ';base64,' + strBase64;
var image = new Image(); /* eslint no-undef : 0 */
image.src = imageData;
image.onload = function () {
var ratio = Math.min(targetWidth / this.width, targetHeight / this.height);
@@ -106,9 +107,9 @@ function resizeImage(successCallback, errorCallback, file, targetWidth, targetHe
canvas.width = imageWidth;
canvas.height = imageHeight;
canvas.getContext("2d").drawImage(this, 0, 0, imageWidth, imageHeight);
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;
@@ -119,23 +120,22 @@ function resizeImage(successCallback, errorCallback, file, targetWidth, targetHe
return fileIO.writeBufferAsync(storagefile, content);
})
.done(function () {
successCallback("ms-appdata:///local/" + storageFileName);
successCallback('ms-appdata:///local/' + storageFileName);
}, errorCallback);
};
})
.done(null, function (err) {
errorCallback(err);
}
);
});
}
// Because of asynchronous method, so let the successCallback be called in it.
function resizeImageBase64 (successCallback, errorCallback, file, targetWidth, targetHeight) {
fileIO.readBufferAsync(file).done(function (buffer) {
var strBase64 = encodeToBase64String(buffer);
var imageData = "data:" + file.contentType + ";base64," + strBase64;
var imageData = 'data:' + file.contentType + ';base64,' + strBase64;
var image = new Image();
var image = new Image(); /* eslint no-undef : 0 */
image.src = imageData;
image.onload = function () {
@@ -147,14 +147,14 @@ function resizeImageBase64(successCallback, errorCallback, file, targetWidth, ta
canvas.width = imageWidth;
canvas.height = imageHeight;
var ctx = canvas.getContext("2d");
var ctx = canvas.getContext('2d');
ctx.drawImage(this, 0, 0, imageWidth, imageHeight);
// The resized file ready for upload
var finalFile = canvas.toDataURL(file.contentType);
// Remove the prefix such as "data:" + contentType + ";base64," , in order to meet the Cordova API.
var arr = finalFile.split(",");
var arr = finalFile.split(',');
var newStr = finalFile.substr(arr[0].length + 1);
successCallback(newStr);
};
@@ -171,11 +171,11 @@ function takePictureFromFile(successCallback, errorCallback, args) {
}
function takePictureFromFileWP (successCallback, errorCallback, args) {
var mediaType = args[6],
destinationType = args[1],
targetWidth = args[3],
targetHeight = args[4],
encodingType = args[5];
var mediaType = args[6];
var destinationType = args[1];
var targetWidth = args[3];
var targetHeight = args[4];
var encodingType = args[5];
/*
Need to add and remove an event listener to catch activation state
@@ -187,28 +187,25 @@ function takePictureFromFileWP(successCallback, errorCallback, args) {
var file = eventArgs.files[0];
if (!file) {
errorCallback("User didn't choose a file.");
webUIApp.removeEventListener("activated", filePickerActivationHandler);
webUIApp.removeEventListener('activated', filePickerActivationHandler);
return;
}
if (destinationType == Camera.DestinationType.FILE_URI || destinationType == Camera.DestinationType.NATIVE_URI) {
if (destinationType === Camera.DestinationType.FILE_URI || destinationType === Camera.DestinationType.NATIVE_URI) {
if (targetHeight > 0 && targetWidth > 0) {
resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType);
}
else {
} else {
var storageFolder = getAppData().localFolder;
file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).done(function (storageFile) {
if(destinationType == Camera.DestinationType.NATIVE_URI) {
successCallback("ms-appdata:///local/" + storageFile.name);
}
else {
if (destinationType === Camera.DestinationType.NATIVE_URI) {
successCallback('ms-appdata:///local/' + storageFile.name);
} else {
successCallback(URL.createObjectURL(storageFile));
}
}, function () {
errorCallback("Can't access localStorage folder.");
});
}
}
else {
} else {
if (targetHeight > 0 && targetWidth > 0) {
resizeImageBase64(successCallback, errorCallback, file, targetWidth, targetHeight);
} else {
@@ -218,46 +215,42 @@ function takePictureFromFileWP(successCallback, errorCallback, args) {
}, errorCallback);
}
}
webUIApp.removeEventListener("activated", filePickerActivationHandler);
webUIApp.removeEventListener('activated', filePickerActivationHandler);
}
};
var fileOpenPicker = new Windows.Storage.Pickers.FileOpenPicker();
if (mediaType == Camera.MediaType.PICTURE) {
fileOpenPicker.fileTypeFilter.replaceAll([".png", ".jpg", ".jpeg"]);
if (mediaType === Camera.MediaType.PICTURE) {
fileOpenPicker.fileTypeFilter.replaceAll(['.png', '.jpg', '.jpeg']);
fileOpenPicker.suggestedStartLocation = pickerLocId.picturesLibrary;
}
else if (mediaType == Camera.MediaType.VIDEO) {
} else if (mediaType === Camera.MediaType.VIDEO) {
fileOpenPicker.fileTypeFilter.replaceAll(windowsPhoneVideoContainers);
fileOpenPicker.suggestedStartLocation = pickerLocId.videosLibrary;
}
else {
fileOpenPicker.fileTypeFilter.replaceAll(["*"]);
} else {
fileOpenPicker.fileTypeFilter.replaceAll(['*']);
fileOpenPicker.suggestedStartLocation = pickerLocId.documentsLibrary;
}
webUIApp.addEventListener("activated", filePickerActivationHandler);
webUIApp.addEventListener('activated', filePickerActivationHandler);
fileOpenPicker.pickSingleFileAndContinue();
}
function takePictureFromFileWindows (successCallback, errorCallback, args) {
var mediaType = args[6],
destinationType = args[1],
targetWidth = args[3],
targetHeight = args[4],
encodingType = args[5];
var mediaType = args[6];
var destinationType = args[1];
var targetWidth = args[3];
var targetHeight = args[4];
var encodingType = args[5];
var fileOpenPicker = new Windows.Storage.Pickers.FileOpenPicker();
if (mediaType == Camera.MediaType.PICTURE) {
fileOpenPicker.fileTypeFilter.replaceAll([".png", ".jpg", ".jpeg"]);
if (mediaType === Camera.MediaType.PICTURE) {
fileOpenPicker.fileTypeFilter.replaceAll(['.png', '.jpg', '.jpeg']);
fileOpenPicker.suggestedStartLocation = pickerLocId.picturesLibrary;
}
else if (mediaType == Camera.MediaType.VIDEO) {
} else if (mediaType === Camera.MediaType.VIDEO) {
fileOpenPicker.fileTypeFilter.replaceAll(windowsVideoContainers);
fileOpenPicker.suggestedStartLocation = pickerLocId.videosLibrary;
}
else {
fileOpenPicker.fileTypeFilter.replaceAll(["*"]);
} else {
fileOpenPicker.fileTypeFilter.replaceAll(['*']);
fileOpenPicker.suggestedStartLocation = pickerLocId.documentsLibrary;
}
@@ -266,25 +259,22 @@ function takePictureFromFileWindows(successCallback, errorCallback, args) {
errorCallback("User didn't choose a file.");
return;
}
if (destinationType == Camera.DestinationType.FILE_URI || destinationType == Camera.DestinationType.NATIVE_URI) {
if (destinationType === Camera.DestinationType.FILE_URI || destinationType === Camera.DestinationType.NATIVE_URI) {
if (targetHeight > 0 && targetWidth > 0) {
resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType);
}
else {
} else {
var storageFolder = getAppData().localFolder;
file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).done(function (storageFile) {
if(destinationType == Camera.DestinationType.NATIVE_URI) {
successCallback("ms-appdata:///local/" + storageFile.name);
}
else {
if (destinationType === Camera.DestinationType.NATIVE_URI) {
successCallback('ms-appdata:///local/' + storageFile.name);
} else {
successCallback(URL.createObjectURL(storageFile));
}
}, function () {
errorCallback("Can't access localStorage folder.");
});
}
}
else {
} else {
if (targetHeight > 0 && targetWidth > 0) {
resizeImageBase64(successCallback, errorCallback, file, targetWidth, targetHeight);
} else {
@@ -311,39 +301,39 @@ function takePictureFromCamera(successCallback, errorCallback, args) {
function takePictureFromCameraWP (successCallback, errorCallback, args) {
// We are running on WP8.1 which lacks CameraCaptureUI class
// so we need to use MediaCapture class instead and implement custom UI for camera
var destinationType = args[1],
targetWidth = args[3],
targetHeight = args[4],
encodingType = args[5],
saveToPhotoAlbum = args[9],
cameraDirection = args[11],
capturePreview = null,
cameraCaptureButton = null,
cameraCancelButton = null,
capture = null,
captureSettings = null,
CaptureNS = Windows.Media.Capture,
sensor = null;
var destinationType = args[1];
var targetWidth = args[3];
var targetHeight = args[4];
var encodingType = args[5];
var saveToPhotoAlbum = args[9];
var cameraDirection = args[11];
var capturePreview = null;
var cameraCaptureButton = null;
var cameraCancelButton = null;
var capture = null;
var captureSettings = null;
var CaptureNS = Windows.Media.Capture;
var sensor = null;
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;";
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.style.cssText = "position: fixed; left: 0; top: 0; width: 100%; height: 100%; z-index: " + (HIGHEST_POSSIBLE_Z_INDEX - 1) + ";";
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
cameraCaptureButton = document.createElement("button");
cameraCaptureButton.innerText = "Take";
cameraCaptureButton.style.cssText = buttonStyle + "position: fixed; left: 0; bottom: 0; margin: 20px; z-index: " + HIGHEST_POSSIBLE_Z_INDEX + ";";
cameraCaptureButton = document.createElement('button');
cameraCaptureButton.innerText = 'Take';
cameraCaptureButton.style.cssText = buttonStyle + 'position: fixed; left: 0; bottom: 0; margin: 20px; z-index: ' + HIGHEST_POSSIBLE_Z_INDEX + ';';
// Create cancel button
cameraCancelButton = document.createElement("button");
cameraCancelButton.innerText = "Cancel";
cameraCancelButton.style.cssText = buttonStyle + "position: fixed; right: 0; bottom: 0; margin: 20px; z-index: " + HIGHEST_POSSIBLE_Z_INDEX + ";";
cameraCancelButton = document.createElement('button');
cameraCancelButton.innerText = 'Cancel';
cameraCancelButton.style.cssText = buttonStyle + 'position: fixed; right: 0; bottom: 0; margin: 20px; z-index: ' + HIGHEST_POSSIBLE_Z_INDEX + ';';
capture = new CaptureNS.MediaCapture();
@@ -365,7 +355,7 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
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);
window.addEventListener('focus', continueVideoOnFocus);
DeviceEnum.DeviceInformation.findAllAsync(DeviceEnum.DeviceClass.videoCapture).then(function (devices) {
if (devices.length <= 0) {
@@ -375,7 +365,7 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
}
devices.forEach(function (currDev) {
if (currDev.enclosureLocation.panel && currDev.enclosureLocation.panel == expectedPanel) {
if (currDev.enclosureLocation.panel && currDev.enclosureLocation.panel === expectedPanel) {
captureSettings.videoDeviceId = currDev.id;
}
});
@@ -414,7 +404,7 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
// Bind events to controls
sensor = Windows.Devices.Sensors.SimpleOrientationSensor.getDefault();
if (sensor !== null) {
sensor.addEventListener("orientationchanged", onOrientationChange);
sensor.addEventListener('orientationchanged', onOrientationChange);
}
// add click events to capture and cancel buttons
@@ -470,7 +460,7 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
cameraCancelButton.removeEventListener('click', onCameraCancelButtonClick);
// Remove the focus event handler
window.removeEventListener("focus", continueVideoOnFocus);
window.removeEventListener('focus', continueVideoOnFocus);
// Remove elements
[capturePreview, cameraCaptureButton, cameraCancelButton].forEach(function (elem) {
@@ -488,11 +478,11 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
function captureAction () {
var encodingProperties,
fileName,
tempFolder = getAppData().temporaryFolder;
var encodingProperties;
var fileName;
var tempFolder = getAppData().temporaryFolder;
if (encodingType == Camera.EncodingType.PNG) {
if (encodingType === Camera.EncodingType.PNG) {
fileName = 'photo.png';
encodingProperties = Windows.Media.MediaProperties.ImageEncodingProperties.createPng();
} else {
@@ -531,7 +521,7 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
}, function () {
photoStream.close();
finalStream.close();
throw new Error("An error has occured while capturing the photo.");
throw new Error('An error has occured while capturing the photo.');
});
});
})
@@ -701,18 +691,18 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
}
function takePictureFromCameraWindows (successCallback, errorCallback, args) {
var destinationType = args[1],
targetWidth = args[3],
targetHeight = args[4],
encodingType = args[5],
allowCrop = !!args[7],
saveToPhotoAlbum = args[9],
WMCapture = Windows.Media.Capture,
cameraCaptureUI = new WMCapture.CameraCaptureUI();
var destinationType = args[1];
var targetWidth = args[3];
var targetHeight = args[4];
var encodingType = args[5];
var allowCrop = !!args[7];
var saveToPhotoAlbum = args[9];
var WMCapture = Windows.Media.Capture;
var cameraCaptureUI = new WMCapture.CameraCaptureUI();
cameraCaptureUI.photoSettings.allowCropping = allowCrop;
if (encodingType == Camera.EncodingType.PNG) {
if (encodingType === Camera.EncodingType.PNG) {
cameraCaptureUI.photoSettings.format = WMCapture.CameraCaptureUIPhotoFormat.png;
} else {
cameraCaptureUI.photoSettings.format = WMCapture.CameraCaptureUIPhotoFormat.jpeg;
@@ -723,14 +713,13 @@ function takePictureFromCameraWindows(successCallback, errorCallback, args) {
var UIMaxRes = WMCapture.CameraCaptureUIMaxPhotoResolution;
var totalPixels = targetWidth * targetHeight;
if (targetWidth == -1 && targetHeight == -1) {
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;
@@ -748,7 +737,7 @@ function takePictureFromCameraWindows(successCallback, errorCallback, args) {
// define focus handler for windows phone 10.0
var savePhotoOnFocus = function () {
window.removeEventListener("focus", savePhotoOnFocus);
window.removeEventListener('focus', savePhotoOnFocus);
// call only when the app is in focus again
savePhoto(cameraPicture, {
destinationType: destinationType,
@@ -761,14 +750,14 @@ function takePictureFromCameraWindows(successCallback, errorCallback, args) {
// 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);
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);
window.removeEventListener('focus', savePhotoOnFocus);
return;
}
cameraPicture = picture;
@@ -784,20 +773,20 @@ function takePictureFromCameraWindows(successCallback, errorCallback, args) {
}, successCallback, errorCallback);
}
}, function () {
errorCallback("Fail to capture a photo.");
window.removeEventListener("focus", savePhotoOnFocus);
errorCallback('Fail to capture a photo.');
window.removeEventListener('focus', savePhotoOnFocus);
});
}
function savePhoto (picture, options, successCallback, errorCallback) {
// success callback for capture operation
var success = function (picture) {
if (options.destinationType == Camera.DestinationType.FILE_URI || options.destinationType == Camera.DestinationType.NATIVE_URI) {
if (options.destinationType === Camera.DestinationType.FILE_URI || options.destinationType === Camera.DestinationType.NATIVE_URI) {
if (options.targetHeight > 0 && options.targetWidth > 0) {
resizeImage(successCallback, errorCallback, picture, options.targetWidth, options.targetHeight, options.encodingType);
} else {
picture.copyAsync(getAppData().localFolder, picture.name, OptUnique).done(function (copiedFile) {
successCallback("ms-appdata:///local/" + copiedFile.name);
successCallback('ms-appdata:///local/' + copiedFile.name);
}, errorCallback);
}
} else {
@@ -818,7 +807,7 @@ function savePhoto(picture, options, successCallback, errorCallback) {
if (!options.saveToPhotoAlbum) {
success(picture);
return;
} else {
var savePicker = new Windows.Storage.Pickers.FileSavePicker();
var saveFile = function (file) {
@@ -835,21 +824,21 @@ function savePhoto(picture, options, successCallback, errorCallback) {
if (updateStatus === Windows.Storage.Provider.FileUpdateStatus.complete) {
success(picture);
} else {
errorCallback("File update status is not complete.");
errorCallback('File update status is not complete.');
}
}, errorCallback);
} else {
errorCallback("Failed to select a file.");
errorCallback('Failed to select a file.');
}
};
savePicker.suggestedStartLocation = pickerLocId.picturesLibrary;
if (options.encodingType === Camera.EncodingType.PNG) {
savePicker.fileTypeChoices.insert("PNG", [".png"]);
savePicker.suggestedFileName = "photo.png";
savePicker.fileTypeChoices.insert('PNG', ['.png']);
savePicker.suggestedFileName = 'photo.png';
} else {
savePicker.fileTypeChoices.insert("JPEG", [".jpg"]);
savePicker.suggestedFileName = "photo.jpg";
savePicker.fileTypeChoices.insert('JPEG', ['.jpg']);
savePicker.suggestedFileName = 'photo.jpg';
}
// If Windows Phone 8.1 use pickSaveFileAndContinue()
@@ -863,10 +852,10 @@ function savePhoto(picture, options, successCallback, errorCallback) {
if (eventArgs.kind === Windows.ApplicationModel.Activation.ActivationKind.pickSaveFileContinuation) {
var file = eventArgs.file;
saveFile(file);
webUIApp.removeEventListener("activated", fileSaveHandler);
webUIApp.removeEventListener('activated', fileSaveHandler);
}
};
webUIApp.addEventListener("activated", fileSaveHandler);
webUIApp.addEventListener('activated', fileSaveHandler);
savePicker.pickSaveFileAndContinue();
} else {
savePicker.pickSaveFileAsync()
@@ -875,4 +864,4 @@ function savePhoto(picture, options, successCallback, errorCallback) {
}
}
require("cordova/exec/proxy").add("Camera",module.exports);
require('cordova/exec/proxy').add('Camera', module.exports);
+14
View File
@@ -0,0 +1,14 @@
{
"name": "cordova-plugin-camera-tests",
"version": "2.4.1-dev",
"description": "",
"cordova": {
"id": "cordova-plugin-camera-tests",
"platforms": []
},
"keywords": [
"ecosystem:cordova"
],
"author": "",
"license": "Apache 2.0"
}
+2 -2
View File
@@ -22,11 +22,11 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:rim="http://www.blackberry.com/ns/widgets"
id="cordova-plugin-camera-tests"
version="2.2.0">
version="3.0.0">
<name>Cordova Camera Plugin Tests</name>
<license>Apache 2.0</license>
<dependency id="cordova-plugin-file" version=">=2.0.0" />
<dependency id="cordova-plugin-file-transfer" />
<js-module src="tests.js" name="tests">
</js-module>
+47 -45
View File
@@ -19,27 +19,27 @@
*
*/
/* globals Camera, resolveLocalFileSystemURI, FileEntry, CameraPopoverOptions, FileTransfer, FileUploadOptions, LocalFileSystem, MSApp */
/* jshint jasmine: true */
/* globals Camera, resolveLocalFileSystemURL, FileEntry, CameraPopoverOptions, FileTransfer, FileUploadOptions, LocalFileSystem, MSApp */
/* eslint-env jasmine */
exports.defineAutoTests = function () {
describe('Camera (navigator.camera)', function () {
it("should exist", function () {
it('should exist', function () {
expect(navigator.camera).toBeDefined();
});
it("should contain a getPicture function", function () {
it('should contain a getPicture function', function () {
expect(navigator.camera.getPicture).toBeDefined();
expect(typeof navigator.camera.getPicture == 'function').toBe(true);
expect(typeof navigator.camera.getPicture === 'function').toBe(true);
});
});
describe('Camera Constants (window.Camera + navigator.camera)', function () {
it("camera.spec.1 window.Camera should exist", function () {
it('camera.spec.1 window.Camera should exist', function () {
expect(window.Camera).toBeDefined();
});
it("camera.spec.2 should contain three DestinationType constants", function () {
it('camera.spec.2 should contain three DestinationType constants', function () {
expect(Camera.DestinationType.DATA_URL).toBe(0);
expect(Camera.DestinationType.FILE_URI).toBe(1);
expect(Camera.DestinationType.NATIVE_URI).toBe(2);
@@ -48,14 +48,14 @@ exports.defineAutoTests = function () {
expect(navigator.camera.DestinationType.NATIVE_URI).toBe(2);
});
it("camera.spec.3 should contain two EncodingType constants", function () {
it('camera.spec.3 should contain two EncodingType constants', function () {
expect(Camera.EncodingType.JPEG).toBe(0);
expect(Camera.EncodingType.PNG).toBe(1);
expect(navigator.camera.EncodingType.JPEG).toBe(0);
expect(navigator.camera.EncodingType.PNG).toBe(1);
});
it("camera.spec.4 should contain three MediaType constants", function () {
it('camera.spec.4 should contain three MediaType constants', function () {
expect(Camera.MediaType.PICTURE).toBe(0);
expect(Camera.MediaType.VIDEO).toBe(1);
expect(Camera.MediaType.ALLMEDIA).toBe(2);
@@ -64,7 +64,7 @@ exports.defineAutoTests = function () {
expect(navigator.camera.MediaType.ALLMEDIA).toBe(2);
});
it("camera.spec.5 should contain three PictureSourceType constants", function () {
it('camera.spec.5 should contain three PictureSourceType constants', function () {
expect(Camera.PictureSourceType.PHOTOLIBRARY).toBe(0);
expect(Camera.PictureSourceType.CAMERA).toBe(1);
expect(Camera.PictureSourceType.SAVEDPHOTOALBUM).toBe(2);
@@ -75,7 +75,6 @@ exports.defineAutoTests = function () {
});
};
/******************************************************************************/
/******************************************************************************/
/******************************************************************************/
@@ -115,7 +114,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
try {
window.atob(url);
// if we got here it is a base64 string (DATA_URL)
url = "data:image/jpeg;base64," + url;
url = 'data:image/jpeg;base64,' + url;
} catch (e) {
// not DATA_URL
}
@@ -142,11 +141,11 @@ exports.defineManualTests = function (contentEl, createActionButton) {
setPicture(data);
// TODO: Fix resolveLocalFileSystemURI to work with native-uri.
if (pictureUrl.indexOf('file:') === 0 || pictureUrl.indexOf('content:') === 0 || pictureUrl.indexOf('ms-appdata:') === 0 || pictureUrl.indexOf('assets-library:') === 0) {
resolveLocalFileSystemURI(data, function (e) {
resolveLocalFileSystemURL(data, function (e) {
fileEntry = e;
logCallback('resolveLocalFileSystemURI()', true)(e.toURL());
logCallback('resolveLocalFileSystemURL()', true)(e.toURL());
readFile();
}, logCallback('resolveLocalFileSystemURI()', false));
}, logCallback('resolveLocalFileSystemURL()', false));
} else if (pictureUrl.indexOf('data:image/jpeg;base64') === 0) {
// do nothing
} else {
@@ -169,15 +168,15 @@ exports.defineManualTests = function (contentEl, createActionButton) {
}
function uploadImage () {
var ft = new FileTransfer(),
options = new FileUploadOptions();
options.fileKey = "photo";
var ft = new FileTransfer();
var options = new FileUploadOptions();
options.fileKey = 'photo';
options.fileName = 'test.jpg';
options.mimeType = "image/jpeg";
options.mimeType = 'image/jpeg';
ft.onprogress = function (progressEvent) {
console.log('progress: ' + progressEvent.loaded + ' of ' + progressEvent.total);
};
var server = "http://cordova-filetransfer.jitsu.com";
var server = 'http://sheltered-retreat-43956.herokuapp.com';
ft.upload(pictureUrl, server + '/upload', win, fail, options);
function win (information_back) {
@@ -201,17 +200,18 @@ exports.defineManualTests = function (contentEl, createActionButton) {
function readFile () {
function onFileReadAsDataURL (evt) {
var img = document.getElementById('camera_image');
img.style.visibility = "visible";
img.style.display = "block";
img.style.visibility = 'visible';
img.style.display = 'block';
img.src = evt.target.result;
log("FileReader.readAsDataURL success");
log('FileReader.readAsDataURL success');
}
function onFileReceived (file) {
log('Got file: ' + JSON.stringify(file));
fileObj = file;
/* eslint-disable no-undef */
var reader = new FileReader();
/* eslint-enable no-undef */
reader.onload = function () {
log('FileReader.readAsDataURL() - length = ' + reader.result.length);
};
@@ -231,7 +231,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
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.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));
}
@@ -251,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 () {
@@ -259,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 () {
@@ -278,7 +278,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
var onFileWriterReceived = function (fileWriter) {
fileWriter.onwrite = logCallback('FileWriter.write', true);
fileWriter.onerror = logCallback('FileWriter.write', false);
fileWriter.write("some text!");
fileWriter.write('some text!');
};
var onFileTruncateWriterReceived = function (fileWriter) {
@@ -322,6 +322,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
}
function testNativeFile2 (inputEl) {
/* eslint-disable no-undef */
if (!inputEl.value) {
alert('No file selected.');
return;
@@ -331,6 +332,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
alert('Got value but no file.');
return;
}
/* eslint-enable no-undef */
var URLApi = window.URL || window.webkitURL;
if (URLApi) {
var blobURL = URLApi.createObjectURL(fileObj);
@@ -349,19 +351,19 @@ exports.defineManualTests = function (contentEl, createActionButton) {
function extractOptions () {
var els = document.querySelectorAll('#image-options select');
var ret = {};
/*jshint -W084 */
/* eslint-disable no-cond-assign */
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 */
/* eslint-enable no-cond-assign */
return ret;
}
@@ -375,13 +377,13 @@ exports.defineManualTests = function (contentEl, createActionButton) {
}
var options = '';
if (typeof values == 'boolean') {
if (typeof values === 'boolean') {
values = { 'true': 1, 'false': 0 };
}
for (var k in values) {
var isSelected = '';
if (selectionDefault) {
if (selectionDefault[0] == k) {
if (selectionDefault[0] === k) {
isSelected = 'selected';
}
}
@@ -400,8 +402,8 @@ exports.defineManualTests = function (contentEl, createActionButton) {
'<b>Status:</b> <div id="camera_status"></div>' +
'img: <img width="100" id="camera_image">' +
'canvas: <canvas id="canvas" width="1" height="1"></canvas>' +
'</div>',
options_div = '<h2>Cordova Camera API Options</h2>' +
'</div>';
var options_div = '<h2>Cordova Camera API Options</h2>' +
'<div id="image-options">' +
createOptionsEl('sourceType', Camera.PictureSourceType, camPictureSourceTypeDefault) +
createOptionsEl('destinationType', Camera.DestinationType, camDestinationTypeDefault) +
@@ -414,9 +416,9 @@ exports.defineManualTests = function (contentEl, createActionButton) {
createOptionsEl('correctOrientation', true, camCorrectOrientationDefault) +
createOptionsEl('saveToPhotoAlbum', true, camSaveToPhotoAlbumDefault) +
createOptionsEl('cameraDirection', Camera.Direction) +
'</div>',
getpicture_div = '<div id="getpicture"></div>',
test_procedure = '<h4>Recommended Test Procedure</h4>' +
'</div>';
var getpicture_div = '<div id="getpicture"></div>';
var test_procedure = '<h4>Recommended Test Procedure</h4>' +
'Options not specified should be the default value' +
'<br>Status box should update with image and info whenever an image is taken or selected from library' +
'</p><div style="background:#B0C4DE;border:1px solid #FFA07A;margin:15px 6px 0px;min-width:295px;max-width:97%;padding:4px 0px 2px 10px;min-height:160px;max-height:200px;overflow:auto">' +
@@ -429,14 +431,14 @@ exports.defineManualTests = function (contentEl, createActionButton) {
'</p><li>sourceType=PHOTOLIBRARY<br>mediaType=ALLMEDIA<br>allowEdit=true<br>Should be able to select pics and videos and edit picture if selected</li>' +
'</p><li>sourceType=CAMERA<br>targetWidth & targetHeight=50<br>allowEdit=false<br>Do Get File Metadata test below and take note of size<br>Repeat test but with width and height=800. Size should be significantly larger.</li>' +
'</p><li>quality=0<br>targetWidth & targetHeight=default<br>allowEdit=false<br>Do Get File Metadata test below and take note of size<br>Repeat test but with quality=80. Size should be significantly larger.</li>' +
'</ol></div>',
inputs_div = '<h2>Native File Inputs</h2>' +
'</ol></div>';
var inputs_div = '<h2>Native File Inputs</h2>' +
'For the following tests, status box should update with file selected' +
'</p><div>input type=file <input type="file" class="testInputTag"></div>' +
'<div>capture=camera <input type="file" accept="image/*;capture=camera" class="testInputTag"></div>' +
'<div>capture=camcorder <input type="file" accept="video/*;capture=camcorder" class="testInputTag"></div>' +
'<div>capture=microphone <input type="file" accept="audio/*;capture=microphone" class="testInputTag"></div>',
actions_div = '<h2>Actions</h2>' +
'<div>capture=microphone <input type="file" accept="audio/*;capture=microphone" class="testInputTag"></div>';
var actions_div = '<h2>Actions</h2>' +
'For the following tests, ensure that an image is set in status box' +
'</p><div id="metadata"></div>' +
'Expected result: Get metadata about file selected.<br>Status box will show, along with the metadata, "Call to FileEntry.getMetadata success, Call to FileEntry.setMetadata success, Call to FileEntry.getParent success"' +
@@ -463,13 +465,13 @@ exports.defineManualTests = function (contentEl, createActionButton) {
contentEl.innerHTML = info_div + options_div + getpicture_div + test_procedure + inputs_div + actions_div;
}
var elements = document.getElementsByClassName("testInputTag");
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);
item.addEventListener('change', listener, false);
}
createActionButton('Get picture', function () {
+174
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;
}
};
+6 -12
View File
@@ -19,9 +19,9 @@
*
*/
var argscheck = require('cordova/argscheck'),
exec = require('cordova/exec'),
Camera = require('./Camera');
var argscheck = require('cordova/argscheck');
var exec = require('cordova/exec');
var Camera = require('./Camera');
// XXX: commented out
// CameraPopoverHandle = require('./CameraPopoverHandle');
@@ -89,26 +89,20 @@ for (var key in Camera) {
*
* 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` 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
@@ -158,7 +152,7 @@ cameraExport.getPicture = function(successCallback, errorCallback, options) {
var args = [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType,
mediaType, allowEdit, correctOrientation, saveToPhotoAlbum, popoverOptions, cameraDirection];
exec(successCallback, errorCallback, "Camera", "takePicture", args);
exec(successCallback, errorCallback, 'Camera', 'takePicture', args);
// XXX: commented out
// return new CameraPopoverHandle();
};
@@ -185,7 +179,7 @@ cameraExport.getPicture = function(successCallback, errorCallback, options) {
* }
*/
cameraExport.cleanup = function (successCallback, errorCallback) {
exec(successCallback, errorCallback, "Camera", "cleanup", []);
exec(successCallback, errorCallback, 'Camera', 'cleanup', []);
};
module.exports = cameraExport;
+15 -2
View File
@@ -24,6 +24,13 @@
*/
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: {
@@ -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
},
/**
+3 -3
View File
@@ -25,9 +25,9 @@ document.addEventListener('DOMContentLoaded', function () {
window.navigator.webkitGetUserMedia(
{ video: true },
function (stream) {
var video = document.getElementById('v'),
canvas = document.getElementById('c'),
camera = document.getElementById('camera');
var video = document.getElementById('v');
var canvas = document.getElementById('c');
var camera = document.getElementById('camera');
video.autoplay = true;
video.width = window.innerWidth;
video.height = window.innerHeight - 100;
+9 -3
View File
@@ -33,7 +33,7 @@ var exec = require('cordova/exec');
* - iOS
*
* @example
* var cameraPopoverHandle = navigator.camera.getPicture(onSuccess, onFail,
* navigator.camera.getPicture(onSuccess, onFail,
* {
* destinationType: Camera.DestinationType.FILE_URI,
* sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
@@ -42,18 +42,24 @@ var exec = require('cordova/exec');
*
* // 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) {
var args = [ popoverOptions ];
exec(null, null, "Camera", "repositionPopover", args);
exec(null, null, 'Camera', 'repositionPopover', args);
};
};