Compare commits

..

2 Commits

Author SHA1 Message Date
Simon MacDonald
31334d5ebc Remove Cordova Android 5 requirement 2016-01-08 13:37:04 -05:00
Simon MacDonald
9b6d91009d Use Android compat libs for permission 2016-01-08 13:32:00 -05:00
28 changed files with 497 additions and 2813 deletions

View File

@@ -1,23 +0,0 @@
<!--
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
- [ ] [ICLA](http://www.apache.org/licenses/icla.txt) has been signed and submitted to secretary@apache.org.
- [ ] [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.

View File

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

View File

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

View File

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

385
README.md
View File

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

View File

@@ -20,90 +20,6 @@
-->
# Release Notes
### 2.3.0 (Sep 08, 2016)
* [CB-11795](https://issues.apache.org/jira/browse/CB-11795) Add 'protective' entry to cordovaDependencies
* [CB-11661](https://issues.apache.org/jira/browse/CB-11661) Add mandatory **iOS 10** privacy description
* [CB-11714](https://issues.apache.org/jira/browse/CB-11714) **windows** added more explicit content-type when converting to target data on canvas
* [CB-11295](https://issues.apache.org/jira/browse/CB-11295) Add **WP8.1** quirk when choosing image from `photoalbum`
* [CB-10067](https://issues.apache.org/jira/browse/CB-10067) Update `PictureSourceType` JSDoc to reflect `README` update
* [CB-9070](https://issues.apache.org/jira/browse/CB-9070) Update `CameraPopoverHandle` docs to reflect `README` update
* Plugin uses `Android Log class` and not `Cordova LOG class`
* [CB-11631](https://issues.apache.org/jira/browse/CB-11631) Appium tests: A working fix for a flaky `selection canceled` failure
* [CB-11709](https://issues.apache.org/jira/browse/CB-11709) Tests should use `resolveLocalFileSystemURL()` instead of deprecated `resolveFileSystemURI()`
* [CB-11695](https://issues.apache.org/jira/browse/CB-11695) Increased session creation timeout for Appium tests
* [CB-11656](https://issues.apache.org/jira/browse/CB-11656) (**Android**) Appium tests: Fixed side menu opening on some more resolutions
* [CB-11376](https://issues.apache.org/jira/browse/CB-11376) (**ios**): fix `CameraUsesGeolocation` error
* [CB-10067](https://issues.apache.org/jira/browse/CB-10067) (**ios**) clarifications on `PictureSourceType`
* [CB-11410](https://issues.apache.org/jira/browse/CB-11410) (**ios**) fix `cameraPopoverHandle.setPosition`
* [CB-9070](https://issues.apache.org/jira/browse/CB-9070) (**ios**) Fixed `CameraPopoverHandle` documentation
* [CB-11447](https://issues.apache.org/jira/browse/CB-11447) Respect output format when retrieving images from gallery
* [CB-11447](https://issues.apache.org/jira/browse/CB-11447) Resolve **iOS** tests failures due to **iOS** quirks
* [CB-11553](https://issues.apache.org/jira/browse/CB-11553) Pend failing Appium tests on Sauce Labs for the time being (reverted from commit b69571724035f41642f3ee612c5b66e1f0c4386c)
* [CB-11553](https://issues.apache.org/jira/browse/CB-11553) Pend failing Appium tests on Sauce Labs for the time being
* [CB-11498](https://issues.apache.org/jira/browse/CB-11498) [**Android**] Appium tests should not fail when there is no camera
* Add badges for paramedic builds on Jenkins
* [CB-11296](https://issues.apache.org/jira/browse/CB-11296) Appium: Better element clicking and session error handling
* [CB-11232](https://issues.apache.org/jira/browse/CB-11232) Appium tests: fixed element tapping on **iOS 9**
* [CB-11183](https://issues.apache.org/jira/browse/CB-11183) Appium tests: Added image verification
* fixed some bad formatting that hid `HTML` tags and added link to sample
* Set **android** quality default value to 50 on the java code
* Moving message in PR template to a comment
* Add pull request template. This closes #213
* [CB-11228](https://issues.apache.org/jira/browse/CB-11228) **browser**: Add classes for styling purposes
* [CB-10139](https://issues.apache.org/jira/browse/CB-10139) **browser**: Respect target width and height
* [CB-11227](https://issues.apache.org/jira/browse/CB-11227) **browser**: Fix incorrect `mime type`
* [CB-11162](https://issues.apache.org/jira/browse/CB-11162) Appium tests: retry spec on failure
* [CB-4078](https://issues.apache.org/jira/browse/CB-4078) Fix for `orientation/scaling` on **Android 4.4+** devices
* [CB-11165](https://issues.apache.org/jira/browse/CB-11165) removed peer dependency
* [CB-11147](https://issues.apache.org/jira/browse/CB-11147) Appium tests: generate descriptive spec names
* [CB-10996](https://issues.apache.org/jira/browse/CB-10996) Adding front matter to `README.md`
* [CB-11128](https://issues.apache.org/jira/browse/CB-11128) Appum tests: Fixed some of the flaky failures
* [CB-11003](https://issues.apache.org/jira/browse/CB-11003) Added Sample section to the Camera plugin README
### 2.2.0 (Apr 15, 2016)
* [CB-10873](https://issues.apache.org/jira/browse/CB-10873) Avoid crash due to usage of uninitialized variable when writing geolocation data to image destination. Properly handle 'CameraUsesGeolocation' option by properly setting geolocation data in EXIF header in all cases
* [CB-11073](https://issues.apache.org/jira/browse/CB-11073) Appium tests stability improvements
* Replace `PermissionHelper.java` with `cordova-plugin-compat`
* Making focus handler work only for **windows 10** phone
* [CB-10865](https://issues.apache.org/jira/browse/CB-10865) Run **ios** native tests on **Travis**
* [CB-10120](https://issues.apache.org/jira/browse/CB-10120) Fixing use of constants and `PermissionHelper`
* [CB-10120](https://issues.apache.org/jira/browse/CB-10120) Fix missing CAMERA permission for **Android M**
* [CB-10756](https://issues.apache.org/jira/browse/CB-10756) Adding sterner warnings about `DATA_URL`
* [CB-10460](https://issues.apache.org/jira/browse/CB-10460) `getRealPath` return null in some cases
### 2.1.1 (Mar 09, 2016)
* [CB-10825](https://issues.apache.org/jira/browse/CB-10825) **Android** should request READ permission for gallery source
* added apache license header to appium files
* [CB-10720](https://issues.apache.org/jira/browse/CB-10720) Fixed spelling, capitalization, and other small issues.
* [CB-10414](https://issues.apache.org/jira/browse/CB-10414) Adding focus handler to resume video when user comes back on leaving the app while preview was running
* Appium tests: adjust swipe distance on **Android**
* [CB-10750](https://issues.apache.org/jira/browse/CB-10750) Appium tests: fail fast if session is irrecoverable
* Adding missing semi colon
* Adding focus handler to make sure filepicker gets launched when app is active on **Windows**
* [CB-10128](https://issues.apache.org/jira/browse/CB-10128) **iOS** Fixed how checks access authorization to camera & library. This closes #146
* [CB-10636](https://issues.apache.org/jira/browse/CB-10636) Add JSHint for plugins
* [CB-10639](https://issues.apache.org/jira/browse/CB-10639) Appium tests: Added some timeouts, Taking a screenshot on failure, Retry taking a picture up to 3 times, Try to restart the Appium session if it's lost
* [CB-10552](https://issues.apache.org/jira/browse/CB-10552) Replacing images in README.md.
* Added a lot of more cases to get the real path on **Android**
* [CB-10625](https://issues.apache.org/jira/browse/CB-10625) **Android** getPicture fails when getting a photo from the Photo Library - Google Photos
* [CB-10619](https://issues.apache.org/jira/browse/CB-10619) Appium tests: Properly switch to webview on **Android**
* [CB-10397](https://issues.apache.org/jira/browse/CB-10397) Added Appium tests
* [CB-10576](https://issues.apache.org/jira/browse/CB-10576) MobileSpec can't get results for **Windows**-Store 8.1 Builds
* chore: edit package.json license to match SPDX id
* [CB-10539](https://issues.apache.org/jira/browse/CB-10539) Commenting out the verySmallQvga maxResolution option on **Windows**
* [CB-10541](https://issues.apache.org/jira/browse/CB-10541) Changing default maxResoltion to be highestAvailable for CameraCaptureUI on **Windows**
* [CB-10113](https://issues.apache.org/jira/browse/CB-10113) **Browse** - Layer camera UI on top of all!
* [CB-10502](https://issues.apache.org/jira/browse/CB-10502) **Browser** - Fix camera plugin exception in Chrome when click capture.
* Adding comments
* Camera tapping fix on **Windows**
### 2.1.0 (Jan 15, 2016)
* added `.ratignore`
* [CB-10319](https://issues.apache.org/jira/browse/CB-10319) **Android** Adding reflective helper methods for permission requests
* [CB-9189](https://issues.apache.org/jira/browse/CB-9189) **Android** Implementing `save/restore` API to handle Activity destruction
* [CB-10241](https://issues.apache.org/jira/browse/CB-10241) App Crash cause by Camera Plugin **iOS 7**
* [CB-8940](https://issues.apache.org/jira/browse/CB-8940) Setting `z-index` values to maximum for UI buttons.
### 2.0.0 (Nov 18, 2015)
* [CB-10035](https://issues.apache.org/jira/browse/CB-10035) Updated `RELEASENOTES` to be newest to oldest
* [CB-8863](https://issues.apache.org/jira/browse/CB-8863) correct block usage for `async` calls

View File

@@ -1,639 +0,0 @@
/*jshint node: true, jasmine: true */
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
// these tests are meant to be executed by Cordova Medic Appium runner
// you can find it here: https://github.com/apache/cordova-medic/
// 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"
'use strict';
var wdHelper = global.WD_HELPER;
var screenshotHelper = global.SCREENSHOT_HELPER;
var wd = wdHelper.getWD();
var cameraConstants = require('../../www/CameraConstants');
var cameraHelper = require('../helpers/cameraHelper');
var MINUTE = 60 * 1000;
var BACK_BUTTON = 4;
var DEFAULT_SCREEN_WIDTH = 360;
var DEFAULT_SCREEN_HEIGHT = 567;
var DEFAULT_WEBVIEW_CONTEXT = 'WEBVIEW';
var PROMISE_PREFIX = 'appium_camera_promise_';
describe('Camera tests Android.', function () {
var driver;
// the name of webview context, it will be changed to match needed context if there are named ones:
var webviewContext = DEFAULT_WEBVIEW_CONTEXT;
// this indicates that the device library has the test picture:
var isTestPictureSaved = false;
// we need to know the screen width and height to properly click on an image in the gallery:
var screenWidth = DEFAULT_SCREEN_WIDTH;
var screenHeight = DEFAULT_SCREEN_HEIGHT;
// promise count to use in promise ID
var promiseCount = 0;
// determine if Appium session is created successfully
var appiumSessionStarted = false;
// determine if camera is present on the device/emulator
var cameraAvailable = false;
// a path to the image we add to the gallery before test run
var fillerImagePath;
function getNextPromiseId() {
promiseCount += 1;
return getCurrentPromiseId();
}
function getCurrentPromiseId() {
return PROMISE_PREFIX + promiseCount;
}
function saveScreenshotAndFail(error) {
fail(error);
return screenshotHelper
.saveScreenshot(driver)
.quit()
.then(function () {
return getDriver();
});
}
// combinines specified options in all possible variations
// you can add more options to test more scenarios
function generateOptions() {
var sourceTypes = [
cameraConstants.PictureSourceType.CAMERA,
cameraConstants.PictureSourceType.PHOTOLIBRARY
];
var destinationTypes = cameraConstants.DestinationType;
var encodingTypes = cameraConstants.EncodingType;
var allowEditOptions = [ true, false ];
var correctOrientationOptions = [ true, false ];
return cameraHelper.generateSpecs(sourceTypes, destinationTypes, encodingTypes, allowEditOptions, correctOrientationOptions);
}
// invokes Camera.getPicture() with the specified options
// and goes through all UI interactions unless 'skipUiInteractions' is true
function getPicture(options, skipUiInteractions) {
var promiseId = getNextPromiseId();
if (!options) {
options = {};
}
return driver
.context(webviewContext)
.execute(cameraHelper.getPicture, [options, promiseId])
.context('NATIVE_APP')
.then(function () {
if (skipUiInteractions) {
return;
}
// selecting a picture from gallery
if (options.hasOwnProperty('sourceType') &&
(options.sourceType === cameraConstants.PictureSourceType.PHOTOLIBRARY ||
options.sourceType === cameraConstants.PictureSourceType.SAVEDPHOTOALBUM)) {
var tapTile = new wd.TouchAction();
var swipeRight = new wd.TouchAction();
tapTile
.tap({
x: Math.round(screenWidth / 4),
y: Math.round(screenHeight / 4)
});
swipeRight
.press({x: 10, y: 150})
.wait(300)
.moveTo({x: Math.round(screenWidth - (screenWidth / 8)), y: 0})
.wait(1500)
.release()
.wait(1000);
if (options.allowEdit) {
return driver
// always wait before performing touchAction
.sleep(7000)
.performTouchAction(tapTile);
}
return driver
.waitForElementByXPath('//android.widget.TextView[@text="Gallery"]', 20000)
.elementByXPath('//android.widget.TextView[@text="Gallery"]')
.elementByXPath('//android.widget.TextView[@text="Gallery"]')
.elementByXPath('//android.widget.TextView[@text="Gallery"]')
.elementByXPath('//android.widget.TextView[@text="Gallery"]')
.fail(function () {
return driver
.performTouchAction(swipeRight)
.waitForElementByXPath('//android.widget.TextView[@text="Gallery"]', 20000)
.elementByXPath('//android.widget.TextView[@text="Gallery"]')
.elementByXPath('//android.widget.TextView[@text="Gallery"]')
.elementByXPath('//android.widget.TextView[@text="Gallery"]')
.elementByXPath('//android.widget.TextView[@text="Gallery"]');
})
.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 / 2)
.elementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]')
.elementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]')
.click()
.waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]', MINUTE / 2)
.elementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]')
.elementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]')
.click();
})
.then(function () {
if (skipUiInteractions) {
return;
}
if (options.allowEdit) {
return driver
.waitForElementByXPath('//*[contains(@resource-id,\'save\')]', MINUTE)
.click();
}
})
.fail(function (failure) {
throw failure;
});
}
// checks if the picture was successfully taken
// if shouldLoad is falsy, ensures that the error callback was called
function checkPicture(shouldLoad, options) {
if (!options) {
options = {};
}
return driver
.context(webviewContext)
.setAsyncScriptTimeout(MINUTE / 2)
.executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId(), options])
.then(function (result) {
if (shouldLoad) {
if (result !== 'OK') {
fail(result);
}
} else if (result.indexOf('ERROR') === -1) {
throw 'Unexpected success callback with result: ' + result;
}
});
}
// deletes the latest image from the gallery
function deleteImage() {
var holdTile = new wd.TouchAction();
holdTile.press({x: Math.round(screenWidth / 4), y: Math.round(screenHeight / 5)}).wait(1000).release();
return driver
// always wait before performing touchAction
.sleep(7000)
.performTouchAction(holdTile)
.elementByXPath('//android.widget.TextView[@text="Delete"]')
.then(function (element) {
return element
.click()
.elementByXPath('//android.widget.Button[@text="OK"]')
.click();
}, function () {
// couldn't find Delete menu item. Possibly there is no image.
return driver;
});
}
function getDriver() {
driver = wdHelper.getDriver('Android');
return driver.getWebviewContext()
.then(function(context) {
webviewContext = context;
return driver.context(webviewContext);
})
.waitForDeviceReady()
.injectLibraries()
.deleteFillerImage(fillerImagePath)
.then(function () {
fillerImagePath = null;
})
.addFillerImage()
.then(function (result) {
if (result && result.indexOf('ERROR:') === 0) {
throw new Error(result);
} else {
fillerImagePath = result;
}
});
}
function recreateSession() {
return driver
.quit()
.finally(function () {
return getDriver();
});
}
function tryRunSpec(spec) {
return driver
.then(spec)
.fail(function () {
return recreateSession()
.then(spec)
.fail(function() {
return recreateSession()
.then(spec);
});
})
.fail(saveScreenshotAndFail);
}
// 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) {
if (!appiumSessionStarted) {
fail('Failed to start a session');
done();
}
}
function checkCamera(pending) {
if (!cameraAvailable) {
pending('This test requires camera');
}
}
it('camera.ui.util configuring driver and starting a session', function (done) {
getDriver()
.then(function () {
appiumSessionStarted = true;
}, fail)
.done(done);
}, 10 * MINUTE);
it('camera.ui.util determine screen dimensions', function (done) {
checkSession(done);
driver
.context(webviewContext)
.execute(function () {
return {
'width': screen.availWidth,
'height': screen.availHeight
};
}, [])
.then(function (size) {
screenWidth = Number(size.width);
screenHeight = Number(size.height);
})
.done(done);
}, MINUTE);
it('camera.ui.util determine camera availability', function (done) {
checkSession(done);
var opts = {
sourceType: cameraConstants.PictureSourceType.CAMERA,
saveToPhotoAlbum: false
};
return driver
.then(function () {
return getPicture(opts);
})
.then(function () {
cameraAvailable = true;
}, function () {
return recreateSession();
})
.done(done);
}, 5 * MINUTE);
describe('Specs.', function () {
// getPicture() with saveToPhotoLibrary = true
it('camera.ui.spec.1 Saving a picture to the photo library', function (done) {
checkSession(done);
checkCamera(pending);
var spec = generateSpec({
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
saveToPhotoAlbum: true
});
tryRunSpec(spec)
.then(function () {
isTestPictureSaved = true;
})
.done(done);
}, 10 * MINUTE);
// getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY
it('camera.ui.spec.2 Selecting only videos', function (done) {
checkSession(done);
var spec = function () {
var options = { sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
mediaType: cameraConstants.MediaType.VIDEO };
return driver
.then(function () {
return getPicture(options, true);
})
.context('NATIVE_APP')
.then(function () {
// try to find "Gallery" menu item
// if there's none, the gallery should be already opened
return driver
.waitForElementByXPath('//android.widget.TextView[@text="Gallery"]', 20000)
.elementByXPath('//android.widget.TextView[@text="Gallery"]')
.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)
// give native app some time to close
.sleep(2000)
// try again! because every ~30th build
// on Sauce Labs this backbutton doesn't work
.elementById('action_bar_title')
.then(function () {
// success means we're still in native app
return driver
.deviceKeyEvent(BACK_BUTTON);
}, function () {
// error means we're already in webview
return driver;
});
}, function () {
// error means we're already in webview
return driver;
});
});
};
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
// getPicture(), then dismiss
// wait for the error callback to be called
it('camera.ui.spec.3 Dismissing the camera', function (done) {
checkSession(done);
checkCamera(pending);
var spec = function () {
var options = {
quality: 50,
allowEdit: true,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.FILE_URI
};
return driver
.then(function () {
return getPicture(options, true);
})
.context("NATIVE_APP")
.waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'cancel\')]', MINUTE / 2)
.elementByXPath('//android.widget.ImageView[contains(@resource-id,\'cancel\')]')
.elementByXPath('//android.widget.ImageView[contains(@resource-id,\'cancel\')]')
.click()
.then(function () {
return checkPicture(false);
});
};
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
// getPicture(), then take picture but dismiss the edit
// wait for the error callback to be called
it('camera.ui.spec.4 Dismissing the edit', function (done) {
checkSession(done);
checkCamera(pending);
var spec = function () {
var options = {
quality: 50,
allowEdit: true,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.FILE_URI
};
return driver
.then(function () {
return getPicture(options, true);
})
.context('NATIVE_APP')
.waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]', MINUTE / 2)
.elementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]')
.elementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]')
.click()
.waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]', MINUTE / 2)
.elementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]')
.elementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]')
.click()
.waitForElementByXPath('//*[contains(@resource-id,\'discard\')]', MINUTE / 2)
.elementByXPath('//*[contains(@resource-id,\'discard\')]')
.elementByXPath('//*[contains(@resource-id,\'discard\')]')
.click()
.then(function () {
return checkPicture(false);
});
};
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.5 Verifying target image size, sourceType=CAMERA', function (done) {
checkSession(done);
checkCamera(pending);
var spec = generateSpec({
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
});
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.6 Verifying target image size, sourceType=PHOTOLIBRARY', function (done) {
checkSession(done);
var spec = generateSpec({
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
});
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.7 Verifying target image size, sourceType=CAMERA, DestinationType=NATIVE_URI', function (done) {
checkSession(done);
checkCamera(pending);
var spec = generateSpec({
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.NATIVE_URI,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
});
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.8 Verifying target image size, sourceType=PHOTOLIBRARY, DestinationType=NATIVE_URI', function (done) {
checkSession(done);
var spec = generateSpec({
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
destinationType: cameraConstants.DestinationType.NATIVE_URI,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
});
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.9 Verifying target image size, sourceType=CAMERA, DestinationType=NATIVE_URI, quality=100', function (done) {
checkSession(done);
checkCamera(pending);
var spec = generateSpec({
quality: 100,
allowEdit: true,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.NATIVE_URI,
saveToPhotoAlbum: false,
targetWidth: 305,
targetHeight: 305
});
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.10 Verifying target image size, sourceType=PHOTOLIBRARY, DestinationType=NATIVE_URI, quality=100', function (done) {
checkSession(done);
var spec = generateSpec({
quality: 100,
allowEdit: true,
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
destinationType: cameraConstants.DestinationType.NATIVE_URI,
saveToPhotoAlbum: false,
targetWidth: 305,
targetHeight: 305
});
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
// combine various options for getPicture()
generateOptions().forEach(function (spec) {
it('camera.ui.spec.11.' + spec.id + ' Combining options. ' + spec.description, function (done) {
checkSession(done);
if (spec.options.sourceType == cameraConstants.PictureSourceType.CAMERA) {
checkCamera(pending);
}
var s = generateSpec(spec.options);
tryRunSpec(s).done(done);
}, 10 * MINUTE);
});
it('camera.ui.util Delete filler picture from device library', function (done) {
driver
.context(webviewContext)
.deleteFillerImage(fillerImagePath)
.done(done);
}, MINUTE);
it('camera.ui.util Delete taken picture from device library', function (done) {
checkSession(done);
if (!isTestPictureSaved) {
// couldn't save test picture earlier, so nothing to delete here
done();
return;
}
// delete exactly one latest picture
// this should be the picture we've taken in the first spec
driver
.context('NATIVE_APP')
.deviceKeyEvent(BACK_BUTTON)
.sleep(1000)
.deviceKeyEvent(BACK_BUTTON)
.sleep(1000)
.deviceKeyEvent(BACK_BUTTON)
.elementById('Apps')
.click()
.elementByXPath('//android.widget.TextView[@text="Gallery"]')
.click()
.elementByXPath('//android.widget.TextView[contains(@text,"Pictures")]')
.click()
.then(deleteImage)
.deviceKeyEvent(BACK_BUTTON)
.sleep(1000)
.deviceKeyEvent(BACK_BUTTON)
.sleep(1000)
.deviceKeyEvent(BACK_BUTTON)
.fail(fail)
.finally(done);
}, 3 * MINUTE);
});
it('camera.ui.util Destroy the session', function (done) {
checkSession(done);
driver
.quit()
.done(done);
}, 5 * MINUTE);
});

View File

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

View File

@@ -1,417 +0,0 @@
/*jshint node: true, jasmine: true */
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
// these tests are meant to be executed by Cordova Medic Appium runner
// you can find it here: https://github.com/apache/cordova-medic/
// 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"
'use strict';
var wdHelper = global.WD_HELPER;
var screenshotHelper = global.SCREENSHOT_HELPER;
var isDevice = global.DEVICE;
var cameraConstants = require('../../www/CameraConstants');
var cameraHelper = require('../helpers/cameraHelper');
var MINUTE = 60 * 1000;
var DEFAULT_WEBVIEW_CONTEXT = 'WEBVIEW_1';
var PROMISE_PREFIX = 'appium_camera_promise_';
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;
function getNextPromiseId() {
promiseCount += 1;
return getCurrentPromiseId();
}
function getCurrentPromiseId() {
return PROMISE_PREFIX + promiseCount;
}
function saveScreenshotAndFail(error) {
fail(error);
return screenshotHelper
.saveScreenshot(driver)
.quit()
.then(function () {
return getDriver();
});
}
// generates test specs by combining all the specified options
// you can add more options to test more scenarios
function generateOptions() {
var sourceTypes = cameraConstants.PictureSourceType;
var destinationTypes = cameraConstants.DestinationType;
var encodingTypes = cameraConstants.EncodingType;
var allowEditOptions = [ true, false ];
var correctOrientationOptions = [ true, false ];
return cameraHelper.generateSpecs(sourceTypes, destinationTypes, encodingTypes, allowEditOptions, correctOrientationOptions);
}
function usePicture() {
return driver
.elementByXPath('//*[@label="Use"]')
.click()
.fail(function () {
// For some reason "Choose" element is not clickable by standard Appium methods
return wdHelper.tapElementByXPath('//UIAButton[@label="Choose"]', driver);
});
}
function getPicture(options, cancelCamera, skipUiInteractions) {
var promiseId = getNextPromiseId();
if (!options) {
options = {};
}
return driver
.context(webviewContext)
.execute(cameraHelper.getPicture, [options, promiseId])
.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')
.click()
.then(function () {
if (!options.allowEdit) {
return driver;
}
return usePicture();
});
}
if (options.hasOwnProperty('sourceType') && options.sourceType === cameraConstants.PictureSourceType.SAVEDPHOTOALBUM) {
return driver
.waitForElementByXPath('//UIACollectionCell', MINUTE / 2)
.click()
.then(function () {
if (!options.allowEdit) {
return driver;
}
return usePicture();
});
}
if (cancelCamera) {
return driver
.waitForElementByXPath('//*[@label="Cancel"]', MINUTE / 2)
.elementByXPath('//*[@label="Cancel"]')
.elementByXPath('//*[@label="Cancel"]')
.click();
}
return driver
.waitForElementByXPath('//*[@label="Take Picture"]', MINUTE / 2)
.click()
.waitForElementByXPath('//*[@label="Use Photo"]', MINUTE / 2)
.click();
})
.fail(fail);
}
// checks if the picture was successfully taken
// if shouldLoad is falsy, ensures that the error callback was called
function checkPicture(shouldLoad, options) {
if (!options) {
options = {};
}
return driver
.context(webviewContext)
.setAsyncScriptTimeout(MINUTE / 2)
.executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId(), options])
.then(function (result) {
if (shouldLoad) {
if (result !== 'OK') {
fail(result);
}
} else if (result.indexOf('ERROR') === -1) {
throw 'Unexpected success callback with result: ' + result;
}
});
}
// takes a picture with the specified options
// and then verifies it
function runSpec(options) {
return driver
.then(function () {
return getPicture(options);
})
.then(function () {
return checkPicture(true, options);
})
.fail(saveScreenshotAndFail);
}
function getDriver() {
driver = wdHelper.getDriver('iOS');
return wdHelper.getWebviewContext(driver)
.then(function(context) {
webviewContext = context;
return driver.context(webviewContext);
})
.then(function () {
return wdHelper.waitForDeviceReady(driver);
})
.then(function () {
return wdHelper.injectLibraries(driver);
});
}
function checkSession(done) {
if (failedToStart) {
fail('Failed to start a session');
done();
}
}
it('camera.ui.util configure driver and start a session', function (done) {
getDriver()
.then(function () {
failedToStart = false;
}, fail)
.done(done);
}, 10 * MINUTE);
describe('Specs.', function () {
// getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY
it('camera.ui.spec.1 Selecting only videos', function (done) {
checkSession(done);
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"]')
.click()
.fail(saveScreenshotAndFail)
.done(done);
}, 3 * MINUTE);
// getPicture(), then dismiss
// wait for the error callback to be called
it('camera.ui.spec.2 Dismissing the camera', function (done) {
checkSession(done);
if (!isDevice) {
pending('Camera is not available on iOS simulator');
}
var options = { sourceType: cameraConstants.PictureSourceType.CAMERA,
saveToPhotoAlbum: false };
driver
.then(function () {
return getPicture(options, true);
})
.then(function () {
return checkPicture(false);
})
.fail(saveScreenshotAndFail)
.done(done);
}, 3 * MINUTE);
it('camera.ui.spec.3 Verifying target image size, sourceType=CAMERA', function (done) {
checkSession(done);
if (!isDevice) {
pending('Camera is not available on iOS simulator');
}
var options = {
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
};
runSpec(options).done(done);
}, 3 * MINUTE);
it('camera.ui.spec.4 Verifying target image size, sourceType=SAVEDPHOTOALBUM', function (done) {
checkSession(done);
var options = {
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
};
runSpec(options).done(done);
}, 3 * MINUTE);
it('camera.ui.spec.5 Verifying target image size, sourceType=PHOTOLIBRARY', function (done) {
checkSession(done);
var options = {
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
};
runSpec(options).done(done);
}, 3 * 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');
checkSession(done);
if (!isDevice) {
pending('Camera is not available on iOS simulator');
}
var options = {
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.FILE_URL,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
};
runSpec(options).done(done);
}, 3 * MINUTE);
it('camera.ui.spec.7 Verifying target image size, sourceType=SAVEDPHOTOALBUM, destinationType=FILE_URL', function (done) {
checkSession(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(done);
}, 3 * MINUTE);
it('camera.ui.spec.8 Verifying target image size, sourceType=PHOTOLIBRARY, destinationType=FILE_URL', function (done) {
checkSession(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(done);
}, 3 * 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');
checkSession(done);
if (!isDevice) {
pending('Camera is not available on iOS simulator');
}
var options = {
quality: 100,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.FILE_URL,
saveToPhotoAlbum: false,
targetWidth: 305,
targetHeight: 305
};
runSpec(options).done(done);
}, 3 * MINUTE);
it('camera.ui.spec.10 Verifying target image size, sourceType=SAVEDPHOTOALBUM, destinationType=FILE_URL, quality=100', function (done) {
checkSession(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(done);
}, 3 * MINUTE);
it('camera.ui.spec.11 Verifying target image size, sourceType=PHOTOLIBRARY, destinationType=FILE_URL, quality=100', function (done) {
checkSession(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(done);
}, 3 * MINUTE);
// combine various options for getPicture()
generateOptions().forEach(function (spec) {
it('camera.ui.spec.12.' + spec.id + ' Combining options. ' + spec.description, function (done) {
checkSession(done);
if (!isDevice && spec.options.sourceType === cameraConstants.PictureSourceType.CAMERA) {
pending('Camera is not available on iOS simulator');
}
// remove this check if you don't mind the tests leaving a photo saved on device
if (spec.options.sourceType === cameraConstants.PictureSourceType.CAMERA &&
spec.options.destinationType === cameraConstants.DestinationType.NATIVE_URI) {
pending('Skipping: cannot prevent iOS from saving the picture to photo library and cannot delete it. ' +
'For more info, see iOS quirks here: https://github.com/apache/cordova-plugin-camera#ios-quirks-1');
}
runSpec(spec.options).done(done);
}, 3 * MINUTE);
});
});
it('camera.ui.util Destroy the session', function (done) {
checkSession(done);
driver
.quit()
.done(done);
}, 5 * MINUTE);
});

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "cordova-plugin-camera",
"version": "2.3.0",
"version": "2.0.1-dev",
"description": "Cordova Camera Plugin",
"cordova": {
"id": "cordova-plugin-camera",
@@ -38,25 +38,18 @@
"cordova-browser",
"cordova-windows"
],
"peerDependencies": {
"cordova-plugin-file": ">=2.0.0"
},
"scripts": {
"precommit": "npm run gen-docs && git add README.md",
"gen-docs": "jsdoc2md --template \"jsdoc2md/TEMPLATE.md\" \"www/**/*.js\" --plugin \"dmd-plugin-cordova-plugin\" > README.md",
"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"
"gen-docs": "jsdoc2md --template \"jsdoc2md/TEMPLATE.md\" \"www/**/*.js\" --plugin \"dmd-plugin-cordova-plugin\" > README.md"
},
"author": "Apache Software Foundation",
"license": "Apache-2.0",
"engines": {
"cordovaDependencies": {
"3.0.0": {
"cordova": ">100"
}
}
},
"license": "Apache 2.0",
"devDependencies": {
"dmd-plugin-cordova-plugin": "^0.1.0",
"husky": "^0.10.1",
"jsdoc-to-markdown": "^1.2.0",
"jshint": "^2.6.0"
"jsdoc-to-markdown": "^1.2.0"
}
}

View File

@@ -22,7 +22,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:rim="http://www.blackberry.com/ns/widgets"
id="cordova-plugin-camera"
version="2.3.0">
version="2.0.1-dev">
<name>Camera</name>
<description>Cordova Camera Plugin</description>
<license>Apache 2.0</license>
@@ -30,8 +30,6 @@
<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" />
<js-module src="www/CameraConstants.js" name="Camera">
<clobbers target="Camera" />
</js-module>
@@ -78,6 +76,8 @@
<clobbers target="CameraPopoverHandle" />
</js-module>
<framework src="com.android.support:support-v4:23+" />
</platform>
<!-- amazon-fireos -->
@@ -150,16 +150,6 @@
<framework src="CoreGraphics.framework" />
<framework src="AVFoundation.framework" />
<preference name="CAMERA_USAGE_DESCRIPTION" default=" " />
<config-file target="*-Info.plist" parent="NSCameraUsageDescription">
<string>$CAMERA_USAGE_DESCRIPTION</string>
</config-file>
<preference name="PHOTOLIBRARY_USAGE_DESCRIPTION" default=" " />
<config-file target="*-Info.plist" parent="NSPhotoLibraryUsageDescription">
<string>$PHOTOLIBRARY_USAGE_DESCRIPTION</string>
</config-file>
<config-file target="*-Info.plist" parent="NSLocationWhenInUseUsageDescription">
<string></string>
</config-file>

View File

@@ -26,42 +26,41 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaResourceApi;
import org.apache.cordova.LOG;
import org.apache.cordova.PermissionHelper;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.media.MediaScannerConnection;
import android.media.MediaScannerConnection.MediaScannerConnectionClient;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.util.Base64;
import android.util.Log;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
/**
* This class launches the camera view, allows the user to take a picture, closes the camera view,
* and returns the captured image. When the camera view is closed, the screen displayed before
@@ -109,7 +108,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
private boolean orientationCorrected; // Has the picture's orientation been corrected
private boolean allowEdit; // Should we allow the user to crop the image.
protected final static String[] permissions = { Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE };
protected final static String[] permissions = { Manifest.permission.READ_EXTERNAL_STORAGE };
public CallbackContext callbackContext;
private int numPics;
@@ -117,9 +116,15 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
private MediaScannerConnection conn; // Used to update gallery app with newly-written files
private Uri scanMe; // Uri of image to be added to content store
private Uri croppedUri;
private ExifHelper exifData; // Exif data from source
protected void getReadPermission(int requestCode)
{
ActivityCompat.requestPermissions(cordova.getActivity(),
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
requestCode);
}
/**
* Executes the request and returns PluginResult.
*
@@ -139,7 +144,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
this.targetWidth = 0;
this.encodingType = JPEG;
this.mediaType = PICTURE;
this.mQuality = 50;
this.mQuality = 80;
//Take the values from the arguments if they're not already defined (this is tricky)
this.destType = args.getInt(1);
@@ -169,14 +174,17 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
this.encodingType = JPEG;
}
try {
try {
if (this.srcType == CAMERA) {
this.callTakePicture(destType, encodingType);
}
else if ((this.srcType == PHOTOLIBRARY) || (this.srcType == SAVEDPHOTOALBUM)) {
// FIXME: Stop always requesting the permission
if(!PermissionHelper.hasPermission(this, permissions[0])) {
PermissionHelper.requestPermission(this, SAVE_TO_ALBUM_SEC, Manifest.permission.READ_EXTERNAL_STORAGE);
// Any options that edit the file require READ permissions in order to try and
// preserve the original exif data and filename in the modified file that is
// created
if(this.mediaType == PICTURE && (this.destType == FILE_URI || this.destType == NATIVE_URI)
&& fileWillBeModified() && !(ContextCompat.checkSelfPermission(cordova.getActivity(), permissions[0]) == PackageManager.PERMISSION_GRANTED)) {
getReadPermission(SAVE_TO_ALBUM_SEC);
} else {
this.getImage(this.srcType, destType, encodingType);
}
@@ -231,47 +239,18 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
* or to display URI in an img tag
* img.src=result;
*
* @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
* @param returnType Set the type of image to return.
* @param encodingType JPEG or PNG
*/
public void callTakePicture(int returnType, int encodingType) {
boolean saveAlbumPermission = PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
boolean takePicturePermission = PermissionHelper.hasPermission(this, Manifest.permission.CAMERA);
// CB-10120: The CAMERA permission does not need to be requested unless it is declared
// in AndroidManifest.xml. This plugin does not declare it, but others may and so we must
// check the package info to determine if the permission is present.
if (!takePicturePermission) {
takePicturePermission = true;
try {
PackageManager packageManager = this.cordova.getActivity().getPackageManager();
String[] permissionsInPackage = packageManager.getPackageInfo(this.cordova.getActivity().getPackageName(), PackageManager.GET_PERMISSIONS).requestedPermissions;
if (permissionsInPackage != null) {
for (String permission : permissionsInPackage) {
if (permission.equals(Manifest.permission.CAMERA)) {
takePicturePermission = false;
break;
}
}
}
} catch (NameNotFoundException e) {
// We are requesting the info for our package, so this should
// never be caught
}
}
if (takePicturePermission && saveAlbumPermission) {
if (ContextCompat.checkSelfPermission(cordova.getActivity(), permissions[0]) == PackageManager.PERMISSION_GRANTED) {
takePicture(returnType, encodingType);
} else if (saveAlbumPermission && !takePicturePermission) {
PermissionHelper.requestPermission(this, TAKE_PIC_SEC, Manifest.permission.CAMERA);
} else if (!saveAlbumPermission && takePicturePermission) {
PermissionHelper.requestPermission(this, TAKE_PIC_SEC, Manifest.permission.READ_EXTERNAL_STORAGE);
} else {
PermissionHelper.requestPermissions(this, TAKE_PIC_SEC, permissions);
getReadPermission(TAKE_PIC_SEC);
}
}
public void takePicture(int returnType, int encodingType)
{
// Save the number of images currently on disk for later
@@ -340,6 +319,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
/**
* Get image from photo library.
*
* @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
* @param srcType The album to get image from.
* @param returnType Set the type of image to return.
* @param encodingType
@@ -365,7 +345,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
}
File photo = createCaptureFile(JPEG);
File photo = createCaptureFile(encodingType);
croppedUri = Uri.fromFile(photo);
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, croppedUri);
} else {
@@ -373,17 +353,17 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
intent.addCategory(Intent.CATEGORY_OPENABLE);
}
} else if (this.mediaType == VIDEO) {
intent.setType("video/*");
title = GET_VIDEO;
intent.setAction(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("video/*");
title = GET_VIDEO;
intent.setAction(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
} else if (this.mediaType == ALLMEDIA) {
// I wanted to make the type 'image/*, video/*' but this does not work on all versions
// of android so I had to go with the wildcard search.
intent.setType("*/*");
title = GET_All;
intent.setAction(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
// I wanted to make the type 'image/*, video/*' but this does not work on all versions
// of android so I had to go with the wildcard search.
intent.setType("*/*");
title = GET_All;
intent.setAction(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
}
if (this.cordova != null) {
this.cordova.startActivityForResult((CordovaPlugin) this, Intent.createChooser(intent,
@@ -391,7 +371,6 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
}
/**
* Brings up the UI to perform crop on passed image URI
*
@@ -427,14 +406,14 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
cropIntent, CROP_CAMERA + destType);
}
} catch (ActivityNotFoundException anfe) {
LOG.e(LOG_TAG, "Crop operation not supported on this device");
Log.e(LOG_TAG, "Crop operation not supported on this device");
try {
processResultFromCamera(destType, cameraIntent);
}
catch (IOException e)
{
e.printStackTrace();
LOG.e(LOG_TAG, "Unable to write to file");
Log.e(LOG_TAG, "Unable to write to file");
}
}
}
@@ -473,7 +452,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// in the gallery and the modified image is saved in the temporary
// directory
if (this.saveToPhotoAlbum) {
galleryUri = Uri.fromFile(new File(getPicturesPath()));
galleryUri = Uri.fromFile(new File(getPicutresPath()));
if(this.allowEdit && this.croppedUri != null) {
writeUncompressedImage(this.croppedUri, galleryUri);
@@ -486,7 +465,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// If sending base64 image back
if (destType == DATA_URL) {
bitmap = getScaledAndRotatedBitmap(sourcePath);
bitmap = getScaledBitmap(sourcePath);
if (bitmap == null) {
// Try to get the bitmap from intent.
@@ -495,11 +474,14 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// Double-check the bitmap.
if (bitmap == null) {
LOG.d(LOG_TAG, "I either have a null image path or bitmap");
Log.d(LOG_TAG, "I either have a null image path or bitmap");
this.failPicture("Unable to create bitmap!");
return;
}
if (rotate != 0 && this.correctOrientation) {
bitmap = getRotatedBitmap(rotate, bitmap, exif);
}
this.processPicture(bitmap, this.encodingType);
@@ -531,15 +513,18 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
} else {
Uri uri = Uri.fromFile(createCaptureFile(this.encodingType, System.currentTimeMillis() + ""));
bitmap = getScaledAndRotatedBitmap(sourcePath);
bitmap = getScaledBitmap(sourcePath);
// Double-check the bitmap.
if (bitmap == null) {
LOG.d(LOG_TAG, "I either have a null image path or bitmap");
Log.d(LOG_TAG, "I either have a null image path or bitmap");
this.failPicture("Unable to create bitmap!");
return;
}
if (rotate != 0 && this.correctOrientation) {
bitmap = getRotatedBitmap(rotate, bitmap, exif);
}
// Add compressed version of captured image to returned media store Uri
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
@@ -570,40 +555,33 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
bitmap = null;
}
private String getPicturesPath()
{
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? ".jpg" : ".png");
File storageDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
String galleryPath = storageDir.getAbsolutePath() + "/" + imageFileName;
return galleryPath;
}
private String getPicutresPath()
{
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? ".jpg" : ".png");
File storageDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
String galleryPath = storageDir.getAbsolutePath() + "/" + imageFileName;
return galleryPath;
}
private void refreshGallery(Uri contentUri)
{
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
mediaScanIntent.setData(contentUri);
this.cordova.getActivity().sendBroadcast(mediaScanIntent);
}
/**
* Converts output image format int value to string value of mime type.
* @param outputFormat int Output format of camera API.
* Must be value of either JPEG or PNG constant
* @return String String value of mime type or empty string if mime type is not supported
*/
private String getMimetypeForFormat(int outputFormat) {
if (outputFormat == PNG) return "image/png";
if (outputFormat == JPEG) return "image/jpeg";
return "";
}
private void refreshGallery(Uri contentUri)
{
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
mediaScanIntent.setData(contentUri);
this.cordova.getActivity().sendBroadcast(mediaScanIntent);
}
private String outputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
// Some content: URIs do not map to file paths (e.g. picasa).
String realPath = FileHelper.getRealPath(uri, this.cordova);
// Get filename from uri
String fileName = realPath != null ?
realPath.substring(realPath.lastIndexOf('/') + 1) :
"modified." + (this.encodingType == JPEG ? "jpg" : "png");
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String fileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? ".jpg" : ".png");
String modifiedPath = getTempDirectoryPath() + "/" + fileName;
OutputStream os = new FileOutputStream(modifiedPath);
@@ -614,14 +592,18 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
bitmap.compress(compressFormat, this.mQuality, os);
os.close();
if (exifData != null && this.encodingType == JPEG) {
if (realPath != null && this.encodingType == JPEG) {
// Create an ExifHelper to save the exif data that is lost during compression
ExifHelper exif = new ExifHelper();
try {
exif.createInFile(realPath);
exif.readExifData();
if (this.correctOrientation && this.orientationCorrected) {
exifData.resetOrientation();
exif.resetOrientation();
}
exifData.createOutFile(modifiedPath);
exifData.writeExifData();
exifData = null;
exif.createOutFile(modifiedPath);
exif.writeExifData();
} catch (IOException e) {
e.printStackTrace();
}
@@ -631,7 +613,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
/**
/**
* Applies all needed transformation to the image received from the gallery.
*
* @param destType In which form should we return the image
@@ -650,7 +632,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
int rotate = 0;
String fileLocation = FileHelper.getRealPath(uri, this.cordova);
LOG.d(LOG_TAG, "File locaton is: " + fileLocation);
Log.d(LOG_TAG, "File locaton is: " + fileLocation);
// If you ask for video or all media type you will automatically get back a file URI
// and there will be no attempt to resize any returned data
@@ -658,36 +640,47 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
this.callbackContext.success(fileLocation);
}
else {
String uriString = uri.toString();
// Get the path to the image. Makes loading so much easier.
String mimeType = FileHelper.getMimeType(uriString, this.cordova);
// This is a special case to just return the path as no scaling,
// rotating, nor compressing needs to be done
if (this.targetHeight == -1 && this.targetWidth == -1 &&
(destType == FILE_URI || destType == NATIVE_URI) && !this.correctOrientation &&
mimeType.equalsIgnoreCase(getMimetypeForFormat(encodingType)))
{
this.callbackContext.success(uriString);
(destType == FILE_URI || destType == NATIVE_URI) && !this.correctOrientation) {
this.callbackContext.success(uri.toString());
} else {
String uriString = uri.toString();
// Get the path to the image. Makes loading so much easier.
String mimeType = FileHelper.getMimeType(uriString, this.cordova);
// If we don't have a valid image so quit.
if (!("image/jpeg".equalsIgnoreCase(mimeType) || "image/png".equalsIgnoreCase(mimeType))) {
LOG.d(LOG_TAG, "I either have a null image path or bitmap");
Log.d(LOG_TAG, "I either have a null image path or bitmap");
this.failPicture("Unable to retrieve path to picture!");
return;
}
Bitmap bitmap = null;
try {
bitmap = getScaledAndRotatedBitmap(uriString);
bitmap = getScaledBitmap(uriString);
} catch (IOException e) {
e.printStackTrace();
}
if (bitmap == null) {
LOG.d(LOG_TAG, "I either have a null image path or bitmap");
Log.d(LOG_TAG, "I either have a null image path or bitmap");
this.failPicture("Unable to create bitmap!");
return;
}
if (this.correctOrientation) {
rotate = getImageOrientation(uri);
if (rotate != 0) {
Matrix matrix = new Matrix();
matrix.setRotate(rotate);
try {
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
this.orientationCorrected = true;
} catch (OutOfMemoryError oom) {
this.orientationCorrected = false;
}
}
}
// If sending base64 image back
if (destType == DATA_URL) {
this.processPicture(bitmap, this.encodingType);
@@ -697,11 +690,9 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
else if (destType == FILE_URI || destType == NATIVE_URI) {
// Did we modify the image?
if ( (this.targetHeight > 0 && this.targetWidth > 0) ||
(this.correctOrientation && this.orientationCorrected) ||
!mimeType.equalsIgnoreCase(getMimetypeForFormat(encodingType)))
{
(this.correctOrientation && this.orientationCorrected) ) {
try {
String modifiedPath = this.outputModifiedBitmap(bitmap, uri);
String modifiedPath = this.ouputModifiedBitmap(bitmap, uri);
// The modified image is cached by the app in order to get around this and not have to delete you
// application cache I'm adding the current system time to the end of the file url.
this.callbackContext.success("file://" + modifiedPath + "?" + System.currentTimeMillis());
@@ -749,7 +740,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
processResultFromCamera(destType, intent);
} catch (IOException e) {
e.printStackTrace();
LOG.e(LOG_TAG, "Unable to write to file");
Log.e(LOG_TAG, "Unable to write to file");
}
}// If cancelled
@@ -811,30 +802,69 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
}
private int exifToDegrees(int exifOrientation) {
if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_90) {
return 90;
} else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_180) {
return 180;
} else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_270) {
return 270;
} else {
return 0;
private int getImageOrientation(Uri uri) {
int rotate = 0;
String[] cols = { MediaStore.Images.Media.ORIENTATION };
try {
Cursor cursor = cordova.getActivity().getContentResolver().query(uri,
cols, null, null, null);
if (cursor != null) {
cursor.moveToPosition(0);
rotate = cursor.getInt(0);
cursor.close();
}
} catch (Exception e) {
// You can get an IllegalArgumentException if ContentProvider doesn't support querying for orientation.
}
return rotate;
}
/**
* Write an inputstream to local disk
* Figure out if the bitmap should be rotated. For instance if the picture was taken in
* portrait mode
*
* @param fis - The InputStream to write
* @param dest - Destination on disk to write to
* @param rotate
* @param bitmap
* @return rotated bitmap
*/
private Bitmap getRotatedBitmap(int rotate, Bitmap bitmap, ExifHelper exif) {
Matrix matrix = new Matrix();
if (rotate == 180) {
matrix.setRotate(rotate);
} else {
matrix.setRotate(rotate, (float) bitmap.getWidth() / 2, (float) bitmap.getHeight() / 2);
}
try
{
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
exif.resetOrientation();
}
catch (OutOfMemoryError oom)
{
// You can run out of memory if the image is very large:
// http://simonmacdonald.blogspot.ca/2012/07/change-to-camera-code-in-phonegap-190.html
// If this happens, simply do not rotate the image and return it unmodified.
// If you do not catch the OutOfMemoryError, the Android app crashes.
}
return bitmap;
}
/**
* In the special case where the default width, height and quality are unchanged
* we just write the file out to disk saving the expensive Bitmap.compress function.
*
* @param uri
* @throws FileNotFoundException
* @throws IOException
*/
private void writeUncompressedImage(InputStream fis, Uri dest) throws FileNotFoundException,
private void writeUncompressedImage(Uri src, Uri dest) throws FileNotFoundException,
IOException {
FileInputStream fis = null;
OutputStream os = null;
try {
fis = new FileInputStream(FileHelper.stripFileProtocol(src.toString()));
os = this.cordova.getActivity().getContentResolver().openOutputStream(dest);
byte[] buffer = new byte[4096];
int len;
@@ -859,21 +889,6 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
}
}
/**
* In the special case where the default width, height and quality are unchanged
* we just write the file out to disk saving the expensive Bitmap.compress function.
*
* @param src
* @throws FileNotFoundException
* @throws IOException
*/
private void writeUncompressedImage(Uri src, Uri dest) throws FileNotFoundException,
IOException {
FileInputStream fis = new FileInputStream(FileHelper.stripFileProtocol(src.toString()));
writeUncompressedImage(fis, dest);
}
/**
* Create entry in media store for image
@@ -899,15 +914,15 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
/**
* Return a scaled and rotated bitmap based on the target width and height
* Return a scaled bitmap based on the target width and height
*
* @param imageUrl
* @param imagePath
* @return
* @throws IOException
*/
private Bitmap getScaledAndRotatedBitmap(String imageUrl) throws IOException {
// If no new width or height were specified, and orientation is not needed return the original bitmap
if (this.targetWidth <= 0 && this.targetHeight <= 0 && !(this.correctOrientation)) {
private Bitmap getScaledBitmap(String imageUrl) throws IOException {
// If no new width or height were specified return the original bitmap
if (this.targetWidth <= 0 && this.targetHeight <= 0) {
InputStream fileStream = null;
Bitmap image = null;
try {
@@ -925,147 +940,53 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
return image;
}
/* Copy the inputstream to a temporary file on the device.
We then use this temporary file to determine the width/height/orientation.
This is the only way to determine the orientation of the photo coming from 3rd party providers (Google Drive, Dropbox,etc)
This also ensures we create a scaled bitmap with the correct orientation
We delete the temporary file once we are done
*/
File localFile = null;
Uri galleryUri = null;
int rotate = 0;
// figure out the original width and height of the image
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
InputStream fileStream = null;
try {
InputStream fileStream = FileHelper.getInputStreamFromUriString(imageUrl, cordova);
fileStream = FileHelper.getInputStreamFromUriString(imageUrl, cordova);
BitmapFactory.decodeStream(fileStream, null, options);
} finally {
if (fileStream != null) {
// Generate a temporary file
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String fileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? ".jpg" : ".png");
localFile = new File(getTempDirectoryPath() + fileName);
galleryUri = Uri.fromFile(localFile);
writeUncompressedImage(fileStream, galleryUri);
try {
String mimeType = FileHelper.getMimeType(imageUrl.toString(), cordova);
if ("image/jpeg".equalsIgnoreCase(mimeType)) {
// ExifInterface doesn't like the file:// prefix
String filePath = galleryUri.toString().replace("file://", "");
// read exifData of source
exifData = new ExifHelper();
exifData.createInFile(filePath);
// Use ExifInterface to pull rotation information
if (this.correctOrientation) {
ExifInterface exif = new ExifInterface(filePath);
rotate = exifToDegrees(exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED));
}
}
} catch (Exception oe) {
LOG.w(LOG_TAG,"Unable to read Exif data: "+ oe.toString());
rotate = 0;
fileStream.close();
} catch (IOException e) {
LOG.d(LOG_TAG,"Exception while closing file input stream.");
}
}
}
catch (Exception e)
//CB-2292: WTF? Why is the width null?
if(options.outWidth == 0 || options.outHeight == 0)
{
LOG.e(LOG_TAG,"Exception while getting input stream: "+ e.toString());
return null;
}
// determine the correct aspect ratio
int[] widthHeight = calculateAspectRatio(options.outWidth, options.outHeight);
// Load in the smallest bitmap possible that is closest to the size we want
options.inJustDecodeBounds = false;
options.inSampleSize = calculateSampleSize(options.outWidth, options.outHeight, this.targetWidth, this.targetHeight);
Bitmap unscaledBitmap = null;
try {
// figure out the original width and height of the image
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
InputStream fileStream = null;
try {
fileStream = FileHelper.getInputStreamFromUriString(galleryUri.toString(), cordova);
BitmapFactory.decodeStream(fileStream, null, options);
} finally {
if (fileStream != null) {
try {
fileStream.close();
} catch (IOException e) {
LOG.d(LOG_TAG, "Exception while closing file input stream.");
}
}
}
//CB-2292: WTF? Why is the width null?
if (options.outWidth == 0 || options.outHeight == 0) {
return null;
}
// User didn't specify output dimensions, but they need orientation
if (this.targetWidth <= 0 && this.targetHeight <= 0) {
this.targetWidth = options.outWidth;
this.targetHeight = options.outHeight;
}
// Setup target width/height based on orientation
int rotatedWidth, rotatedHeight;
boolean rotated= false;
if (rotate == 90 || rotate == 270) {
rotatedWidth = options.outHeight;
rotatedHeight = options.outWidth;
rotated = true;
} else {
rotatedWidth = options.outWidth;
rotatedHeight = options.outHeight;
}
// determine the correct aspect ratio
int[] widthHeight = calculateAspectRatio(rotatedWidth, rotatedHeight);
// Load in the smallest bitmap possible that is closest to the size we want
options.inJustDecodeBounds = false;
options.inSampleSize = calculateSampleSize(rotatedWidth, rotatedHeight, widthHeight[0], widthHeight[1]);
Bitmap unscaledBitmap = null;
try {
fileStream = FileHelper.getInputStreamFromUriString(galleryUri.toString(), cordova);
unscaledBitmap = BitmapFactory.decodeStream(fileStream, null, options);
} finally {
if (fileStream != null) {
try {
fileStream.close();
} catch (IOException e) {
LOG.d(LOG_TAG, "Exception while closing file input stream.");
}
}
}
if (unscaledBitmap == null) {
return null;
}
int scaledWidth = (!rotated) ? widthHeight[0] : widthHeight[1];
int scaledHeight = (!rotated) ? widthHeight[1] : widthHeight[0];
Bitmap scaledBitmap = Bitmap.createScaledBitmap(unscaledBitmap, scaledWidth, scaledHeight, true);
if (scaledBitmap != unscaledBitmap) {
unscaledBitmap.recycle();
unscaledBitmap = null;
}
if (this.correctOrientation && (rotate != 0)) {
Matrix matrix = new Matrix();
matrix.setRotate(rotate);
fileStream = FileHelper.getInputStreamFromUriString(imageUrl, cordova);
unscaledBitmap = BitmapFactory.decodeStream(fileStream, null, options);
} finally {
if (fileStream != null) {
try {
scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true);
this.orientationCorrected = true;
} catch (OutOfMemoryError oom) {
this.orientationCorrected = false;
fileStream.close();
} catch (IOException e) {
LOG.d(LOG_TAG,"Exception while closing file input stream.");
}
}
return scaledBitmap;
}
finally {
// delete the temporary copy
if (localFile != null) {
localFile.delete();
}
if (unscaledBitmap == null) {
return null;
}
return Bitmap.createScaledBitmap(unscaledBitmap, widthHeight[0], widthHeight[1], true);
}
/**
@@ -1086,11 +1007,11 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
// Only the width was specified
else if (newWidth > 0 && newHeight <= 0) {
newHeight = (int)((double)(newWidth / (double)origWidth) * origHeight);
newHeight = (newWidth * origHeight) / origWidth;
}
// only the height was specified
else if (newWidth <= 0 && newHeight > 0) {
newWidth = (int)((double)(newHeight / (double)origHeight) * origWidth);
newWidth = (newHeight * origWidth) / origHeight;
}
// If the user specified both a positive width and height
// (potentially different aspect ratio) then the width or height is
@@ -1134,7 +1055,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
} else {
return srcHeight / dstHeight;
}
}
}
/**
* Creates a cursor that can be used to determine how many images we have.
@@ -1294,6 +1215,11 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
}
private boolean fileWillBeModified() {
return (this.targetWidth > 0 && this.targetHeight > 0) ||
this.correctOrientation || this.allowEdit;
}
/**
* Taking or choosing a picture launches another Activity, so we need to implement the
* save/restore APIs to handle the case where the CordovaActivity is killed by the OS
@@ -1347,4 +1273,4 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
this.callbackContext = callbackContext;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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