mirror of
https://github.com/apache/cordova-plugin-camera.git
synced 2026-02-03 00:06:46 +08:00
Compare commits
145 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d456eeb711 | ||
|
|
f761934814 | ||
|
|
43b3a911c2 | ||
|
|
8f033f53db | ||
|
|
51d665a7a1 | ||
|
|
9f480b10cb | ||
|
|
a6706aef5f | ||
|
|
fb213b2b38 | ||
|
|
3e24b15934 | ||
|
|
2acda2d2f9 | ||
|
|
97d2014d62 | ||
|
|
b2107e0818 | ||
|
|
16c4325fef | ||
|
|
80342b0ddd | ||
|
|
55d419a36e | ||
|
|
5574490bc3 | ||
|
|
f1b0ccec56 | ||
|
|
e69240f251 | ||
|
|
5b3a99a6db | ||
|
|
3b8f64e330 | ||
|
|
f667ba6179 | ||
|
|
77539558e6 | ||
|
|
cba99940a7 | ||
|
|
7155b636d5 | ||
|
|
160ed2d03f | ||
|
|
c3d7e55ad4 | ||
|
|
1b218cd8a0 | ||
|
|
51e5c02dcb | ||
|
|
26d13b0245 | ||
|
|
ef617cc943 | ||
|
|
1422b0a4f2 | ||
|
|
ba9a803b69 | ||
|
|
9615620843 | ||
|
|
a33c35152e | ||
|
|
eb98015e8a | ||
|
|
3c48ea9c9c | ||
|
|
2286bb3bb2 | ||
|
|
9badea4c95 | ||
|
|
180f7b5510 | ||
|
|
d0b381aad8 | ||
|
|
926fbf0e8c | ||
|
|
e4ff41c07c | ||
|
|
4fc25154f3 | ||
|
|
7f616d16f1 | ||
|
|
dfbca19a7a | ||
|
|
86e546f868 | ||
|
|
f2ca5ed79f | ||
|
|
ab7e02f0b8 | ||
|
|
899f6d8059 | ||
|
|
aa8a5945dd | ||
|
|
c06480f4e3 | ||
|
|
5ca4d8b082 | ||
|
|
bba8283d98 | ||
|
|
c27725ce66 | ||
|
|
415412cfef | ||
|
|
5ebda25164 | ||
|
|
d29c767f07 | ||
|
|
0ad5bdd9ff | ||
|
|
d6bd9ae3b3 | ||
|
|
81f9433606 | ||
|
|
05594c4646 | ||
|
|
a364e79482 | ||
|
|
42fc8e0bcd | ||
|
|
ee5537694a | ||
|
|
9eba35e2f6 | ||
|
|
8b83171ee2 | ||
|
|
c9e6a9a38a | ||
|
|
cc48945f37 | ||
|
|
8b3410bcc6 | ||
|
|
485a11e0f4 | ||
|
|
2d47a26271 | ||
|
|
2d2352f695 | ||
|
|
2f003d2b49 | ||
|
|
3a90bb7d55 | ||
|
|
b13cbdeb16 | ||
|
|
ee192d94b4 | ||
|
|
d9eb83bcb9 | ||
|
|
84f96c1067 | ||
|
|
61064ae3ed | ||
|
|
9ec8aea073 | ||
|
|
9db952e161 | ||
|
|
06d609cfa4 | ||
|
|
cc1076d3cb | ||
|
|
48d4213b2d | ||
|
|
9b566d3f0b | ||
|
|
b63a0d83e0 | ||
|
|
9fe94479e2 | ||
|
|
2f89666db7 | ||
|
|
f8682b9162 | ||
|
|
0ed6406864 | ||
|
|
fa58e83fca | ||
|
|
a3af38ad5b | ||
|
|
c98607c613 | ||
|
|
b89645c749 | ||
|
|
1beeafb6e8 | ||
|
|
7813ad9bef | ||
|
|
6e19147b09 | ||
|
|
9f159d757a | ||
|
|
62d1b01e81 | ||
|
|
92d67d990d | ||
|
|
0accbf560b | ||
|
|
e588907ac7 | ||
|
|
3ed3d887ca | ||
|
|
f010394af8 | ||
|
|
00e0a7dc46 | ||
|
|
fee72c7c04 | ||
|
|
5807458a1d | ||
|
|
c13e9f327b | ||
|
|
7e8fe0bae9 | ||
|
|
3f8c53f7f3 | ||
|
|
4e439d85c3 | ||
|
|
b62fdf50f7 | ||
|
|
744d72a33b | ||
|
|
3d26986bfd | ||
|
|
fed798e6c7 | ||
|
|
2027d69606 | ||
|
|
7129fb2c12 | ||
|
|
b695717240 | ||
|
|
acff98058f | ||
|
|
8a7326969f | ||
|
|
a05f169984 | ||
|
|
dcc81bfbe1 | ||
|
|
278b527702 | ||
|
|
d7ca7edf88 | ||
|
|
f283502545 | ||
|
|
a831e15a91 | ||
|
|
3586ea58a2 | ||
|
|
cc840b6cef | ||
|
|
0115458ce8 | ||
|
|
2eef096861 | ||
|
|
cf35b1bb2a | ||
|
|
39bff2f41d | ||
|
|
7551778e13 | ||
|
|
e3a431cbeb | ||
|
|
832d6e3bea | ||
|
|
2a7469e065 | ||
|
|
af98d57417 | ||
|
|
57b177f3fb | ||
|
|
f2b4eeded0 | ||
|
|
d4a55f20ec | ||
|
|
68e18a97d1 | ||
|
|
bfaef0ff25 | ||
|
|
0ba547cd3c | ||
|
|
6d058fe9e7 | ||
|
|
daf5fa48dd |
28
.appveyor.yml
Normal file
28
.appveyor.yml
Normal 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
.eslintrc.yml
Normal file
10
.eslintrc.yml
Normal 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
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
<!--
|
||||
Please make sure the checklist boxes are all checked before submitting the PR. The checklist
|
||||
is intended as a quick reference, for complete details please see our Contributor Guidelines:
|
||||
|
||||
http://cordova.apache.org/contribute/contribute_guidelines.html
|
||||
|
||||
Thanks!
|
||||
-->
|
||||
|
||||
### Platforms affected
|
||||
|
||||
|
||||
### What does this PR do?
|
||||
|
||||
|
||||
### What testing has been done on this change?
|
||||
|
||||
|
||||
### Checklist
|
||||
- [ ] [Reported an issue](http://cordova.apache.org/contribute/issues.html) in the JIRA database
|
||||
- [ ] Commit message follows the format: "CB-3232: (android) Fix bug with resolving file paths", where CB-xxxx is the JIRA ID & "android" is the platform affected.
|
||||
- [ ] Added automated test coverage as appropriate for this change.
|
||||
16
.jshintrc
16
.jshintrc
@@ -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
|
||||
}
|
||||
}
|
||||
96
.travis.yml
96
.travis.yml
@@ -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
|
||||
|
||||
|
||||
334
README.md
334
README.md
@@ -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.
|
||||
-->
|
||||
|
||||
[](https://travis-ci.org/apache/cordova-plugin-camera)
|
||||
|AppVeyor|Travis CI|
|
||||
|:-:|:-:|
|
||||
|[](https://ci.appveyor.com/project/ApacheSoftwareFoundation/cordova-plugin-camera)|[](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,26 +73,64 @@ 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)
|
||||
* [.getPicture(successCallback, errorCallback, options)](#module_camera.getPicture)
|
||||
* [.cleanup()](#module_camera.cleanup)
|
||||
* [.onError](#module_camera.onError) : <code>function</code>
|
||||
* [.onSuccess](#module_camera.onSuccess) : <code>function</code>
|
||||
* [.CameraOptions](#module_camera.CameraOptions) : <code>Object</code>
|
||||
* [.getPicture(successCallback, errorCallback, options)](#module_camera.getPicture)
|
||||
* [.cleanup()](#module_camera.cleanup)
|
||||
* [.onError](#module_camera.onError) : <code>function</code>
|
||||
* [.onSuccess](#module_camera.onSuccess) : <code>function</code>
|
||||
* [.CameraOptions](#module_camera.CameraOptions) : <code>Object</code>
|
||||
|
||||
|
||||
* [Camera](#module_Camera)
|
||||
* [.DestinationType](#module_Camera.DestinationType) : <code>enum</code>
|
||||
* [.EncodingType](#module_Camera.EncodingType) : <code>enum</code>
|
||||
* [.MediaType](#module_Camera.MediaType) : <code>enum</code>
|
||||
* [.PictureSourceType](#module_Camera.PictureSourceType) : <code>enum</code>
|
||||
* [.PopoverArrowDirection](#module_Camera.PopoverArrowDirection) : <code>enum</code>
|
||||
* [.Direction](#module_Camera.Direction) : <code>enum</code>
|
||||
* [.DestinationType](#module_Camera.DestinationType) : <code>enum</code>
|
||||
* [.EncodingType](#module_Camera.EncodingType) : <code>enum</code>
|
||||
* [.MediaType](#module_Camera.MediaType) : <code>enum</code>
|
||||
* [.PictureSourceType](#module_Camera.PictureSourceType) : <code>enum</code>
|
||||
* [.PopoverArrowDirection](#module_Camera.PopoverArrowDirection) : <code>enum</code>
|
||||
* [.Direction](#module_Camera.Direction) : <code>enum</code>
|
||||
|
||||
* [CameraPopoverHandle](#module_CameraPopoverHandle)
|
||||
* [CameraPopoverOptions](#module_CameraPopoverOptions)
|
||||
@@ -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);
|
||||
}
|
||||
```
|
||||
|
||||
116
RELEASENOTES.md
116
RELEASENOTES.md
@@ -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
|
||||
|
||||
@@ -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
|
||||
// always wait before performing touchAction
|
||||
.sleep(7000)
|
||||
.performTouchAction(tapTile);
|
||||
}
|
||||
return driver
|
||||
.elementByXPath('//android.widget.TextView[@text="Gallery"]')
|
||||
.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"]');
|
||||
})
|
||||
.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;
|
||||
.waitForElementByAndroidUIAutomator('new UiSelector().text("Gallery");', 20000)
|
||||
})
|
||||
.click()
|
||||
// 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,166 +256,457 @@ 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);
|
||||
var options = {
|
||||
quality: 50,
|
||||
allowEdit: false,
|
||||
sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
|
||||
saveToPhotoAlbum: false,
|
||||
targetWidth: 210,
|
||||
targetHeight: 210
|
||||
};
|
||||
return driver
|
||||
.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)
|
||||
.sleep(2000)
|
||||
.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;
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
return wdHelper.injectLibraries(driver);
|
||||
// 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
it('camera.ui.util configuring driver and starting a session', function (done) {
|
||||
getDriver()
|
||||
.fail(fail)
|
||||
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);
|
||||
}, 5 * 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) {
|
||||
return driver
|
||||
.context(webviewContext)
|
||||
.execute(function () {
|
||||
return {
|
||||
'width': window.innerWidth,
|
||||
'height': window.innerHeight
|
||||
};
|
||||
}, [])
|
||||
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 the picture to photo library', function (done) {
|
||||
var options = {
|
||||
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
|
||||
};
|
||||
driver
|
||||
.then(function () {
|
||||
return getPicture(options);
|
||||
})
|
||||
checkSession(done);
|
||||
checkCamera(opts, pending);
|
||||
|
||||
var spec = generateSpec(opts);
|
||||
tryRunSpec(spec)
|
||||
.then(function () {
|
||||
isTestPictureSaved = true;
|
||||
return checkPicture(true);
|
||||
})
|
||||
.fail(saveScreenshotAndFail)
|
||||
.done(done);
|
||||
}, 3 * MINUTE);
|
||||
}, 10 * 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.';
|
||||
});
|
||||
})
|
||||
.deviceKeyEvent(BACK_BUTTON)
|
||||
.elementByXPath('//android.widget.TextView[@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);
|
||||
}, function () {
|
||||
// error means we're already in webview
|
||||
return driver;
|
||||
});
|
||||
})
|
||||
.fail(saveScreenshotAndFail)
|
||||
.done(done);
|
||||
}, 3 * MINUTE);
|
||||
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,
|
||||
allowEdit: true,
|
||||
sourceType: cameraConstants.PictureSourceType.CAMERA,
|
||||
destinationType: cameraConstants.DestinationType.FILE_URI };
|
||||
driver
|
||||
.then(function () {
|
||||
return getPicture(options, true);
|
||||
})
|
||||
.context("NATIVE_APP")
|
||||
.waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'cancel\')]', MINUTE / 2)
|
||||
.click()
|
||||
.then(function () {
|
||||
return checkPicture(false);
|
||||
})
|
||||
.fail(saveScreenshotAndFail)
|
||||
.done(done);
|
||||
}, 3 * MINUTE);
|
||||
var options = {
|
||||
quality: 50,
|
||||
allowEdit: true,
|
||||
sourceType: cameraConstants.PictureSourceType.CAMERA,
|
||||
destinationType: cameraConstants.DestinationType.FILE_URI
|
||||
};
|
||||
checkSession(done);
|
||||
checkCamera(options, pending);
|
||||
var spec = function () {
|
||||
return driver
|
||||
.then(function () {
|
||||
return getPicture(options, true);
|
||||
})
|
||||
.context(CONTEXT_NATIVE_APP)
|
||||
.waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*cancel.*")', MINUTE / 2)
|
||||
.click()
|
||||
.then(function () {
|
||||
return checkPicture(false);
|
||||
});
|
||||
};
|
||||
|
||||
tryRunSpec(spec).done(done);
|
||||
}, 10 * MINUTE);
|
||||
|
||||
// getPicture(), then take picture but dismiss the edit
|
||||
// wait for the error callback to be called
|
||||
it('camera.ui.spec.4 Dismissing the edit', function (done) {
|
||||
var options = { quality: 50,
|
||||
allowEdit: true,
|
||||
sourceType: cameraConstants.PictureSourceType.CAMERA,
|
||||
destinationType: cameraConstants.DestinationType.FILE_URI };
|
||||
driver
|
||||
.then(function () {
|
||||
return getPicture(options, true);
|
||||
})
|
||||
.context('NATIVE_APP')
|
||||
.waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]', MINUTE / 2)
|
||||
.click()
|
||||
.waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]', MINUTE / 2)
|
||||
.click()
|
||||
.waitForElementByXPath('//*[contains(@resource-id,\'discard\')]', MINUTE / 2)
|
||||
.click()
|
||||
.then(function () {
|
||||
return checkPicture(false);
|
||||
})
|
||||
.fail(saveScreenshotAndFail)
|
||||
.done(done);
|
||||
}, 3 * MINUTE);
|
||||
var options = {
|
||||
quality: 50,
|
||||
allowEdit: true,
|
||||
sourceType: cameraConstants.PictureSourceType.CAMERA,
|
||||
destinationType: cameraConstants.DestinationType.FILE_URI
|
||||
};
|
||||
checkSession(done);
|
||||
checkCamera(options, pending);
|
||||
var spec = function () {
|
||||
return driver
|
||||
.then(function () {
|
||||
return getPicture(options, true);
|
||||
})
|
||||
.waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*shutter.*")', MINUTE / 2)
|
||||
.click()
|
||||
.waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*done.*")', MINUTE / 2)
|
||||
.click()
|
||||
.then(function () {
|
||||
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();
|
||||
})
|
||||
.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()
|
||||
.fail(function () {
|
||||
// no cling is all right
|
||||
// it is not a brand new emulator, then
|
||||
});
|
||||
})
|
||||
.elementByAndroidUIAutomator('new UiSelector().text("Gallery")')
|
||||
.click()
|
||||
.elementByXPath('//android.widget.TextView[contains(@text,"Pictures")]')
|
||||
.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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
// if taking picture from photolibrary, don't vary 'correctOrientation' option
|
||||
if (sourceTypes[sourceType] === cameraConstants.PictureSourceType.PHOTOLIBRARY) {
|
||||
specs.push({
|
||||
for (correctOrientation in correctOrientationOptions) {
|
||||
// if taking picture from photolibrary, don't vary 'correctOrientation' option
|
||||
if ((sourceTypes[sourceType] === cameraConstants.PictureSourceType.PHOTOLIBRARY ||
|
||||
sourceTypes[sourceType] === cameraConstants.PictureSourceType.SAVEDPHOTOALBUM) &&
|
||||
correctOrientation === true) { continue; }
|
||||
var spec = {
|
||||
'id': id++,
|
||||
'options': {
|
||||
'destinationType': destinationTypes[destinationType],
|
||||
@@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -1,17 +1,61 @@
|
||||
---
|
||||
title: Camera
|
||||
description: Take pictures with the device camera.
|
||||
---
|
||||
{{>cdv-license~}}
|
||||
|
||||
[](https://travis-ci.org/apache/cordova-plugin-camera)
|
||||
|AppVeyor|Travis CI|
|
||||
|:-:|:-:|
|
||||
|[](https://ci.appveyor.com/project/ApacheSoftwareFoundation/cordova-plugin-camera)|[](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);
|
||||
}
|
||||
```
|
||||
|
||||
39
package.json
39
package.json
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
52
plugin.xml
52
plugin.xml
@@ -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,16 +71,31 @@
|
||||
<config-file target="AndroidManifest.xml" parent="/*">
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
</config-file>
|
||||
<config-file target="AndroidManifest.xml" parent="application">
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true" >
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/provider_paths"/>
|
||||
</provider>
|
||||
</config-file>
|
||||
|
||||
<source-file src="src/android/CameraLauncher.java" target-dir="src/org/apache/cordova/camera" />
|
||||
<source-file src="src/android/CordovaUri.java" target-dir="src/org/apache/cordova/camera" />
|
||||
<source-file src="src/android/FileHelper.java" target-dir="src/org/apache/cordova/camera" />
|
||||
<source-file src="src/android/ExifHelper.java" target-dir="src/org/apache/cordova/camera" />
|
||||
<source-file src="src/android/xml/provider_paths.xml" target-dir="res/xml" />
|
||||
|
||||
<js-module src="www/CameraPopoverHandle.js" name="CameraPopoverHandle">
|
||||
<clobbers target="CameraPopoverHandle" />
|
||||
</js-module>
|
||||
</js-module>
|
||||
|
||||
</platform>
|
||||
<framework src="com.android.support:support-v4:24.1.1+" />
|
||||
|
||||
</platform>
|
||||
|
||||
<!-- amazon-fireos -->
|
||||
<platform name="amazon-fireos">
|
||||
@@ -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>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
104
src/android/CordovaUri.java
Normal file
104
src/android/CordovaUri.java
Normal 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
src/android/xml/provider_paths.xml
Normal file
21
src/android/xml/provider_paths.xml
Normal 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>
|
||||
@@ -22,19 +22,19 @@
|
||||
/* globals qnx, FileError, PluginResult */
|
||||
|
||||
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 = {
|
||||
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;
|
||||
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)
|
||||
};
|
||||
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)
|
||||
};
|
||||
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
|
||||
// check for camera card - it isn't currently availble in work perimeter
|
||||
window.qnx.webplatform.getApplication().invocation.queryTargets(
|
||||
{
|
||||
type: 'image/jpeg',
|
||||
@@ -47,14 +47,14 @@ window.qnx.webplatform.getApplication().invocation.queryTargets(
|
||||
}
|
||||
);
|
||||
|
||||
//open a webview with getUserMedia camera card implementation when camera card not available
|
||||
// open a webview with getUserMedia camera card implementation when camera card not available
|
||||
function showCameraDialog (done, cancel, fail) {
|
||||
var wv = qnx.webplatform.createWebView(function () {
|
||||
wv.url = 'local:///chrome/camera.html';
|
||||
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();
|
||||
@@ -85,29 +85,29 @@ 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; };
|
||||
// create unique name for saved file (same pattern as BB10 camera app)
|
||||
function imgName () {
|
||||
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;
|
||||
// convert dataURI to Blob
|
||||
function dataURItoBlob (dataURI) {
|
||||
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);
|
||||
}
|
||||
return new Blob([new DataView(arrayBuffer)], { type: mimeString });
|
||||
}
|
||||
|
||||
//save dataURI to file system and call success with path
|
||||
function saveImage(data, success, fail) {
|
||||
// save dataURI to file system and call success with path
|
||||
function saveImage (data, success, fail) {
|
||||
var name = savePath + imgName();
|
||||
require('lib/webview').setSandbox(false);
|
||||
window.webkitRequestFileSystem(window.PERSISTENT, 0, function (fs) {
|
||||
@@ -123,91 +123,91 @@ function saveImage(data, success, fail) {
|
||||
}, fail);
|
||||
}
|
||||
|
||||
function encodeBase64(filePath, callback) {
|
||||
var sandbox = window.qnx.webplatform.getController().setFileSystemSandbox, // save original sandbox value
|
||||
errorHandler = function (err) {
|
||||
var msg = "An error occured: ";
|
||||
function encodeBase64 (filePath, callback) {
|
||||
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";
|
||||
break;
|
||||
switch (err.code) {
|
||||
case FileError.NOT_FOUND_ERR:
|
||||
msg += 'File or directory not found';
|
||||
break;
|
||||
|
||||
case FileError.NOT_READABLE_ERR:
|
||||
msg += "File or directory not readable";
|
||||
break;
|
||||
case FileError.NOT_READABLE_ERR:
|
||||
msg += 'File or directory not readable';
|
||||
break;
|
||||
|
||||
case FileError.PATH_EXISTS_ERR:
|
||||
msg += "File or directory already exists";
|
||||
break;
|
||||
case FileError.PATH_EXISTS_ERR:
|
||||
msg += 'File or directory already exists';
|
||||
break;
|
||||
|
||||
case FileError.TYPE_MISMATCH_ERR:
|
||||
msg += "Invalid file type";
|
||||
break;
|
||||
case FileError.TYPE_MISMATCH_ERR:
|
||||
msg += 'Invalid file type';
|
||||
break;
|
||||
|
||||
default:
|
||||
msg += "Unknown Error";
|
||||
break;
|
||||
}
|
||||
default:
|
||||
msg += 'Unknown Error';
|
||||
break;
|
||||
}
|
||||
|
||||
// set it back to original value
|
||||
window.qnx.webplatform.getController().setFileSystemSandbox = sandbox;
|
||||
callback(msg);
|
||||
},
|
||||
gotFile = function (fileEntry) {
|
||||
fileEntry.file(function (file) {
|
||||
var reader = new FileReader();
|
||||
// set it back to original value
|
||||
window.qnx.webplatform.getController().setFileSystemSandbox = sandbox;
|
||||
callback(msg);
|
||||
};
|
||||
var gotFile = function (fileEntry) {
|
||||
fileEntry.file(function (file) {
|
||||
var reader = new FileReader();
|
||||
|
||||
reader.onloadend = function (e) {
|
||||
// set it back to original value
|
||||
window.qnx.webplatform.getController().setFileSystemSandbox = sandbox;
|
||||
callback(this.result);
|
||||
};
|
||||
reader.onloadend = function (e) {
|
||||
// set it back to original value
|
||||
window.qnx.webplatform.getController().setFileSystemSandbox = sandbox;
|
||||
callback(this.result);
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
}, errorHandler);
|
||||
},
|
||||
onInitFs = function (fs) {
|
||||
window.qnx.webplatform.getController().setFileSystemSandbox = false;
|
||||
fs.root.getFile(filePath, {create: false}, gotFile, errorHandler);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}, errorHandler);
|
||||
};
|
||||
var onInitFs = function (fs) {
|
||||
window.qnx.webplatform.getController().setFileSystemSandbox = false;
|
||||
fs.root.getFile(filePath, {create: false}, gotFile, errorHandler);
|
||||
};
|
||||
|
||||
window.webkitRequestFileSystem(window.TEMPORARY, 10 * 1024 * 1024, onInitFs, errorHandler); // set size to 10MB max
|
||||
}
|
||||
|
||||
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) {
|
||||
if (destinationType === DestinationType.FILE_URI) {
|
||||
data = "file://" + data;
|
||||
result.callbackOk(data, false);
|
||||
} else {
|
||||
encodeBase64(data, function (data) {
|
||||
if (/^data:/.test(data)) {
|
||||
data = data.slice(data.indexOf(",") + 1);
|
||||
result.callbackOk(data, false);
|
||||
} else {
|
||||
result.callbackError(data, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
cancel = function (reason) {
|
||||
result.callbackError(reason, false);
|
||||
},
|
||||
invoked = function (error) {
|
||||
if (error) {
|
||||
result.callbackError(error, false);
|
||||
}
|
||||
};
|
||||
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;
|
||||
result.callbackOk(data, false);
|
||||
} else {
|
||||
encodeBase64(data, function (data) {
|
||||
if (/^data:/.test(data)) {
|
||||
data = data.slice(data.indexOf(',') + 1);
|
||||
result.callbackOk(data, false);
|
||||
} else {
|
||||
result.callbackError(data, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
var cancel = function (reason) {
|
||||
result.callbackError(reason, false);
|
||||
};
|
||||
var invoked = function (error) {
|
||||
if (error) {
|
||||
result.callbackError(error, false);
|
||||
}
|
||||
};
|
||||
|
||||
switch(sourceType) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -21,19 +21,20 @@
|
||||
|
||||
var HIGHEST_POSSIBLE_Z_INDEX = 2147483647;
|
||||
|
||||
function takePicture(success, error, opts) {
|
||||
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();
|
||||
reader.onload = function(readerEvent) {
|
||||
input.onchange = function (inputEvent) {
|
||||
var reader = new FileReader(); /* eslint no-undef : 0 */
|
||||
reader.onload = function (readerEvent) {
|
||||
input.parentNode.removeChild(input);
|
||||
|
||||
var imageData = readerEvent.target.result;
|
||||
@@ -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() {
|
||||
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.
|
||||
@@ -91,7 +100,7 @@ function capture(success, errorCallback) {
|
||||
navigator.mozGetUserMedia ||
|
||||
navigator.msGetUserMedia;
|
||||
|
||||
var successCallback = function(stream) {
|
||||
var successCallback = function (stream) {
|
||||
localMediaStream = stream;
|
||||
video.src = window.URL.createObjectURL(localMediaStream);
|
||||
video.play();
|
||||
@@ -108,7 +117,7 @@ function capture(success, errorCallback) {
|
||||
|
||||
module.exports = {
|
||||
takePicture: takePicture,
|
||||
cleanup: function(){}
|
||||
cleanup: function () {}
|
||||
};
|
||||
|
||||
require("cordova/exec/proxy").add("Camera",module.exports);
|
||||
require('cordova/exec/proxy').add('Camera', module.exports);
|
||||
|
||||
@@ -21,17 +21,17 @@
|
||||
|
||||
/* globals MozActivity */
|
||||
|
||||
function takePicture(success, error, opts) {
|
||||
function takePicture (success, error, opts) {
|
||||
var pick = new MozActivity({
|
||||
name: "pick",
|
||||
name: 'pick',
|
||||
data: {
|
||||
type: ["image/*"]
|
||||
type: ['image/*']
|
||||
}
|
||||
});
|
||||
|
||||
pick.onerror = error || function() {};
|
||||
pick.onerror = error || function () {};
|
||||
|
||||
pick.onsuccess = function() {
|
||||
pick.onsuccess = function () {
|
||||
// image is returned as Blob in this.result.blob
|
||||
// we need to call success with url or base64 encoded image
|
||||
if (opts && opts.destinationType === 0) {
|
||||
@@ -47,7 +47,7 @@ function takePicture(success, error, opts) {
|
||||
|
||||
module.exports = {
|
||||
takePicture: takePicture,
|
||||
cleanup: function(){}
|
||||
cleanup: function () {}
|
||||
};
|
||||
|
||||
require("cordova/exec/proxy").add("Camera", module.exports);
|
||||
require('cordova/exec/proxy').add('Camera', module.exports);
|
||||
|
||||
@@ -42,7 +42,7 @@ static NSString* toBase64(NSData* data) {
|
||||
SEL s1 = NSSelectorFromString(@"cdv_base64EncodedString");
|
||||
SEL s2 = NSSelectorFromString(@"base64EncodedString");
|
||||
SEL s3 = NSSelectorFromString(@"base64EncodedStringWithOptions:");
|
||||
|
||||
|
||||
if ([data respondsToSelector:s1]) {
|
||||
NSString* (*func)(id, SEL) = (void *)[data methodForSelector:s1];
|
||||
return func(data, s1);
|
||||
@@ -66,7 +66,7 @@ static NSString* toBase64(NSData* data) {
|
||||
pictureOptions.quality = [command argumentAtIndex:0 withDefault:@(50)];
|
||||
pictureOptions.destinationType = [[command argumentAtIndex:1 withDefault:@(DestinationTypeFileUri)] unsignedIntegerValue];
|
||||
pictureOptions.sourceType = [[command argumentAtIndex:2 withDefault:@(UIImagePickerControllerSourceTypeCamera)] unsignedIntegerValue];
|
||||
|
||||
|
||||
NSNumber* targetWidth = [command argumentAtIndex:3 withDefault:nil];
|
||||
NSNumber* targetHeight = [command argumentAtIndex:4 withDefault:nil];
|
||||
pictureOptions.targetSize = CGSizeMake(0, 0);
|
||||
@@ -81,10 +81,10 @@ static NSString* toBase64(NSData* data) {
|
||||
pictureOptions.saveToPhotoAlbum = [[command argumentAtIndex:9 withDefault:@(NO)] boolValue];
|
||||
pictureOptions.popoverOptions = [command argumentAtIndex:10 withDefault:nil];
|
||||
pictureOptions.cameraDirection = [[command argumentAtIndex:11 withDefault:@(UIImagePickerControllerCameraDeviceRear)] unsignedIntegerValue];
|
||||
|
||||
|
||||
pictureOptions.popoverSupported = NO;
|
||||
pictureOptions.usesGeolocation = NO;
|
||||
|
||||
|
||||
return pictureOptions;
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ static NSString* toBase64(NSData* data) {
|
||||
- (NSURL*) urlTransformer:(NSURL*)url
|
||||
{
|
||||
NSURL* urlToTransform = url;
|
||||
|
||||
|
||||
// for backwards compatibility - we check if this property is there
|
||||
SEL sel = NSSelectorFromString(@"urlTransformer");
|
||||
if ([self.commandDelegate respondsToSelector:sel]) {
|
||||
@@ -120,7 +120,7 @@ static NSString* toBase64(NSData* data) {
|
||||
urlToTransform = urlTransformer(url);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return urlToTransform;
|
||||
}
|
||||
|
||||
@@ -139,16 +139,16 @@ static NSString* toBase64(NSData* data) {
|
||||
- (void)takePicture:(CDVInvokedUrlCommand*)command
|
||||
{
|
||||
self.hasPendingOperation = YES;
|
||||
|
||||
|
||||
__weak CDVCamera* weakSelf = self;
|
||||
|
||||
[self.commandDelegate runInBackground:^{
|
||||
|
||||
|
||||
CDVPictureOptions* pictureOptions = [CDVPictureOptions createFromTakePictureArguments:command];
|
||||
pictureOptions.popoverSupported = [weakSelf popoverSupported];
|
||||
pictureOptions.usesGeolocation = [weakSelf usesGeolocation];
|
||||
pictureOptions.cropToSize = NO;
|
||||
|
||||
|
||||
BOOL hasCamera = [UIImagePickerController isSourceTypeAvailable:pictureOptions.sourceType];
|
||||
if (!hasCamera) {
|
||||
NSLog(@"Camera.getPicture: source type %lu not available.", (unsigned long)pictureOptions.sourceType);
|
||||
@@ -184,12 +184,12 @@ static NSString* toBase64(NSData* data) {
|
||||
|
||||
CDVCameraPicker* cameraPicker = [CDVCameraPicker createFromPictureOptions:pictureOptions];
|
||||
weakSelf.pickerController = cameraPicker;
|
||||
|
||||
|
||||
cameraPicker.delegate = weakSelf;
|
||||
cameraPicker.callbackId = command.callbackId;
|
||||
// we need to capture this state for memory warnings that dealloc this object
|
||||
cameraPicker.webView = weakSelf.webView;
|
||||
|
||||
|
||||
// Perform UI operations on the main thread
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// If a popover is already open, close it; we only want one at a time.
|
||||
@@ -240,9 +240,13 @@ static NSString* toBase64(NSData* data) {
|
||||
|
||||
- (void)repositionPopover:(CDVInvokedUrlCommand*)command
|
||||
{
|
||||
NSDictionary* options = [command argumentAtIndex:0 withDefault:nil];
|
||||
if (([[self pickerController] pickerPopoverController] != nil) && [[[self pickerController] pickerPopoverController] isPopoverVisible]) {
|
||||
|
||||
[self displayPopover:options];
|
||||
[[[self pickerController] pickerPopoverController] dismissPopoverAnimated:NO];
|
||||
|
||||
NSDictionary* options = [command argumentAtIndex:0 withDefault:nil];
|
||||
[self displayPopover:options];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)integerValueForKey:(NSDictionary*)dict key:(NSString*)key defaultValue:(NSInteger)defaultValue
|
||||
@@ -287,7 +291,7 @@ static NSString* toBase64(NSData* data) {
|
||||
{
|
||||
if([navigationController isKindOfClass:[UIImagePickerController class]]){
|
||||
UIImagePickerController* cameraPicker = (UIImagePickerController*)navigationController;
|
||||
|
||||
|
||||
if(![cameraPicker.mediaTypes containsObject:(NSString*)kUTTypeImage]){
|
||||
[viewController.navigationItem setTitle:NSLocalizedString(@"Videos", nil)];
|
||||
}
|
||||
@@ -347,7 +351,7 @@ static NSString* toBase64(NSData* data) {
|
||||
- (NSData*)processImage:(UIImage*)image info:(NSDictionary*)info options:(CDVPictureOptions*)options
|
||||
{
|
||||
NSData* data = nil;
|
||||
|
||||
|
||||
switch (options.encodingType) {
|
||||
case EncodingTypePNG:
|
||||
data = UIImagePNGRepresentation(image);
|
||||
@@ -360,18 +364,18 @@ static NSString* toBase64(NSData* data) {
|
||||
} else {
|
||||
data = UIImageJPEGRepresentation(image, [options.quality floatValue] / 100.0f);
|
||||
}
|
||||
|
||||
|
||||
if (options.usesGeolocation) {
|
||||
NSDictionary* controllerMetadata = [info objectForKey:@"UIImagePickerControllerMediaMetadata"];
|
||||
if (controllerMetadata) {
|
||||
self.data = data;
|
||||
self.metadata = [[NSMutableDictionary alloc] init];
|
||||
|
||||
|
||||
NSMutableDictionary* EXIFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyExifDictionary]mutableCopy];
|
||||
if (EXIFDictionary) {
|
||||
[self.metadata setObject:EXIFDictionary forKey:(NSString*)kCGImagePropertyExifDictionary];
|
||||
}
|
||||
|
||||
|
||||
if (IsAtLeastiOSVersion(@"8.0")) {
|
||||
[[self locationManager] performSelector:NSSelectorFromString(@"requestWhenInUseAuthorization") withObject:nil afterDelay:0];
|
||||
}
|
||||
@@ -383,7 +387,7 @@ static NSString* toBase64(NSData* data) {
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -392,13 +396,13 @@ static NSString* toBase64(NSData* data) {
|
||||
NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath];
|
||||
NSFileManager* fileMgr = [[NSFileManager alloc] init]; // recommended by Apple (vs [NSFileManager defaultManager]) to be threadsafe
|
||||
NSString* filePath;
|
||||
|
||||
|
||||
// generate unique file name
|
||||
int i = 1;
|
||||
do {
|
||||
filePath = [NSString stringWithFormat:@"%@/%@%03d.%@", docsPath, CDV_PHOTO_PREFIX, i++, extension];
|
||||
} while ([fileMgr fileExistsAtPath:filePath]);
|
||||
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
@@ -411,13 +415,13 @@ static NSString* toBase64(NSData* data) {
|
||||
} else {
|
||||
image = [info objectForKey:UIImagePickerControllerOriginalImage];
|
||||
}
|
||||
|
||||
|
||||
if (options.correctOrientation) {
|
||||
image = [image imageCorrectedForCaptureOrientation];
|
||||
}
|
||||
|
||||
|
||||
UIImage* scaledImage = nil;
|
||||
|
||||
|
||||
if ((options.targetSize.width > 0) && (options.targetSize.height > 0)) {
|
||||
// if cropToSize, resize image and crop to target size, otherwise resize to fit target without cropping
|
||||
if (options.cropToSize) {
|
||||
@@ -426,7 +430,7 @@ static NSString* toBase64(NSData* data) {
|
||||
scaledImage = [image imageByScalingNotCroppingForSize:options.targetSize];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (scaledImage == nil ? image : scaledImage);
|
||||
}
|
||||
|
||||
@@ -468,11 +472,11 @@ static NSString* toBase64(NSData* data) {
|
||||
image = [self retrieveImage:info options:options];
|
||||
NSData* data = [self processImage:image info:info options:options];
|
||||
if (data) {
|
||||
|
||||
|
||||
NSString* extension = options.encodingType == EncodingTypePNG? @"png" : @"jpg";
|
||||
NSString* filePath = [self tempFilePath:extension];
|
||||
NSError* err = nil;
|
||||
|
||||
|
||||
// save file
|
||||
if (![data writeToFile:filePath options:NSAtomicWrite error:&err]) {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
|
||||
@@ -494,7 +498,7 @@ static NSString* toBase64(NSData* data) {
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
|
||||
if (saveToPhotoAlbum && image) {
|
||||
ALAssetsLibrary* library = [ALAssetsLibrary new];
|
||||
[library writeImageToSavedPhotosAlbum:image.CGImage orientation:(ALAssetOrientation)(image.imageOrientation) completionBlock:nil];
|
||||
@@ -513,16 +517,18 @@ static NSString* toBase64(NSData* data) {
|
||||
{
|
||||
__weak CDVCameraPicker* cameraPicker = (CDVCameraPicker*)picker;
|
||||
__weak CDVCamera* weakSelf = self;
|
||||
|
||||
|
||||
dispatch_block_t invoke = ^(void) {
|
||||
__block CDVPluginResult* result = nil;
|
||||
|
||||
|
||||
NSString* mediaType = [info objectForKey:UIImagePickerControllerMediaType];
|
||||
if ([mediaType isEqualToString:(NSString*)kUTTypeImage]) {
|
||||
[weakSelf resultForImage:cameraPicker.pictureOptions info:info completion:^(CDVPluginResult* res) {
|
||||
[weakSelf.commandDelegate sendPluginResult:res callbackId:cameraPicker.callbackId];
|
||||
weakSelf.hasPendingOperation = NO;
|
||||
weakSelf.pickerController = nil;
|
||||
if (![self usesGeolocation] || picker.sourceType != UIImagePickerControllerSourceTypeCamera) {
|
||||
[weakSelf.commandDelegate sendPluginResult:res callbackId:cameraPicker.callbackId];
|
||||
weakSelf.hasPendingOperation = NO;
|
||||
weakSelf.pickerController = nil;
|
||||
}
|
||||
}];
|
||||
}
|
||||
else {
|
||||
@@ -532,7 +538,7 @@ static NSString* toBase64(NSData* data) {
|
||||
weakSelf.pickerController = nil;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if (cameraPicker.pictureOptions.popoverSupported && (cameraPicker.pickerPopoverController != nil)) {
|
||||
[cameraPicker.pickerPopoverController dismissPopoverAnimated:YES];
|
||||
cameraPicker.pickerPopoverController.delegate = nil;
|
||||
@@ -555,7 +561,7 @@ static NSString* toBase64(NSData* data) {
|
||||
{
|
||||
__weak CDVCameraPicker* cameraPicker = (CDVCameraPicker*)picker;
|
||||
__weak CDVCamera* weakSelf = self;
|
||||
|
||||
|
||||
dispatch_block_t invoke = ^ (void) {
|
||||
CDVPluginResult* result;
|
||||
if (picker.sourceType == UIImagePickerControllerSourceTypeCamera && [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] != ALAuthorizationStatusAuthorized) {
|
||||
@@ -563,12 +569,12 @@ 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"];
|
||||
}
|
||||
|
||||
|
||||
|
||||
[weakSelf.commandDelegate sendPluginResult:result callbackId:cameraPicker.callbackId];
|
||||
|
||||
|
||||
weakSelf.hasPendingOperation = NO;
|
||||
weakSelf.pickerController = nil;
|
||||
};
|
||||
@@ -581,11 +587,11 @@ static NSString* toBase64(NSData* data) {
|
||||
if (locationManager != nil) {
|
||||
return locationManager;
|
||||
}
|
||||
|
||||
|
||||
locationManager = [[CLLocationManager alloc] init];
|
||||
[locationManager setDesiredAccuracy:kCLLocationAccuracyNearestTenMeters];
|
||||
[locationManager setDelegate:self];
|
||||
|
||||
|
||||
return locationManager;
|
||||
}
|
||||
|
||||
@@ -594,15 +600,15 @@ static NSString* toBase64(NSData* data) {
|
||||
if (locationManager == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
[self.locationManager stopUpdatingLocation];
|
||||
self.locationManager = nil;
|
||||
|
||||
|
||||
NSMutableDictionary *GPSDictionary = [[NSMutableDictionary dictionary] init];
|
||||
|
||||
|
||||
CLLocationDegrees latitude = newLocation.coordinate.latitude;
|
||||
CLLocationDegrees longitude = newLocation.coordinate.longitude;
|
||||
|
||||
|
||||
// latitude
|
||||
if (latitude < 0.0) {
|
||||
latitude = latitude * -1.0f;
|
||||
@@ -611,7 +617,7 @@ static NSString* toBase64(NSData* data) {
|
||||
[GPSDictionary setObject:@"N" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
|
||||
}
|
||||
[GPSDictionary setObject:[NSNumber numberWithFloat:latitude] forKey:(NSString*)kCGImagePropertyGPSLatitude];
|
||||
|
||||
|
||||
// longitude
|
||||
if (longitude < 0.0) {
|
||||
longitude = longitude * -1.0f;
|
||||
@@ -621,7 +627,7 @@ static NSString* toBase64(NSData* data) {
|
||||
[GPSDictionary setObject:@"E" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
|
||||
}
|
||||
[GPSDictionary setObject:[NSNumber numberWithFloat:longitude] forKey:(NSString*)kCGImagePropertyGPSLongitude];
|
||||
|
||||
|
||||
// altitude
|
||||
CGFloat altitude = newLocation.altitude;
|
||||
if (!isnan(altitude)){
|
||||
@@ -633,7 +639,7 @@ static NSString* toBase64(NSData* data) {
|
||||
}
|
||||
[GPSDictionary setObject:[NSNumber numberWithFloat:altitude] forKey:(NSString *)kCGImagePropertyGPSAltitude];
|
||||
}
|
||||
|
||||
|
||||
// Time and date
|
||||
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
||||
[formatter setDateFormat:@"HH:mm:ss.SSSSSS"];
|
||||
@@ -641,7 +647,7 @@ static NSString* toBase64(NSData* data) {
|
||||
[GPSDictionary setObject:[formatter stringFromDate:newLocation.timestamp] forKey:(NSString *)kCGImagePropertyGPSTimeStamp];
|
||||
[formatter setDateFormat:@"yyyy:MM:dd"];
|
||||
[GPSDictionary setObject:[formatter stringFromDate:newLocation.timestamp] forKey:(NSString *)kCGImagePropertyGPSDateStamp];
|
||||
|
||||
|
||||
[self.metadata setObject:GPSDictionary forKey:(NSString *)kCGImagePropertyGPSDictionary];
|
||||
[self imagePickerControllerReturnImageResult];
|
||||
}
|
||||
@@ -654,7 +660,7 @@ static NSString* toBase64(NSData* data) {
|
||||
|
||||
[self.locationManager stopUpdatingLocation];
|
||||
self.locationManager = nil;
|
||||
|
||||
|
||||
[self imagePickerControllerReturnImageResult];
|
||||
}
|
||||
|
||||
@@ -662,26 +668,26 @@ static NSString* toBase64(NSData* data) {
|
||||
{
|
||||
CDVPictureOptions* options = self.pickerController.pictureOptions;
|
||||
CDVPluginResult* result = nil;
|
||||
|
||||
|
||||
if (self.metadata) {
|
||||
CGImageSourceRef sourceImage = CGImageSourceCreateWithData((__bridge CFDataRef)self.data, NULL);
|
||||
CFStringRef sourceType = CGImageSourceGetType(sourceImage);
|
||||
|
||||
|
||||
CGImageDestinationRef destinationImage = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)self.data, sourceType, 1, NULL);
|
||||
CGImageDestinationAddImageFromSource(destinationImage, sourceImage, 0, (__bridge CFDictionaryRef)self.metadata);
|
||||
CGImageDestinationFinalize(destinationImage);
|
||||
|
||||
|
||||
CFRelease(sourceImage);
|
||||
CFRelease(destinationImage);
|
||||
}
|
||||
|
||||
|
||||
switch (options.destinationType) {
|
||||
case DestinationTypeFileUri:
|
||||
{
|
||||
NSError* err = nil;
|
||||
NSString* extension = self.pickerController.pictureOptions.encodingType == EncodingTypePNG ? @"png":@"jpg";
|
||||
NSString* filePath = [self tempFilePath:extension];
|
||||
|
||||
|
||||
// save file
|
||||
if (![self.data writeToFile:filePath options:NSAtomicWrite error:&err]) {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
|
||||
@@ -700,16 +706,16 @@ static NSString* toBase64(NSData* data) {
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
|
||||
if (result) {
|
||||
[self.commandDelegate sendPluginResult:result callbackId:self.pickerController.callbackId];
|
||||
}
|
||||
|
||||
|
||||
self.hasPendingOperation = NO;
|
||||
self.pickerController = nil;
|
||||
self.data = nil;
|
||||
self.metadata = nil;
|
||||
|
||||
|
||||
if (options.saveToPhotoAlbum) {
|
||||
ALAssetsLibrary *library = [ALAssetsLibrary new];
|
||||
[library writeImageDataToSavedPhotosAlbum:self.data metadata:self.metadata completionBlock:nil];
|
||||
@@ -729,14 +735,14 @@ static NSString* toBase64(NSData* data) {
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
SEL sel = NSSelectorFromString(@"setNeedsStatusBarAppearanceUpdate");
|
||||
if ([self respondsToSelector:sel]) {
|
||||
[self performSelector:sel withObject:nil afterDelay:0];
|
||||
}
|
||||
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
}
|
||||
|
||||
@@ -746,7 +752,7 @@ static NSString* toBase64(NSData* data) {
|
||||
cameraPicker.pictureOptions = pictureOptions;
|
||||
cameraPicker.sourceType = pictureOptions.sourceType;
|
||||
cameraPicker.allowsEditing = pictureOptions.allowsEditing;
|
||||
|
||||
|
||||
if (cameraPicker.sourceType == UIImagePickerControllerSourceTypeCamera) {
|
||||
// We only allow taking pictures (no video) in this API.
|
||||
cameraPicker.mediaTypes = @[(NSString*)kUTTypeImage];
|
||||
@@ -758,8 +764,8 @@ static NSString* toBase64(NSData* data) {
|
||||
NSArray* mediaArray = @[(NSString*)(pictureOptions.mediaType == MediaTypeVideo ? kUTTypeMovie : kUTTypeImage)];
|
||||
cameraPicker.mediaTypes = mediaArray;
|
||||
}
|
||||
|
||||
|
||||
return cameraPicker;
|
||||
}
|
||||
|
||||
@end
|
||||
@end
|
||||
|
||||
81
src/osx/CDVCamera.h
Normal file
81
src/osx/CDVCamera.h
Normal 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
src/osx/CDVCamera.m
Normal file
258
src/osx/CDVCamera.m
Normal 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
|
||||
@@ -19,13 +19,10 @@
|
||||
*
|
||||
*/
|
||||
|
||||
/*jshint unused:true, undef:true, browser:true */
|
||||
/*global Windows:true, URL:true, module:true, require:true, WinJS: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';
|
||||
@@ -77,12 +74,16 @@ var DEFAULT_ASPECT_RATIO = '1.8';
|
||||
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";
|
||||
function resizeImage (successCallback, errorCallback, file, targetWidth, targetHeight, encodingType) {
|
||||
var tempPhotoFileName = '';
|
||||
var targetContentType = '';
|
||||
|
||||
if (encodingType === Camera.EncodingType.PNG) {
|
||||
tempPhotoFileName = 'camera_cordova_temp_return.png';
|
||||
targetContentType = 'image/png';
|
||||
} else {
|
||||
tempPhotoFileName = "camera_cordova_temp_return.jpg";
|
||||
tempPhotoFileName = 'camera_cordova_temp_return.jpg';
|
||||
targetContentType = 'image/jpeg';
|
||||
}
|
||||
|
||||
var storageFolder = getAppData().localFolder;
|
||||
@@ -90,12 +91,12 @@ function resizeImage(successCallback, errorCallback, file, targetWidth, targetHe
|
||||
.then(function (storageFile) {
|
||||
return fileIO.readBufferAsync(storageFile);
|
||||
})
|
||||
.then(function(buffer) {
|
||||
.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() {
|
||||
image.onload = function () {
|
||||
var ratio = Math.min(targetWidth / this.width, targetHeight / this.height);
|
||||
var imageWidth = ratio * this.width;
|
||||
var imageHeight = ratio * 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,26 +120,25 @@ 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) {
|
||||
.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) {
|
||||
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() {
|
||||
image.onload = function () {
|
||||
var ratio = Math.min(targetWidth / this.width, targetHeight / this.height);
|
||||
var imageWidth = ratio * this.width;
|
||||
var imageHeight = ratio * this.height;
|
||||
@@ -147,21 +147,21 @@ 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);
|
||||
};
|
||||
}, function(err) { errorCallback(err); });
|
||||
}, function (err) { errorCallback(err); });
|
||||
}
|
||||
|
||||
function takePictureFromFile(successCallback, errorCallback, args) {
|
||||
function takePictureFromFile (successCallback, errorCallback, args) {
|
||||
// Detect Windows Phone
|
||||
if (navigator.appVersion.indexOf('Windows Phone 8.1') >= 0) {
|
||||
takePictureFromFileWP(successCallback, errorCallback, args);
|
||||
@@ -170,94 +170,87 @@ 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];
|
||||
function takePictureFromFileWP (successCallback, errorCallback, args) {
|
||||
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
|
||||
Using FileOpenPicker will suspend the app and it's required to catch the PickSingleFileAndContinue
|
||||
https://msdn.microsoft.com/en-us/library/windows/apps/xaml/dn631755.aspx
|
||||
*/
|
||||
var filePickerActivationHandler = function(eventArgs) {
|
||||
var filePickerActivationHandler = function (eventArgs) {
|
||||
if (eventArgs.kind === Windows.ApplicationModel.Activation.ActivationKind.pickFileContinuation) {
|
||||
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 {
|
||||
fileIO.readBufferAsync(file).done(function (buffer) {
|
||||
var strBase64 =encodeToBase64String(buffer);
|
||||
var strBase64 = encodeToBase64String(buffer);
|
||||
successCallback(strBase64);
|
||||
}, 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];
|
||||
function takePictureFromFileWindows (successCallback, errorCallback, args) {
|
||||
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,30 +259,27 @@ 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 {
|
||||
successCallback(URL.createObjectURL(storageFile));
|
||||
}
|
||||
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 {
|
||||
fileIO.readBufferAsync(file).done(function (buffer) {
|
||||
var strBase64 =encodeToBase64String(buffer);
|
||||
var strBase64 = encodeToBase64String(buffer);
|
||||
successCallback(strBase64);
|
||||
}, errorCallback);
|
||||
}
|
||||
@@ -299,7 +289,7 @@ function takePictureFromFileWindows(successCallback, errorCallback, args) {
|
||||
});
|
||||
}
|
||||
|
||||
function takePictureFromCamera(successCallback, errorCallback, args) {
|
||||
function takePictureFromCamera (successCallback, errorCallback, args) {
|
||||
// Check if necessary API available
|
||||
if (!Windows.Media.Capture.CameraCaptureUI) {
|
||||
takePictureFromCameraWP(successCallback, errorCallback, args);
|
||||
@@ -308,42 +298,42 @@ function takePictureFromCamera(successCallback, errorCallback, args) {
|
||||
}
|
||||
}
|
||||
|
||||
function takePictureFromCameraWP(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() {
|
||||
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();
|
||||
|
||||
@@ -351,21 +341,21 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
|
||||
captureSettings.streamingCaptureMode = CaptureNS.StreamingCaptureMode.video;
|
||||
}
|
||||
|
||||
function continueVideoOnFocus() {
|
||||
function continueVideoOnFocus () {
|
||||
// if preview is defined it would be stuck, play it
|
||||
if (capturePreview) {
|
||||
capturePreview.play();
|
||||
}
|
||||
}
|
||||
|
||||
function startCameraPreview() {
|
||||
function startCameraPreview () {
|
||||
// Search for available camera devices
|
||||
// This is necessary to detect which camera (front or back) we should use
|
||||
var DeviceEnum = Windows.Devices.Enumeration;
|
||||
var expectedPanel = cameraDirection === 1 ? DeviceEnum.Panel.front : DeviceEnum.Panel.back;
|
||||
|
||||
// Add focus event handler to capture the event when user suspends the app and comes back while the preview is on
|
||||
window.addEventListener("focus", continueVideoOnFocus);
|
||||
window.addEventListener('focus', continueVideoOnFocus);
|
||||
|
||||
DeviceEnum.DeviceInformation.findAllAsync(DeviceEnum.DeviceClass.videoCapture).then(function (devices) {
|
||||
if (devices.length <= 0) {
|
||||
@@ -374,8 +364,8 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
|
||||
return;
|
||||
}
|
||||
|
||||
devices.forEach(function(currDev) {
|
||||
if (currDev.enclosureLocation.panel && currDev.enclosureLocation.panel == expectedPanel) {
|
||||
devices.forEach(function (currDev) {
|
||||
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
|
||||
@@ -455,7 +445,7 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
|
||||
});
|
||||
}
|
||||
|
||||
function destroyCameraPreview() {
|
||||
function destroyCameraPreview () {
|
||||
// If sensor is available, remove event listener
|
||||
if (sensor !== null) {
|
||||
sensor.removeEventListener('orientationchanged', onOrientationChange);
|
||||
@@ -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) {
|
||||
@@ -486,13 +476,13 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
|
||||
}
|
||||
}
|
||||
|
||||
function captureAction() {
|
||||
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 {
|
||||
@@ -501,41 +491,41 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
|
||||
}
|
||||
|
||||
tempFolder.createFileAsync(fileName, OptUnique)
|
||||
.then(function(tempCapturedFile) {
|
||||
.then(function (tempCapturedFile) {
|
||||
return new WinJS.Promise(function (complete) {
|
||||
var photoStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
|
||||
var finalStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
|
||||
capture.capturePhotoToStreamAsync(encodingProperties, photoStream)
|
||||
.then(function() {
|
||||
.then(function () {
|
||||
return Windows.Graphics.Imaging.BitmapDecoder.createAsync(photoStream);
|
||||
})
|
||||
.then(function(dec) {
|
||||
.then(function (dec) {
|
||||
finalStream.size = 0; // BitmapEncoder requires the output stream to be empty
|
||||
return Windows.Graphics.Imaging.BitmapEncoder.createForTranscodingAsync(finalStream, dec);
|
||||
})
|
||||
.then(function(enc) {
|
||||
.then(function (enc) {
|
||||
// We need to rotate the photo wrt sensor orientation
|
||||
enc.bitmapTransform.rotation = orientationToRotation(sensor.getCurrentOrientation());
|
||||
return enc.flushAsync();
|
||||
})
|
||||
.then(function() {
|
||||
.then(function () {
|
||||
return tempCapturedFile.openAsync(Windows.Storage.FileAccessMode.readWrite);
|
||||
})
|
||||
.then(function(fileStream) {
|
||||
.then(function (fileStream) {
|
||||
return Windows.Storage.Streams.RandomAccessStream.copyAndCloseAsync(finalStream, fileStream);
|
||||
})
|
||||
.done(function() {
|
||||
.done(function () {
|
||||
photoStream.close();
|
||||
finalStream.close();
|
||||
complete(tempCapturedFile);
|
||||
}, function() {
|
||||
}, 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.');
|
||||
});
|
||||
});
|
||||
})
|
||||
.done(function(capturedFile) {
|
||||
.done(function (capturedFile) {
|
||||
destroyCameraPreview();
|
||||
savePhoto(capturedFile, {
|
||||
destinationType: destinationType,
|
||||
@@ -544,13 +534,13 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
|
||||
encodingType: encodingType,
|
||||
saveToPhotoAlbum: saveToPhotoAlbum
|
||||
}, successCallback, errorCallback);
|
||||
}, function(err) {
|
||||
}, function (err) {
|
||||
destroyCameraPreview();
|
||||
errorCallback(err);
|
||||
});
|
||||
}
|
||||
|
||||
function getAspectRatios(capture) {
|
||||
function getAspectRatios (capture) {
|
||||
var videoDeviceController = capture.videoDeviceController;
|
||||
var photoAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.photo).map(function (element) {
|
||||
return (element.width / element.height).toFixed(1);
|
||||
@@ -579,7 +569,7 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
|
||||
});
|
||||
}
|
||||
|
||||
function setAspectRatio(capture, aspect) {
|
||||
function setAspectRatio (capture, aspect) {
|
||||
// Max photo resolution with desired aspect ratio
|
||||
var videoDeviceController = capture.videoDeviceController;
|
||||
var photoResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.photo)
|
||||
@@ -620,7 +610,7 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
|
||||
/**
|
||||
* When Capture button is clicked, try to capture a picture and return
|
||||
*/
|
||||
function onCameraCaptureButtonClick() {
|
||||
function onCameraCaptureButtonClick () {
|
||||
// Make sure user can't click more than once
|
||||
if (this.getAttribute('clicked') === '1') {
|
||||
return false;
|
||||
@@ -633,7 +623,7 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
|
||||
/**
|
||||
* When Cancel button is clicked, destroy camera preview and return with error callback
|
||||
*/
|
||||
function onCameraCancelButtonClick() {
|
||||
function onCameraCancelButtonClick () {
|
||||
// Make sure user can't click more than once
|
||||
if (this.getAttribute('clicked') === '1') {
|
||||
return false;
|
||||
@@ -648,7 +638,7 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
|
||||
* When the phone orientation change, get the event and change camera preview rotation
|
||||
* @param {Object} e - SimpleOrientationSensorOrientationChangedEventArgs
|
||||
*/
|
||||
function onOrientationChange(e) {
|
||||
function onOrientationChange (e) {
|
||||
setPreviewRotation(e.orientation);
|
||||
}
|
||||
|
||||
@@ -658,29 +648,29 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
|
||||
* @param {number} orientation - Windows.Devices.Sensors.SimpleOrientation
|
||||
* @return {number} - Windows.Media.Capture.VideoRotation
|
||||
*/
|
||||
function orientationToRotation(orientation) {
|
||||
function orientationToRotation (orientation) {
|
||||
// VideoRotation enumerable and BitmapRotation enumerable have the same values
|
||||
// https://msdn.microsoft.com/en-us/library/windows/apps/windows.media.capture.videorotation.aspx
|
||||
// https://msdn.microsoft.com/en-us/library/windows/apps/windows.graphics.imaging.bitmaprotation.aspx
|
||||
|
||||
switch (orientation) {
|
||||
// portrait
|
||||
case Windows.Devices.Sensors.SimpleOrientation.notRotated:
|
||||
return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
|
||||
// landscape
|
||||
case Windows.Devices.Sensors.SimpleOrientation.rotated90DegreesCounterclockwise:
|
||||
return Windows.Media.Capture.VideoRotation.none;
|
||||
// portrait-flipped (not supported by WinPhone Apps)
|
||||
case Windows.Devices.Sensors.SimpleOrientation.rotated180DegreesCounterclockwise:
|
||||
// Falling back to portrait default
|
||||
return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
|
||||
// landscape-flipped
|
||||
case Windows.Devices.Sensors.SimpleOrientation.rotated270DegreesCounterclockwise:
|
||||
return Windows.Media.Capture.VideoRotation.clockwise180Degrees;
|
||||
// faceup & facedown
|
||||
default:
|
||||
// Falling back to portrait default
|
||||
return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
|
||||
// portrait
|
||||
case Windows.Devices.Sensors.SimpleOrientation.notRotated:
|
||||
return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
|
||||
// landscape
|
||||
case Windows.Devices.Sensors.SimpleOrientation.rotated90DegreesCounterclockwise:
|
||||
return Windows.Media.Capture.VideoRotation.none;
|
||||
// portrait-flipped (not supported by WinPhone Apps)
|
||||
case Windows.Devices.Sensors.SimpleOrientation.rotated180DegreesCounterclockwise:
|
||||
// Falling back to portrait default
|
||||
return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
|
||||
// landscape-flipped
|
||||
case Windows.Devices.Sensors.SimpleOrientation.rotated270DegreesCounterclockwise:
|
||||
return Windows.Media.Capture.VideoRotation.clockwise180Degrees;
|
||||
// faceup & facedown
|
||||
default:
|
||||
// Falling back to portrait default
|
||||
return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -688,7 +678,7 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
|
||||
* Rotates the current MediaCapture's video
|
||||
* @param {number} orientation - Windows.Devices.Sensors.SimpleOrientation
|
||||
*/
|
||||
function setPreviewRotation(orientation) {
|
||||
function setPreviewRotation (orientation) {
|
||||
capture.setPreviewRotation(orientationToRotation(orientation));
|
||||
}
|
||||
|
||||
@@ -700,19 +690,19 @@ 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();
|
||||
function takePictureFromCameraWindows (successCallback, errorCallback, args) {
|
||||
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) {
|
||||
/* 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;
|
||||
@@ -745,10 +734,10 @@ function takePictureFromCameraWindows(successCallback, errorCallback, args) {
|
||||
cameraCaptureUI.photoSettings.maxResolution = maxRes;
|
||||
|
||||
var cameraPicture;
|
||||
|
||||
|
||||
// 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,
|
||||
@@ -760,15 +749,15 @@ 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);
|
||||
if (navigator.appVersion.indexOf('Windows Phone 10.0') >= 0) {
|
||||
window.addEventListener('focus', savePhotoOnFocus);
|
||||
}
|
||||
|
||||
cameraCaptureUI.captureFileAsync(WMCapture.CameraCaptureUIMode.photo).done(function (picture) {
|
||||
if (!picture) {
|
||||
errorCallback("User didn't capture a photo.");
|
||||
// Remove the focus handler if present
|
||||
window.removeEventListener("focus", savePhotoOnFocus);
|
||||
window.removeEventListener('focus', savePhotoOnFocus);
|
||||
return;
|
||||
}
|
||||
cameraPicture = picture;
|
||||
@@ -784,31 +773,31 @@ 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) {
|
||||
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) {
|
||||
var success = function (picture) {
|
||||
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);
|
||||
},errorCallback);
|
||||
successCallback('ms-appdata:///local/' + copiedFile.name);
|
||||
}, errorCallback);
|
||||
}
|
||||
} else {
|
||||
if (options.targetHeight > 0 && options.targetWidth > 0) {
|
||||
resizeImageBase64(successCallback, errorCallback, picture, options.targetWidth, options.targetHeight);
|
||||
} else {
|
||||
fileIO.readBufferAsync(picture).done(function(buffer) {
|
||||
fileIO.readBufferAsync(picture).done(function (buffer) {
|
||||
var strBase64 = encodeToBase64String(buffer);
|
||||
picture.deleteAsync().done(function() {
|
||||
picture.deleteAsync().done(function () {
|
||||
successCallback(strBase64);
|
||||
}, function(err) {
|
||||
}, function (err) {
|
||||
errorCallback(err);
|
||||
});
|
||||
}, errorCallback);
|
||||
@@ -818,38 +807,38 @@ function savePhoto(picture, options, successCallback, errorCallback) {
|
||||
|
||||
if (!options.saveToPhotoAlbum) {
|
||||
success(picture);
|
||||
return;
|
||||
|
||||
} else {
|
||||
var savePicker = new Windows.Storage.Pickers.FileSavePicker();
|
||||
var saveFile = function(file) {
|
||||
var saveFile = function (file) {
|
||||
if (file) {
|
||||
// Prevent updates to the remote version of the file until we're done
|
||||
Windows.Storage.CachedFileManager.deferUpdates(file);
|
||||
picture.moveAndReplaceAsync(file)
|
||||
.then(function() {
|
||||
.then(function () {
|
||||
// Let Windows know that we're finished changing the file so
|
||||
// the other app can update the remote version of the file.
|
||||
return Windows.Storage.CachedFileManager.completeUpdatesAsync(file);
|
||||
})
|
||||
.done(function(updateStatus) {
|
||||
.done(function (updateStatus) {
|
||||
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()
|
||||
@@ -859,14 +848,14 @@ function savePhoto(picture, options, successCallback, errorCallback) {
|
||||
Using FileSavePicker will suspend the app and it's required to catch the pickSaveFileContinuation
|
||||
https://msdn.microsoft.com/en-us/library/windows/apps/xaml/dn631755.aspx
|
||||
*/
|
||||
var fileSaveHandler = function(eventArgs) {
|
||||
var fileSaveHandler = function (eventArgs) {
|
||||
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
tests/package.json
Normal file
14
tests/package.json
Normal 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"
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
146
tests/tests.js
146
tests/tests.js
@@ -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 () {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
@@ -86,7 +85,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
var fileEntry = null;
|
||||
var pageStartTime = +new Date();
|
||||
|
||||
//default camera options
|
||||
// default camera options
|
||||
var camQualityDefault = ['50', 50];
|
||||
var camDestinationTypeDefault = ['FILE_URI', 1];
|
||||
var camPictureSourceTypeDefault = ['CAMERA', 1];
|
||||
@@ -96,12 +95,12 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
var camCorrectOrientationDefault = ['correctOrientation', false];
|
||||
var camSaveToPhotoAlbumDefault = ['saveToPhotoAlbum', true];
|
||||
|
||||
function log(value) {
|
||||
function log (value) {
|
||||
console.log(value);
|
||||
document.getElementById('camera_status').textContent += (new Date() - pageStartTime) / 1000 + ': ' + value + '\n';
|
||||
}
|
||||
|
||||
function clearStatus() {
|
||||
function clearStatus () {
|
||||
document.getElementById('camera_status').innerHTML = '';
|
||||
document.getElementById('camera_image').src = 'about:blank';
|
||||
var canvas = document.getElementById('canvas');
|
||||
@@ -111,11 +110,11 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
fileEntry = null;
|
||||
}
|
||||
|
||||
function setPicture(url, callback) {
|
||||
function setPicture (url, callback) {
|
||||
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
|
||||
}
|
||||
@@ -134,19 +133,19 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
};
|
||||
}
|
||||
|
||||
function onGetPictureError(e) {
|
||||
function onGetPictureError (e) {
|
||||
log('Error getting picture: ' + (e.code || e));
|
||||
}
|
||||
|
||||
function getPictureWin(data) {
|
||||
function getPictureWin (data) {
|
||||
setPicture(data);
|
||||
// TODO: Fix resolveLocalFileSystemURI to work with native-uri.
|
||||
if (pictureUrl.indexOf('file:') === 0 || pictureUrl.indexOf('content:') === 0 || pictureUrl.indexOf('ms-appdata:') === 0 || 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 {
|
||||
@@ -155,7 +154,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
}
|
||||
}
|
||||
|
||||
function getPicture() {
|
||||
function getPicture () {
|
||||
clearStatus();
|
||||
var options = extractOptions();
|
||||
log('Getting picture with options: ' + JSON.stringify(options));
|
||||
@@ -168,27 +167,27 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
};
|
||||
}
|
||||
|
||||
function uploadImage() {
|
||||
var ft = new FileTransfer(),
|
||||
options = new FileUploadOptions();
|
||||
options.fileKey = "photo";
|
||||
function uploadImage () {
|
||||
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) {
|
||||
function win (information_back) {
|
||||
log('upload complete');
|
||||
}
|
||||
function fail(message) {
|
||||
function fail (message) {
|
||||
log('upload failed: ' + JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
function logCallback(apiName, success) {
|
||||
function logCallback (apiName, success) {
|
||||
return function () {
|
||||
log('Call to ' + apiName + (success ? ' success: ' : ' failed: ') + JSON.stringify([].slice.call(arguments)));
|
||||
};
|
||||
@@ -198,20 +197,21 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
* Select image from library using a NATIVE_URI destination type
|
||||
* This calls FileEntry.getMetadata, FileEntry.setMetadata, FileEntry.getParent, FileEntry.file, and FileReader.readAsDataURL.
|
||||
*/
|
||||
function readFile() {
|
||||
function onFileReadAsDataURL(evt) {
|
||||
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) {
|
||||
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);
|
||||
};
|
||||
@@ -228,10 +228,10 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
}
|
||||
}
|
||||
|
||||
function getFileInfo() {
|
||||
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));
|
||||
}
|
||||
@@ -240,7 +240,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
* Copy image from library using a NATIVE_URI destination type
|
||||
* This calls FileEntry.copyTo and FileEntry.moveTo.
|
||||
*/
|
||||
function copyImage() {
|
||||
function copyImage () {
|
||||
var onFileSystemReceived = function (fileSystem) {
|
||||
var destDirEntry = fileSystem.root;
|
||||
var origName = fileEntry.name;
|
||||
@@ -249,17 +249,17 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
fileEntry.copyTo(destDirEntry, 'copied_file.png', logCallback('FileEntry.copyTo', true), logCallback('FileEntry.copyTo', false));
|
||||
fileEntry.moveTo(destDirEntry, 'moved_file.png', logCallback('FileEntry.moveTo', true), logCallback('FileEntry.moveTo', false));
|
||||
|
||||
//cleanup
|
||||
//rename moved file back to original name so other tests can reference image
|
||||
resolveLocalFileSystemURI(destDirEntry.nativeURL+'moved_file.png', function(fileEntry) {
|
||||
// cleanup
|
||||
// rename moved file back to original name so other tests can reference image
|
||||
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 () {
|
||||
console.log('Cleanup: failed to rename file back to original name');
|
||||
});
|
||||
|
||||
//remove copied file
|
||||
resolveLocalFileSystemURI(destDirEntry.nativeURL+'copied_file.png', function(fileEntry) {
|
||||
// remove copied file
|
||||
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 () {
|
||||
@@ -274,11 +274,11 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
* Write image to library using a NATIVE_URI destination type
|
||||
* This calls FileEntry.createWriter, FileWriter.write, and FileWriter.truncate.
|
||||
*/
|
||||
function writeImage() {
|
||||
function writeImage () {
|
||||
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) {
|
||||
@@ -291,7 +291,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
fileEntry.createWriter(onFileTruncateWriterReceived, null);
|
||||
}
|
||||
|
||||
function displayImageUsingCanvas() {
|
||||
function displayImageUsingCanvas () {
|
||||
var canvas = document.getElementById('canvas');
|
||||
var img = document.getElementById('camera_image');
|
||||
var w = img.width;
|
||||
@@ -308,11 +308,11 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
* Remove image from library using a NATIVE_URI destination type
|
||||
* This calls FileEntry.remove.
|
||||
*/
|
||||
function removeImage() {
|
||||
function removeImage () {
|
||||
fileEntry.remove(logCallback('FileEntry.remove', true), logCallback('FileEntry.remove', false));
|
||||
}
|
||||
|
||||
function testInputTag(inputEl) {
|
||||
function testInputTag (inputEl) {
|
||||
clearStatus();
|
||||
// iOS 6 likes to dead-lock in the onchange context if you
|
||||
// do any alerts or try to remote-debug.
|
||||
@@ -321,7 +321,8 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function testNativeFile2(inputEl) {
|
||||
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);
|
||||
@@ -346,26 +348,26 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
}
|
||||
}
|
||||
|
||||
function extractOptions() {
|
||||
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;
|
||||
}
|
||||
|
||||
function createOptionsEl(name, values, selectionDefault) {
|
||||
function createOptionsEl (name, values, selectionDefault) {
|
||||
var openDiv = '<div style="display: inline-block">' + name + ': ';
|
||||
var select = '<select name=' + name + ' id="' + name + '">';
|
||||
|
||||
@@ -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"' +
|
||||
@@ -456,20 +458,20 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
// We need to wrap this code due to Windows security restrictions
|
||||
// see http://msdn.microsoft.com/en-us/library/windows/apps/hh465380.aspx#differences for details
|
||||
if (window.MSApp && window.MSApp.execUnsafeLocalFunction) {
|
||||
MSApp.execUnsafeLocalFunction(function() {
|
||||
MSApp.execUnsafeLocalFunction(function () {
|
||||
contentEl.innerHTML = info_div + options_div + getpicture_div + test_procedure + inputs_div + actions_div;
|
||||
});
|
||||
} else {
|
||||
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
types/index.d.ts
vendored
Normal file
174
types/index.d.ts
vendored
Normal 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;
|
||||
}
|
||||
};
|
||||
@@ -19,11 +19,11 @@
|
||||
*
|
||||
*/
|
||||
|
||||
var argscheck = require('cordova/argscheck'),
|
||||
exec = require('cordova/exec'),
|
||||
Camera = require('./Camera');
|
||||
// XXX: commented out
|
||||
//CameraPopoverHandle = require('./CameraPopoverHandle');
|
||||
var argscheck = require('cordova/argscheck');
|
||||
var exec = require('cordova/exec');
|
||||
var Camera = require('./Camera');
|
||||
// XXX: commented out
|
||||
// CameraPopoverHandle = require('./CameraPopoverHandle');
|
||||
|
||||
/**
|
||||
* @namespace navigator
|
||||
@@ -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
|
||||
@@ -137,7 +131,7 @@ for (var key in Camera) {
|
||||
* @param {module:camera.onError} errorCallback
|
||||
* @param {module:camera.CameraOptions} options CameraOptions
|
||||
*/
|
||||
cameraExport.getPicture = function(successCallback, errorCallback, options) {
|
||||
cameraExport.getPicture = function (successCallback, errorCallback, options) {
|
||||
argscheck.checkArgs('fFO', 'Camera.getPicture', arguments);
|
||||
options = options || {};
|
||||
var getValue = argscheck.getValue;
|
||||
@@ -156,11 +150,11 @@ cameraExport.getPicture = function(successCallback, errorCallback, options) {
|
||||
var cameraDirection = getValue(options.cameraDirection, Camera.Direction.BACK);
|
||||
|
||||
var args = [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType,
|
||||
mediaType, allowEdit, correctOrientation, saveToPhotoAlbum, popoverOptions, cameraDirection];
|
||||
mediaType, allowEdit, correctOrientation, saveToPhotoAlbum, popoverOptions, cameraDirection];
|
||||
|
||||
exec(successCallback, errorCallback, "Camera", "takePicture", args);
|
||||
exec(successCallback, errorCallback, 'Camera', 'takePicture', args);
|
||||
// XXX: commented out
|
||||
//return new CameraPopoverHandle();
|
||||
// return new CameraPopoverHandle();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -184,8 +178,8 @@ cameraExport.getPicture = function(successCallback, errorCallback, options) {
|
||||
* alert('Failed because: ' + message);
|
||||
* }
|
||||
*/
|
||||
cameraExport.cleanup = function(successCallback, errorCallback) {
|
||||
exec(successCallback, errorCallback, "Camera", "cleanup", []);
|
||||
cameraExport.cleanup = function (successCallback, errorCallback) {
|
||||
exec(successCallback, errorCallback, 'Camera', 'cleanup', []);
|
||||
};
|
||||
|
||||
module.exports = cameraExport;
|
||||
|
||||
@@ -23,66 +23,79 @@
|
||||
* @module Camera
|
||||
*/
|
||||
module.exports = {
|
||||
/**
|
||||
* @enum {number}
|
||||
*/
|
||||
DestinationType:{
|
||||
/** Return base64 encoded string. DATA_URL can be very memory intensive and cause app crashes or out of memory errors. Use FILE_URI or NATIVE_URI if possible */
|
||||
DATA_URL: 0,
|
||||
/** Return file uri (content://media/external/images/media/2 for Android) */
|
||||
FILE_URI: 1,
|
||||
/** Return native uri (eg. asset-library://... for iOS) */
|
||||
NATIVE_URI: 2
|
||||
},
|
||||
/**
|
||||
* @enum {number}
|
||||
*/
|
||||
EncodingType:{
|
||||
/** Return JPEG encoded image */
|
||||
JPEG: 0,
|
||||
/** Return PNG encoded image */
|
||||
PNG: 1
|
||||
},
|
||||
/**
|
||||
* @enum {number}
|
||||
*/
|
||||
MediaType:{
|
||||
/** Allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType */
|
||||
PICTURE: 0,
|
||||
/** Allow selection of video only, ONLY RETURNS URL */
|
||||
VIDEO: 1,
|
||||
/** Allow selection from all media types */
|
||||
ALLMEDIA : 2
|
||||
},
|
||||
/**
|
||||
* @enum {number}
|
||||
*/
|
||||
PictureSourceType:{
|
||||
/** Choose image from picture library (same as SAVEDPHOTOALBUM for Android) */
|
||||
PHOTOLIBRARY : 0,
|
||||
/** Take picture from camera */
|
||||
CAMERA : 1,
|
||||
/** Choose image from picture library (same as PHOTOLIBRARY for Android) */
|
||||
SAVEDPHOTOALBUM : 2
|
||||
},
|
||||
/**
|
||||
* Matches iOS UIPopoverArrowDirection constants to specify arrow location on popover.
|
||||
* @enum {number}
|
||||
*/
|
||||
PopoverArrowDirection:{
|
||||
ARROW_UP : 1,
|
||||
ARROW_DOWN : 2,
|
||||
ARROW_LEFT : 4,
|
||||
ARROW_RIGHT : 8,
|
||||
ARROW_ANY : 15
|
||||
},
|
||||
/**
|
||||
* @enum {number}
|
||||
*/
|
||||
Direction:{
|
||||
/** Use the back-facing camera */
|
||||
BACK: 0,
|
||||
/** Use the front-facing camera */
|
||||
FRONT: 1
|
||||
}
|
||||
/**
|
||||
* @description
|
||||
* Defines the output format of `Camera.getPicture` call.
|
||||
* _Note:_ On iOS passing `DestinationType.NATIVE_URI` along with
|
||||
* `PictureSourceType.PHOTOLIBRARY` or `PictureSourceType.SAVEDPHOTOALBUM` will
|
||||
* disable any image modifications (resize, quality change, cropping, etc.) due
|
||||
* to implementation specific.
|
||||
*
|
||||
* @enum {number}
|
||||
*/
|
||||
DestinationType: {
|
||||
/** Return base64 encoded string. DATA_URL can be very memory intensive and cause app crashes or out of memory errors. Use FILE_URI or NATIVE_URI if possible */
|
||||
DATA_URL: 0,
|
||||
/** Return file uri (content://media/external/images/media/2 for Android) */
|
||||
FILE_URI: 1,
|
||||
/** Return native uri (eg. asset-library://... for iOS) */
|
||||
NATIVE_URI: 2
|
||||
},
|
||||
/**
|
||||
* @enum {number}
|
||||
*/
|
||||
EncodingType: {
|
||||
/** Return JPEG encoded image */
|
||||
JPEG: 0,
|
||||
/** Return PNG encoded image */
|
||||
PNG: 1
|
||||
},
|
||||
/**
|
||||
* @enum {number}
|
||||
*/
|
||||
MediaType: {
|
||||
/** Allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType */
|
||||
PICTURE: 0,
|
||||
/** Allow selection of video only, ONLY RETURNS URL */
|
||||
VIDEO: 1,
|
||||
/** Allow selection from all media types */
|
||||
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 the device's photo library (same as SAVEDPHOTOALBUM for Android) */
|
||||
PHOTOLIBRARY: 0,
|
||||
/** Take picture from camera */
|
||||
CAMERA: 1,
|
||||
/** Choose image only from the device's Camera Roll album (same as PHOTOLIBRARY for Android) */
|
||||
SAVEDPHOTOALBUM: 2
|
||||
},
|
||||
/**
|
||||
* Matches iOS UIPopoverArrowDirection constants to specify arrow location on popover.
|
||||
* @enum {number}
|
||||
*/
|
||||
PopoverArrowDirection: {
|
||||
ARROW_UP: 1,
|
||||
ARROW_DOWN: 2,
|
||||
ARROW_LEFT: 4,
|
||||
ARROW_RIGHT: 8,
|
||||
ARROW_ANY: 15
|
||||
},
|
||||
/**
|
||||
* @enum {number}
|
||||
*/
|
||||
Direction: {
|
||||
/** Use the back-facing camera */
|
||||
BACK: 0,
|
||||
/** Use the front-facing camera */
|
||||
FRONT: 1
|
||||
}
|
||||
};
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
* @ignore in favour of iOS' one
|
||||
* A handle to an image picker popover.
|
||||
*/
|
||||
var CameraPopoverHandle = function() {
|
||||
this.setPosition = function(popoverOptions) {
|
||||
var CameraPopoverHandle = function () {
|
||||
this.setPosition = function (popoverOptions) {
|
||||
console.log('CameraPopoverHandle.setPosition is only supported on iOS.');
|
||||
};
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
var Camera = require('./Camera');
|
||||
|
||||
/**
|
||||
/**
|
||||
* @namespace navigator
|
||||
*/
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
var CameraPopoverHandle = function () {
|
||||
/**
|
||||
* 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) {
|
||||
this.setPosition = function (popoverOptions) {
|
||||
var args = [ popoverOptions ];
|
||||
exec(null, null, "Camera", "repositionPopover", args);
|
||||
exec(null, null, 'Camera', 'repositionPopover', args);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user