Compare commits

...

43 Commits

Author SHA1 Message Date
Steve Gill
d124e03cb9 Updated version and RELEASENOTES.md for release 2.2.0 2016-04-15 13:08:26 -07:00
Omar Mefire
4202fff7ac CB-10873
- Avoid crash due to usage of uninitialized variable when writing geolocation data to image destination
    - Properly handle 'CameraUsesGeolocation' option by properly setting geolocation data in EXIF header in all cases

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

Rebased by MatthewBooth and riknoll

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

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

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

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

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

16
.jshintrc Normal file
View File

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

1
.ratignore Normal file
View File

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

8
.travis.yml Normal file
View File

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

View File

@@ -17,6 +17,8 @@
# under the License.
-->
[![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
@@ -97,11 +99,11 @@ Documentation consists of template and API docs produced from the plugin JS code
### 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
@@ -115,7 +117,7 @@ The return value is sent to the [`cameraSuccess`](#module_camera.onSuccess) call
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).
@@ -136,11 +138,17 @@ than `DATA_URL`.
__Supported Platforms__
![](doc/img/android-success.png) ![](doc/img/blackberry-success.png) ![](doc/img/browser-success.png) ![](doc/img/firefox-success.png) ![](doc/img/fireos-success.png) ![](doc/img/ios-success.png) ![](doc/img/windows-success.png) ![](doc/img/wp8-success.png) ![](doc/img/ubuntu-success.png)
- Android
- BlackBerry
- Browser
- Firefox
- FireOS
- iOS
- Windows
- WP8
- Ubuntu
* [More examples](#camera-getPicture-examples)
* [Quirks](#camera-getPicture-quirks)
More examples [here](#camera-getPicture-examples). Quirks [here](#camera-getPicture-quirks).
**Kind**: static method of <code>[camera](#module_camera)</code>
@@ -163,7 +171,7 @@ after calling [`camera.getPicture`](#module_camera.getPicture). Applies only whe
__Supported Platforms__
![](doc/img/android-fail.png) ![](doc/img/blackberry-fail.png) ![](doc/img/browser-fail.png) ![](doc/img/firefox-fail.png) ![](doc/img/fireos-fail.png) ![](doc/img/ios-success.png) ![](doc/img/windows-fail.png) ![](doc/img/wp8-fail.png) ![](doc/img/ubuntu-fail.png)
- iOS
**Kind**: static method of <code>[camera](#module_camera)</code>
**Example**
@@ -241,7 +249,7 @@ Optional parameters to customize the camera settings.
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| DATA_URL | <code>number</code> | <code>0</code> | Return base64 encoded string |
| DATA_URL | <code>number</code> | <code>0</code> | Return base64 encoded string. DATA_URL can be very memory intensive and cause app crashes or out of memory errors. Use FILE_URI or NATIVE_URI if possible |
| FILE_URI | <code>number</code> | <code>1</code> | Return file uri (content://media/external/images/media/2 for Android) |
| NATIVE_URI | <code>number</code> | <code>2</code> | Return native uri (eg. asset-library://... for iOS) |
@@ -306,13 +314,7 @@ Matches iOS UIPopoverArrowDirection constants to specify arrow location on popov
<a name="module_CameraPopoverOptions"></a>
## CameraPopoverOptions
iOS-only parameters that specify the anchor element location and arrow
direction of the popover when selecting images from an iPad's library
or album.
Note that the size of the popover may change to adjust to the
direction of the arrow and orientation of the screen. Make sure to
account for orientation changes when specifying the anchor element
location.
iOS-only parameters that specify the anchor element location and arrow
direction of the popover when selecting images from an iPad's library
or album.
Note that the size of the popover may change to adjust to the
@@ -331,12 +333,12 @@ A handle to an image picker popover.
---
![](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)
<a name="module_CameraPopoverHandle"></a>
## CameraPopoverHandle
A handle to an image picker popover.
__Supported Platforms__
{
- iOS
**Example**
@@ -355,21 +357,6 @@ window.onorientationchange = function() {
}
```
---
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);
}
## `camera.getPicture` Errata
@@ -384,6 +371,27 @@ Take a photo and retrieve the image's file location:
function onSuccess(imageURI) {
var image = document.getElementById('myImage');
image.src = imageURI;
}
function onFail(message) {
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) {
@@ -394,14 +402,14 @@ Take a photo and retrieve the image's file location:
- __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.
scenario, the image may not appear when the cordova activity is restored.
<preference name="CameraUsesGeolocation" value="false" />
#### Amazon Fire OS Quirks <a name="camera-getPicture-quirks"></a>
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.
See [the Android Lifecycle guide](http://cordova.apache.org/docs/en/dev/guide/platforms/android/lifecycle.html)
#### Android Quirks
Android uses intents to launch the camera activity on the device to capture
@@ -409,11 +417,11 @@ successful.
scenario, the result from the plugin call will be delivered via the resume event.
See [the Android Lifecycle guide][android_lifecycle]
for more information. The `pendingResult.result` value will contain the value that
Can only return photos as base64-encoded image.
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
successful.
Camera plugin is currently implemented using [Web Activities](https://hacks.mozilla.org/2013/01/introducing-web-activities/).
#### Browser Quirks
Can only return photos as Base64-encoded image.
@@ -510,7 +518,11 @@ Tizen only supports a `destinationType` of
#### Windows Phone 7 and 8 Quirks
- 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 `allowEdit` parameter.
- Ignores the `correctOrientation` parameter.
- 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].

View File

@@ -20,6 +20,50 @@
-->
# Release Notes
### 2.2.0 (Apr 15, 2016)
* CB-10873 Avoid crash due to usage of uninitialized variable when writing geolocation data to image destination. Properly handle 'CameraUsesGeolocation' option by properly setting geolocation data in EXIF header in all cases
* CB-11073 Appium tests stability improvements
* Replace `PermissionHelper.java` with `cordova-plugin-compat`
* Making focus handler work only for **windows 10** phone
* CB-10865 Run **ios** native tests on **Travis**
* CB-10120 Fixing use of constants and `PermissionHelper`
* CB-10120 Fix missing CAMERA permission for **Android M**
* CB-10756 Adding sterner warnings about `DATA_URL`
* CB-10460 `getRealPath` return null in some cases
### 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 **Android** Adding reflective helper methods for permission requests
* CB-9189 **Android** Implementing `save/restore` API to handle Activity destruction
* CB-10241 App Crash cause by Camera Plugin **iOS 7**
* CB-8940 Setting `z-index` values to maximum for UI buttons.
### 2.0.0 (Nov 18, 2015)
* [CB-10035](https://issues.apache.org/jira/browse/CB-10035) Updated `RELEASENOTES` to be newest to oldest
* [CB-8863](https://issues.apache.org/jira/browse/CB-8863) correct block usage for `async` calls

View File

@@ -0,0 +1,411 @@
/*jshint node: true, jasmine: true */
/* global navigator, Q */
/*
*
* 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;
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 generateSpecs() {
var sourceTypes = [
cameraConstants.PictureSourceType.CAMERA,
cameraConstants.PictureSourceType.PHOTOLIBRARY
],
destinationTypes = cameraConstants.DestinationType,
encodingTypes = [
cameraConstants.EncodingType.JPEG,
cameraConstants.EncodingType.PNG
],
allowEditOptions = [
true,
false
];
return cameraHelper.generateSpecs(sourceTypes, destinationTypes, encodingTypes, allowEditOptions);
}
// 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 / 5)});
swipeRight.press({x: 10, y: 100})
.wait(300)
.moveTo({x: Math.round(screenWidth / 2), y: 100})
.release();
return driver
.elementByXPath('//android.widget.TextView[@text="Gallery"]')
.fail(function () {
return driver
.performTouchAction(swipeRight)
.elementByXPath('//android.widget.TextView[@text="Gallery"]');
})
.then(function (element) {
return element
.click()
// we need to sleep here to give a sidebar some time to close
// if we don't sleep here, sometimes we would click on a sidebar
// in the next step
.sleep(3000);
}, function () {
// the gallery is already opened, just go on:
return driver;
})
.performTouchAction(tapTile);
}
// taking a picture from camera
return driver
.waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]', MINUTE)
.click()
.waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]', MINUTE)
.click();
})
.then(function () {
if (skipUiInteractions) {
return;
}
if (options.allowEdit) {
return driver
.waitForElementByXPath('//*[contains(@resource-id,\'save\')]', MINUTE)
.click();
}
})
.fail(fail);
}
// checks if the picture was successfully taken
// if shouldLoad is falsy, ensures that the error callback was called
function checkPicture(shouldLoad) {
return driver
.context(webviewContext)
.setAsyncScriptTimeout(MINUTE)
.executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId()])
.then(function (result) {
if (shouldLoad) {
expect(result.length).toBeGreaterThan(0);
if (result.indexOf('ERROR') >= 0) {
return fail(result);
}
} else {
if (result.indexOf('ERROR') === -1) {
return fail('Unexpected success callback with result: ' + result);
}
expect(result.indexOf('ERROR')).toBe(0);
}
});
}
function runCombinedSpec(spec) {
return driver
.then(function () {
return getPicture(spec.options);
})
.then(function () {
return checkPicture(true);
})
.fail(saveScreenshotAndFail);
}
// deletes the latest image from the gallery
function deleteImage() {
var holdTile = new wd.TouchAction();
holdTile.press({x: Math.round(screenWidth / 4), y: Math.round(screenHeight / 5)}).wait(1000).release();
return driver
.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 wdHelper.getWebviewContext(driver)
.then(function(context) {
webviewContext = context;
return driver.context(webviewContext);
})
.then(function () {
return wdHelper.waitForDeviceReady(driver);
})
.then(function () {
return wdHelper.injectLibraries(driver);
});
}
it('camera.ui.util configuring driver and starting a session', function (done) {
getDriver()
.fail(fail)
.done(done);
}, 5 * MINUTE);
it('camera.ui.util determine screen dimensions', function (done) {
return driver
.context(webviewContext)
.execute(function () {
return {
'width': window.innerWidth,
'height': window.innerHeight
};
}, [])
.then(function (size) {
screenWidth = Number(size.width);
screenHeight = Number(size.height);
})
.done(done);
}, MINUTE);
describe('Specs.', function () {
// getPicture() with saveToPhotoLibrary = true
it('camera.ui.spec.1 Saving the picture to photo library', function (done) {
var options = {
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
saveToPhotoAlbum: true
};
driver
.then(function () {
return getPicture(options);
})
.then(function () {
isTestPictureSaved = true;
return checkPicture(true);
})
.fail(saveScreenshotAndFail)
.done(done);
}, 3 * MINUTE);
// getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY
it('camera.ui.spec.2 Selecting only videos', function (done) {
var options = { sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
mediaType: cameraConstants.MediaType.VIDEO };
driver
.then(function () {
return getPicture(options, true);
})
.context('NATIVE_APP')
.then(function () {
// try to find "Gallery" menu item
// if there's none, the gallery should be already opened
return driver
.elementByXPath('//android.widget.TextView[@text="Gallery"]')
.then(function (element) {
return element.click();
}, function () {
return driver;
});
})
.then(function () {
// if the gallery is opened on the videos page,
// there should be a "Choose video" caption
return driver
.elementByXPath('//*[@text="Choose video"]')
.fail(function () {
throw 'Couldn\'t find "Choose video" element.';
});
})
.deviceKeyEvent(BACK_BUTTON)
.elementByXPath('//android.widget.TextView[@text="Gallery"]')
.deviceKeyEvent(BACK_BUTTON)
.finally(function () {
return driver
.elementById('action_bar_title')
.then(function () {
// success means we're still in native app
return driver
.deviceKeyEvent(BACK_BUTTON);
}, function () {
// error means we're already in webview
return driver;
});
})
.fail(saveScreenshotAndFail)
.done(done);
}, 3 * MINUTE);
// getPicture(), then dismiss
// wait for the error callback to be called
it('camera.ui.spec.3 Dismissing the camera', function (done) {
var options = { quality: 50,
allowEdit: true,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.FILE_URI };
driver
.then(function () {
return getPicture(options, true);
})
.context("NATIVE_APP")
.waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'cancel\')]', MINUTE / 2)
.click()
.then(function () {
return checkPicture(false);
})
.fail(saveScreenshotAndFail)
.done(done);
}, 3 * MINUTE);
// getPicture(), then take picture but dismiss the edit
// wait for the error callback to be called
it('camera.ui.spec.4 Dismissing the edit', function (done) {
var options = { quality: 50,
allowEdit: true,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.FILE_URI };
driver
.then(function () {
return getPicture(options, true);
})
.context('NATIVE_APP')
.waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]', MINUTE / 2)
.click()
.waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]', MINUTE / 2)
.click()
.waitForElementByXPath('//*[contains(@resource-id,\'discard\')]', MINUTE / 2)
.click()
.then(function () {
return checkPicture(false);
})
.fail(saveScreenshotAndFail)
.done(done);
}, 3 * MINUTE);
// combine various options for getPicture()
generateSpecs().forEach(function (spec) {
it('camera.ui.spec.5.' + spec.id + ' Combining options', function (done) {
runCombinedSpec(spec)
.done(done);
}, 3 * MINUTE);
});
it('camera.ui.util Delete test image from device library', function (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
return 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) {
driver
.quit()
.done(done);
}, MINUTE);
});

View File

@@ -0,0 +1,105 @@
/*jshint node: true */
/* global Q */
/*
*
* 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');
module.exports.generateSpecs = function (sourceTypes, destinationTypes, encodingTypes, allowEditOptions) {
var destinationType,
sourceType,
encodingType,
allowEdit,
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)) {
// if taking picture from photolibrary, don't vary 'correctOrientation' option
if (sourceTypes[sourceType] === cameraConstants.PictureSourceType.PHOTOLIBRARY) {
specs.push({
'id': id++,
'options': {
'destinationType': destinationTypes[destinationType],
'sourceType': sourceTypes[sourceType],
'encodingType': encodingTypes[encodingType],
'allowEdit': allowEditOptions[allowEdit],
'saveToPhotoAlbum': false,
}
});
} else {
specs.push({
'id': id++,
'options': {
'destinationType': destinationTypes[destinationType],
'sourceType': sourceTypes[sourceType],
'encodingType': encodingTypes[encodingType],
'correctOrientation': true,
'allowEdit': allowEditOptions[allowEdit],
'saveToPhotoAlbum': false,
}
}, {
'id': id++,
'options': {
'destinationType': destinationTypes[destinationType],
'sourceType': sourceTypes[sourceType],
'encodingType': encodingTypes[encodingType],
'correctOrientation': false,
'allowEdit': allowEditOptions[allowEdit],
'saveToPhotoAlbum': false,
}
});
}
}
}
}
}
}
}
}
}
return specs;
};
module.exports.getPicture = function (opts, pid) {
navigator._appiumPromises[pid] = Q.defer();
navigator.camera.getPicture(function (result) {
navigator._appiumPromises[pid].resolve(result);
}, function (err) {
navigator._appiumPromises[pid].reject(err);
}, opts);
};
module.exports.checkPicture = function (pid, cb) {
navigator._appiumPromises[pid].promise
.then(function (result) {
cb(result);
}, function (err) {
cb('ERROR: ' + err);
});
};

View File

@@ -0,0 +1,264 @@
/*jshint node: true, jasmine: true */
/* global navigator, Q */
/*
*
* 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 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;
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 generateSpecs() {
var sourceTypes = [
cameraConstants.PictureSourceType.CAMERA,
cameraConstants.PictureSourceType.PHOTOLIBRARY
],
destinationTypes = cameraConstants.DestinationType,
encodingTypes = [
cameraConstants.EncodingType.JPEG,
cameraConstants.EncodingType.PNG
],
allowEditOptions = [
true,
false
];
return cameraHelper.generateSpecs(sourceTypes, destinationTypes, encodingTypes, allowEditOptions);
}
function usePicture() {
return driver
.elementByXPath('//*[@label="Use"]')
.click()
.fail(function () {
return driver
// For some reason "Choose" element is not clickable by standard Appium methods
// So getting its position and tapping there using TouchAction
.elementByXPath('//UIAButton[@label="Choose"]')
.getLocation()
.then(function (loc) {
var tapChoose = new wd.TouchAction();
tapChoose.tap(loc);
return driver
.performTouchAction(tapChoose);
});
});
}
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)
.click();
}
return driver
.waitForElementByXPath('//*[@label="Take Picture"]', MINUTE / 2)
.click()
.elementByXPath('//*[@label="Use Photo"]')
.click();
})
.fail(fail);
}
// checks if the picture was successfully taken
// if shouldLoad is falsy, ensures that the error callback was called
function checkPicture(shouldLoad) {
return driver
.context(webviewContext)
.setAsyncScriptTimeout(MINUTE)
.executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId()])
.then(function (result) {
if (shouldLoad) {
expect(result.length).toBeGreaterThan(0);
if (result.indexOf('ERROR') >= 0) {
return fail(result);
}
} else {
if (result.indexOf('ERROR') === -1) {
return fail('Unexpected success callback with result: ' + result);
}
expect(result.indexOf('ERROR')).toBe(0);
}
});
}
function runCombinedSpec(spec) {
return driver
.then(function () {
return getPicture(spec.options);
})
.then(function () {
return checkPicture(true);
})
.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);
});
}
it('camera.ui.util configure driver and start a session', function (done) {
getDriver()
.fail(fail)
.finally(done);
}, 5 * MINUTE);
describe('Specs.', function () {
// getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY
it('camera.ui.spec.1 Selecting only videos', function (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) {
// camera is not available on the iOS simulator
if (!isDevice) {
pending();
}
var options = { sourceType: cameraConstants.PictureSourceType.CAMERA };
driver
.then(function () {
return getPicture(options, true);
})
.then(function () {
return checkPicture(false);
})
.fail(saveScreenshotAndFail)
.done(done);
}, 3 * MINUTE);
// combine various options for getPicture()
generateSpecs().forEach(function (spec) {
it('camera.ui.spec.3.' + spec.id + ' Combining options', function (done) {
// camera is not available on iOS simulator
if (!isDevice && spec.options.sourceType === cameraConstants.PictureSourceType.CAMERA) {
pending();
}
runCombinedSpec(spec).done(done);
}, 3 * MINUTE);
});
});
it('camera.ui.util.4 Destroy the session', function (done) {
driver
.quit()
.done(done);
}, MINUTE);
});

View File

@@ -1,5 +1,7 @@
{{>cdv-license~}}
[![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
@@ -32,21 +34,6 @@ the system's image library.
#### Example <a name="camera-getPicture-examples"></a>
Take a photo and retrieve it as a base64-encoded image:
navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
destinationType: Camera.DestinationType.DATA_URL
});
function onSuccess(imageData) {
var image = document.getElementById('myImage');
image.src = "data:image/jpeg;base64," + imageData;
}
function onFail(message) {
alert('Failed because: ' + message);
}
Take a photo and retrieve the image's file location:
navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
@@ -61,6 +48,27 @@ Take a photo and retrieve the image's file location:
alert('Failed because: ' + message);
}
Take a photo and retrieve it as a Base64-encoded image:
/**
* Warning: Using DATA_URL is not recommended! The DATA_URL destination
* type is very memory intensive, even with a low quality setting. Using it
* can result in out of memory errors and application crashes. Use FILE_URI
* or NATIVE_URI instead.
*/
navigator.camera.getPicture(onSuccess, onFail, { quality: 25,
destinationType: Camera.DestinationType.DATA_URL
});
function onSuccess(imageData) {
var image = document.getElementById('myImage');
image.src = "data:image/jpeg;base64," + imageData;
}
function onFail(message) {
alert('Failed because: ' + message);
}
#### Preferences (iOS)
- __CameraUsesGeolocation__ (boolean, defaults to false). For capturing JPEGs, set to true to get geolocation data in the EXIF header. This will trigger a request for geolocation permissions if set to true.
@@ -71,14 +79,14 @@ Take a photo and retrieve the image's file location:
Amazon Fire OS uses intents to launch the camera activity on the device to capture
images, and on phones with low memory, the Cordova activity may be killed. In this
scenario, the image may not appear when the cordova activity is restored.
scenario, the image may not appear when the Cordova activity is restored.
#### Android Quirks
Android uses intents to launch the camera activity on the device to capture
images, and on phones with low memory, the Cordova activity may be killed. In this
scenario, the result from the plugin call will be delivered via the resume event.
See [the Android Lifecycle guide](http://cordova.apache.org/docs/en/dev/guide/platforms/android/lifecycle.html)
See [the Android Lifecycle guide][android_lifecycle]
for more information. The `pendingResult.result` value will contain the value that
would be passed to the callbacks (either the URI/URL or an error message). Check
the `pendingResult.pluginStatus` to determine whether or not the call was
@@ -86,11 +94,11 @@ successful.
#### Browser Quirks
Can only return photos as base64-encoded image.
Can only return photos as Base64-encoded image.
#### Firefox OS Quirks
Camera plugin is currently implemented using [Web Activities](https://hacks.mozilla.org/2013/01/introducing-web-activities/).
Camera plugin is currently implemented using [Web Activities][web_activities].
#### iOS Quirks
@@ -187,7 +195,11 @@ Tizen only supports a `destinationType` of
- Ignores the `cameraDirection` parameter.
- Ignores the `saveToPhotoAlbum` parameter. IMPORTANT: All images taken with the wp7/8 cordova camera API are always copied to the phone's camera roll. Depending on the user's settings, this could also mean the image is auto-uploaded to their OneDrive. This could potentially mean the image is available to a wider audience than your app intended. If this a blocker for your application, you will need to implement the CameraCaptureTask as documented on msdn : [http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh394006.aspx](http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh394006.aspx)
You may also comment or up-vote the related issue in the [issue tracker](https://issues.apache.org/jira/browse/CB-2083)
- Ignores the `saveToPhotoAlbum` parameter. IMPORTANT: All images taken with the WP8/8 Cordova camera API are always copied to the phone's camera roll. Depending on the user's settings, this could also mean the image is auto-uploaded to their OneDrive. This could potentially mean the image is available to a wider audience than your app intended. If this is a blocker for your application, you will need to implement the CameraCaptureTask as [documented on MSDN][msdn_wp8_docs]. You may also comment or up-vote the related issue in the [issue tracker][wp8_bug].
- Ignores the `mediaType` property of `cameraOptions` as the Windows Phone SDK does not provide a way to choose videos from PHOTOLIBRARY.
[android_lifecycle]: http://cordova.apache.org/docs/en/dev/guide/platforms/android/lifecycle.html
[web_activities]: https://hacks.mozilla.org/2013/01/introducing-web-activities/
[wp8_bug]: https://issues.apache.org/jira/browse/CB-2083
[msdn_wp8_docs]: http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh394006.aspx

View File

@@ -1,6 +1,6 @@
{
"name": "cordova-plugin-camera",
"version": "2.0.1-dev",
"version": "2.2.0",
"description": "Cordova Camera Plugin",
"cordova": {
"id": "cordova-plugin-camera",
@@ -43,13 +43,16 @@
},
"scripts": {
"precommit": "npm run gen-docs && git add README.md",
"gen-docs": "jsdoc2md --template \"jsdoc2md/TEMPLATE.md\" \"www/**/*.js\" --plugin \"dmd-plugin-cordova-plugin\" > README.md"
"gen-docs": "jsdoc2md --template \"jsdoc2md/TEMPLATE.md\" \"www/**/*.js\" --plugin \"dmd-plugin-cordova-plugin\" > README.md",
"test": "npm run jshint",
"jshint": "node node_modules/jshint/bin/jshint www && node node_modules/jshint/bin/jshint src && node node_modules/jshint/bin/jshint tests"
},
"author": "Apache Software Foundation",
"license": "Apache 2.0",
"license": "Apache-2.0",
"devDependencies": {
"dmd-plugin-cordova-plugin": "^0.1.0",
"husky": "^0.10.1",
"jsdoc-to-markdown": "^1.2.0"
"jsdoc-to-markdown": "^1.2.0",
"jshint": "^2.6.0"
}
}

View File

@@ -22,7 +22,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:rim="http://www.blackberry.com/ns/widgets"
id="cordova-plugin-camera"
version="2.0.1-dev">
version="2.2.0">
<name>Camera</name>
<description>Cordova Camera Plugin</description>
<license>Apache 2.0</license>
@@ -30,9 +30,7 @@
<repo>https://git-wip-us.apache.org/repos/asf/cordova-plugin-camera.git</repo>
<issue>https://issues.apache.org/jira/browse/CB/component/12320645</issue>
<engines>
<engine name="cordova-android" version=">=5.0.0-dev" />
</engines>
<dependency id="cordova-plugin-compat" version="^1.0.0" />
<js-module src="www/CameraConstants.js" name="Camera">
<clobbers target="Camera" />
@@ -54,12 +52,12 @@
<feature name="Camera">
<param name="firefoxos-package" value="Camera" />
</feature>
</config-file>
</config-file>
<js-module src="src/firefoxos/CameraProxy.js" name="CameraProxy">
<runs />
</js-module>
</platform>
</platform>
<!-- android -->
<platform name="android">
@@ -102,7 +100,7 @@
</js-module>
</platform>
<!-- ubuntu -->
<platform name="ubuntu">
<config-file target="config.xml" parent="/*">
@@ -151,11 +149,11 @@
<framework src="MobileCoreServices.framework" />
<framework src="CoreGraphics.framework" />
<framework src="AVFoundation.framework" />
<config-file target="*-Info.plist" parent="NSLocationWhenInUseUsageDescription">
<string></string>
</config-file>
</platform>
<!-- blackberry10 -->

View File

@@ -34,6 +34,7 @@ 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;
@@ -58,6 +59,9 @@ 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.content.pm.PermissionInfo;
/**
* 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
@@ -105,7 +109,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.READ_EXTERNAL_STORAGE };
protected final static String[] permissions = { Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE };
public CallbackContext callbackContext;
private int numPics;
@@ -115,11 +119,6 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
private Uri croppedUri;
protected void getReadPermission(int requestCode)
{
cordova.requestPermission(this, requestCode, Manifest.permission.READ_EXTERNAL_STORAGE);
}
/**
* Executes the request and returns PluginResult.
*
@@ -174,12 +173,9 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
this.callTakePicture(destType, encodingType);
}
else if ((this.srcType == PHOTOLIBRARY) || (this.srcType == SAVEDPHOTOALBUM)) {
// 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() && !cordova.hasPermission(permissions[0])) {
getReadPermission(SAVE_TO_ALBUM_SEC);
// FIXME: Stop always requesting the permission
if(!PermissionHelper.hasPermission(this, permissions[0])) {
PermissionHelper.requestPermission(this, SAVE_TO_ALBUM_SEC, Manifest.permission.READ_EXTERNAL_STORAGE);
} else {
this.getImage(this.srcType, destType, encodingType);
}
@@ -238,14 +234,43 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
* @param returnType Set the type of image to return.
*/
public void callTakePicture(int returnType, int encodingType) {
if (cordova.hasPermission(permissions[0])) {
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) {
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 {
getReadPermission(TAKE_PIC_SEC);
PermissionHelper.requestPermissions(this, TAKE_PIC_SEC, permissions);
}
}
public void takePicture(int returnType, int encodingType)
{
// Save the number of images currently on disk for later
@@ -1210,11 +1235,6 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
}
}
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

View File

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

View File

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

View File

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

View File

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

View File

@@ -358,24 +358,24 @@ static NSString* toBase64(NSData* data) {
// use image unedited as requested , don't resize
data = UIImageJPEGRepresentation(image, 1.0);
} else {
if (options.usesGeolocation) {
NSDictionary* controllerMetadata = [info objectForKey:@"UIImagePickerControllerMediaMetadata"];
if (controllerMetadata) {
self.data = data;
self.metadata = [[NSMutableDictionary alloc] init];
NSMutableDictionary* EXIFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyExifDictionary]mutableCopy];
if (EXIFDictionary) {
[self.metadata setObject:EXIFDictionary forKey:(NSString*)kCGImagePropertyExifDictionary];
}
if (IsAtLeastiOSVersion(@"8.0")) {
[[self locationManager] performSelector:NSSelectorFromString(@"requestWhenInUseAuthorization") withObject:nil afterDelay:0];
}
[[self locationManager] startUpdatingLocation];
data = UIImageJPEGRepresentation(image, [options.quality floatValue] / 100.0f);
}
if (options.usesGeolocation) {
NSDictionary* controllerMetadata = [info objectForKey:@"UIImagePickerControllerMediaMetadata"];
if (controllerMetadata) {
self.data = data;
self.metadata = [[NSMutableDictionary alloc] init];
NSMutableDictionary* EXIFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyExifDictionary]mutableCopy];
if (EXIFDictionary) {
[self.metadata setObject:EXIFDictionary forKey:(NSString*)kCGImagePropertyExifDictionary];
}
} else {
data = UIImageJPEGRepresentation(image, [options.quality floatValue] / 100.0f);
if (IsAtLeastiOSVersion(@"8.0")) {
[[self locationManager] performSelector:NSSelectorFromString(@"requestWhenInUseAuthorization") withObject:nil afterDelay:0];
}
[[self locationManager] startUpdatingLocation];
}
}
}
@@ -558,11 +558,14 @@ static NSString* toBase64(NSData* data) {
dispatch_block_t invoke = ^ (void) {
CDVPluginResult* result;
if ([ALAssetsLibrary authorizationStatus] == ALAuthorizationStatusAuthorized) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no image selected"];
} else {
if (picker.sourceType == UIImagePickerControllerSourceTypeCamera && [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] != ALAuthorizationStatusAuthorized) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"has no access to camera"];
} else if (picker.sourceType != UIImagePickerControllerSourceTypeCamera && [ALAssetsLibrary authorizationStatus] != ALAuthorizationStatusAuthorized) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"has no access to assets"];
} else {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no image selected"];
}
[weakSelf.commandDelegate sendPluginResult:result callbackId:cameraPicker.callbackId];

View File

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

View File

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

View File

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

View File

@@ -22,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.0.1-dev">
version="2.2.0">
<name>Cordova Camera Plugin Tests</name>
<license>Apache 2.0</license>

View File

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

View File

@@ -25,7 +25,7 @@ var argscheck = require('cordova/argscheck'),
// XXX: commented out
//CameraPopoverHandle = require('./CameraPopoverHandle');
/**
/**
* @namespace navigator
*/
@@ -80,37 +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. 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
@@ -119,11 +119,18 @@ for (var key in Camera) {
*
* __Supported Platforms__
*
* ![](doc/img/android-success.png) ![](doc/img/blackberry-success.png) ![](doc/img/browser-success.png) ![](doc/img/firefox-success.png) ![](doc/img/fireos-success.png) ![](doc/img/ios-success.png) ![](doc/img/windows-success.png) ![](doc/img/wp8-success.png) ![](doc/img/ubuntu-success.png)
* - Android
* - BlackBerry
* - Browser
* - Firefox
* - FireOS
* - iOS
* - Windows
* - WP8
* - Ubuntu
*
* * [More examples](#camera-getPicture-examples)
* More examples [here](#camera-getPicture-examples). Quirks [here](#camera-getPicture-quirks).
*
* * [Quirks](#camera-getPicture-quirks)
* @example
* navigator.camera.getPicture(cameraSuccess, cameraError, cameraOptions);
* @param {module:camera.onSuccess} successCallback
@@ -164,11 +171,11 @@ cameraExport.getPicture = function(successCallback, errorCallback, options) {
*
* __Supported Platforms__
*
* ![](doc/img/android-fail.png) ![](doc/img/blackberry-fail.png) ![](doc/img/browser-fail.png) ![](doc/img/firefox-fail.png) ![](doc/img/fireos-fail.png) ![](doc/img/ios-success.png) ![](doc/img/windows-fail.png) ![](doc/img/wp8-fail.png) ![](doc/img/ubuntu-fail.png)
* - iOS
*
* @example
* navigator.camera.cleanup(onSuccess, onFail);
*
*
* function onSuccess() {
* console.log("Camera cleanup success.")
* }

View File

@@ -27,7 +27,7 @@ module.exports = {
* @enum {number}
*/
DestinationType:{
/** Return base64 encoded string */
/** Return base64 encoded string. DATA_URL can be very memory intensive and cause app crashes or out of memory errors. Use FILE_URI or NATIVE_URI if possible */
DATA_URL: 0,
/** Return file uri (content://media/external/images/media/2 for Android) */
FILE_URI: 1,

View File

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

View File

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

View File

@@ -21,7 +21,7 @@
var exec = require('cordova/exec');
/**
/**
* @namespace navigator
*/
@@ -30,16 +30,16 @@ var exec = require('cordova/exec');
*
* __Supported Platforms__
*
* ![](doc/img/android-fail.png) ![](doc/img/blackberry-fail.png) ![](doc/img/browser-fail.png) ![](doc/img/firefox-fail.png) ![](doc/img/fireos-fail.png) ![](doc/img/ios-success.png) ![](doc/img/windows-fail.png) ![](doc/img/wp8-fail.png) ![](doc/img/ubuntu-fail.png)
* - iOS
*
* @example
* var cameraPopoverHandle = navigator.camera.getPicture(onSuccess, onFail,
* {
* {
* 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 cameraPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY);