Merge remote-tracking branch 'refs/remotes/apache/master'

Rebase from Master
This commit is contained in:
swbradshaw 2016-08-26 20:30:35 -04:00
commit 0ed6406864
16 changed files with 2243 additions and 249 deletions

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

@ -0,0 +1,23 @@
<!--
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

@ -21,7 +21,9 @@ description: Take pictures with the device camera.
# under the License. # under the License.
--> -->
[![Build Status](https://travis-ci.org/apache/cordova-plugin-camera.svg?branch=master)](https://travis-ci.org/apache/cordova-plugin-camera) |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 # cordova-plugin-camera
@ -73,7 +75,7 @@ Documentation consists of template and API docs produced from the plugin JS code
--- ---
# API Reference # API Reference <a name="reference"></a>
* [camera](#module_camera) * [camera](#module_camera)
@ -114,26 +116,20 @@ Once the user snaps the photo, the camera application closes and the application
If `Camera.sourceType` is `Camera.PictureSourceType.PHOTOLIBRARY` or If `Camera.sourceType` is `Camera.PictureSourceType.PHOTOLIBRARY` or
`Camera.PictureSourceType.SAVEDPHOTOALBUM`, then a dialog displays `Camera.PictureSourceType.SAVEDPHOTOALBUM`, then a dialog displays
that allows users to select an existing image. The that allows users to select an existing image.
`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 The return value is sent to the [`cameraSuccess`](#module_camera.onSuccess) callback function, in
one of the following formats, depending on the specified one of the following formats, depending on the specified
`cameraOptions`: `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). - A `String` representing the image file location on local storage (default).
You can do whatever you want with the encoded image or URI, for You can do whatever you want with the encoded image or URI, for
example: example:
- Render the image in an `<img>` tag, as in the example below - Render the image in an `<img>` tag, as in the example below
- Save the data locally (`LocalStorage`, [Lawnchair](http://brianleroux.github.com/lawnchair/), etc.) - Save the data locally (`LocalStorage`, [Lawnchair](http://brianleroux.github.com/lawnchair/), etc.)
- Post the data to a remote server - Post the data to a remote server
__NOTE__: Photo resolution on newer devices is quite good. Photos __NOTE__: Photo resolution on newer devices is quite good. Photos
@ -256,6 +252,12 @@ Optional parameters to customize the camera settings.
<a name="module_Camera.DestinationType"></a> <a name="module_Camera.DestinationType"></a>
### Camera.DestinationType : <code>enum</code> ### 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> **Kind**: static enum property of <code>[Camera](#module_Camera)</code>
**Properties** **Properties**
@ -291,14 +293,19 @@ Optional parameters to customize the camera settings.
<a name="module_Camera.PictureSourceType"></a> <a name="module_Camera.PictureSourceType"></a>
### Camera.PictureSourceType : <code>enum</code> ### 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> **Kind**: static enum property of <code>[Camera](#module_Camera)</code>
**Properties** **Properties**
| Name | Type | Default | Description | | Name | Type | Default | Description |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| PHOTOLIBRARY | <code>number</code> | <code>0</code> | Choose image from picture library (same as SAVEDPHOTOALBUM for Android) | | PHOTOLIBRARY | <code>number</code> | <code>0</code> | Choose image from the device's photo library (same as SAVEDPHOTOALBUM for Android) |
| CAMERA | <code>number</code> | <code>1</code> | Take picture from camera | | CAMERA | <code>number</code> | <code>1</code> | Take picture from camera |
| SAVEDPHOTOALBUM | <code>number</code> | <code>2</code> | Choose image from picture library (same as PHOTOLIBRARY for Android) | | SAVEDPHOTOALBUM | <code>number</code> | <code>2</code> | Choose image only from the device's Camera Roll album (same as PHOTOLIBRARY for Android) |
<a name="module_Camera.PopoverArrowDirection"></a> <a name="module_Camera.PopoverArrowDirection"></a>
@ -362,7 +369,7 @@ __Supported Platforms__
**Example** **Example**
```js ```js
var cameraPopoverHandle = navigator.camera.getPicture(onSuccess, onFail, navigator.camera.getPicture(onSuccess, onFail,
{ {
destinationType: Camera.DestinationType.FILE_URI, destinationType: Camera.DestinationType.FILE_URI,
sourceType: Camera.PictureSourceType.PHOTOLIBRARY, sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
@ -371,6 +378,7 @@ var cameraPopoverHandle = navigator.camera.getPicture(onSuccess, onFail,
// Reposition the popover if the orientation changes. // Reposition the popover if the orientation changes.
window.onorientationchange = function() { window.onorientationchange = function() {
var cameraPopoverHandle = new CameraPopoverHandle();
var cameraPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY); var cameraPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY);
cameraPopoverHandle.setPosition(cameraPopoverOptions); cameraPopoverHandle.setPosition(cameraPopoverOptions);
} }
@ -464,6 +472,16 @@ displays:
Invoking the native camera application while the device is connected Invoking the native camera application while the device is connected
via Zune does not work, and triggers an error callback. 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 Quirks
Tizen only supports a `destinationType` of Tizen only supports a `destinationType` of
@ -529,6 +547,8 @@ Tizen only supports a `destinationType` of
- When using `destinationType.NATIVE_URI` and `sourceType.CAMERA`, photos are saved in the saved photo album regardless on the value of `saveToPhotoAlbum` parameter. - When using `destinationType.NATIVE_URI` and `sourceType.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 #### Tizen Quirks
- options not supported - options not supported
@ -618,7 +638,7 @@ function displayImage(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. 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 ```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 *"> <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 *">

View File

@ -52,6 +52,12 @@ describe('Camera tests Android.', function () {
var screenHeight = DEFAULT_SCREEN_HEIGHT; var screenHeight = DEFAULT_SCREEN_HEIGHT;
// promise count to use in promise ID // promise count to use in promise ID
var promiseCount = 0; 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() { function getNextPromiseId() {
promiseCount += 1; promiseCount += 1;
@ -72,9 +78,9 @@ describe('Camera tests Android.', function () {
}); });
} }
// generates test specs by combining all the specified options // combinines specified options in all possible variations
// you can add more options to test more scenarios // you can add more options to test more scenarios
function generateSpecs() { function generateOptions() {
var sourceTypes = [ var sourceTypes = [
cameraConstants.PictureSourceType.CAMERA, cameraConstants.PictureSourceType.CAMERA,
cameraConstants.PictureSourceType.PHOTOLIBRARY cameraConstants.PictureSourceType.PHOTOLIBRARY
@ -112,12 +118,13 @@ describe('Camera tests Android.', function () {
tapTile tapTile
.tap({ .tap({
x: Math.round(screenWidth / 4), x: Math.round(screenWidth / 4),
y: Math.round(screenHeight / 5) y: Math.round(screenHeight / 4)
}); });
swipeRight swipeRight
.press({x: 10, y: 100}) .press({x: 10, y: 150})
.wait(300) .wait(300)
.moveTo({x: Math.round(screenWidth / 2), y: 0}) .moveTo({x: Math.round(screenWidth - (screenWidth / 8)), y: 0})
.wait(1500)
.release() .release()
.wait(1000); .wait(1000);
if (options.allowEdit) { if (options.allowEdit) {
@ -127,10 +134,18 @@ describe('Camera tests Android.', function () {
.performTouchAction(tapTile); .performTouchAction(tapTile);
} }
return driver 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"]') .elementByXPath('//android.widget.TextView[@text="Gallery"]')
.fail(function () { .fail(function () {
return driver return driver
.performTouchAction(swipeRight) .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"]'); .elementByXPath('//android.widget.TextView[@text="Gallery"]');
}) })
.click() .click()
@ -140,9 +155,13 @@ describe('Camera tests Android.', function () {
} }
// taking a picture from camera // taking a picture from camera
return driver return driver
.waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]', MINUTE) .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() .click()
.waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]', MINUTE) .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(); .click();
}) })
.then(function () { .then(function () {
@ -156,44 +175,31 @@ describe('Camera tests Android.', function () {
} }
}) })
.fail(function (failure) { .fail(function (failure) {
console.log(failure); throw failure;
fail(failure);
}); });
} }
// checks if the picture was successfully taken // checks if the picture was successfully taken
// if shouldLoad is falsy, ensures that the error callback was called // if shouldLoad is falsy, ensures that the error callback was called
function checkPicture(shouldLoad) { function checkPicture(shouldLoad, options) {
if (!options) {
options = {};
}
return driver return driver
.context(webviewContext) .context(webviewContext)
.setAsyncScriptTimeout(MINUTE) .setAsyncScriptTimeout(MINUTE / 2)
.executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId()]) .executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId(), options])
.then(function (result) { .then(function (result) {
if (shouldLoad) { if (shouldLoad) {
expect(result.length).toBeGreaterThan(0); if (result !== 'OK') {
if (result.indexOf('ERROR') >= 0) { fail(result);
return fail(result);
} }
} else { } else if (result.indexOf('ERROR') === -1) {
if (result.indexOf('ERROR') === -1) { throw 'Unexpected success callback with result: ' + result;
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 // deletes the latest image from the gallery
function deleteImage() { function deleteImage() {
var holdTile = new wd.TouchAction(); var holdTile = new wd.TouchAction();
@ -216,32 +222,93 @@ describe('Camera tests Android.', function () {
function getDriver() { function getDriver() {
driver = wdHelper.getDriver('Android'); driver = wdHelper.getDriver('Android');
return wdHelper.getWebviewContext(driver) return driver.getWebviewContext()
.then(function(context) { .then(function(context) {
webviewContext = context; webviewContext = context;
return driver.context(webviewContext); return driver.context(webviewContext);
}) })
.waitForDeviceReady()
.injectLibraries()
.deleteFillerImage(fillerImagePath)
.then(function () { .then(function () {
return wdHelper.waitForDeviceReady(driver); fillerImagePath = null;
}) })
.then(function () { .addFillerImage()
return wdHelper.injectLibraries(driver); .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) { it('camera.ui.util configuring driver and starting a session', function (done) {
getDriver() getDriver()
.fail(fail) .then(function () {
appiumSessionStarted = true;
}, fail)
.done(done); .done(done);
}, 5 * MINUTE); }, 10 * MINUTE);
it('camera.ui.util determine screen dimensions', function (done) { it('camera.ui.util determine screen dimensions', function (done) {
return driver checkSession(done);
driver
.context(webviewContext) .context(webviewContext)
.execute(function () { .execute(function () {
return { return {
'width': window.innerWidth, 'width': screen.availWidth,
'height': window.innerHeight 'height': screen.availHeight
}; };
}, []) }, [])
.then(function (size) { .then(function (size) {
@ -251,131 +318,287 @@ describe('Camera tests Android.', function () {
.done(done); .done(done);
}, MINUTE); }, 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 () { describe('Specs.', function () {
// getPicture() with saveToPhotoLibrary = true // getPicture() with saveToPhotoLibrary = true
it('camera.ui.spec.1 Saving the picture to photo library', function (done) { it('camera.ui.spec.1 Saving a picture to the photo library', function (done) {
var options = { checkSession(done);
checkCamera(pending);
var spec = generateSpec({
quality: 50, quality: 50,
allowEdit: false, allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA, sourceType: cameraConstants.PictureSourceType.CAMERA,
saveToPhotoAlbum: true saveToPhotoAlbum: true
}; });
driver
.then(function () { tryRunSpec(spec)
return getPicture(options);
})
.then(function () { .then(function () {
isTestPictureSaved = true; isTestPictureSaved = true;
return checkPicture(true);
}) })
.fail(saveScreenshotAndFail)
.done(done); .done(done);
}, 3 * MINUTE); }, 10 * MINUTE);
// getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY // getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY
it('camera.ui.spec.2 Selecting only videos', function (done) { it('camera.ui.spec.2 Selecting only videos', function (done) {
var options = { sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY, checkSession(done);
mediaType: cameraConstants.MediaType.VIDEO }; var spec = function () {
driver var options = { sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
.then(function () { mediaType: cameraConstants.MediaType.VIDEO };
return getPicture(options, true); return driver
}) .then(function () {
.context('NATIVE_APP') return getPicture(options, true);
.then(function () { })
// try to find "Gallery" menu item .context('NATIVE_APP')
// if there's none, the gallery should be already opened .then(function () {
return driver // try to find "Gallery" menu item
.elementByXPath('//android.widget.TextView[@text="Gallery"]') // if there's none, the gallery should be already opened
.then(function (element) { return driver
return element.click(); .waitForElementByXPath('//android.widget.TextView[@text="Gallery"]', 20000)
}, function () { .elementByXPath('//android.widget.TextView[@text="Gallery"]')
return driver; .elementByXPath('//android.widget.TextView[@text="Gallery"]')
}); .then(function (element) {
}) return element.click();
.then(function () { }, function () {
// if the gallery is opened on the videos page, return driver;
// there should be a "Choose video" caption });
return driver })
.elementByXPath('//*[@text="Choose video"]') .then(function () {
.fail(function () { // if the gallery is opened on the videos page,
throw 'Couldn\'t find "Choose video" element.'; // there should be a "Choose video" caption
}); return driver
}) .elementByXPath('//*[@text="Choose video"]')
.deviceKeyEvent(BACK_BUTTON) .fail(function () {
.elementByXPath('//android.widget.TextView[@text="Gallery"]') throw 'Couldn\'t find "Choose video" element.';
.deviceKeyEvent(BACK_BUTTON) });
.finally(function () { })
return driver .deviceKeyEvent(BACK_BUTTON)
.elementById('action_bar_title') .elementByXPath('//android.widget.TextView[@text="Gallery"]')
.then(function () { .deviceKeyEvent(BACK_BUTTON)
// success means we're still in native app .finally(function () {
return driver return driver
.deviceKeyEvent(BACK_BUTTON); .elementById('action_bar_title')
}, function () { .then(function () {
// error means we're already in webview // success means we're still in native app
return driver; return driver
}); .deviceKeyEvent(BACK_BUTTON)
}) // give native app some time to close
.fail(saveScreenshotAndFail) .sleep(2000)
.done(done); // try again! because every ~30th build
}, 3 * MINUTE); // 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 // getPicture(), then dismiss
// wait for the error callback to be called // wait for the error callback to be called
it('camera.ui.spec.3 Dismissing the camera', function (done) { it('camera.ui.spec.3 Dismissing the camera', function (done) {
var options = { quality: 50, checkSession(done);
allowEdit: true, checkCamera(pending);
sourceType: cameraConstants.PictureSourceType.CAMERA, var spec = function () {
destinationType: cameraConstants.DestinationType.FILE_URI }; var options = {
driver quality: 50,
.then(function () { allowEdit: true,
return getPicture(options, true); sourceType: cameraConstants.PictureSourceType.CAMERA,
}) destinationType: cameraConstants.DestinationType.FILE_URI
.context("NATIVE_APP") };
.waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'cancel\')]', MINUTE / 2) return driver
.click() .then(function () {
.then(function () { return getPicture(options, true);
return checkPicture(false); })
}) .context("NATIVE_APP")
.fail(saveScreenshotAndFail) .waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'cancel\')]', MINUTE / 2)
.done(done); .elementByXPath('//android.widget.ImageView[contains(@resource-id,\'cancel\')]')
}, 3 * MINUTE); .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 // getPicture(), then take picture but dismiss the edit
// wait for the error callback to be called // wait for the error callback to be called
it('camera.ui.spec.4 Dismissing the edit', function (done) { it('camera.ui.spec.4 Dismissing the edit', function (done) {
var options = { quality: 50, checkSession(done);
allowEdit: true, checkCamera(pending);
sourceType: cameraConstants.PictureSourceType.CAMERA, var spec = function () {
destinationType: cameraConstants.DestinationType.FILE_URI }; var options = {
driver quality: 50,
.then(function () { allowEdit: true,
return getPicture(options, true); sourceType: cameraConstants.PictureSourceType.CAMERA,
}) destinationType: cameraConstants.DestinationType.FILE_URI
.context('NATIVE_APP') };
.waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]', MINUTE / 2) return driver
.click() .then(function () {
.waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]', MINUTE / 2) return getPicture(options, true);
.click() })
.waitForElementByXPath('//*[contains(@resource-id,\'discard\')]', MINUTE / 2) .context('NATIVE_APP')
.click() .waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]', MINUTE / 2)
.then(function () { .elementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]')
return checkPicture(false); .elementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]')
}) .click()
.fail(saveScreenshotAndFail) .waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]', MINUTE / 2)
.done(done); .elementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]')
}, 3 * MINUTE); .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() // combine various options for getPicture()
generateSpecs().forEach(function (spec) { generateOptions().forEach(function (spec) {
it('camera.ui.spec.5.' + spec.id + ' Combining options. ' + spec.description, function (done) { it('camera.ui.spec.11.' + spec.id + ' Combining options. ' + spec.description, function (done) {
runCombinedSpec(spec) checkSession(done);
.done(done); if (spec.options.sourceType == cameraConstants.PictureSourceType.CAMERA) {
}, 3 * MINUTE); 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 test image from device library', function (done) { it('camera.ui.util Delete taken picture from device library', function (done) {
checkSession(done);
if (!isTestPictureSaved) { if (!isTestPictureSaved) {
// couldn't save test picture earlier, so nothing to delete here // couldn't save test picture earlier, so nothing to delete here
done(); done();
@ -383,7 +606,7 @@ describe('Camera tests Android.', function () {
} }
// delete exactly one latest picture // delete exactly one latest picture
// this should be the picture we've taken in the first spec // this should be the picture we've taken in the first spec
return driver driver
.context('NATIVE_APP') .context('NATIVE_APP')
.deviceKeyEvent(BACK_BUTTON) .deviceKeyEvent(BACK_BUTTON)
.sleep(1000) .sleep(1000)
@ -408,6 +631,7 @@ describe('Camera tests Android.', function () {
}); });
it('camera.ui.util Destroy the session', function (done) { it('camera.ui.util Destroy the session', function (done) {
checkSession(done);
driver driver
.quit() .quit()
.done(done); .done(done);

View File

@ -1,5 +1,5 @@
/*jshint node: true */ /*jshint node: true */
/* global Q */ /* global Q, resolveLocalFileSystemURL, Camera, cordova */
/* /*
* *
* Licensed to the Apache Software Foundation (ASF) under one * Licensed to the Apache Software Foundation (ASF) under one
@ -94,7 +94,13 @@ module.exports.generateSpecs = function (sourceTypes, destinationTypes, encoding
return specs; 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) { module.exports.getPicture = function (opts, pid) {
if (navigator._appiumPromises[pid - 1]) {
navigator._appiumPromises[pid - 1] = null;
}
navigator._appiumPromises[pid] = Q.defer(); navigator._appiumPromises[pid] = Q.defer();
navigator.camera.getPicture(function (result) { navigator.camera.getPicture(function (result) {
navigator._appiumPromises[pid].resolve(result); navigator._appiumPromises[pid].resolve(result);
@ -103,11 +109,197 @@ module.exports.getPicture = function (opts, pid) {
}, opts); }, opts);
}; };
module.exports.checkPicture = function (pid, cb) { // verifies taken picture when the promise is resolved,
// calls a callback with 'OK' if everything is good,
// calls a callback with 'ERROR: <error message>' if something is wrong
// note that this function is executed in the context of tested app
// and not in the context of tests
module.exports.checkPicture = function (pid, options, 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 navigator._appiumPromises[pid].promise
.then(function (result) { .then(function (result) {
cb(result); verifyResult(result);
}, function (err) { })
cb('ERROR: ' + err); .fail(function (e) {
errorCallback(e);
}); });
}; };

View File

@ -30,7 +30,6 @@
var wdHelper = global.WD_HELPER; var wdHelper = global.WD_HELPER;
var screenshotHelper = global.SCREENSHOT_HELPER; var screenshotHelper = global.SCREENSHOT_HELPER;
var wd = wdHelper.getWD();
var isDevice = global.DEVICE; var isDevice = global.DEVICE;
var cameraConstants = require('../../www/CameraConstants'); var cameraConstants = require('../../www/CameraConstants');
var cameraHelper = require('../helpers/cameraHelper'); var cameraHelper = require('../helpers/cameraHelper');
@ -44,6 +43,8 @@ describe('Camera tests iOS.', function () {
var webviewContext = DEFAULT_WEBVIEW_CONTEXT; var webviewContext = DEFAULT_WEBVIEW_CONTEXT;
// promise count to use in promise ID // promise count to use in promise ID
var promiseCount = 0; var promiseCount = 0;
// going to set this to false if session is created successfully
var failedToStart = true;
function getNextPromiseId() { function getNextPromiseId() {
promiseCount += 1; promiseCount += 1;
@ -66,7 +67,7 @@ describe('Camera tests iOS.', function () {
// generates test specs by combining all the specified options // generates test specs by combining all the specified options
// you can add more options to test more scenarios // you can add more options to test more scenarios
function generateSpecs() { function generateOptions() {
var sourceTypes = cameraConstants.PictureSourceType; var sourceTypes = cameraConstants.PictureSourceType;
var destinationTypes = cameraConstants.DestinationType; var destinationTypes = cameraConstants.DestinationType;
var encodingTypes = cameraConstants.EncodingType; var encodingTypes = cameraConstants.EncodingType;
@ -81,17 +82,8 @@ describe('Camera tests iOS.', function () {
.elementByXPath('//*[@label="Use"]') .elementByXPath('//*[@label="Use"]')
.click() .click()
.fail(function () { .fail(function () {
return driver // For some reason "Choose" element is not clickable by standard Appium methods
// For some reason "Choose" element is not clickable by standard Appium methods return wdHelper.tapElementByXPath('//UIAButton[@label="Choose"]', driver);
// 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);
});
}); });
} }
@ -136,12 +128,14 @@ describe('Camera tests iOS.', function () {
if (cancelCamera) { if (cancelCamera) {
return driver return driver
.waitForElementByXPath('//*[@label="Cancel"]', MINUTE / 2) .waitForElementByXPath('//*[@label="Cancel"]', MINUTE / 2)
.elementByXPath('//*[@label="Cancel"]')
.elementByXPath('//*[@label="Cancel"]')
.click(); .click();
} }
return driver return driver
.waitForElementByXPath('//*[@label="Take Picture"]', MINUTE / 2) .waitForElementByXPath('//*[@label="Take Picture"]', MINUTE / 2)
.click() .click()
.elementByXPath('//*[@label="Use Photo"]') .waitForElementByXPath('//*[@label="Use Photo"]', MINUTE / 2)
.click(); .click();
}) })
.fail(fail); .fail(fail);
@ -149,33 +143,34 @@ describe('Camera tests iOS.', function () {
// checks if the picture was successfully taken // checks if the picture was successfully taken
// if shouldLoad is falsy, ensures that the error callback was called // if shouldLoad is falsy, ensures that the error callback was called
function checkPicture(shouldLoad) { function checkPicture(shouldLoad, options) {
if (!options) {
options = {};
}
return driver return driver
.context(webviewContext) .context(webviewContext)
.setAsyncScriptTimeout(MINUTE) .setAsyncScriptTimeout(MINUTE / 2)
.executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId()]) .executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId(), options])
.then(function (result) { .then(function (result) {
if (shouldLoad) { if (shouldLoad) {
expect(result.length).toBeGreaterThan(0); if (result !== 'OK') {
if (result.indexOf('ERROR') >= 0) { fail(result);
return fail(result);
} }
} else { } else if (result.indexOf('ERROR') === -1) {
if (result.indexOf('ERROR') === -1) { throw 'Unexpected success callback with result: ' + result;
return fail('Unexpected success callback with result: ' + result);
}
expect(result.indexOf('ERROR')).toBe(0);
} }
}); });
} }
function runCombinedSpec(spec) { // takes a picture with the specified options
// and then verifies it
function runSpec(options) {
return driver return driver
.then(function () { .then(function () {
return getPicture(spec.options); return getPicture(options);
}) })
.then(function () { .then(function () {
return checkPicture(true); return checkPicture(true, options);
}) })
.fail(saveScreenshotAndFail); .fail(saveScreenshotAndFail);
} }
@ -195,15 +190,25 @@ describe('Camera tests iOS.', function () {
}); });
} }
function checkSession(done) {
if (failedToStart) {
fail('Failed to start a session');
done();
}
}
it('camera.ui.util configure driver and start a session', function (done) { it('camera.ui.util configure driver and start a session', function (done) {
getDriver() getDriver()
.fail(fail) .then(function () {
.finally(done); failedToStart = false;
}, 5 * MINUTE); }, fail)
.done(done);
}, 10 * MINUTE);
describe('Specs.', function () { describe('Specs.', function () {
// getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY // getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY
it('camera.ui.spec.1 Selecting only videos', function (done) { it('camera.ui.spec.1 Selecting only videos', function (done) {
checkSession(done);
var options = { sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY, var options = { sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
mediaType: cameraConstants.MediaType.VIDEO }; mediaType: cameraConstants.MediaType.VIDEO };
driver driver
@ -219,11 +224,12 @@ describe('Camera tests iOS.', function () {
// getPicture(), then dismiss // getPicture(), then dismiss
// wait for the error callback to be called // wait for the error callback to be called
it('camera.ui.spec.2 Dismissing the camera', function (done) { it('camera.ui.spec.2 Dismissing the camera', function (done) {
// camera is not available on the iOS simulator checkSession(done);
if (!isDevice) { if (!isDevice) {
pending(); pending('Camera is not available on iOS simulator');
} }
var options = { sourceType: cameraConstants.PictureSourceType.CAMERA }; var options = { sourceType: cameraConstants.PictureSourceType.CAMERA,
saveToPhotoAlbum: false };
driver driver
.then(function () { .then(function () {
return getPicture(options, true); return getPicture(options, true);
@ -235,20 +241,175 @@ describe('Camera tests iOS.', function () {
.done(done); .done(done);
}, 3 * MINUTE); }, 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() // combine various options for getPicture()
generateSpecs().forEach(function (spec) { generateOptions().forEach(function (spec) {
it('camera.ui.spec.3.' + spec.id + ' Combining options. ' + spec.description, function (done) { it('camera.ui.spec.12.' + spec.id + ' Combining options. ' + spec.description, function (done) {
// camera is not available on iOS simulator checkSession(done);
if (!isDevice && spec.options.sourceType === cameraConstants.PictureSourceType.CAMERA) { if (!isDevice && spec.options.sourceType === cameraConstants.PictureSourceType.CAMERA) {
pending(); pending('Camera is not available on iOS simulator');
} }
runCombinedSpec(spec).done(done);
// remove this check if you don't mind the tests leaving a photo saved on device
if (spec.options.sourceType === cameraConstants.PictureSourceType.CAMERA &&
spec.options.destinationType === cameraConstants.DestinationType.NATIVE_URI) {
pending('Skipping: cannot prevent iOS from saving the picture to photo library and cannot delete it. ' +
'For more info, see iOS quirks here: https://github.com/apache/cordova-plugin-camera#ios-quirks-1');
}
runSpec(spec.options).done(done);
}, 3 * MINUTE); }, 3 * MINUTE);
}); });
}); });
it('camera.ui.util.4 Destroy the session', function (done) { it('camera.ui.util Destroy the session', function (done) {
checkSession(done);
driver driver
.quit() .quit()
.done(done); .done(done);

View File

@ -4,7 +4,9 @@ description: Take pictures with the device camera.
--- ---
{{>cdv-license~}} {{>cdv-license~}}
[![Build Status](https://travis-ci.org/apache/cordova-plugin-camera.svg?branch=master)](https://travis-ci.org/apache/cordova-plugin-camera) |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 # cordova-plugin-camera
@ -15,7 +17,7 @@ the system's image library.
--- ---
# API Reference # API Reference <a name="reference"></a>
{{#orphans~}} {{#orphans~}}
{{>member-index}} {{>member-index}}
@ -120,6 +122,16 @@ displays:
Invoking the native camera application while the device is connected Invoking the native camera application while the device is connected
via Zune does not work, and triggers an error callback. 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 Quirks
Tizen only supports a `destinationType` of Tizen only supports a `destinationType` of
@ -185,6 +197,8 @@ Tizen only supports a `destinationType` of
- When using `destinationType.NATIVE_URI` and `sourceType.CAMERA`, photos are saved in the saved photo album regardless on the value of `saveToPhotoAlbum` parameter. - When using `destinationType.NATIVE_URI` and `sourceType.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 #### Tizen Quirks
- options not supported - options not supported
@ -274,7 +288,7 @@ function displayImage(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. 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 ```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 *"> <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 *">

View File

@ -150,6 +150,11 @@
<framework src="CoreGraphics.framework" /> <framework src="CoreGraphics.framework" />
<framework src="AVFoundation.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>
<config-file target="*-Info.plist" parent="NSLocationWhenInUseUsageDescription"> <config-file target="*-Info.plist" parent="NSLocationWhenInUseUsageDescription">
<string></string> <string></string>
</config-file> </config-file>

View File

@ -59,7 +59,6 @@ import android.os.Environment;
import android.provider.DocumentsContract; import android.provider.DocumentsContract;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.util.Base64; import android.util.Base64;
import android.util.Log;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
@ -140,7 +139,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
this.targetWidth = 0; this.targetWidth = 0;
this.encodingType = JPEG; this.encodingType = JPEG;
this.mediaType = PICTURE; this.mediaType = PICTURE;
this.mQuality = 80; this.mQuality = 50;
//Take the values from the arguments if they're not already defined (this is tricky) //Take the values from the arguments if they're not already defined (this is tricky)
this.destType = args.getInt(1); this.destType = args.getInt(1);
@ -366,7 +365,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
intent.putExtra("aspectX", 1); intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1); intent.putExtra("aspectY", 1);
} }
File photo = createCaptureFile(encodingType); File photo = createCaptureFile(JPEG);
croppedUri = Uri.fromFile(photo); croppedUri = Uri.fromFile(photo);
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, croppedUri); intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, croppedUri);
} else { } else {
@ -428,14 +427,14 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
cropIntent, CROP_CAMERA + destType); cropIntent, CROP_CAMERA + destType);
} }
} catch (ActivityNotFoundException anfe) { } 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 { try {
processResultFromCamera(destType, cameraIntent); processResultFromCamera(destType, cameraIntent);
} }
catch (IOException e) catch (IOException e)
{ {
e.printStackTrace(); e.printStackTrace();
Log.e(LOG_TAG, "Unable to write to file"); LOG.e(LOG_TAG, "Unable to write to file");
} }
} }
} }
@ -496,7 +495,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// Double-check the bitmap. // Double-check the bitmap.
if (bitmap == null) { 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!"); this.failPicture("Unable to create bitmap!");
return; return;
} }
@ -536,7 +535,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// Double-check the bitmap. // Double-check the bitmap.
if (bitmap == null) { 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!"); this.failPicture("Unable to create bitmap!");
return; return;
} }
@ -588,6 +587,18 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
this.cordova.getActivity().sendBroadcast(mediaScanIntent); this.cordova.getActivity().sendBroadcast(mediaScanIntent);
} }
/**
* Converts output image format int value to string value of mime type.
* @param outputFormat int Output format of camera API.
* Must be value of either JPEG or PNG constant
* @return String String value of mime type or empty string if mime type is not supported
*/
private String getMimetypeForFormat(int outputFormat) {
if (outputFormat == PNG) return "image/png";
if (outputFormat == JPEG) return "image/jpeg";
return "";
}
private String outputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { private String outputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
@ -639,7 +650,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
int rotate = 0; int rotate = 0;
String fileLocation = FileHelper.getRealPath(uri, this.cordova); 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 // 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 // and there will be no attempt to resize any returned data
@ -647,18 +658,21 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
this.callbackContext.success(fileLocation); this.callbackContext.success(fileLocation);
} }
else { 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, // This is a special case to just return the path as no scaling,
// rotating, nor compressing needs to be done // rotating, nor compressing needs to be done
if (this.targetHeight == -1 && this.targetWidth == -1 && if (this.targetHeight == -1 && this.targetWidth == -1 &&
(destType == FILE_URI || destType == NATIVE_URI) && !this.correctOrientation) { (destType == FILE_URI || destType == NATIVE_URI) && !this.correctOrientation &&
this.callbackContext.success(uri.toString()); mimeType.equalsIgnoreCase(getMimetypeForFormat(encodingType)))
{
this.callbackContext.success(uriString);
} else { } 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 we don't have a valid image so quit.
if (!("image/jpeg".equalsIgnoreCase(mimeType) || "image/png".equalsIgnoreCase(mimeType))) { 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!"); this.failPicture("Unable to retrieve path to picture!");
return; return;
} }
@ -669,7 +683,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
e.printStackTrace(); e.printStackTrace();
} }
if (bitmap == null) { 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!"); this.failPicture("Unable to create bitmap!");
return; return;
} }
@ -683,7 +697,9 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
else if (destType == FILE_URI || destType == NATIVE_URI) { else if (destType == FILE_URI || destType == NATIVE_URI) {
// Did we modify the image? // Did we modify the image?
if ( (this.targetHeight > 0 && this.targetWidth > 0) || if ( (this.targetHeight > 0 && this.targetWidth > 0) ||
(this.correctOrientation && this.orientationCorrected) ) { (this.correctOrientation && this.orientationCorrected) ||
!mimeType.equalsIgnoreCase(getMimetypeForFormat(encodingType)))
{
try { try {
String modifiedPath = this.outputModifiedBitmap(bitmap, uri); String modifiedPath = this.outputModifiedBitmap(bitmap, uri);
// The modified image is cached by the app in order to get around this and not have to delete you // The modified image is cached by the app in order to get around this and not have to delete you
@ -733,7 +749,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
processResultFromCamera(destType, intent); processResultFromCamera(destType, intent);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
Log.e(LOG_TAG, "Unable to write to file"); LOG.e(LOG_TAG, "Unable to write to file");
} }
}// If cancelled }// If cancelled

File diff suppressed because it is too large Load Diff

View File

@ -23,11 +23,12 @@ var HIGHEST_POSSIBLE_Z_INDEX = 2147483647;
function takePicture(success, error, opts) { function takePicture(success, error, opts) {
if (opts && opts[2] === 1) { if (opts && opts[2] === 1) {
capture(success, error); capture(success, error, opts);
} else { } else {
var input = document.createElement('input'); var input = document.createElement('input');
input.style.position = 'relative'; input.style.position = 'relative';
input.style.zIndex = HIGHEST_POSSIBLE_Z_INDEX; input.style.zIndex = HIGHEST_POSSIBLE_Z_INDEX;
input.className = 'cordova-camera-select';
input.type = 'file'; input.type = 'file';
input.name = 'files[]'; input.name = 'files[]';
@ -48,28 +49,36 @@ function takePicture(success, error, opts) {
} }
} }
function capture(success, errorCallback) { function capture(success, errorCallback, opts) {
var localMediaStream; 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 video = document.createElement('video');
var button = document.createElement('button'); var button = document.createElement('button');
var parent = document.createElement('div'); var parent = document.createElement('div');
parent.style.position = 'relative'; parent.style.position = 'relative';
parent.style.zIndex = HIGHEST_POSSIBLE_Z_INDEX; parent.style.zIndex = HIGHEST_POSSIBLE_Z_INDEX;
parent.className = 'cordova-camera-capture';
parent.appendChild(video); parent.appendChild(video);
parent.appendChild(button); parent.appendChild(button);
video.width = 320; video.width = targetWidth;
video.height = 240; video.height = targetHeight;
button.innerHTML = 'Capture!'; button.innerHTML = 'Capture!';
button.onclick = function() { button.onclick = function() {
// create a canvas and capture a frame from video stream // create a canvas and capture a frame from video stream
var canvas = document.createElement('canvas'); var canvas = document.createElement('canvas');
canvas.getContext('2d').drawImage(video, 0, 0, 320, 240); canvas.width = targetWidth;
canvas.height = targetHeight;
canvas.getContext('2d').drawImage(video, 0, 0, targetWidth, targetHeight);
// convert image stored in canvas to base64 encoded image // convert image stored in canvas to base64 encoded image
var imageData = canvas.toDataURL('img/png'); var imageData = canvas.toDataURL('image/png');
imageData = imageData.replace('data:image/png;base64,', ''); imageData = imageData.replace('data:image/png;base64,', '');
// stop video stream, remove video and button. // stop video stream, remove video and button.

View File

@ -240,9 +240,13 @@ static NSString* toBase64(NSData* data) {
- (void)repositionPopover:(CDVInvokedUrlCommand*)command - (void)repositionPopover:(CDVInvokedUrlCommand*)command
{ {
NSDictionary* options = [command argumentAtIndex:0 withDefault:nil]; if (([[self pickerController] pickerPopoverController] != nil) && [[[self pickerController] pickerPopoverController] isPopoverVisible]) {
[self displayPopover:options]; [[[self pickerController] pickerPopoverController] dismissPopoverAnimated:NO];
NSDictionary* options = [command argumentAtIndex:0 withDefault:nil];
[self displayPopover:options];
}
} }
- (NSInteger)integerValueForKey:(NSDictionary*)dict key:(NSString*)key defaultValue:(NSInteger)defaultValue - (NSInteger)integerValueForKey:(NSDictionary*)dict key:(NSString*)key defaultValue:(NSInteger)defaultValue
@ -520,9 +524,11 @@ static NSString* toBase64(NSData* data) {
NSString* mediaType = [info objectForKey:UIImagePickerControllerMediaType]; NSString* mediaType = [info objectForKey:UIImagePickerControllerMediaType];
if ([mediaType isEqualToString:(NSString*)kUTTypeImage]) { if ([mediaType isEqualToString:(NSString*)kUTTypeImage]) {
[weakSelf resultForImage:cameraPicker.pictureOptions info:info completion:^(CDVPluginResult* res) { [weakSelf resultForImage:cameraPicker.pictureOptions info:info completion:^(CDVPluginResult* res) {
[weakSelf.commandDelegate sendPluginResult:res callbackId:cameraPicker.callbackId]; if (![self usesGeolocation] || picker.sourceType != UIImagePickerControllerSourceTypeCamera) {
weakSelf.hasPendingOperation = NO; [weakSelf.commandDelegate sendPluginResult:res callbackId:cameraPicker.callbackId];
weakSelf.pickerController = nil; weakSelf.hasPendingOperation = NO;
weakSelf.pickerController = nil;
}
}]; }];
} }
else { else {

View File

@ -79,10 +79,14 @@ var HIGHEST_POSSIBLE_Z_INDEX = 2147483647;
// Resize method // Resize method
function resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType) { function resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType) {
var tempPhotoFileName = ""; var tempPhotoFileName = "";
var targetContentType = "";
if (encodingType == Camera.EncodingType.PNG) { if (encodingType == Camera.EncodingType.PNG) {
tempPhotoFileName = "camera_cordova_temp_return.png"; tempPhotoFileName = "camera_cordova_temp_return.png";
targetContentType = "image/png";
} else { } else {
tempPhotoFileName = "camera_cordova_temp_return.jpg"; tempPhotoFileName = "camera_cordova_temp_return.jpg";
targetContentType = "image/jpeg";
} }
var storageFolder = getAppData().localFolder; var storageFolder = getAppData().localFolder;
@ -108,7 +112,7 @@ function resizeImage(successCallback, errorCallback, file, targetWidth, targetHe
canvas.getContext("2d").drawImage(this, 0, 0, imageWidth, imageHeight); canvas.getContext("2d").drawImage(this, 0, 0, imageWidth, imageHeight);
var fileContent = canvas.toDataURL(file.contentType).split(',')[1]; var fileContent = canvas.toDataURL(targetContentType).split(',')[1];
var storageFolder = getAppData().localFolder; var storageFolder = getAppData().localFolder;
@ -745,7 +749,7 @@ function takePictureFromCameraWindows(successCallback, errorCallback, args) {
cameraCaptureUI.photoSettings.maxResolution = maxRes; cameraCaptureUI.photoSettings.maxResolution = maxRes;
var cameraPicture; var cameraPicture;
// define focus handler for windows phone 10.0 // define focus handler for windows phone 10.0
var savePhotoOnFocus = function () { var savePhotoOnFocus = function () {
window.removeEventListener("focus", savePhotoOnFocus); window.removeEventListener("focus", savePhotoOnFocus);
@ -760,7 +764,7 @@ function takePictureFromCameraWindows(successCallback, errorCallback, args) {
}; };
// if windows phone 10, add and delete focus eventHandler to capture the focus back from cameraUI to app // 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) { if (navigator.appVersion.indexOf('Windows Phone 10.0') >= 0) {
window.addEventListener("focus", savePhotoOnFocus); window.addEventListener("focus", savePhotoOnFocus);
} }

View File

@ -19,7 +19,7 @@
* *
*/ */
/* globals Camera, resolveLocalFileSystemURI, FileEntry, CameraPopoverOptions, FileTransfer, FileUploadOptions, LocalFileSystem, MSApp */ /* globals Camera, resolveLocalFileSystemURL, FileEntry, CameraPopoverOptions, FileTransfer, FileUploadOptions, LocalFileSystem, MSApp */
/* jshint jasmine: true */ /* jshint jasmine: true */
exports.defineAutoTests = function () { exports.defineAutoTests = function () {
@ -142,11 +142,11 @@ exports.defineManualTests = function (contentEl, createActionButton) {
setPicture(data); setPicture(data);
// TODO: Fix resolveLocalFileSystemURI to work with native-uri. // 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) { if (pictureUrl.indexOf('file:') === 0 || pictureUrl.indexOf('content:') === 0 || pictureUrl.indexOf('ms-appdata:') === 0 || pictureUrl.indexOf('assets-library:') === 0) {
resolveLocalFileSystemURI(data, function (e) { resolveLocalFileSystemURL(data, function (e) {
fileEntry = e; fileEntry = e;
logCallback('resolveLocalFileSystemURI()', true)(e.toURL()); logCallback('resolveLocalFileSystemURL()', true)(e.toURL());
readFile(); readFile();
}, logCallback('resolveLocalFileSystemURI()', false)); }, logCallback('resolveLocalFileSystemURL()', false));
} else if (pictureUrl.indexOf('data:image/jpeg;base64') === 0) { } else if (pictureUrl.indexOf('data:image/jpeg;base64') === 0) {
// do nothing // do nothing
} else { } else {
@ -251,7 +251,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
//cleanup //cleanup
//rename moved file back to original name so other tests can reference image //rename moved file back to original name so other tests can reference image
resolveLocalFileSystemURI(destDirEntry.nativeURL+'moved_file.png', function(fileEntry) { resolveLocalFileSystemURL(destDirEntry.nativeURL+'moved_file.png', function(fileEntry) {
fileEntry.moveTo(destDirEntry, origName, logCallback('FileEntry.moveTo', true), logCallback('FileEntry.moveTo', false)); fileEntry.moveTo(destDirEntry, origName, logCallback('FileEntry.moveTo', true), logCallback('FileEntry.moveTo', false));
console.log('Cleanup: successfully renamed file back to original name'); console.log('Cleanup: successfully renamed file back to original name');
}, function () { }, function () {
@ -259,7 +259,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
}); });
//remove copied file //remove copied file
resolveLocalFileSystemURI(destDirEntry.nativeURL+'copied_file.png', function(fileEntry) { resolveLocalFileSystemURL(destDirEntry.nativeURL+'copied_file.png', function(fileEntry) {
fileEntry.remove(logCallback('FileEntry.remove', true), logCallback('FileEntry.remove', false)); fileEntry.remove(logCallback('FileEntry.remove', true), logCallback('FileEntry.remove', false));
console.log('Cleanup: successfully removed copied file'); console.log('Cleanup: successfully removed copied file');
}, function () { }, function () {

View File

@ -89,26 +89,20 @@ for (var key in Camera) {
* *
* If `Camera.sourceType` is `Camera.PictureSourceType.PHOTOLIBRARY` or * If `Camera.sourceType` is `Camera.PictureSourceType.PHOTOLIBRARY` or
* `Camera.PictureSourceType.SAVEDPHOTOALBUM`, then a dialog displays * `Camera.PictureSourceType.SAVEDPHOTOALBUM`, then a dialog displays
* that allows users to select an existing image. The * that allows users to select an existing image.
* `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 * The return value is sent to the [`cameraSuccess`]{@link module:camera.onSuccess} callback function, in
* one of the following formats, depending on the specified * one of the following formats, depending on the specified
* `cameraOptions`: * `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). * - A `String` representing the image file location on local storage (default).
* *
* You can do whatever you want with the encoded image or URI, for * You can do whatever you want with the encoded image or URI, for
* example: * example:
* *
* - Render the image in an `<img>` tag, as in the example below * - Render the image in an `<img>` tag, as in the example below
*
* - Save the data locally (`LocalStorage`, [Lawnchair](http://brianleroux.github.com/lawnchair/), etc.) * - Save the data locally (`LocalStorage`, [Lawnchair](http://brianleroux.github.com/lawnchair/), etc.)
*
* - Post the data to a remote server * - Post the data to a remote server
* *
* __NOTE__: Photo resolution on newer devices is quite good. Photos * __NOTE__: Photo resolution on newer devices is quite good. Photos

View File

@ -24,6 +24,13 @@
*/ */
module.exports = { 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} * @enum {number}
*/ */
DestinationType:{ DestinationType:{
@ -55,14 +62,20 @@ module.exports = {
ALLMEDIA : 2 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} * @enum {number}
*/ */
PictureSourceType:{ PictureSourceType:{
/** Choose image from picture library (same as SAVEDPHOTOALBUM for Android) */ /** Choose image from the device's photo library (same as SAVEDPHOTOALBUM for Android) */
PHOTOLIBRARY : 0, PHOTOLIBRARY : 0,
/** Take picture from camera */ /** Take picture from camera */
CAMERA : 1, CAMERA : 1,
/** Choose image from picture library (same as PHOTOLIBRARY for Android) */ /** Choose image only from the device's Camera Roll album (same as PHOTOLIBRARY for Android) */
SAVEDPHOTOALBUM : 2 SAVEDPHOTOALBUM : 2
}, },
/** /**

View File

@ -33,7 +33,7 @@ var exec = require('cordova/exec');
* - iOS * - iOS
* *
* @example * @example
* var cameraPopoverHandle = navigator.camera.getPicture(onSuccess, onFail, * navigator.camera.getPicture(onSuccess, onFail,
* { * {
* destinationType: Camera.DestinationType.FILE_URI, * destinationType: Camera.DestinationType.FILE_URI,
* sourceType: Camera.PictureSourceType.PHOTOLIBRARY, * sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
@ -42,13 +42,19 @@ var exec = require('cordova/exec');
* *
* // Reposition the popover if the orientation changes. * // Reposition the popover if the orientation changes.
* window.onorientationchange = function() { * window.onorientationchange = function() {
* var cameraPopoverHandle = new CameraPopoverHandle();
* var cameraPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY); * var cameraPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY);
* cameraPopoverHandle.setPosition(cameraPopoverOptions); * cameraPopoverHandle.setPosition(cameraPopoverOptions);
* } * }
* @module CameraPopoverHandle * @module CameraPopoverHandle
*/ */
var CameraPopoverHandle = function() { var CameraPopoverHandle = function() {
/** Set the position of the popover. /**
* Can be used to reposition the image selection dialog,
* for example, when the device orientation changes.
* @memberof CameraPopoverHandle
* @instance
* @method setPosition
* @param {module:CameraPopoverOptions} popoverOptions * @param {module:CameraPopoverOptions} popoverOptions
*/ */
this.setPosition = function(popoverOptions) { this.setPosition = function(popoverOptions) {