mirror of
https://github.com/apache/cordova-plugin-camera.git
synced 2026-02-03 00:06:46 +08:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d124e03cb9 | ||
|
|
4202fff7ac | ||
|
|
def399fe51 | ||
|
|
82c9f4524a | ||
|
|
a9c18710f2 | ||
|
|
624ddd5ced | ||
|
|
fb871d40e2 | ||
|
|
0cd962466d | ||
|
|
c12206ebc8 | ||
|
|
826aca3524 | ||
|
|
76c129c95e | ||
|
|
fac7a53383 | ||
|
|
1348d2e138 | ||
|
|
c5f5a46e3e | ||
|
|
37dd471d0e | ||
|
|
a19c75253a | ||
|
|
c20e031d42 | ||
|
|
68a1150939 | ||
|
|
9c906b2ab7 | ||
|
|
56b9469313 | ||
|
|
d51e23ad7b | ||
|
|
2cd2528d1c | ||
|
|
5f7f4f3e55 | ||
|
|
20dcaf2cb3 | ||
|
|
b16c5234d5 | ||
|
|
c1948fc0d4 | ||
|
|
f792aaacc3 | ||
|
|
16636d18f2 | ||
|
|
019346d188 | ||
|
|
61b77951e1 | ||
|
|
a060fb36f3 | ||
|
|
77653183dd | ||
|
|
df734a522c | ||
|
|
654286d373 | ||
|
|
76ad059c9c | ||
|
|
5b38453262 | ||
|
|
e48a7e5c5c | ||
|
|
06fcbf05a2 | ||
|
|
9a9081b0d4 | ||
|
|
6f7ce333cc | ||
|
|
0f32b78c82 | ||
|
|
eb009471ab | ||
|
|
1d32ea46f0 |
16
.jshintrc
Normal file
16
.jshintrc
Normal 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
1
.ratignore
Normal file
@@ -0,0 +1 @@
|
||||
TEMPLATE.md
|
||||
8
.travis.yml
Normal file
8
.travis.yml
Normal 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
|
||||
90
README.md
90
README.md
@@ -17,6 +17,8 @@
|
||||
# under the License.
|
||||
-->
|
||||
|
||||
[](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__
|
||||
|
||||
        
|
||||
- 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__
|
||||
|
||||
        
|
||||
- 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.
|
||||
|
||||
---
|
||||
|
||||
        
|
||||
<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].
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
411
appium-tests/android/android.spec.js
Normal file
411
appium-tests/android/android.spec.js
Normal 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);
|
||||
});
|
||||
105
appium-tests/helpers/cameraHelper.js
Normal file
105
appium-tests/helpers/cameraHelper.js
Normal 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);
|
||||
});
|
||||
};
|
||||
264
appium-tests/ios/ios.spec.js
Normal file
264
appium-tests/ios/ios.spec.js
Normal 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);
|
||||
});
|
||||
@@ -1,5 +1,7 @@
|
||||
{{>cdv-license~}}
|
||||
|
||||
[](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
|
||||
|
||||
11
package.json
11
package.json
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
18
plugin.xml
18
plugin.xml
@@ -22,7 +22,7 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:rim="http://www.blackberry.com/ns/widgets"
|
||||
id="cordova-plugin-camera"
|
||||
version="2.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 -->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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'"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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__
|
||||
*
|
||||
*         
|
||||
* - 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__
|
||||
*
|
||||
*         
|
||||
* - iOS
|
||||
*
|
||||
* @example
|
||||
* navigator.camera.cleanup(onSuccess, onFail);
|
||||
*
|
||||
*
|
||||
* function onSuccess() {
|
||||
* console.log("Camera cleanup success.")
|
||||
* }
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
*/
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.getElementById('back').onclick = function () {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
var exec = require('cordova/exec');
|
||||
|
||||
/**
|
||||
/**
|
||||
* @namespace navigator
|
||||
*/
|
||||
|
||||
@@ -30,16 +30,16 @@ var exec = require('cordova/exec');
|
||||
*
|
||||
* __Supported Platforms__
|
||||
*
|
||||
*         
|
||||
* - 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);
|
||||
|
||||
Reference in New Issue
Block a user