mirror of
https://github.com/apache/cordova-plugin-camera.git
synced 2026-04-10 00:00:10 +08:00
Compare commits
5 Commits
rel/5.0.2
...
erisu-loca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf95068ae0 | ||
|
|
ba0b415609 | ||
|
|
b839b4fbbb | ||
|
|
62e1cf5220 | ||
|
|
cde2d17625 |
@@ -12,17 +12,12 @@ image:
|
||||
- Visual Studio 2017
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: "10"
|
||||
- nodejs_version: "12"
|
||||
- nodejs_version: "14"
|
||||
nodejs_version: "6"
|
||||
|
||||
platform:
|
||||
- x86
|
||||
- x64
|
||||
matrix:
|
||||
- PLATFORM: windows-10-store
|
||||
|
||||
install:
|
||||
- ps: Install-Product node $env:nodejs_version
|
||||
- node --version
|
||||
- npm install -g github:apache/cordova-paramedic
|
||||
- npm install -g cordova
|
||||
@@ -30,4 +25,4 @@ install:
|
||||
build: off
|
||||
|
||||
test_script:
|
||||
- cordova-paramedic --config pr\windows-10-store --plugin . --justBuild
|
||||
- cordova-paramedic --config pr\%PLATFORM% --plugin . --justBuild
|
||||
|
||||
22
.asf.yaml
22
.asf.yaml
@@ -1,22 +0,0 @@
|
||||
# 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.
|
||||
|
||||
notifications:
|
||||
commits: commits@cordova.apache.org
|
||||
issues: issues@cordova.apache.org
|
||||
pullrequests_status: issues@cordova.apache.org
|
||||
pullrequests_comment: issues@cordova.apache.org
|
||||
@@ -1,23 +1,10 @@
|
||||
# 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.
|
||||
|
||||
root: true
|
||||
extends: '@cordova/eslint-config/browser'
|
||||
|
||||
overrides:
|
||||
- files: [tests/**/*.js]
|
||||
extends: '@cordova/eslint-config/node-tests'
|
||||
extends: semistandard
|
||||
rules:
|
||||
indent:
|
||||
- error
|
||||
- 4
|
||||
camelcase: off
|
||||
padded-blocks: off
|
||||
operator-linebreak: off
|
||||
no-throw-literal: off
|
||||
@@ -1,3 +0,0 @@
|
||||
.*
|
||||
appveyor.yml
|
||||
tests
|
||||
1
.ratignore
Normal file
1
.ratignore
Normal file
@@ -0,0 +1 @@
|
||||
TEMPLATE.md
|
||||
46
.travis.yml
46
.travis.yml
@@ -1,29 +1,25 @@
|
||||
# This Travis configuration file is built after a Cordova Paramedic
|
||||
# specific template with minimal modifications and adaptations:
|
||||
# https://github.com/apache/cordova-paramedic/blob/master/.travis.yml
|
||||
|
||||
sudo: false
|
||||
|
||||
addons:
|
||||
jwt:
|
||||
# SAUCE_ACCESS_KEY
|
||||
# sauce labs key
|
||||
secure: QivPLlqTVvOo3TJeHxuBOfxU6lho1I0IxQ3b68yntkEQQJko6kzleXHfgjf0a8aw8m38E3+fxaBWF1bGyucGwOLDWY8Ddt2P2xg44zdXH5EXHd9oIqAgngIdzLvUtH3Db2TbQEtIGOkrnNR2STovjqB7vHGLASQrgs4oL7r32/s=
|
||||
|
||||
env:
|
||||
global:
|
||||
- SAUCE_USERNAME=snay
|
||||
- TRAVIS_NODE_VERSION=12
|
||||
- ANDROID_API_LEVEL=29
|
||||
- ANDROID_BUILD_TOOLS_VERSION=29.0.2
|
||||
- TRAVIS_NODE_VERSION=6
|
||||
- ANDROID_API_LEVEL=28
|
||||
- ANDROID_BUILD_TOOLS_VERSION=28.0.3
|
||||
|
||||
language: node_js
|
||||
node_js: 14
|
||||
node_js: 6
|
||||
|
||||
# yaml anchor/alias: https://medium.com/@tommyvn/travis-yml-dry-with-anchors-8b6a3ac1b027
|
||||
|
||||
_ios: &_ios
|
||||
os: osx
|
||||
osx_image: xcode11.6
|
||||
osx_image: xcode10.2
|
||||
|
||||
_android: &_android
|
||||
language: android
|
||||
@@ -35,16 +31,15 @@ _android: &_android
|
||||
- build-tools-$ANDROID_BUILD_TOOLS_VERSION
|
||||
- android-$ANDROID_API_LEVEL
|
||||
licenses:
|
||||
- "android-sdk-preview-license-.+"
|
||||
- "android-sdk-license-.+"
|
||||
- "google-gdk-license-.+"
|
||||
- 'android-sdk-preview-license-.+'
|
||||
- 'android-sdk-license-.+'
|
||||
- 'google-gdk-license-.+'
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# additional tests
|
||||
- env: ADDITIONAL_TESTS_DIR=./tests/ios
|
||||
os: osx
|
||||
osx_image: xcode11.5
|
||||
- env: ADDITIONAL_TESTS=./tests/ios
|
||||
language: objective-c
|
||||
|
||||
# local tests, without saucelabs
|
||||
- env: PLATFORM=local/browser
|
||||
@@ -65,6 +60,8 @@ matrix:
|
||||
- env: PLATFORM=ios-12.2
|
||||
<<: *_ios
|
||||
|
||||
- env: PLATFORM=android-4.4
|
||||
<<: *_android
|
||||
- env: PLATFORM=android-5.1
|
||||
<<: *_android
|
||||
- env: PLATFORM=android-6.0
|
||||
@@ -77,18 +74,16 @@ matrix:
|
||||
<<: *_android
|
||||
- env: PLATFORM=android-8.1
|
||||
<<: *_android
|
||||
- env: PLATFORM=android-9.0
|
||||
<<: *_android
|
||||
|
||||
before_install:
|
||||
# manually install Node for `language: android`
|
||||
- if [[ "$PLATFORM" =~ android ]]; then nvm install $TRAVIS_NODE_VERSION; fi
|
||||
- if [[ "$PLATFORM" =~ android ]]; then nvm install $TRAVIS_NODE_VERSION; fi
|
||||
- node --version
|
||||
- if [[ "$PLATFORM" =~ android ]]; then gradle --version; fi
|
||||
- if [[ "$PLATFORM" =~ ios ]]; then npm install -g ios-deploy; fi
|
||||
- npm install -g cordova
|
||||
# install paramedic if not running on paramedic repo
|
||||
- if ! [[ "$TRAVIS_REPO_SLUG" =~ cordova-paramedic ]]; then npm install -g github:apache/cordova-paramedic; fi
|
||||
- if ! [[ "$TRAVIS_REPO_SLUG" =~ cordova-paramedic ]]; then npm install github:apache/cordova-paramedic\#plugin-test-fixes; fi
|
||||
|
||||
install:
|
||||
- npm install
|
||||
@@ -107,12 +102,11 @@ before_script:
|
||||
PARAMEDIC_COMMAND="cordova-paramedic"
|
||||
fi
|
||||
- PARAMEDIC_BUILDNAME=travis-$TRAVIS_REPO_SLUG-$TRAVIS_JOB_NUMBER
|
||||
|
||||
|
||||
script:
|
||||
- $TEST_COMMAND
|
||||
- |
|
||||
if [[ "$ADDITIONAL_TESTS_DIR" != "" ]];
|
||||
then cd $ADDITIONAL_TESTS_DIR && npm install && npm test;
|
||||
else
|
||||
$PARAMEDIC_COMMAND --config ./pr/$PLATFORM --plugin $PARAMEDIC_PLUGIN_TO_TEST --buildName $PARAMEDIC_BUILDNAME;
|
||||
- if [[ "$ADDITIONAL_TESTS" != "" ]];
|
||||
then cd $ADDITIONAL_TESTS && npm install && npm test;
|
||||
else
|
||||
$PARAMEDIC_COMMAND --config ./pr/$PLATFORM --plugin $PARAMEDIC_PLUGIN_TO_TEST --buildName $PARAMEDIC_BUILDNAME;
|
||||
fi
|
||||
|
||||
@@ -25,13 +25,13 @@ Anyone can contribute to Cordova. And we need your contributions.
|
||||
|
||||
There are multiple ways to contribute: report bugs, improve the docs, and
|
||||
contribute code.
|
||||
|
||||
For instructions on this, start with the
|
||||
|
||||
For instructions on this, start with the
|
||||
[contribution overview](http://cordova.apache.org/contribute/).
|
||||
|
||||
The details are explained there, but the important items are:
|
||||
- Check for Github issues that corresponds to your contribution and link or create them if necessary.
|
||||
- Sign and submit an Apache ICLA (Contributor License Agreement).
|
||||
- Have a Jira issue open that corresponds to your contribution.
|
||||
- Run the tests so your patch doesn't break existing functionality.
|
||||
|
||||
We look forward to your contributions!
|
||||
|
||||
|
||||
24
README.md
24
README.md
@@ -276,14 +276,19 @@ Optional parameters to customize the camera settings.
|
||||
|
||||
### Camera.DestinationType : <code>enum</code>
|
||||
Defines the output format of `Camera.getPicture` call.
|
||||
_Note:_ On iOS passing `DestinationType.NATIVE_URI` along with
|
||||
`PictureSourceType.PHOTOLIBRARY` or `PictureSourceType.SAVEDPHOTOALBUM` will
|
||||
disable any image modifications (resize, quality change, cropping, etc.) due
|
||||
to implementation specific.
|
||||
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Properties**
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| DATA_URL | <code>number</code> | <code>0</code> | Return base64 encoded string. DATA_URL can be very memory intensive and cause app crashes or out of memory errors. Use FILE_URI if possible |
|
||||
| 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) |
|
||||
|
||||
<a name="module_Camera.EncodingType"></a>
|
||||
|
||||
@@ -312,6 +317,9 @@ Defines the output format of `Camera.getPicture` call.
|
||||
|
||||
### Camera.PictureSourceType : <code>enum</code>
|
||||
Defines the output format of `Camera.getPicture` call.
|
||||
_Note:_ On iOS passing `PictureSourceType.PHOTOLIBRARY` or `PictureSourceType.SAVEDPHOTOALBUM`
|
||||
along with `DestinationType.NATIVE_URI` will disable any image modifications (resize, quality
|
||||
change, cropping, etc.) due to implementation specific.
|
||||
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Properties**
|
||||
@@ -427,7 +435,7 @@ 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
|
||||
* instead.
|
||||
* or NATIVE_URI instead.
|
||||
*/
|
||||
navigator.camera.getPicture(onSuccess, onFail, { quality: 25,
|
||||
destinationType: Camera.DestinationType.DATA_URL
|
||||
@@ -474,6 +482,11 @@ displays:
|
||||
// do your thing here!
|
||||
}, 0);
|
||||
|
||||
#### Windows Phone 7 Quirks
|
||||
|
||||
Invoking the native camera application while the device is connected
|
||||
via Zune does not work, and triggers an error callback.
|
||||
|
||||
#### Windows quirks
|
||||
|
||||
On Windows Phone 8.1 using `SAVEDPHOTOALBUM` or `PHOTOLIBRARY` as a source type causes application to suspend until file picker returns the selected image and
|
||||
@@ -490,7 +503,7 @@ More information about Windows Phone 8.1 picker APIs is here: [How to continue y
|
||||
|
||||
- Any `cameraDirection` value results in a back-facing photo. (= You can only use the back camera)
|
||||
|
||||
- **`allowEdit` is unpredictable on Android and it should not be used!** The Android implementation of this plugin tries to find and use an application on the user's device to do image cropping. The plugin has no control over what application the user selects to perform the image cropping and it is very possible that the user could choose an incompatible option and cause the plugin to fail. This sometimes works because most devices come with an application that handles cropping in a way that is compatible with this plugin (Google Photos), but it is unwise to rely on that being the case. If image editing is essential to your application, consider seeking a third party library or plugin that provides its own image editing utility for a more robust solution.
|
||||
- **`allowEdit` is unpredictable on Android and it should not be used!** The Android implementation of this plugin tries to find and use an application on the user's device to do image cropping. The plugin has no control over what application the user selects to perform the image cropping and it is very possible that the user could choose an incompatible option and cause the plugin to fail. This sometimes works because most devices come with an application that handles cropping in a way that is compatible with this plugin (Google Plus Photos), but it is unwise to rely on that being the case. If image editing is essential to your application, consider seeking a third party library or plugin that provides its own image editing utility for a more robust solution.
|
||||
|
||||
- `Camera.PictureSourceType.PHOTOLIBRARY` and `Camera.PictureSourceType.SAVEDPHOTOALBUM` both display the same photo album.
|
||||
|
||||
@@ -500,6 +513,9 @@ More information about Windows Phone 8.1 picker APIs is here: [How to continue y
|
||||
|
||||
- When using `destinationType.FILE_URI`, photos are saved in the application's temporary directory. The contents of the application's temporary directory is deleted when the application ends.
|
||||
|
||||
- When using `destinationType.NATIVE_URI` and `sourceType.CAMERA`, photos are saved in the saved photo album regardless on the value of `saveToPhotoAlbum` parameter.
|
||||
|
||||
- When using `destinationType.NATIVE_URI` and `sourceType.PHOTOLIBRARY` or `sourceType.SAVEDPHOTOALBUM`, all editing options are ignored and link is returned to original picture.
|
||||
|
||||
[android_lifecycle]: http://cordova.apache.org/docs/en/dev/guide/platforms/android/lifecycle.html
|
||||
|
||||
@@ -529,7 +545,7 @@ function setOptions(srcType) {
|
||||
encodingType: Camera.EncodingType.JPEG,
|
||||
mediaType: Camera.MediaType.PICTURE,
|
||||
allowEdit: true,
|
||||
correctOrientation: true
|
||||
correctOrientation: true //Corrects Android orientation quirks
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -20,80 +20,6 @@
|
||||
-->
|
||||
# Release Notes
|
||||
|
||||
### 5.0.2 (May 11, 2021)
|
||||
* [GH-728](https://github.com/apache/cordova-plugin-camera/pull/728) plugin release preparation - audit fix
|
||||
* [GH-700](https://github.com/apache/cordova-plugin-camera/pull/700) Bugfix [issue 665](https://github.com/apache/cordova-plugin-camera/issues/665) - app crashes after taking a picture due to a bug in the camera plugin when app is resumed
|
||||
* [GH-691](https://github.com/apache/cordova-plugin-camera/pull/691) ci: add node-14.x to workflow (#691)
|
||||
|
||||
### 5.0.1 (Nov 04, 2020)
|
||||
|
||||
* [GH-686](https://github.com/apache/cordova-plugin-camera/pull/686) chore(android): add missing apache license header
|
||||
* [GH-685](https://github.com/apache/cordova-plugin-camera/pull/685) fix(ios): correctly append exif on **iOS** 14
|
||||
* [GH-669](https://github.com/apache/cordova-plugin-camera/pull/669) fix(android): save to photo gallery - fixes issues [#341](https://github.com/apache/cordova-plugin-camera/pull/341) & [#577](https://github.com/apache/cordova-plugin-camera/pull/577)
|
||||
* [GH-672](https://github.com/apache/cordova-plugin-camera/pull/672) chore: Fix JIRA links in RELEASENOTES.md
|
||||
* [GH-664](https://github.com/apache/cordova-plugin-camera/pull/664) chore: Update RELEASENOTES
|
||||
|
||||
### 5.0.0 (Sep 14, 2020)
|
||||
|
||||
* [GH-648](https://github.com/apache/cordova-plugin-camera/pull/648) ci(travis): update osx xcode image
|
||||
* [GH-637](https://github.com/apache/cordova-plugin-camera/pull/637) breaking: remove `NATIVE_URI` DestinationType
|
||||
* [GH-628](https://github.com/apache/cordova-plugin-camera/pull/628) breaking: bump project requirements
|
||||
* [GH-634](https://github.com/apache/cordova-plugin-camera/pull/634) chore: remove deprecated `file-transfer` plugin
|
||||
* [GH-632](https://github.com/apache/cordova-plugin-camera/pull/632) fix(android): return error if file url is null
|
||||
* [GH-510](https://github.com/apache/cordova-plugin-camera/pull/510) fix(android): use provider prefix to avoid conflicts other plugin providers
|
||||
* [GH-617](https://github.com/apache/cordova-plugin-camera/pull/617) breaking(android): stop using `CordovaUri` helper class
|
||||
* [GH-630](https://github.com/apache/cordova-plugin-camera/pull/630) chore: add `package-lock.json`
|
||||
* [GH-631](https://github.com/apache/cordova-plugin-camera/pull/631) chore(package): use short notation
|
||||
* [GH-629](https://github.com/apache/cordova-plugin-camera/pull/629) feat: migrate to `@cordova/eslint-config@3.x`
|
||||
* [GH-626](https://github.com/apache/cordova-plugin-camera/pull/626) ci: fix additional tests
|
||||
* [GH-627](https://github.com/apache/cordova-plugin-camera/pull/627) breaking: bump version 5.0.0-dev
|
||||
* [GH-612](https://github.com/apache/cordova-plugin-camera/pull/612) fix(ios): `tempFilePath` called twice if using `CameraUsesGeolocation`
|
||||
* [GH-588](https://github.com/apache/cordova-plugin-camera/pull/588) Cache images in device storage, devices have enough space now.
|
||||
* [GH-508](https://github.com/apache/cordova-plugin-camera/pull/508) docs(readme): app renamed to Google Photos
|
||||
* chore(asf): update git notification settings
|
||||
* [GH-580](https://github.com/apache/cordova-plugin-camera/pull/580) fix(ios): return copy of video when picking from gallery on **iOS** 13
|
||||
* Update CONTRIBUTING.md
|
||||
* [GH-551](https://github.com/apache/cordova-plugin-camera/pull/551) Fix UI API called on a background thread
|
||||
* [GH-576](https://github.com/apache/cordova-plugin-camera/pull/576) ci: updates Node.js versions
|
||||
* [GH-575](https://github.com/apache/cordova-plugin-camera/pull/575) chore(npm): adds ignore list
|
||||
* [GH-513](https://github.com/apache/cordova-plugin-camera/pull/513) docs(README): remove confusing comment
|
||||
* [GH-512](https://github.com/apache/cordova-plugin-camera/pull/512) docs(README): remove orphan **Windows** phone 7 note
|
||||
* [GH-306](https://github.com/apache/cordova-plugin-camera/pull/306) ImagePicker returning same image
|
||||
|
||||
### 4.1.0 (Jun 27, 2019)
|
||||
|
||||
- docs: remove outdated test docs translations ([`06dc38f`](https://github.com/apache/cordova-plugin-camera/commit/06dc38f))
|
||||
- build: remove `.ratignore` file that is not needed any more ([`5dc9527`](https://github.com/apache/cordova-plugin-camera/commit/5dc9527))
|
||||
- chore: fix repo and issue urls and license in package.json and plugin.xml ([`cad8bd1`](https://github.com/apache/cordova-plugin-camera/commit/cad8bd1))
|
||||
- fix: temporarily remove Appium tests to unbreak CI ([#468](https://github.com/apache/cordova-plugin-camera/issues/468)) ([`19d8e2f`](https://github.com/apache/cordova-plugin-camera/commit/19d8e2f))
|
||||
- ci(travis): Update Travis CI configuration for new paramedic ([#455](https://github.com/apache/cordova-plugin-camera/issues/455)) ([`cffd0ac`](https://github.com/apache/cordova-plugin-camera/commit/cffd0ac))
|
||||
- fix(android): Fix NullPointerException error on some Android phones ([#429](https://github.com/apache/cordova-plugin-camera/issues/429)) ([`295e928`](https://github.com/apache/cordova-plugin-camera/commit/295e928))
|
||||
- ci: Update CI Environment Setup for Node.js 6 ([#438](https://github.com/apache/cordova-plugin-camera/issues/438)) ([`fae190e`](https://github.com/apache/cordova-plugin-camera/commit/fae190e))
|
||||
- refactor(android): Enhancement: Camera plugin code cleanup ([#425](https://github.com/apache/cordova-plugin-camera/issues/425)) ([`a13665d`](https://github.com/apache/cordova-plugin-camera/commit/a13665d))
|
||||
- fix(android): Exif data lost on many cases ([#331](https://github.com/apache/cordova-plugin-camera/issues/331)) ([`81b878d`](https://github.com/apache/cordova-plugin-camera/commit/81b878d))
|
||||
- chore(github): Add or update GitHub pull request and issue template ([`b261d31`](https://github.com/apache/cordova-plugin-camera/commit/b261d31))
|
||||
- fix(ios): fixes UIImagePickerController cancel handling for iOS11+ ([#377](https://github.com/apache/cordova-plugin-camera/issues/377)) ([`24c8b6c`](https://github.com/apache/cordova-plugin-camera/commit/24c8b6c))
|
||||
- docs: Remove deprecated platforms from docs ([#394](https://github.com/apache/cordova-plugin-camera/issues/394)) ([`7ddb3df`](https://github.com/apache/cordova-plugin-camera/commit/7ddb3df))
|
||||
- fix(android): return DATA_URL for ALLMEDIA if it's an image ([#382](https://github.com/apache/cordova-plugin-camera/issues/382)) ([`60e7795`](https://github.com/apache/cordova-plugin-camera/commit/60e7795))
|
||||
- refactor(ios): [CB-13813](https://issues.apache.org/jira/browse/CB-13813): Remove old iOS code ([#381](https://github.com/apache/cordova-plugin-camera/issues/381)) ([`ce77aab`](https://github.com/apache/cordova-plugin-camera/commit/ce77aab))
|
||||
- feat(ios): [CB-13865](https://issues.apache.org/jira/browse/CB-13865): (Ipad) Making popover Window Size configurable using popoverOptions - imagePicker ([#314](https://github.com/apache/cordova-plugin-camera/issues/314)) ([`cd72047`](https://github.com/apache/cordova-plugin-camera/commit/cd72047))
|
||||
- chore(types): [CB-13837](https://issues.apache.org/jira/browse/CB-13837): fix TypeScript Definition for CameraPopoverOptions ([#379](https://github.com/apache/cordova-plugin-camera/issues/379)) ([`86b0bf2`](https://github.com/apache/cordova-plugin-camera/commit/86b0bf2))
|
||||
- docs(android): clarify android quirk of cameraDirection ([`a5a3d88`](https://github.com/apache/cordova-plugin-camera/commit/a5a3d88), [`bfbe4a1`](https://github.com/apache/cordova-plugin-camera/commit/bfbe4a1))
|
||||
- chore(release): Bump minor version ([#370](https://github.com/apache/cordova-plugin-camera/issues/370)) ([`eed4433`](https://github.com/apache/cordova-plugin-camera/commit/eed4433))
|
||||
- build: Remove automatic README generation ([#365](https://github.com/apache/cordova-plugin-camera/issues/365)) ([`07e8574`](https://github.com/apache/cordova-plugin-camera/commit/07e8574))
|
||||
- docs: remove JIRA link ([`bcb26fb`](https://github.com/apache/cordova-plugin-camera/commit/bcb26fb))
|
||||
- ci(travis): also accept terms for android sdk `android-27` ([`a346212`](https://github.com/apache/cordova-plugin-camera/commit/a346212))
|
||||
- docs: remove outdated docs translations that haven't been touched for 3 years ([`403682b`](https://github.com/apache/cordova-plugin-camera/commit/403682b))
|
||||
- fix(android): [CB-14097](https://issues.apache.org/jira/browse/CB-14097): Fix crash when selecting some files with getPicture ([#322](https://github.com/apache/cordova-plugin-camera/issues/322)) ([`5c23b65`](https://github.com/apache/cordova-plugin-camera/commit/5c23b65))
|
||||
- fix(browser): [CB-13384](https://issues.apache.org/jira/browse/CB-13384): Added deprecation of video.src compatibility ([#288](https://github.com/apache/cordova-plugin-camera/issues/288)) ([`5163d38`](https://github.com/apache/cordova-plugin-camera/commit/5163d38))
|
||||
- fix(browser): Remove audio flag from getUserMedia ([#284](https://github.com/apache/cordova-plugin-camera/issues/284)) ([`36343a8`](https://github.com/apache/cordova-plugin-camera/commit/36343a8))
|
||||
- docs: replace warning emoji with warning unicode ([#317](https://github.com/apache/cordova-plugin-camera/issues/317)) ([`ead7d5e`](https://github.com/apache/cordova-plugin-camera/commit/ead7d5e))
|
||||
- feat(android): Update engines to use variables ([#323](https://github.com/apache/cordova-plugin-camera/issues/323)) ([`6899c5e`](https://github.com/apache/cordova-plugin-camera/commit/6899c5e))
|
||||
- feat(android): [CB-14017](https://issues.apache.org/jira/browse/CB-14017): Make com.android.support:support-v4 version configurable ([#318](https://github.com/apache/cordova-plugin-camera/issues/318)) ([`e334656`](https://github.com/apache/cordova-plugin-camera/commit/e334656))
|
||||
- refactor(android): [CB-14047](https://issues.apache.org/jira/browse/CB-14047): CameraLauncher: Replacing Repeated String literals with final variables ([#319](https://github.com/apache/cordova-plugin-camera/issues/319)) ([`5ec121b`](https://github.com/apache/cordova-plugin-camera/commit/5ec121b))
|
||||
- fix(windows): [CB-11714](https://issues.apache.org/jira/browse/CB-11714): added extra check for content-type in savePhoto() without options.targetWidth/Height ([#242](https://github.com/apache/cordova-plugin-camera/issues/242)) ([`a201722`](https://github.com/apache/cordova-plugin-camera/commit/a201722), [`dc73954`](https://github.com/apache/cordova-plugin-camera/commit/dc73954), [`dca4b9c`](https://github.com/apache/cordova-plugin-camera/commit/dca4b9c), [`c1b9772`](https://github.com/apache/cordova-plugin-camera/commit/c1b9772), [`eb57b02`](https://github.com/apache/cordova-plugin-camera/commit/eb57b02))
|
||||
|
||||
|
||||
### 4.0.3 (Apr 12, 2018)
|
||||
* [CB-12593](https://issues.apache.org/jira/browse/CB-12593) **Android** Fix potential `FileProvider` conflicts
|
||||
* Fix a mistake in the examples of usage descriptions (#313)
|
||||
|
||||
751
appium-tests/android/android.spec.js
Normal file
751
appium-tests/android/android.spec.js
Normal file
@@ -0,0 +1,751 @@
|
||||
/*
|
||||
*
|
||||
* 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 ParaMedic Appium runner
|
||||
// you can find it here: https://github.com/apache/cordova-paramedic/
|
||||
// it is not necessary to do a full CI setup to run these tests
|
||||
// Run:
|
||||
// node cordova-paramedic/main.js --platform android --plugin cordova-plugin-camera --skipMainTests --target <emulator name>
|
||||
// Please note only Android 5.1 and 4.4 are supported at this point.
|
||||
|
||||
'use strict';
|
||||
|
||||
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_';
|
||||
var CONTEXT_NATIVE_APP = 'NATIVE_APP';
|
||||
|
||||
describe('Camera tests Android.', function () {
|
||||
var driver;
|
||||
// the name of webview context, it will be changed to match needed context if there are named ones:
|
||||
var webviewContext = DEFAULT_WEBVIEW_CONTEXT;
|
||||
// this indicates that the device library has the test picture:
|
||||
var isTestPictureSaved = false;
|
||||
// we need to know the screen width and height to properly click on an image in the gallery:
|
||||
var screenWidth = DEFAULT_SCREEN_WIDTH;
|
||||
var screenHeight = DEFAULT_SCREEN_HEIGHT;
|
||||
// promise count to use in promise ID
|
||||
var promiseCount = 0;
|
||||
// determine if Appium session is created successfully
|
||||
var appiumSessionStarted = false;
|
||||
// determine if camera is present on the device/emulator
|
||||
var cameraAvailable = false;
|
||||
// determine if emulator is within a range of acceptable resolutions able to run these tests
|
||||
var isResolutionBad = true;
|
||||
// a path to the image we add to the gallery before test run
|
||||
var fillerImagePath;
|
||||
var isAndroid7 = getIsAndroid7();
|
||||
|
||||
function getIsAndroid7() {
|
||||
if (global.USE_SAUCE) {
|
||||
return global.SAUCE_CAPS && (parseFloat(global.SAUCE_CAPS.platformVersion) >= 7);
|
||||
} else {
|
||||
// this is most likely null, meaning we cannot determine if it is Android 7 or not
|
||||
// paramedic needs to be modified to receive and pass the platform version when testing locally
|
||||
return global.PLATFORM_VERSION && (parseFloat(global.PLATFORM_VERSION) >= 7);
|
||||
}
|
||||
}
|
||||
|
||||
function getNextPromiseId() {
|
||||
promiseCount += 1;
|
||||
return getCurrentPromiseId();
|
||||
}
|
||||
|
||||
function getCurrentPromiseId() {
|
||||
return PROMISE_PREFIX + promiseCount;
|
||||
}
|
||||
|
||||
function gracefullyFail(error) {
|
||||
fail(error);
|
||||
return driver
|
||||
.quit()
|
||||
.then(function () {
|
||||
return getDriver();
|
||||
});
|
||||
}
|
||||
|
||||
// combinines specified options in all possible variations
|
||||
// you can add more options to test more scenarios
|
||||
function generateOptions() {
|
||||
var sourceTypes = [
|
||||
cameraConstants.PictureSourceType.CAMERA,
|
||||
cameraConstants.PictureSourceType.PHOTOLIBRARY
|
||||
];
|
||||
var destinationTypes = cameraConstants.DestinationType;
|
||||
var encodingTypes = cameraConstants.EncodingType;
|
||||
var allowEditOptions = [ true, false ];
|
||||
var correctOrientationOptions = [ true, false ];
|
||||
|
||||
return cameraHelper.generateSpecs(sourceTypes, destinationTypes, encodingTypes, allowEditOptions, correctOrientationOptions);
|
||||
}
|
||||
|
||||
// invokes Camera.getPicture() with the specified options
|
||||
// and goes through all UI interactions unless 'skipUiInteractions' is true
|
||||
function getPicture(options, skipUiInteractions) {
|
||||
var promiseId = getNextPromiseId();
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
// assign default values
|
||||
if (!options.hasOwnProperty('allowEdit')) {
|
||||
options.allowEdit = true;
|
||||
}
|
||||
if (!options.hasOwnProperty('destinationType')) {
|
||||
options.destinationType = cameraConstants.DestinationType.FILE_URI;
|
||||
}
|
||||
if (!options.hasOwnProperty('sourceType')) {
|
||||
options.destinationType = cameraConstants.PictureSourceType.CAMERA;
|
||||
}
|
||||
|
||||
return driver
|
||||
.context(webviewContext)
|
||||
.execute(cameraHelper.getPicture, [options, promiseId])
|
||||
.context(CONTEXT_NATIVE_APP)
|
||||
.then(function () {
|
||||
if (skipUiInteractions) {
|
||||
return;
|
||||
}
|
||||
// selecting a picture from gallery
|
||||
if (options.hasOwnProperty('sourceType') &&
|
||||
(options.sourceType === cameraConstants.PictureSourceType.PHOTOLIBRARY ||
|
||||
options.sourceType === cameraConstants.PictureSourceType.SAVEDPHOTOALBUM)) {
|
||||
var tapTile = new wd.TouchAction();
|
||||
var swipeRight = new wd.TouchAction();
|
||||
tapTile
|
||||
.tap({
|
||||
x: Math.round(screenWidth / 4),
|
||||
y: Math.round(screenHeight / 4)
|
||||
});
|
||||
swipeRight
|
||||
.press({x: 10, y: Math.round(screenHeight / 4)})
|
||||
.wait(300)
|
||||
.moveTo({x: Math.round(screenWidth - (screenWidth / 8)), y: 0})
|
||||
.wait(1500)
|
||||
.release()
|
||||
.wait(1000);
|
||||
if (options.allowEdit) {
|
||||
return driver
|
||||
// always wait before performing touchAction
|
||||
.sleep(7000)
|
||||
.performTouchAction(tapTile);
|
||||
}
|
||||
return driver
|
||||
.waitForElementByAndroidUIAutomator('new UiSelector().text("Gallery");', 20000)
|
||||
.fail(function () {
|
||||
// If the Gallery button is not present, swipe right to reveal the Gallery button!
|
||||
return driver
|
||||
.performTouchAction(swipeRight)
|
||||
.waitForElementByAndroidUIAutomator('new UiSelector().text("Gallery");', 20000)
|
||||
})
|
||||
.click()
|
||||
// always wait before performing touchAction
|
||||
.sleep(7000)
|
||||
.performTouchAction(tapTile);
|
||||
}
|
||||
// taking a picture from camera
|
||||
return driver
|
||||
.waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*shutter.*")', MINUTE / 2)
|
||||
.click()
|
||||
.waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*done.*")', MINUTE / 2)
|
||||
.click()
|
||||
.then(function () {
|
||||
if (isAndroid7 && options.allowEdit) {
|
||||
return driver
|
||||
.elementByAndroidUIAutomator('new UiSelector().text("Crop picture");', 20000)
|
||||
.click()
|
||||
.fail(function () {
|
||||
// don't freak out just yet...
|
||||
return driver;
|
||||
})
|
||||
.elementByAndroidUIAutomator('new UiSelector().text("JUST ONCE");', 20000)
|
||||
.click()
|
||||
.fail(function () {
|
||||
// maybe someone's hit that "ALWAYS" button?
|
||||
return driver;
|
||||
});
|
||||
}
|
||||
return driver;
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
if (skipUiInteractions) {
|
||||
return;
|
||||
}
|
||||
if (options.allowEdit) {
|
||||
var saveText = isAndroid7 ? 'SAVE' : 'Save';
|
||||
return driver
|
||||
.waitForElementByAndroidUIAutomator('new UiSelector().text("' + saveText + '")', MINUTE)
|
||||
.click();
|
||||
}
|
||||
})
|
||||
.fail(function (failure) {
|
||||
throw failure;
|
||||
});
|
||||
}
|
||||
|
||||
// checks if the picture was successfully taken
|
||||
// if shouldLoad is falsy, ensures that the error callback was called
|
||||
function checkPicture(shouldLoad, options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
return driver
|
||||
.context(webviewContext)
|
||||
.setAsyncScriptTimeout(MINUTE / 2)
|
||||
.executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId(), options, isAndroid7])
|
||||
.then(function (result) {
|
||||
if (shouldLoad) {
|
||||
if (result !== 'OK') {
|
||||
fail(result);
|
||||
}
|
||||
} else if (result.indexOf('ERROR') === -1) {
|
||||
throw 'Unexpected success callback with result: ' + result;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// deletes the latest image from the gallery
|
||||
function deleteImage() {
|
||||
var holdTile = new wd.TouchAction();
|
||||
holdTile
|
||||
.press({x: Math.round(screenWidth / 4), y: Math.round(screenHeight / 5)})
|
||||
.wait(1000)
|
||||
.release();
|
||||
return driver
|
||||
// always wait before performing touchAction
|
||||
.sleep(7000)
|
||||
.performTouchAction(holdTile)
|
||||
.elementByAndroidUIAutomator('new UiSelector().text("Delete")')
|
||||
.then(function (element) {
|
||||
return element
|
||||
.click()
|
||||
.elementByAndroidUIAutomator('new UiSelector().text("OK")')
|
||||
.click();
|
||||
}, function () {
|
||||
// couldn't find Delete menu item. Possibly there is no image.
|
||||
return driver;
|
||||
});
|
||||
}
|
||||
|
||||
function getDriver() {
|
||||
driver = wdHelper.getDriver('Android');
|
||||
return driver.getWebviewContext()
|
||||
.then(function(context) {
|
||||
webviewContext = context;
|
||||
return driver.context(webviewContext);
|
||||
})
|
||||
.waitForDeviceReady()
|
||||
.injectLibraries()
|
||||
.then(function () {
|
||||
var options = {
|
||||
quality: 50,
|
||||
allowEdit: false,
|
||||
sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
|
||||
saveToPhotoAlbum: false,
|
||||
targetWidth: 210,
|
||||
targetHeight: 210
|
||||
};
|
||||
return driver
|
||||
.then(function () { return getPicture(options, true); })
|
||||
.context(CONTEXT_NATIVE_APP)
|
||||
// case insensitive select, will be handy with Android 7 support
|
||||
.elementByXPath('//android.widget.Button[translate(@text, "alow", "ALOW")="ALLOW"]')
|
||||
.click()
|
||||
.fail(function noAlert() { })
|
||||
.deviceKeyEvent(BACK_BUTTON)
|
||||
.sleep(2000)
|
||||
.elementById('action_bar_title')
|
||||
.then(function () {
|
||||
// success means we're still in native app
|
||||
return driver
|
||||
.deviceKeyEvent(BACK_BUTTON);
|
||||
}, function () {
|
||||
// error means we're already in webview
|
||||
return driver;
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
// doing it inside a function because otherwise
|
||||
// it would not hook up to the webviewContext var change
|
||||
// in the first methods of this chain
|
||||
return driver.context(webviewContext);
|
||||
})
|
||||
.deleteFillerImage(fillerImagePath)
|
||||
.then(function () {
|
||||
fillerImagePath = null;
|
||||
})
|
||||
.addFillerImage()
|
||||
.then(function (result) {
|
||||
if (result && result.indexOf('ERROR:') === 0) {
|
||||
throw new Error(result);
|
||||
} else {
|
||||
fillerImagePath = result;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function recreateSession() {
|
||||
return driver
|
||||
.quit()
|
||||
.finally(function () {
|
||||
return getDriver();
|
||||
});
|
||||
}
|
||||
|
||||
function tryRunSpec(spec) {
|
||||
return driver
|
||||
.then(spec)
|
||||
.fail(function () {
|
||||
return recreateSession()
|
||||
.then(spec)
|
||||
.fail(function() {
|
||||
return recreateSession()
|
||||
.then(spec);
|
||||
});
|
||||
})
|
||||
.fail(gracefullyFail);
|
||||
}
|
||||
|
||||
// produces a generic spec function which
|
||||
// takes a picture with specified options
|
||||
// and then verifies it
|
||||
function generateSpec(options) {
|
||||
return function () {
|
||||
return driver
|
||||
.then(function () {
|
||||
return getPicture(options);
|
||||
})
|
||||
.then(function () {
|
||||
return checkPicture(true, options);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function checkSession(done, skipResolutionCheck) {
|
||||
if (!appiumSessionStarted) {
|
||||
fail('Failed to start a session ' + (lastFailureReason ? lastFailureReason : ''));
|
||||
done();
|
||||
}
|
||||
if (!skipResolutionCheck && isResolutionBad) {
|
||||
fail('The resolution of this target device is not within the appropriate range of width: blah-blah and height: bleh-bleh. The target\'s current resolution is: ' + isResolutionBad);
|
||||
}
|
||||
}
|
||||
|
||||
function checkCamera(options, pending) {
|
||||
if (!cameraAvailable) {
|
||||
pending('Skipping because this test requires a functioning camera on the Android device/emulator, and this test suite\'s functional camera test failed on your target environment.');
|
||||
} else if (isAndroid7 && options.allowEdit) {
|
||||
// TODO: Check if it is fixed some day
|
||||
pending('Skipping because can\'t test with allowEdit=true on Android 7: getting unexpected "Camera cancelled" message.');
|
||||
} else if (isAndroid7 && (options.sourceType !== cameraConstants.PictureSourceType.CAMERA)) {
|
||||
pending('Skipping because can\'t click on the gallery tile on Android 7.');
|
||||
}
|
||||
}
|
||||
|
||||
afterAll(function (done) {
|
||||
checkSession(done);
|
||||
driver
|
||||
.quit()
|
||||
.done(done);
|
||||
}, MINUTE);
|
||||
|
||||
it('camera.ui.util configuring driver and starting a session', function (done) {
|
||||
// retry up to 3 times
|
||||
getDriver()
|
||||
.fail(function () {
|
||||
return getDriver()
|
||||
.fail(function () {
|
||||
return getDriver()
|
||||
.fail(fail);
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
appiumSessionStarted = true;
|
||||
})
|
||||
.done(done);
|
||||
}, 30 * MINUTE);
|
||||
|
||||
it('camera.ui.util determine screen dimensions', function (done) {
|
||||
checkSession(done, /*skipResolutionCheck?*/ true); // skip the resolution check here since we are about to find out in this spec!
|
||||
driver
|
||||
.context(CONTEXT_NATIVE_APP)
|
||||
.getWindowSize()
|
||||
.then(function (size) {
|
||||
screenWidth = Number(size.width);
|
||||
screenHeight = Number(size.height);
|
||||
isResolutionBad = false;
|
||||
/*
|
||||
TODO: what are acceptable resolution values?
|
||||
need to check what the emulators used in CI return.
|
||||
and also what local device definitions work and dont
|
||||
*/
|
||||
})
|
||||
.done(done);
|
||||
}, MINUTE);
|
||||
|
||||
it('camera.ui.util determine camera availability', function (done) {
|
||||
checkSession(done);
|
||||
var opts = {
|
||||
sourceType: cameraConstants.PictureSourceType.CAMERA,
|
||||
saveToPhotoAlbum: false
|
||||
};
|
||||
|
||||
return driver
|
||||
.then(function () {
|
||||
return getPicture(opts);
|
||||
})
|
||||
.then(function () {
|
||||
cameraAvailable = true;
|
||||
}, function () {
|
||||
return recreateSession();
|
||||
})
|
||||
.done(done);
|
||||
}, 5 * MINUTE);
|
||||
|
||||
describe('Specs.', function () {
|
||||
// getPicture() with saveToPhotoLibrary = true
|
||||
it('camera.ui.spec.1 Saving a picture to the photo library', function (done) {
|
||||
var opts = {
|
||||
quality: 50,
|
||||
allowEdit: false,
|
||||
sourceType: cameraConstants.PictureSourceType.CAMERA,
|
||||
saveToPhotoAlbum: true
|
||||
};
|
||||
checkSession(done);
|
||||
checkCamera(opts, pending);
|
||||
|
||||
var spec = generateSpec(opts);
|
||||
tryRunSpec(spec)
|
||||
.then(function () {
|
||||
isTestPictureSaved = true;
|
||||
})
|
||||
.done(done);
|
||||
}, 10 * MINUTE);
|
||||
|
||||
// getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY
|
||||
it('camera.ui.spec.2 Selecting only videos', function (done) {
|
||||
checkSession(done);
|
||||
var spec = function () {
|
||||
var options = { sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
|
||||
mediaType: cameraConstants.MediaType.VIDEO };
|
||||
return driver
|
||||
.then(function () {
|
||||
return getPicture(options, true);
|
||||
})
|
||||
.context(CONTEXT_NATIVE_APP)
|
||||
.then(function () {
|
||||
// try to find "Gallery" menu item
|
||||
// if there's none, the gallery should be already opened
|
||||
return driver
|
||||
.waitForElementByAndroidUIAutomator('new UiSelector().text("Gallery")', 20000)
|
||||
.then(function (element) {
|
||||
return element.click();
|
||||
}, function () {
|
||||
return driver;
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
// if the gallery is opened on the videos page,
|
||||
// there should be a "Choose video" or "Select video" caption
|
||||
var videoSelector = isAndroid7 ? 'new UiSelector().text("Select video")' : 'new UiSelector().text("Choose video")';
|
||||
return driver
|
||||
.elementByAndroidUIAutomator(videoSelector)
|
||||
.fail(function () {
|
||||
throw 'Couldn\'t find a "Choose/select video" element.';
|
||||
});
|
||||
})
|
||||
.deviceKeyEvent(BACK_BUTTON)
|
||||
.elementByAndroidUIAutomator('new UiSelector().text("Gallery")')
|
||||
.deviceKeyEvent(BACK_BUTTON)
|
||||
.finally(function () {
|
||||
return driver
|
||||
.elementById('action_bar_title')
|
||||
.then(function () {
|
||||
// success means we're still in native app
|
||||
return driver
|
||||
.deviceKeyEvent(BACK_BUTTON)
|
||||
// give native app some time to close
|
||||
.sleep(2000)
|
||||
// try again! because every ~30th build
|
||||
// on Sauce Labs this backbutton doesn't work
|
||||
.elementById('action_bar_title')
|
||||
.then(function () {
|
||||
// success means we're still in native app
|
||||
return driver
|
||||
.deviceKeyEvent(BACK_BUTTON);
|
||||
}, function () {
|
||||
// error means we're already in webview
|
||||
return driver;
|
||||
});
|
||||
}, function () {
|
||||
// error means we're already in webview
|
||||
return driver;
|
||||
});
|
||||
});
|
||||
};
|
||||
tryRunSpec(spec).done(done);
|
||||
}, 10 * MINUTE);
|
||||
|
||||
// getPicture(), then dismiss
|
||||
// wait for the error callback to be called
|
||||
it('camera.ui.spec.3 Dismissing the camera', function (done) {
|
||||
var options = {
|
||||
quality: 50,
|
||||
allowEdit: true,
|
||||
sourceType: cameraConstants.PictureSourceType.CAMERA,
|
||||
destinationType: cameraConstants.DestinationType.FILE_URI
|
||||
};
|
||||
checkSession(done);
|
||||
checkCamera(options, pending);
|
||||
var spec = function () {
|
||||
return driver
|
||||
.then(function () {
|
||||
return getPicture(options, true);
|
||||
})
|
||||
.context(CONTEXT_NATIVE_APP)
|
||||
.waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*cancel.*")', MINUTE / 2)
|
||||
.click()
|
||||
.then(function () {
|
||||
return checkPicture(false);
|
||||
});
|
||||
};
|
||||
|
||||
tryRunSpec(spec).done(done);
|
||||
}, 10 * MINUTE);
|
||||
|
||||
// getPicture(), then take picture but dismiss the edit
|
||||
// wait for the error callback to be called
|
||||
it('camera.ui.spec.4 Dismissing the edit', function (done) {
|
||||
var options = {
|
||||
quality: 50,
|
||||
allowEdit: true,
|
||||
sourceType: cameraConstants.PictureSourceType.CAMERA,
|
||||
destinationType: cameraConstants.DestinationType.FILE_URI
|
||||
};
|
||||
checkSession(done);
|
||||
checkCamera(options, pending);
|
||||
var spec = function () {
|
||||
return driver
|
||||
.then(function () {
|
||||
return getPicture(options, true);
|
||||
})
|
||||
.waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*shutter.*")', MINUTE / 2)
|
||||
.click()
|
||||
.waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*done.*")', MINUTE / 2)
|
||||
.click()
|
||||
.then(function () {
|
||||
if (isAndroid7 && options.allowEdit) {
|
||||
return driver
|
||||
.waitForElementByAndroidUIAutomator('new UiSelector().text("Crop picture");', 20000)
|
||||
.click()
|
||||
.waitForElementByAndroidUIAutomator('new UiSelector().text("JUST ONCE");', 20000)
|
||||
.click()
|
||||
.deviceKeyEvent(BACK_BUTTON);
|
||||
}
|
||||
return driver
|
||||
.waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*discard.*")', MINUTE / 2)
|
||||
.click();
|
||||
})
|
||||
.then(function () {
|
||||
return checkPicture(false);
|
||||
});
|
||||
};
|
||||
|
||||
tryRunSpec(spec).done(done);
|
||||
}, 10 * MINUTE);
|
||||
|
||||
it('camera.ui.spec.5 Verifying target image size, sourceType=CAMERA', function (done) {
|
||||
var opts = {
|
||||
quality: 50,
|
||||
allowEdit: false,
|
||||
sourceType: cameraConstants.PictureSourceType.CAMERA,
|
||||
saveToPhotoAlbum: false,
|
||||
targetWidth: 210,
|
||||
targetHeight: 210
|
||||
};
|
||||
checkSession(done);
|
||||
checkCamera(opts, pending);
|
||||
var spec = generateSpec(opts);
|
||||
|
||||
tryRunSpec(spec).done(done);
|
||||
}, 10 * MINUTE);
|
||||
|
||||
it('camera.ui.spec.6 Verifying target image size, sourceType=PHOTOLIBRARY', function (done) {
|
||||
var opts = {
|
||||
quality: 50,
|
||||
allowEdit: false,
|
||||
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
|
||||
saveToPhotoAlbum: false,
|
||||
targetWidth: 210,
|
||||
targetHeight: 210
|
||||
};
|
||||
checkSession(done);
|
||||
checkCamera(opts, pending);
|
||||
var spec = generateSpec(opts);
|
||||
|
||||
tryRunSpec(spec).done(done);
|
||||
}, 10 * MINUTE);
|
||||
|
||||
it('camera.ui.spec.7 Verifying target image size, sourceType=CAMERA, DestinationType=NATIVE_URI', function (done) {
|
||||
var opts = {
|
||||
quality: 50,
|
||||
allowEdit: true,
|
||||
sourceType: cameraConstants.PictureSourceType.CAMERA,
|
||||
destinationType: cameraConstants.DestinationType.NATIVE_URI,
|
||||
saveToPhotoAlbum: false,
|
||||
targetWidth: 210,
|
||||
targetHeight: 210
|
||||
};
|
||||
checkSession(done);
|
||||
checkCamera(opts, pending);
|
||||
var spec = generateSpec(opts);
|
||||
|
||||
tryRunSpec(spec).done(done);
|
||||
}, 10 * MINUTE);
|
||||
|
||||
it('camera.ui.spec.8 Verifying target image size, sourceType=PHOTOLIBRARY, DestinationType=NATIVE_URI', function (done) {
|
||||
var opts = {
|
||||
quality: 50,
|
||||
allowEdit: false,
|
||||
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
|
||||
destinationType: cameraConstants.DestinationType.NATIVE_URI,
|
||||
saveToPhotoAlbum: false,
|
||||
targetWidth: 210,
|
||||
targetHeight: 210
|
||||
};
|
||||
checkSession(done);
|
||||
checkCamera(opts, pending);
|
||||
|
||||
var spec = generateSpec(opts);
|
||||
tryRunSpec(spec).done(done);
|
||||
}, 10 * MINUTE);
|
||||
|
||||
it('camera.ui.spec.9 Verifying target image size, sourceType=CAMERA, DestinationType=NATIVE_URI, quality=100', function (done) {
|
||||
var opts = {
|
||||
quality: 50,
|
||||
allowEdit: true,
|
||||
sourceType: cameraConstants.PictureSourceType.CAMERA,
|
||||
destinationType: cameraConstants.DestinationType.NATIVE_URI,
|
||||
saveToPhotoAlbum: false,
|
||||
targetWidth: 305,
|
||||
targetHeight: 305
|
||||
};
|
||||
checkSession(done);
|
||||
checkCamera(opts, pending);
|
||||
var spec = generateSpec(opts);
|
||||
|
||||
tryRunSpec(spec).done(done);
|
||||
}, 10 * MINUTE);
|
||||
|
||||
it('camera.ui.spec.10 Verifying target image size, sourceType=PHOTOLIBRARY, DestinationType=NATIVE_URI, quality=100', function (done) {
|
||||
var opts = {
|
||||
quality: 100,
|
||||
allowEdit: true,
|
||||
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
|
||||
destinationType: cameraConstants.DestinationType.NATIVE_URI,
|
||||
saveToPhotoAlbum: false,
|
||||
targetWidth: 305,
|
||||
targetHeight: 305
|
||||
};
|
||||
checkSession(done);
|
||||
checkCamera(opts, pending);
|
||||
var spec = generateSpec(opts);
|
||||
|
||||
tryRunSpec(spec).done(done);
|
||||
}, 10 * MINUTE);
|
||||
|
||||
// combine various options for getPicture()
|
||||
generateOptions().forEach(function (spec) {
|
||||
it('camera.ui.spec.11.' + spec.id + ' Combining options. ' + spec.description, function (done) {
|
||||
checkSession(done);
|
||||
checkCamera(spec.options, pending);
|
||||
|
||||
var s = generateSpec(spec.options);
|
||||
tryRunSpec(s).done(done);
|
||||
}, 10 * MINUTE);
|
||||
});
|
||||
|
||||
it('camera.ui.util Delete filler picture from device library', function (done) {
|
||||
if (isAndroid7 || global.USE_SAUCE) {
|
||||
pending();
|
||||
}
|
||||
driver
|
||||
.context(webviewContext)
|
||||
.deleteFillerImage(fillerImagePath)
|
||||
.done(done);
|
||||
}, MINUTE);
|
||||
|
||||
it('camera.ui.util Delete taken picture from device library', function (done) {
|
||||
if (isAndroid7 || global.USE_SAUCE) {
|
||||
pending();
|
||||
}
|
||||
checkSession(done);
|
||||
if (!isTestPictureSaved) {
|
||||
// couldn't save test picture earlier, so nothing to delete here
|
||||
done();
|
||||
return;
|
||||
}
|
||||
// delete exactly one latest picture
|
||||
// this should be the picture we've taken in the first spec
|
||||
driver
|
||||
.context(CONTEXT_NATIVE_APP)
|
||||
.deviceKeyEvent(BACK_BUTTON)
|
||||
.sleep(1000)
|
||||
.deviceKeyEvent(BACK_BUTTON)
|
||||
.sleep(1000)
|
||||
.deviceKeyEvent(BACK_BUTTON)
|
||||
.elementById('Apps')
|
||||
.click()
|
||||
.then(function () {
|
||||
return driver
|
||||
.elementByXPath('//android.widget.Button[@text="OK"]')
|
||||
.click()
|
||||
.fail(function () {
|
||||
// no cling is all right
|
||||
// it is not a brand new emulator, then
|
||||
});
|
||||
})
|
||||
.elementByAndroidUIAutomator('new UiSelector().text("Gallery")')
|
||||
.click()
|
||||
.elementByAndroidUIAutomator('new UiSelector().textContains("Pictures")')
|
||||
.click()
|
||||
.then(deleteImage)
|
||||
.deviceKeyEvent(BACK_BUTTON)
|
||||
.sleep(1000)
|
||||
.deviceKeyEvent(BACK_BUTTON)
|
||||
.sleep(1000)
|
||||
.deviceKeyEvent(BACK_BUTTON)
|
||||
.fail(fail)
|
||||
.finally(done);
|
||||
}, 3 * MINUTE);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
311
appium-tests/helpers/cameraHelper.js
Normal file
311
appium-tests/helpers/cameraHelper.js
Normal file
@@ -0,0 +1,311 @@
|
||||
/* global Q, resolveLocalFileSystemURL, Camera, cordova */
|
||||
/*
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var cameraConstants = require('../../www/CameraConstants');
|
||||
|
||||
function findKeyByValue(set, value) {
|
||||
for (var k in set) {
|
||||
if (set.hasOwnProperty(k)) {
|
||||
if (set[k] == value) {
|
||||
return k;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getDescription(spec) {
|
||||
var desc = '';
|
||||
|
||||
desc += 'sourceType: ' + findKeyByValue(cameraConstants.PictureSourceType, spec.options.sourceType);
|
||||
desc += ', destinationType: ' + findKeyByValue(cameraConstants.DestinationType, spec.options.destinationType);
|
||||
desc += ', encodingType: ' + findKeyByValue(cameraConstants.EncodingType, spec.options.encodingType);
|
||||
desc += ', allowEdit: ' + spec.options.allowEdit.toString();
|
||||
desc += ', correctOrientation: ' + spec.options.correctOrientation.toString();
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
module.exports.generateSpecs = function (sourceTypes, destinationTypes, encodingTypes, allowEditOptions, correctOrientationOptions) {
|
||||
var destinationType,
|
||||
sourceType,
|
||||
encodingType,
|
||||
allowEdit,
|
||||
correctOrientation,
|
||||
specs = [],
|
||||
id = 1;
|
||||
for (destinationType in destinationTypes) {
|
||||
if (destinationTypes.hasOwnProperty(destinationType)) {
|
||||
for (sourceType in sourceTypes) {
|
||||
if (sourceTypes.hasOwnProperty(sourceType)) {
|
||||
for (encodingType in encodingTypes) {
|
||||
if (encodingTypes.hasOwnProperty(encodingType)) {
|
||||
for (allowEdit in allowEditOptions) {
|
||||
if (allowEditOptions.hasOwnProperty(allowEdit)) {
|
||||
for (correctOrientation in correctOrientationOptions) {
|
||||
// if taking picture from photolibrary, don't vary 'correctOrientation' option
|
||||
if ((sourceTypes[sourceType] === cameraConstants.PictureSourceType.PHOTOLIBRARY ||
|
||||
sourceTypes[sourceType] === cameraConstants.PictureSourceType.SAVEDPHOTOALBUM) &&
|
||||
correctOrientation === true) { continue; }
|
||||
var spec = {
|
||||
'id': id++,
|
||||
'options': {
|
||||
'destinationType': destinationTypes[destinationType],
|
||||
'sourceType': sourceTypes[sourceType],
|
||||
'encodingType': encodingTypes[encodingType],
|
||||
'allowEdit': allowEditOptions[allowEdit],
|
||||
'saveToPhotoAlbum': false,
|
||||
'correctOrientation': correctOrientationOptions[correctOrientation]
|
||||
}
|
||||
};
|
||||
spec.description = getDescription(spec);
|
||||
specs.push(spec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return specs;
|
||||
};
|
||||
|
||||
// calls getPicture() and saves the result in promise
|
||||
// note that this function is executed in the context of tested app
|
||||
// and not in the context of tests
|
||||
module.exports.getPicture = function (opts, pid) {
|
||||
if (navigator._appiumPromises[pid - 1]) {
|
||||
navigator._appiumPromises[pid - 1] = null;
|
||||
}
|
||||
navigator._appiumPromises[pid] = Q.defer();
|
||||
navigator.camera.getPicture(function (result) {
|
||||
navigator._appiumPromises[pid].resolve(result);
|
||||
}, function (err) {
|
||||
navigator._appiumPromises[pid].reject(err);
|
||||
}, opts);
|
||||
};
|
||||
|
||||
// verifies taken picture when the promise is resolved,
|
||||
// calls a callback with 'OK' if everything is good,
|
||||
// calls a callback with 'ERROR: <error message>' if something is wrong
|
||||
// note that this function is executed in the context of tested app
|
||||
// and not in the context of tests
|
||||
module.exports.checkPicture = function (pid, options, skipContentCheck, cb) {
|
||||
var isIos = cordova.platformId === "ios";
|
||||
var isAndroid = cordova.platformId === "android";
|
||||
// skip image type check if it's unmodified on Android:
|
||||
// https://github.com/apache/cordova-plugin-camera/#android-quirks-1
|
||||
var skipFileTypeCheckAndroid = isAndroid && options.quality === 100 &&
|
||||
!options.targetWidth && !options.targetHeight &&
|
||||
!options.correctOrientation;
|
||||
|
||||
// Skip image type check if destination is NATIVE_URI and source - device's photoalbum
|
||||
// https://github.com/apache/cordova-plugin-camera/#ios-quirks-1
|
||||
var skipFileTypeCheckiOS = isIos && options.destinationType === Camera.DestinationType.NATIVE_URI &&
|
||||
(options.sourceType === Camera.PictureSourceType.PHOTOLIBRARY ||
|
||||
options.sourceType === Camera.PictureSourceType.SAVEDPHOTOALBUM);
|
||||
|
||||
var skipFileTypeCheck = skipFileTypeCheckAndroid || skipFileTypeCheckiOS;
|
||||
|
||||
var desiredType = 'JPEG';
|
||||
var mimeType = 'image/jpeg';
|
||||
if (options.encodingType === Camera.EncodingType.PNG) {
|
||||
desiredType = 'PNG';
|
||||
mimeType = 'image/png';
|
||||
}
|
||||
|
||||
function errorCallback(msg) {
|
||||
if (msg.hasOwnProperty('message')) {
|
||||
msg = msg.message;
|
||||
}
|
||||
cb('ERROR: ' + msg);
|
||||
}
|
||||
|
||||
// verifies the image we get from plugin
|
||||
function verifyResult(result) {
|
||||
if (result.length === 0) {
|
||||
errorCallback('The result is empty.');
|
||||
return;
|
||||
} else if (isIos && options.destinationType === Camera.DestinationType.NATIVE_URI && result.indexOf('assets-library:') !== 0) {
|
||||
errorCallback('Expected "' + result.substring(0, 150) + '"to start with "assets-library:"');
|
||||
return;
|
||||
} else if (isIos && options.destinationType === Camera.DestinationType.FILE_URI && result.indexOf('file:') !== 0) {
|
||||
errorCallback('Expected "' + result.substring(0, 150) + '"to start with "file:"');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
window.atob(result);
|
||||
// if we got here it is a base64 string (DATA_URL)
|
||||
result = "data:" + mimeType + ";base64," + result;
|
||||
} catch (e) {
|
||||
// not DATA_URL
|
||||
if (options.destinationType === Camera.DestinationType.DATA_URL) {
|
||||
errorCallback('Expected ' + result.substring(0, 150) + 'not to be DATA_URL');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (result.indexOf('file:') === 0 ||
|
||||
result.indexOf('content:') === 0 ||
|
||||
result.indexOf('assets-library:') === 0) {
|
||||
|
||||
if (!window.resolveLocalFileSystemURL) {
|
||||
errorCallback('Cannot read file. Please install cordova-plugin-file to fix this.');
|
||||
return;
|
||||
}
|
||||
if (skipContentCheck) {
|
||||
cb('OK');
|
||||
return;
|
||||
}
|
||||
resolveLocalFileSystemURL(result, function (entry) {
|
||||
if (skipFileTypeCheck) {
|
||||
displayFile(entry);
|
||||
} else {
|
||||
verifyFile(entry);
|
||||
}
|
||||
}, function (err) {
|
||||
errorCallback(err);
|
||||
});
|
||||
} else {
|
||||
displayImage(result);
|
||||
}
|
||||
} catch (e) {
|
||||
errorCallback(e);
|
||||
}
|
||||
}
|
||||
|
||||
// verifies that the file type matches the requested type
|
||||
function verifyFile(entry) {
|
||||
try {
|
||||
var reader = new FileReader();
|
||||
reader.onloadend = function(e) {
|
||||
var arr = (new Uint8Array(e.target.result)).subarray(0, 4);
|
||||
var header = '';
|
||||
for(var i = 0; i < arr.length; i++) {
|
||||
header += arr[i].toString(16);
|
||||
}
|
||||
var actualType = 'unknown';
|
||||
|
||||
switch (header) {
|
||||
case "89504e47":
|
||||
actualType = 'PNG';
|
||||
break;
|
||||
case 'ffd8ffe0':
|
||||
case 'ffd8ffe1':
|
||||
case 'ffd8ffe2':
|
||||
actualType = 'JPEG';
|
||||
break;
|
||||
}
|
||||
|
||||
if (actualType === desiredType) {
|
||||
displayFile(entry);
|
||||
} else {
|
||||
errorCallback('File type mismatch. Expected ' + desiredType + ', got ' + actualType);
|
||||
}
|
||||
};
|
||||
reader.onerror = function (e) {
|
||||
errorCallback(e);
|
||||
};
|
||||
entry.file(function (file) {
|
||||
reader.readAsArrayBuffer(file);
|
||||
}, function (e) {
|
||||
errorCallback(e);
|
||||
});
|
||||
} catch (e) {
|
||||
errorCallback(e);
|
||||
}
|
||||
}
|
||||
|
||||
// reads the file, then displays the image
|
||||
function displayFile(entry) {
|
||||
function onFileReceived(file) {
|
||||
var reader = new FileReader();
|
||||
reader.onerror = function (e) {
|
||||
errorCallback(e);
|
||||
};
|
||||
reader.onloadend = function (evt) {
|
||||
displayImage(evt.target.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
entry.file(onFileReceived, function (e) {
|
||||
errorCallback(e);
|
||||
});
|
||||
}
|
||||
|
||||
function displayImage(image) {
|
||||
try {
|
||||
var imgEl = document.getElementById('camera_test_image');
|
||||
if (!imgEl) {
|
||||
imgEl = document.createElement('img');
|
||||
imgEl.id = 'camera_test_image';
|
||||
document.body.appendChild(imgEl);
|
||||
}
|
||||
var timedOut = false;
|
||||
var loadTimeout = setTimeout(function () {
|
||||
timedOut = true;
|
||||
imgEl.src = '';
|
||||
errorCallback('The image did not load: ' + image.substring(0, 150));
|
||||
}, 10000);
|
||||
var done = function (status) {
|
||||
if (!timedOut) {
|
||||
clearTimeout(loadTimeout);
|
||||
imgEl.src = '';
|
||||
cb(status);
|
||||
}
|
||||
};
|
||||
imgEl.onload = function () {
|
||||
try {
|
||||
// aspect ratio is preserved so only one dimension should match
|
||||
if ((typeof options.targetWidth === 'number' && imgEl.naturalWidth !== options.targetWidth) &&
|
||||
(typeof options.targetHeight === 'number' && imgEl.naturalHeight !== options.targetHeight))
|
||||
{
|
||||
done('ERROR: Wrong image size: ' + imgEl.naturalWidth + 'x' + imgEl.naturalHeight +
|
||||
'. Requested size: ' + options.targetWidth + 'x' + options.targetHeight);
|
||||
} else {
|
||||
done('OK');
|
||||
}
|
||||
} catch (e) {
|
||||
errorCallback(e);
|
||||
}
|
||||
};
|
||||
imgEl.src = image;
|
||||
} catch (e) {
|
||||
errorCallback(e);
|
||||
}
|
||||
}
|
||||
|
||||
navigator._appiumPromises[pid].promise
|
||||
.then(function (result) {
|
||||
verifyResult(result);
|
||||
})
|
||||
.fail(function (e) {
|
||||
errorCallback(e);
|
||||
});
|
||||
};
|
||||
512
appium-tests/ios/ios.spec.js
Normal file
512
appium-tests/ios/ios.spec.js
Normal file
@@ -0,0 +1,512 @@
|
||||
/*
|
||||
*
|
||||
* 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 Paramedic test runner
|
||||
// you can find it here: https://github.com/apache/cordova-paramedic/
|
||||
// it is not necessary to do a full CI setup to run these tests
|
||||
// just run "node cordova-paramedic/main.js --platform ios --plugin cordova-plugin-camera"
|
||||
|
||||
'use strict';
|
||||
|
||||
var wdHelper = global.WD_HELPER;
|
||||
var screenshotHelper = global.SCREENSHOT_HELPER;
|
||||
var isDevice = global.DEVICE;
|
||||
var cameraConstants = require('../../www/CameraConstants');
|
||||
var cameraHelper = require('../helpers/cameraHelper');
|
||||
|
||||
var MINUTE = 60 * 1000;
|
||||
var DEFAULT_WEBVIEW_CONTEXT = 'WEBVIEW_1';
|
||||
var PROMISE_PREFIX = 'appium_camera_promise_';
|
||||
var CONTEXT_NATIVE_APP = 'NATIVE_APP';
|
||||
|
||||
describe('Camera tests iOS.', function () {
|
||||
var driver;
|
||||
var webviewContext = DEFAULT_WEBVIEW_CONTEXT;
|
||||
// promise count to use in promise ID
|
||||
var promiseCount = 0;
|
||||
// going to set this to false if session is created successfully
|
||||
var failedToStart = true;
|
||||
// points out which UI automation to use
|
||||
var isXCUI = false;
|
||||
// spec counter to restart the session
|
||||
var specsRun = 0;
|
||||
|
||||
function getNextPromiseId() {
|
||||
promiseCount += 1;
|
||||
return getCurrentPromiseId();
|
||||
}
|
||||
|
||||
function getCurrentPromiseId() {
|
||||
return PROMISE_PREFIX + promiseCount;
|
||||
}
|
||||
|
||||
function gracefullyFail(error) {
|
||||
fail(error);
|
||||
return driver
|
||||
.quit()
|
||||
.then(function () {
|
||||
return getDriver();
|
||||
});
|
||||
}
|
||||
|
||||
// generates test specs by combining all the specified options
|
||||
// you can add more options to test more scenarios
|
||||
function generateOptions() {
|
||||
var sourceTypes = cameraConstants.PictureSourceType;
|
||||
var destinationTypes = cameraConstants.DestinationType;
|
||||
var encodingTypes = cameraConstants.EncodingType;
|
||||
var allowEditOptions = [ true, false ];
|
||||
var correctOrientationOptions = [ true, false ];
|
||||
|
||||
return cameraHelper.generateSpecs(sourceTypes, destinationTypes, encodingTypes, allowEditOptions, correctOrientationOptions);
|
||||
}
|
||||
|
||||
function usePicture(allowEdit) {
|
||||
return driver
|
||||
.sleep(10)
|
||||
.then(function () {
|
||||
if (isXCUI) {
|
||||
return driver.waitForElementByAccessibilityId('Choose', MINUTE / 3).click();
|
||||
} else {
|
||||
if (allowEdit) {
|
||||
return wdHelper.tapElementByXPath('//UIAButton[@label="Choose"]', driver);
|
||||
}
|
||||
return driver.elementByXPath('//*[@label="Use"]').click();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clickPhoto() {
|
||||
if (isXCUI) {
|
||||
// iOS >=10
|
||||
return driver
|
||||
.context(CONTEXT_NATIVE_APP)
|
||||
.elementsByXPath('//XCUIElementTypeCell')
|
||||
.then(function(photos) {
|
||||
if (photos.length == 0) {
|
||||
return driver
|
||||
.sleep(0) // driver.source is not a function o.O
|
||||
.source()
|
||||
.then(function (src) {
|
||||
console.log(src);
|
||||
gracefullyFail('Couldn\'t find an image to click');
|
||||
});
|
||||
}
|
||||
// intentionally clicking the second photo here
|
||||
// the first one is not clickable for some reason
|
||||
return photos[1].click();
|
||||
});
|
||||
}
|
||||
// iOS <10
|
||||
return driver
|
||||
.elementByXPath('//UIACollectionCell')
|
||||
.click();
|
||||
}
|
||||
|
||||
function getPicture(options, cancelCamera, skipUiInteractions) {
|
||||
var promiseId = getNextPromiseId();
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
// assign defaults
|
||||
if (!options.hasOwnProperty('allowEdit')) {
|
||||
options.allowEdit = true;
|
||||
}
|
||||
if (!options.hasOwnProperty('destinationType')) {
|
||||
options.destinationType = cameraConstants.DestinationType.FILE_URI;
|
||||
}
|
||||
if (!options.hasOwnProperty('sourceType')) {
|
||||
options.destinationType = cameraConstants.PictureSourceType.CAMERA;
|
||||
}
|
||||
|
||||
return driver
|
||||
.context(webviewContext)
|
||||
.execute(cameraHelper.getPicture, [options, promiseId])
|
||||
.context(CONTEXT_NATIVE_APP)
|
||||
.then(function () {
|
||||
if (skipUiInteractions) {
|
||||
return;
|
||||
}
|
||||
if (options.hasOwnProperty('sourceType') && options.sourceType === cameraConstants.PictureSourceType.PHOTOLIBRARY) {
|
||||
return driver
|
||||
.waitForElementByAccessibilityId('Camera Roll', MINUTE / 2)
|
||||
.click()
|
||||
.then(function () {
|
||||
return clickPhoto();
|
||||
})
|
||||
.then(function () {
|
||||
if (!options.allowEdit) {
|
||||
return driver;
|
||||
}
|
||||
return usePicture(options.allowEdit);
|
||||
});
|
||||
}
|
||||
if (options.hasOwnProperty('sourceType') && options.sourceType === cameraConstants.PictureSourceType.SAVEDPHOTOALBUM) {
|
||||
return clickPhoto()
|
||||
.then(function () {
|
||||
if (!options.allowEdit) {
|
||||
return driver;
|
||||
}
|
||||
return usePicture(options.allowEdit);
|
||||
});
|
||||
}
|
||||
if (cancelCamera) {
|
||||
return driver
|
||||
.waitForElementByAccessibilityId('Cancel', MINUTE / 2)
|
||||
.click();
|
||||
}
|
||||
return driver
|
||||
.waitForElementByAccessibilityId('Take Picture', MINUTE / 2)
|
||||
.click()
|
||||
.waitForElementByAccessibilityId('Use Photo', MINUTE / 2)
|
||||
.click();
|
||||
})
|
||||
.fail(fail);
|
||||
}
|
||||
|
||||
// checks if the picture was successfully taken
|
||||
// if shouldLoad is falsy, ensures that the error callback was called
|
||||
function checkPicture(shouldLoad, options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
return driver
|
||||
.context(webviewContext)
|
||||
.setAsyncScriptTimeout(MINUTE / 2)
|
||||
.executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId(), options, false])
|
||||
.then(function (result) {
|
||||
if (shouldLoad) {
|
||||
if (result !== 'OK') {
|
||||
fail(result);
|
||||
}
|
||||
} else if (result.indexOf('ERROR') === -1) {
|
||||
throw 'Unexpected success callback with result: ' + result;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// takes a picture with the specified options
|
||||
// and then verifies it
|
||||
function runSpec(options, done, pending) {
|
||||
if (options.sourceType === cameraConstants.PictureSourceType.CAMERA && !isDevice) {
|
||||
pending('Camera is not available on iOS simulator');
|
||||
}
|
||||
checkSession(done);
|
||||
specsRun += 1;
|
||||
return driver
|
||||
.then(function () {
|
||||
return getPicture(options);
|
||||
})
|
||||
.then(function () {
|
||||
return checkPicture(true, options);
|
||||
})
|
||||
.fail(gracefullyFail);
|
||||
}
|
||||
|
||||
function getDriver() {
|
||||
failedToStart = true;
|
||||
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);
|
||||
})
|
||||
.sessionCapabilities()
|
||||
.then(function (caps) {
|
||||
var platformVersion = parseFloat(caps.platformVersion);
|
||||
isXCUI = platformVersion >= 10.0;
|
||||
})
|
||||
.then(function () {
|
||||
var options = {
|
||||
quality: 50,
|
||||
allowEdit: false,
|
||||
sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
|
||||
saveToPhotoAlbum: false,
|
||||
targetWidth: 210,
|
||||
targetHeight: 210
|
||||
};
|
||||
return driver
|
||||
.then(function () { return getPicture(options, false, true); })
|
||||
.context(CONTEXT_NATIVE_APP)
|
||||
.acceptAlert()
|
||||
.then(function alertDismissed() {
|
||||
// TODO: once we move to only XCUITest-based (which is force on you in either iOS 10+ or Xcode 8+)
|
||||
// UI tests, we will have to:
|
||||
// a) remove use of autoAcceptAlerts appium capability since it no longer functions in XCUITest
|
||||
// b) can remove this entire then() clause, as we do not need to explicitly handle the acceptAlert
|
||||
// failure callback, since we will be guaranteed to hit the permission dialog on startup.
|
||||
}, function noAlert() {
|
||||
// in case the contacts permission alert never showed up: no problem, don't freak out.
|
||||
// This can happen if:
|
||||
// a) The application-under-test already had photos permissions granted to it
|
||||
// b) Appium's autoAcceptAlerts capability is provided (and functioning)
|
||||
})
|
||||
.elementByAccessibilityId('Cancel', 10000)
|
||||
.click();
|
||||
})
|
||||
.then(function () {
|
||||
failedToStart = false;
|
||||
});
|
||||
}
|
||||
|
||||
function checkSession(done) {
|
||||
if (failedToStart) {
|
||||
fail('Failed to start a session');
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
it('camera.ui.util configure driver and start a session', function (done) {
|
||||
// retry up to 3 times
|
||||
getDriver()
|
||||
.fail(function () {
|
||||
return getDriver()
|
||||
.fail(function () {
|
||||
return getDriver()
|
||||
.fail(fail);
|
||||
});
|
||||
})
|
||||
.fail(fail)
|
||||
.done(done);
|
||||
}, 30 * MINUTE);
|
||||
|
||||
describe('Specs.', function () {
|
||||
afterEach(function (done) {
|
||||
if (specsRun >= 19) {
|
||||
specsRun = 0;
|
||||
// we need to restart the session regularly because for some reason
|
||||
// when running against iOS 10 simulator on SauceLabs,
|
||||
// Appium cannot handle more than ~20 specs at one session
|
||||
// the error would be as follows:
|
||||
// "Could not proxy command to remote server. Original error: Error: connect ECONNREFUSED 127.0.0.1:8100"
|
||||
checkSession(done);
|
||||
return driver
|
||||
.quit()
|
||||
.then(function () {
|
||||
return getDriver()
|
||||
.fail(function () {
|
||||
return getDriver()
|
||||
.fail(function () {
|
||||
return getDriver()
|
||||
.fail(fail);
|
||||
});
|
||||
});
|
||||
})
|
||||
.done(done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
}, 30 * MINUTE);
|
||||
|
||||
// getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY
|
||||
it('camera.ui.spec.1 Selecting only videos', function (done) {
|
||||
checkSession(done);
|
||||
specsRun += 1;
|
||||
var options = { sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
|
||||
mediaType: cameraConstants.MediaType.VIDEO };
|
||||
driver
|
||||
// skip ui unteractions
|
||||
.then(function () { return getPicture(options, false, true); })
|
||||
.waitForElementByXPath('//*[contains(@label,"Videos")]', MINUTE / 2)
|
||||
.elementByAccessibilityId('Cancel')
|
||||
.click()
|
||||
.fail(gracefullyFail)
|
||||
.done(done);
|
||||
}, 7 * MINUTE);
|
||||
|
||||
// getPicture(), then dismiss
|
||||
// wait for the error callback to be called
|
||||
it('camera.ui.spec.2 Dismissing the camera', function (done) {
|
||||
checkSession(done);
|
||||
if (!isDevice) {
|
||||
pending('Camera is not available on iOS simulator');
|
||||
}
|
||||
specsRun += 1;
|
||||
var options = { sourceType: cameraConstants.PictureSourceType.CAMERA,
|
||||
saveToPhotoAlbum: false };
|
||||
driver
|
||||
.then(function () {
|
||||
return getPicture(options, true);
|
||||
})
|
||||
.then(function () {
|
||||
return checkPicture(false);
|
||||
})
|
||||
.fail(gracefullyFail)
|
||||
.done(done);
|
||||
}, 7 * MINUTE);
|
||||
|
||||
it('camera.ui.spec.3 Verifying target image size, sourceType=CAMERA', function (done) {
|
||||
var options = {
|
||||
quality: 50,
|
||||
allowEdit: false,
|
||||
sourceType: cameraConstants.PictureSourceType.CAMERA,
|
||||
saveToPhotoAlbum: false,
|
||||
targetWidth: 210,
|
||||
targetHeight: 210
|
||||
};
|
||||
|
||||
runSpec(options, done, pending).done(done);
|
||||
}, 7 * MINUTE);
|
||||
|
||||
it('camera.ui.spec.4 Verifying target image size, sourceType=SAVEDPHOTOALBUM', function (done) {
|
||||
var options = {
|
||||
quality: 50,
|
||||
allowEdit: false,
|
||||
sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
|
||||
saveToPhotoAlbum: false,
|
||||
targetWidth: 210,
|
||||
targetHeight: 210
|
||||
};
|
||||
|
||||
runSpec(options, done, pending).done(done);
|
||||
}, 7 * MINUTE);
|
||||
|
||||
it('camera.ui.spec.5 Verifying target image size, sourceType=PHOTOLIBRARY', function (done) {
|
||||
var options = {
|
||||
quality: 50,
|
||||
allowEdit: false,
|
||||
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
|
||||
saveToPhotoAlbum: false,
|
||||
targetWidth: 210,
|
||||
targetHeight: 210
|
||||
};
|
||||
|
||||
runSpec(options, done, pending).done(done);
|
||||
}, 7 * MINUTE);
|
||||
|
||||
it('camera.ui.spec.6 Verifying target image size, sourceType=CAMERA, destinationType=FILE_URL', function (done) {
|
||||
// remove this line if you don't mind the tests leaving a photo saved on device
|
||||
pending('Cannot prevent iOS from saving the picture to photo library');
|
||||
|
||||
var options = {
|
||||
quality: 50,
|
||||
allowEdit: false,
|
||||
sourceType: cameraConstants.PictureSourceType.CAMERA,
|
||||
destinationType: cameraConstants.DestinationType.FILE_URL,
|
||||
saveToPhotoAlbum: false,
|
||||
targetWidth: 210,
|
||||
targetHeight: 210
|
||||
};
|
||||
|
||||
runSpec(options, done, pending).done(done);
|
||||
}, 7 * MINUTE);
|
||||
|
||||
it('camera.ui.spec.7 Verifying target image size, sourceType=SAVEDPHOTOALBUM, destinationType=FILE_URL', function (done) {
|
||||
var options = {
|
||||
quality: 50,
|
||||
allowEdit: false,
|
||||
sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
|
||||
destinationType: cameraConstants.DestinationType.FILE_URL,
|
||||
saveToPhotoAlbum: false,
|
||||
targetWidth: 210,
|
||||
targetHeight: 210
|
||||
};
|
||||
|
||||
runSpec(options, done, pending).done(done);
|
||||
}, 7 * MINUTE);
|
||||
|
||||
it('camera.ui.spec.8 Verifying target image size, sourceType=PHOTOLIBRARY, destinationType=FILE_URL', function (done) {
|
||||
var options = {
|
||||
quality: 50,
|
||||
allowEdit: false,
|
||||
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
|
||||
destinationType: cameraConstants.DestinationType.FILE_URL,
|
||||
saveToPhotoAlbum: false,
|
||||
targetWidth: 210,
|
||||
targetHeight: 210
|
||||
};
|
||||
|
||||
runSpec(options, done, pending).done(done);
|
||||
}, 7 * MINUTE);
|
||||
|
||||
it('camera.ui.spec.9 Verifying target image size, sourceType=CAMERA, destinationType=FILE_URL, quality=100', function (done) {
|
||||
// remove this line if you don't mind the tests leaving a photo saved on device
|
||||
pending('Cannot prevent iOS from saving the picture to photo library');
|
||||
|
||||
var options = {
|
||||
quality: 100,
|
||||
allowEdit: false,
|
||||
sourceType: cameraConstants.PictureSourceType.CAMERA,
|
||||
destinationType: cameraConstants.DestinationType.FILE_URL,
|
||||
saveToPhotoAlbum: false,
|
||||
targetWidth: 305,
|
||||
targetHeight: 305
|
||||
};
|
||||
runSpec(options, done, pending).done(done);
|
||||
}, 7 * MINUTE);
|
||||
|
||||
it('camera.ui.spec.10 Verifying target image size, sourceType=SAVEDPHOTOALBUM, destinationType=FILE_URL, quality=100', function (done) {
|
||||
var options = {
|
||||
quality: 100,
|
||||
allowEdit: false,
|
||||
sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
|
||||
destinationType: cameraConstants.DestinationType.FILE_URL,
|
||||
saveToPhotoAlbum: false,
|
||||
targetWidth: 305,
|
||||
targetHeight: 305
|
||||
};
|
||||
|
||||
runSpec(options, done, pending).done(done);
|
||||
}, 7 * MINUTE);
|
||||
|
||||
it('camera.ui.spec.11 Verifying target image size, sourceType=PHOTOLIBRARY, destinationType=FILE_URL, quality=100', function (done) {
|
||||
var options = {
|
||||
quality: 100,
|
||||
allowEdit: false,
|
||||
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
|
||||
destinationType: cameraConstants.DestinationType.FILE_URL,
|
||||
saveToPhotoAlbum: false,
|
||||
targetWidth: 305,
|
||||
targetHeight: 305
|
||||
};
|
||||
|
||||
runSpec(options, done, pending).done(done);
|
||||
}, 7 * MINUTE);
|
||||
|
||||
// combine various options for getPicture()
|
||||
generateOptions().forEach(function (spec) {
|
||||
it('camera.ui.spec.12.' + spec.id + ' Combining options. ' + spec.description, function (done) {
|
||||
// remove this check if you don't mind the tests leaving a photo saved on device
|
||||
if (spec.options.sourceType === cameraConstants.PictureSourceType.CAMERA &&
|
||||
spec.options.destinationType === cameraConstants.DestinationType.NATIVE_URI) {
|
||||
pending('Skipping: cannot prevent iOS from saving the picture to photo library and cannot delete it. ' +
|
||||
'For more info, see iOS quirks here: https://github.com/apache/cordova-plugin-camera#ios-quirks-1');
|
||||
}
|
||||
|
||||
runSpec(spec.options, done, pending).done(done);
|
||||
}, 7 * MINUTE);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('camera.ui.util Destroy the session', function (done) {
|
||||
checkSession(done);
|
||||
driver
|
||||
.quit()
|
||||
.done(done);
|
||||
}, 5 * MINUTE);
|
||||
});
|
||||
1724
package-lock.json
generated
1724
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
28
package.json
28
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cordova-plugin-camera",
|
||||
"version": "5.0.2",
|
||||
"version": "4.1.0-dev",
|
||||
"description": "Cordova Camera Plugin",
|
||||
"types": "./types/index.d.ts",
|
||||
"cordova": {
|
||||
@@ -13,8 +13,13 @@
|
||||
"osx"
|
||||
]
|
||||
},
|
||||
"repository": "github:apache/cordova-plugin-camera",
|
||||
"bugs": "https://github.com/apache/cordova-plugin-camera/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/apache/cordova-plugin-camera"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://issues.apache.org/jira/browse/CB"
|
||||
},
|
||||
"keywords": [
|
||||
"cordova",
|
||||
"camera",
|
||||
@@ -26,8 +31,8 @@
|
||||
"cordova-osx"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "npm run lint",
|
||||
"lint": "eslint ."
|
||||
"test": "npm run eslint",
|
||||
"eslint": "node node_modules/eslint/bin/eslint www && node node_modules/eslint/bin/eslint src && node node_modules/eslint/bin/eslint tests"
|
||||
},
|
||||
"author": "Apache Software Foundation",
|
||||
"license": "Apache-2.0",
|
||||
@@ -41,16 +46,17 @@
|
||||
"cordova": ">=7.1.0"
|
||||
},
|
||||
"5.0.0": {
|
||||
"cordova-android": ">=9.0.0",
|
||||
"cordova-ios": ">=5.1.0",
|
||||
"cordova": ">=9.0.0"
|
||||
},
|
||||
"6.0.0": {
|
||||
"cordova": ">100"
|
||||
}
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cordova/eslint-config": "^3.0.0"
|
||||
"eslint": "^4.3.0",
|
||||
"eslint-config-semistandard": "^11.0.0",
|
||||
"eslint-config-standard": "^10.2.1",
|
||||
"eslint-plugin-import": "^2.3.0",
|
||||
"eslint-plugin-node": "^5.0.0",
|
||||
"eslint-plugin-promise": "^3.5.0",
|
||||
"eslint-plugin-standard": "^3.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
24
plugin.xml
24
plugin.xml
@@ -20,19 +20,19 @@
|
||||
|
||||
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:rim="http://www.blackberry.com/ns/widgets"
|
||||
id="cordova-plugin-camera"
|
||||
version="5.0.2">
|
||||
version="4.1.0-dev">
|
||||
<name>Camera</name>
|
||||
<description>Cordova Camera Plugin</description>
|
||||
<license>Apache 2.0</license>
|
||||
<keywords>cordova,camera</keywords>
|
||||
<repo>https://github.com/apache/cordova-plugin-camera</repo>
|
||||
<issue>https://github.com/apache/cordova-plugin-camera/issues</issue>
|
||||
<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" version=">=9.0.0"/>
|
||||
<engine name="cordova-android" version=">=9.0.0" />
|
||||
<engine name="cordova-ios" version=">=5.1.0" />
|
||||
<engine name="cordova" version=">=7.1.0"/>
|
||||
<engine name="cordova-android" version=">=6.3.0" />
|
||||
</engines>
|
||||
|
||||
<js-module src="www/CameraConstants.js" name="Camera">
|
||||
@@ -43,6 +43,8 @@
|
||||
<clobbers target="CameraPopoverOptions" />
|
||||
</js-module>
|
||||
|
||||
|
||||
|
||||
<js-module src="www/Camera.js" name="camera">
|
||||
<clobbers target="navigator.camera" />
|
||||
</js-module>
|
||||
@@ -60,7 +62,7 @@
|
||||
<config-file target="AndroidManifest.xml" parent="application">
|
||||
<provider
|
||||
android:name="org.apache.cordova.camera.FileProvider"
|
||||
android:authorities="${applicationId}.cordova.plugin.camera.provider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true" >
|
||||
<meta-data
|
||||
@@ -70,10 +72,10 @@
|
||||
</config-file>
|
||||
|
||||
<source-file src="src/android/CameraLauncher.java" target-dir="src/org/apache/cordova/camera" />
|
||||
<source-file src="src/android/CordovaUri.java" target-dir="src/org/apache/cordova/camera" />
|
||||
<source-file src="src/android/FileHelper.java" target-dir="src/org/apache/cordova/camera" />
|
||||
<source-file src="src/android/ExifHelper.java" target-dir="src/org/apache/cordova/camera" />
|
||||
<source-file src="src/android/FileProvider.java" target-dir="src/org/apache/cordova/camera" />
|
||||
<source-file src="src/android/GalleryPathVO.java" target-dir="src/org/apache/cordova/camera" />
|
||||
<source-file src="src/android/xml/camera_provider_paths.xml" target-dir="res/xml" />
|
||||
|
||||
<js-module src="www/CameraPopoverHandle.js" name="CameraPopoverHandle">
|
||||
@@ -152,12 +154,12 @@
|
||||
<js-module src="www/CameraPopoverHandle.js" name="CameraPopoverHandle">
|
||||
<clobbers target="CameraPopoverHandle" />
|
||||
</js-module>
|
||||
|
||||
|
||||
<header-file src="src/osx/CDVCamera.h" />
|
||||
<source-file src="src/osx/CDVCamera.m" />
|
||||
|
||||
|
||||
<framework src="Quartz.framework" />
|
||||
<framework src="AppKit.framework" />
|
||||
</platform>
|
||||
|
||||
|
||||
</plugin>
|
||||
|
||||
@@ -21,7 +21,6 @@ package org.apache.cordova.camera;
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
@@ -35,7 +34,6 @@ import android.media.ExifInterface;
|
||||
import android.media.MediaScannerConnection;
|
||||
import android.media.MediaScannerConnection.MediaScannerConnectionClient;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
@@ -53,6 +51,7 @@ import org.json.JSONException;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
@@ -70,6 +69,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
|
||||
|
||||
private static final int DATA_URL = 0; // Return base64 encoded string
|
||||
private static final int FILE_URI = 1; // Return file uri (content://media/external/images/media/2 for Android)
|
||||
private static final int NATIVE_URI = 2; // On Android, this is the same as FILE_URI
|
||||
|
||||
private static final int PHOTOLIBRARY = 0; // Choose image from picture library (same as SAVEDPHOTOALBUM for Android)
|
||||
private static final int CAMERA = 1; // Take picture from camera
|
||||
@@ -92,7 +92,6 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
|
||||
private static final String GET_All = "Get All";
|
||||
private static final String CROPPED_URI_KEY = "croppedUri";
|
||||
private static final String IMAGE_URI_KEY = "imageUri";
|
||||
private static final String IMAGE_FILE_PATH_KEY = "imageFilePath";
|
||||
|
||||
private static final String TAKE_PICTURE_ACTION = "takePicture";
|
||||
|
||||
@@ -110,8 +109,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
|
||||
private int mQuality; // Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
|
||||
private int targetWidth; // desired width of the image
|
||||
private int targetHeight; // desired height of the image
|
||||
private Uri imageUri; // Uri of captured image
|
||||
private String imageFilePath; // File where the image is stored
|
||||
private CordovaUri imageUri; // Uri of captured image
|
||||
private int encodingType; // Type of encoding to use
|
||||
private int mediaType; // What type of media to retrieve
|
||||
private int destType; // Source type (needs to be saved for the permission handling)
|
||||
@@ -129,7 +127,6 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
|
||||
private MediaScannerConnection conn; // Used to update gallery app with newly-written files
|
||||
private Uri scanMe; // Uri of image to be added to content store
|
||||
private Uri croppedUri;
|
||||
private String croppedFilePath;
|
||||
private ExifHelper exifData; // Exif data from source
|
||||
private String applicationId;
|
||||
|
||||
@@ -223,7 +220,17 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
private String getTempDirectoryPath() {
|
||||
File cache = cordova.getActivity().getCacheDir();
|
||||
File cache = null;
|
||||
|
||||
// SD Card Mounted
|
||||
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
||||
cache = cordova.getActivity().getExternalCacheDir();
|
||||
}
|
||||
// Use internal storage
|
||||
else {
|
||||
cache = cordova.getActivity().getCacheDir();
|
||||
}
|
||||
|
||||
// Create the cache directory if it doesn't exist
|
||||
cache.mkdirs();
|
||||
return cache.getAbsolutePath();
|
||||
@@ -293,11 +300,10 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
|
||||
|
||||
// Specify file so that large image is captured and returned
|
||||
File photo = createCaptureFile(encodingType);
|
||||
this.imageFilePath = photo.getAbsolutePath();
|
||||
this.imageUri = FileProvider.getUriForFile(cordova.getActivity(),
|
||||
applicationId + ".cordova.plugin.camera.provider",
|
||||
photo);
|
||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
|
||||
this.imageUri = new CordovaUri(FileProvider.getUriForFile(cordova.getActivity(),
|
||||
applicationId + ".provider",
|
||||
photo));
|
||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri.getCorrectUri());
|
||||
//We can write to this URI, this will hopefully allow us to write files to get to the next step
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
|
||||
@@ -364,7 +370,6 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
|
||||
Intent intent = new Intent();
|
||||
String title = GET_PICTURE;
|
||||
croppedUri = null;
|
||||
croppedFilePath = null;
|
||||
if (this.mediaType == PICTURE) {
|
||||
intent.setType("image/*");
|
||||
if (this.allowEdit) {
|
||||
@@ -380,9 +385,8 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
|
||||
intent.putExtra("aspectX", 1);
|
||||
intent.putExtra("aspectY", 1);
|
||||
}
|
||||
File croppedFile = createCaptureFile(JPEG);
|
||||
croppedFilePath = croppedFile.getAbsolutePath();
|
||||
croppedUri = Uri.fromFile(croppedFile);
|
||||
File photo = createCaptureFile(JPEG);
|
||||
croppedUri = Uri.fromFile(photo);
|
||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, croppedUri);
|
||||
} else {
|
||||
intent.setAction(Intent.ACTION_GET_CONTENT);
|
||||
@@ -407,57 +411,57 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Brings up the UI to perform crop on passed image URI
|
||||
*
|
||||
* @param picUri
|
||||
*/
|
||||
private void performCrop(Uri picUri, int destType, Intent cameraIntent) {
|
||||
try {
|
||||
Intent cropIntent = new Intent("com.android.camera.action.CROP");
|
||||
// indicate image type and Uri
|
||||
cropIntent.setDataAndType(picUri, "image/*");
|
||||
// set crop properties
|
||||
cropIntent.putExtra("crop", "true");
|
||||
|
||||
/**
|
||||
* Brings up the UI to perform crop on passed image URI
|
||||
*
|
||||
* @param picUri
|
||||
*/
|
||||
private void performCrop(Uri picUri, int destType, Intent cameraIntent) {
|
||||
try {
|
||||
Intent cropIntent = new Intent("com.android.camera.action.CROP");
|
||||
// indicate image type and Uri
|
||||
cropIntent.setDataAndType(picUri, "image/*");
|
||||
// set crop properties
|
||||
cropIntent.putExtra("crop", "true");
|
||||
|
||||
|
||||
// indicate output X and Y
|
||||
if (targetWidth > 0) {
|
||||
cropIntent.putExtra("outputX", targetWidth);
|
||||
}
|
||||
if (targetHeight > 0) {
|
||||
cropIntent.putExtra("outputY", targetHeight);
|
||||
}
|
||||
if (targetHeight > 0 && targetWidth > 0 && targetWidth == targetHeight) {
|
||||
cropIntent.putExtra("aspectX", 1);
|
||||
cropIntent.putExtra("aspectY", 1);
|
||||
}
|
||||
// create new file handle to get full resolution crop
|
||||
croppedFilePath = createCaptureFile(this.encodingType, System.currentTimeMillis() + "").getAbsolutePath();
|
||||
croppedUri = Uri.parse(croppedFilePath);
|
||||
cropIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
cropIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
cropIntent.putExtra("output", croppedUri);
|
||||
|
||||
|
||||
// start the activity - we handle returning in onActivityResult
|
||||
|
||||
if (this.cordova != null) {
|
||||
this.cordova.startActivityForResult((CordovaPlugin) this,
|
||||
cropIntent, CROP_CAMERA + destType);
|
||||
}
|
||||
} catch (ActivityNotFoundException anfe) {
|
||||
LOG.e(LOG_TAG, "Crop operation not supported on this device");
|
||||
try {
|
||||
processResultFromCamera(destType, cameraIntent);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
LOG.e(LOG_TAG, "Unable to write to file");
|
||||
}
|
||||
// indicate output X and Y
|
||||
if (targetWidth > 0) {
|
||||
cropIntent.putExtra("outputX", targetWidth);
|
||||
}
|
||||
if (targetHeight > 0) {
|
||||
cropIntent.putExtra("outputY", targetHeight);
|
||||
}
|
||||
if (targetHeight > 0 && targetWidth > 0 && targetWidth == targetHeight) {
|
||||
cropIntent.putExtra("aspectX", 1);
|
||||
cropIntent.putExtra("aspectY", 1);
|
||||
}
|
||||
// create new file handle to get full resolution crop
|
||||
croppedUri = Uri.fromFile(createCaptureFile(this.encodingType, System.currentTimeMillis() + ""));
|
||||
cropIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
cropIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
cropIntent.putExtra("output", croppedUri);
|
||||
|
||||
|
||||
// start the activity - we handle returning in onActivityResult
|
||||
|
||||
if (this.cordova != null) {
|
||||
this.cordova.startActivityForResult((CordovaPlugin) this,
|
||||
cropIntent, CROP_CAMERA + destType);
|
||||
}
|
||||
} catch (ActivityNotFoundException anfe) {
|
||||
LOG.e(LOG_TAG, "Crop operation not supported on this device");
|
||||
try {
|
||||
processResultFromCamera(destType, cameraIntent);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
LOG.e(LOG_TAG, "Unable to write to file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all needed transformation to the image received from the camera.
|
||||
@@ -472,8 +476,8 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
|
||||
ExifHelper exif = new ExifHelper();
|
||||
|
||||
String sourcePath = (this.allowEdit && this.croppedUri != null) ?
|
||||
this.croppedFilePath :
|
||||
this.imageFilePath;
|
||||
FileHelper.stripFileProtocol(this.croppedUri.toString()) :
|
||||
this.imageUri.getFilePath();
|
||||
|
||||
|
||||
if (this.encodingType == JPEG) {
|
||||
@@ -495,18 +499,16 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
|
||||
// in the gallery and the modified image is saved in the temporary
|
||||
// directory
|
||||
if (this.saveToPhotoAlbum) {
|
||||
GalleryPathVO galleryPathVO = getPicturesPath();
|
||||
galleryUri = Uri.fromFile(new File(galleryPathVO.getGalleryPath()));
|
||||
galleryUri = Uri.fromFile(new File(getPicturesPath()));
|
||||
|
||||
if (this.allowEdit && this.croppedUri != null) {
|
||||
writeUncompressedImage(croppedUri, galleryUri);
|
||||
} else {
|
||||
if (Build.VERSION.SDK_INT <= 28) { // Between LOLLIPOP_MR1 and P, can be changed later to the constant Build.VERSION_CODES.P
|
||||
writeTakenPictureToGalleryLowerThanAndroidQ(galleryUri);
|
||||
} else { // Android Q or higher
|
||||
writeTakenPictureToGalleryStartingFromAndroidQ(galleryPathVO);
|
||||
}
|
||||
Uri imageUri = this.imageUri.getFileUri();
|
||||
writeUncompressedImage(imageUri, galleryUri);
|
||||
}
|
||||
|
||||
refreshGallery(galleryUri);
|
||||
}
|
||||
|
||||
// If sending base64 image back
|
||||
@@ -534,7 +536,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
|
||||
}
|
||||
|
||||
// If sending filename back
|
||||
else if (destType == FILE_URI) {
|
||||
else if (destType == FILE_URI || destType == NATIVE_URI) {
|
||||
// If all this is true we shouldn't compress the image.
|
||||
if (this.targetHeight == -1 && this.targetWidth == -1 && this.mQuality == 100 &&
|
||||
!this.correctOrientation) {
|
||||
@@ -547,10 +549,10 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
|
||||
Uri uri = Uri.fromFile(createCaptureFile(this.encodingType, System.currentTimeMillis() + ""));
|
||||
|
||||
if (this.allowEdit && this.croppedUri != null) {
|
||||
Uri croppedUri = Uri.parse(croppedFilePath);
|
||||
Uri croppedUri = Uri.fromFile(new File(getFileNameFromUri(this.croppedUri)));
|
||||
writeUncompressedImage(croppedUri, uri);
|
||||
} else {
|
||||
Uri imageUri = this.imageUri;
|
||||
Uri imageUri = this.imageUri.getFileUri();
|
||||
writeUncompressedImage(imageUri, uri);
|
||||
}
|
||||
|
||||
@@ -570,7 +572,9 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
|
||||
|
||||
// Add compressed version of captured image to returned media store Uri
|
||||
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
|
||||
CompressFormat compressFormat = getCompressFormatForEncodingType(encodingType);
|
||||
CompressFormat compressFormat = encodingType == JPEG ?
|
||||
CompressFormat.JPEG :
|
||||
CompressFormat.PNG;
|
||||
|
||||
bitmap.compress(compressFormat, this.mQuality, os);
|
||||
os.close();
|
||||
@@ -594,45 +598,22 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
this.cleanup(FILE_URI, this.imageUri, galleryUri, bitmap);
|
||||
this.cleanup(FILE_URI, this.imageUri.getFileUri(), galleryUri, bitmap);
|
||||
bitmap = null;
|
||||
}
|
||||
|
||||
private void writeTakenPictureToGalleryLowerThanAndroidQ(Uri galleryUri) throws IOException {
|
||||
writeUncompressedImage(imageUri, galleryUri);
|
||||
refreshGallery(galleryUri);
|
||||
}
|
||||
|
||||
private void writeTakenPictureToGalleryStartingFromAndroidQ(GalleryPathVO galleryPathVO) throws IOException {
|
||||
// Starting from Android Q, working with the ACTION_MEDIA_SCANNER_SCAN_FILE intent is deprecated
|
||||
// https://developer.android.com/reference/android/content/Intent#ACTION_MEDIA_SCANNER_SCAN_FILE
|
||||
// we must start working with the MediaStore from Android Q on.
|
||||
ContentResolver resolver = this.cordova.getActivity().getContentResolver();
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, galleryPathVO.getGalleryFileName());
|
||||
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, getMimetypeForFormat(encodingType));
|
||||
Uri galleryOutputUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
|
||||
|
||||
InputStream fileStream = org.apache.cordova.camera.FileHelper.getInputStreamFromUriString(imageUri.toString(), cordova);
|
||||
writeUncompressedImage(fileStream, galleryOutputUri);
|
||||
}
|
||||
|
||||
private CompressFormat getCompressFormatForEncodingType(int encodingType) {
|
||||
return encodingType == JPEG ? CompressFormat.JPEG : CompressFormat.PNG;
|
||||
}
|
||||
|
||||
private GalleryPathVO getPicturesPath() {
|
||||
private String getPicturesPath() {
|
||||
String timeStamp = new SimpleDateFormat(TIME_FORMAT).format(new Date());
|
||||
String imageFileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? JPEG_EXTENSION : PNG_EXTENSION);
|
||||
File storageDir = Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_PICTURES);
|
||||
storageDir.mkdirs();
|
||||
return new GalleryPathVO(storageDir.getAbsolutePath(), imageFileName);
|
||||
String galleryPath = storageDir.getAbsolutePath() + "/" + imageFileName;
|
||||
return galleryPath;
|
||||
}
|
||||
|
||||
private void refreshGallery(Uri contentUri) {
|
||||
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
|
||||
// Starting from Android Q, working with the ACTION_MEDIA_SCANNER_SCAN_FILE intent is deprecated
|
||||
mediaScanIntent.setData(contentUri);
|
||||
this.cordova.getActivity().sendBroadcast(mediaScanIntent);
|
||||
}
|
||||
@@ -664,7 +645,9 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
|
||||
String modifiedPath = getTempDirectoryPath() + "/" + fileName;
|
||||
|
||||
OutputStream os = new FileOutputStream(modifiedPath);
|
||||
CompressFormat compressFormat = getCompressFormatForEncodingType(this.encodingType);
|
||||
CompressFormat compressFormat = this.encodingType == JPEG ?
|
||||
CompressFormat.JPEG :
|
||||
CompressFormat.PNG;
|
||||
|
||||
bitmap.compress(compressFormat, this.mQuality, os);
|
||||
os.close();
|
||||
@@ -701,80 +684,74 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
|
||||
return;
|
||||
}
|
||||
}
|
||||
int rotate = 0;
|
||||
|
||||
String fileLocation = FileHelper.getRealPath(uri, this.cordova);
|
||||
LOG.d(LOG_TAG, "File location is: " + fileLocation);
|
||||
|
||||
String uriString = uri.toString();
|
||||
String finalLocation = fileLocation != null ? fileLocation : uriString;
|
||||
String mimeType = FileHelper.getMimeType(uriString, this.cordova);
|
||||
|
||||
if (finalLocation == null) {
|
||||
this.failPicture("Error retrieving result.");
|
||||
} else {
|
||||
// If you ask for video or the selected file doesn't have JPEG or PNG mime type
|
||||
// there will be no attempt to resize any returned data
|
||||
if (this.mediaType == VIDEO || !(JPEG_MIME_TYPE.equalsIgnoreCase(mimeType) || PNG_MIME_TYPE.equalsIgnoreCase(mimeType))) {
|
||||
this.callbackContext.success(fileLocation);
|
||||
}
|
||||
else {
|
||||
|
||||
// If you ask for video or the selected file doesn't have JPEG or PNG mime type
|
||||
// there will be no attempt to resize any returned data
|
||||
if (this.mediaType == VIDEO || !(JPEG_MIME_TYPE.equalsIgnoreCase(mimeType) || PNG_MIME_TYPE.equalsIgnoreCase(mimeType))) {
|
||||
this.callbackContext.success(finalLocation);
|
||||
}
|
||||
else {
|
||||
|
||||
// This is a special case to just return the path as no scaling,
|
||||
// rotating, nor compressing needs to be done
|
||||
if (this.targetHeight == -1 && this.targetWidth == -1 &&
|
||||
destType == FILE_URI && !this.correctOrientation &&
|
||||
mimeType != null && mimeType.equalsIgnoreCase(getMimetypeForFormat(encodingType)))
|
||||
{
|
||||
this.callbackContext.success(finalLocation);
|
||||
} else {
|
||||
Bitmap bitmap = null;
|
||||
try {
|
||||
bitmap = getScaledAndRotatedBitmap(uriString);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (bitmap == null) {
|
||||
LOG.d(LOG_TAG, "I either have a null image path or bitmap");
|
||||
this.failPicture("Unable to create bitmap!");
|
||||
return;
|
||||
}
|
||||
|
||||
// If sending base64 image back
|
||||
if (destType == DATA_URL) {
|
||||
this.processPicture(bitmap, this.encodingType);
|
||||
}
|
||||
|
||||
// If sending filename back
|
||||
else if (destType == FILE_URI) {
|
||||
// Did we modify the image?
|
||||
if ( (this.targetHeight > 0 && this.targetWidth > 0) ||
|
||||
(this.correctOrientation && this.orientationCorrected) ||
|
||||
!mimeType.equalsIgnoreCase(getMimetypeForFormat(encodingType)))
|
||||
{
|
||||
try {
|
||||
String modifiedPath = this.outputModifiedBitmap(bitmap, uri);
|
||||
// The modified image is cached by the app in order to get around this and not have to delete you
|
||||
// application cache I'm adding the current system time to the end of the file url.
|
||||
this.callbackContext.success("file://" + modifiedPath + "?" + System.currentTimeMillis());
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
this.failPicture("Error retrieving image.");
|
||||
}
|
||||
} else {
|
||||
this.callbackContext.success(finalLocation);
|
||||
}
|
||||
}
|
||||
if (bitmap != null) {
|
||||
bitmap.recycle();
|
||||
bitmap = null;
|
||||
}
|
||||
System.gc();
|
||||
// This is a special case to just return the path as no scaling,
|
||||
// rotating, nor compressing needs to be done
|
||||
if (this.targetHeight == -1 && this.targetWidth == -1 &&
|
||||
(destType == FILE_URI || destType == NATIVE_URI) && !this.correctOrientation &&
|
||||
mimeType != null && mimeType.equalsIgnoreCase(getMimetypeForFormat(encodingType)))
|
||||
{
|
||||
this.callbackContext.success(uriString);
|
||||
} else {
|
||||
Bitmap bitmap = null;
|
||||
try {
|
||||
bitmap = getScaledAndRotatedBitmap(uriString);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (bitmap == null) {
|
||||
LOG.d(LOG_TAG, "I either have a null image path or bitmap");
|
||||
this.failPicture("Unable to create bitmap!");
|
||||
return;
|
||||
}
|
||||
|
||||
// If sending base64 image back
|
||||
if (destType == DATA_URL) {
|
||||
this.processPicture(bitmap, this.encodingType);
|
||||
}
|
||||
|
||||
// If sending filename back
|
||||
else if (destType == FILE_URI || destType == NATIVE_URI) {
|
||||
// Did we modify the image?
|
||||
if ( (this.targetHeight > 0 && this.targetWidth > 0) ||
|
||||
(this.correctOrientation && this.orientationCorrected) ||
|
||||
!mimeType.equalsIgnoreCase(getMimetypeForFormat(encodingType)))
|
||||
{
|
||||
try {
|
||||
String modifiedPath = this.outputModifiedBitmap(bitmap, uri);
|
||||
// The modified image is cached by the app in order to get around this and not have to delete you
|
||||
// application cache I'm adding the current system time to the end of the file url.
|
||||
this.callbackContext.success("file://" + modifiedPath + "?" + System.currentTimeMillis());
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
this.failPicture("Error retrieving image.");
|
||||
}
|
||||
} else {
|
||||
this.callbackContext.success(fileLocation);
|
||||
}
|
||||
}
|
||||
if (bitmap != null) {
|
||||
bitmap.recycle();
|
||||
bitmap = null;
|
||||
}
|
||||
System.gc();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -822,7 +799,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
|
||||
try {
|
||||
if (this.allowEdit) {
|
||||
Uri tmpFile = FileProvider.getUriForFile(cordova.getActivity(),
|
||||
applicationId + ".cordova.plugin.camera.provider",
|
||||
applicationId + ".provider",
|
||||
createCaptureFile(this.encodingType));
|
||||
performCrop(tmpFile, destType, intent);
|
||||
} else {
|
||||
@@ -921,11 +898,34 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
|
||||
private void writeUncompressedImage(Uri src, Uri dest) throws FileNotFoundException,
|
||||
IOException {
|
||||
|
||||
InputStream fis = FileHelper.getInputStreamFromUriString(src.toString(), cordova);
|
||||
FileInputStream fis = new FileInputStream(FileHelper.stripFileProtocol(src.toString()));
|
||||
writeUncompressedImage(fis, dest);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create entry in media store for image
|
||||
*
|
||||
* @return uri
|
||||
*/
|
||||
private Uri getUriFromMediaStore() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(MediaStore.Images.Media.MIME_TYPE, JPEG_MIME_TYPE);
|
||||
Uri uri;
|
||||
try {
|
||||
uri = this.cordova.getActivity().getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
|
||||
} catch (RuntimeException e) {
|
||||
LOG.d(LOG_TAG, "Can't write to external media storage.");
|
||||
try {
|
||||
uri = this.cordova.getActivity().getContentResolver().insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, values);
|
||||
} catch (RuntimeException ex) {
|
||||
LOG.d(LOG_TAG, "Can't write to internal media storage.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a scaled and rotated bitmap based on the target width and height
|
||||
*
|
||||
@@ -1256,7 +1256,9 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
|
||||
*/
|
||||
public void processPicture(Bitmap bitmap, int encodingType) {
|
||||
ByteArrayOutputStream jpeg_data = new ByteArrayOutputStream();
|
||||
CompressFormat compressFormat = getCompressFormatForEncodingType(encodingType);
|
||||
CompressFormat compressFormat = encodingType == JPEG ?
|
||||
CompressFormat.JPEG :
|
||||
CompressFormat.PNG;
|
||||
|
||||
try {
|
||||
if (bitmap.compress(compressFormat, mQuality, jpeg_data)) {
|
||||
@@ -1344,15 +1346,11 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
|
||||
state.putBoolean("saveToPhotoAlbum", this.saveToPhotoAlbum);
|
||||
|
||||
if (this.croppedUri != null) {
|
||||
state.putString(CROPPED_URI_KEY, this.croppedFilePath);
|
||||
state.putString(CROPPED_URI_KEY, this.croppedUri.toString());
|
||||
}
|
||||
|
||||
if (this.imageUri != null) {
|
||||
state.putString(IMAGE_URI_KEY, this.imageFilePath);
|
||||
}
|
||||
|
||||
if (this.imageFilePath != null) {
|
||||
state.putString(IMAGE_FILE_PATH_KEY, this.imageFilePath);
|
||||
state.putString(IMAGE_URI_KEY, this.imageUri.getFileUri().toString());
|
||||
}
|
||||
|
||||
return state;
|
||||
@@ -1377,13 +1375,28 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
|
||||
|
||||
if (state.containsKey(IMAGE_URI_KEY)) {
|
||||
//I have no idea what type of URI is being passed in
|
||||
this.imageUri = Uri.parse(state.getString(IMAGE_URI_KEY));
|
||||
}
|
||||
|
||||
if (state.containsKey(IMAGE_FILE_PATH_KEY)) {
|
||||
this.imageFilePath = state.getString(IMAGE_FILE_PATH_KEY);
|
||||
this.imageUri = new CordovaUri(Uri.parse(state.getString(IMAGE_URI_KEY)));
|
||||
}
|
||||
|
||||
this.callbackContext = callbackContext;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is dirty, but it does the job.
|
||||
*
|
||||
* Since the FilesProvider doesn't really provide you a way of getting a URL from the file,
|
||||
* and since we actually need the Camera to create the file for us most of the time, we don't
|
||||
* actually write the file, just generate the location based on a timestamp, we need to get it
|
||||
* back from the Intent.
|
||||
*
|
||||
* However, the FilesProvider preserves the path, so we can at least write to it from here, since
|
||||
* we own the context in this case.
|
||||
*/
|
||||
private String getFileNameFromUri(Uri uri) {
|
||||
String fullUri = uri.toString();
|
||||
String partial_path = fullUri.split("external_files")[1];
|
||||
File external_storage = Environment.getExternalStorageDirectory();
|
||||
String path = external_storage.getAbsolutePath() + partial_path;
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
104
src/android/CordovaUri.java
Normal file
104
src/android/CordovaUri.java
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cordova.camera;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.support.v4.content.FileProvider;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/*
|
||||
* This class exists because Andorid FilesProvider doesn't work on Android 4.4.4 and below and throws
|
||||
* weird errors. I'm not sure why writing to shared cache directories is somehow verboten, but it is
|
||||
* and this error is irritating for a Compatibility library to have.
|
||||
*
|
||||
*/
|
||||
|
||||
public class CordovaUri {
|
||||
|
||||
private Uri androidUri;
|
||||
private String fileName;
|
||||
private Uri fileUri;
|
||||
|
||||
/*
|
||||
* We always expect a FileProvider string to be passed in for the file that we create
|
||||
*
|
||||
*/
|
||||
CordovaUri (Uri inputUri)
|
||||
{
|
||||
//Determine whether the file is a content or file URI
|
||||
if(inputUri.getScheme().equals("content"))
|
||||
{
|
||||
androidUri = inputUri;
|
||||
fileName = getFileNameFromUri(androidUri);
|
||||
fileUri = Uri.parse("file://" + fileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
fileUri = inputUri;
|
||||
fileName = FileHelper.stripFileProtocol(inputUri.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public Uri getFileUri()
|
||||
{
|
||||
return fileUri;
|
||||
}
|
||||
|
||||
public String getFilePath()
|
||||
{
|
||||
return fileName;
|
||||
}
|
||||
|
||||
/*
|
||||
* This only gets called by takePicture
|
||||
*/
|
||||
|
||||
public Uri getCorrectUri()
|
||||
{
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
||||
return androidUri;
|
||||
else
|
||||
return fileUri;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is dirty, but it does the job.
|
||||
*
|
||||
* Since the FilesProvider doesn't really provide you a way of getting a URL from the file,
|
||||
* and since we actually need the Camera to create the file for us most of the time, we don't
|
||||
* actually write the file, just generate the location based on a timestamp, we need to get it
|
||||
* back from the Intent.
|
||||
*
|
||||
* However, the FilesProvider preserves the path, so we can at least write to it from here, since
|
||||
* we own the context in this case.
|
||||
*/
|
||||
|
||||
private String getFileNameFromUri(Uri uri) {
|
||||
String fullUri = uri.toString();
|
||||
String partial_path = fullUri.split("external_files")[1];
|
||||
File external_storage = Environment.getExternalStorageDirectory();
|
||||
String path = external_storage.getAbsolutePath() + partial_path;
|
||||
return path;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,16 @@ public class FileHelper {
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static String getRealPath(Uri uri, CordovaInterface cordova) {
|
||||
return FileHelper.getRealPathFromURI(cordova.getActivity(), uri);
|
||||
String realPath = null;
|
||||
|
||||
if (Build.VERSION.SDK_INT < 11)
|
||||
realPath = FileHelper.getRealPathFromURI_BelowAPI11(cordova.getActivity(), uri);
|
||||
|
||||
// SDK >= 11
|
||||
else
|
||||
realPath = FileHelper.getRealPathFromURI_API11_And_Above(cordova.getActivity(), uri);
|
||||
|
||||
return realPath;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,9 +75,11 @@ public class FileHelper {
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public static String getRealPathFromURI(final Context context, final Uri uri) {
|
||||
public static String getRealPathFromURI_API11_And_Above(final Context context, final Uri uri) {
|
||||
|
||||
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
|
||||
// DocumentProvider
|
||||
if (DocumentsContract.isDocumentUri(context, uri)) {
|
||||
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
|
||||
|
||||
// ExternalStorageProvider
|
||||
if (isExternalStorageDocument(uri)) {
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova.camera;
|
||||
|
||||
public class GalleryPathVO {
|
||||
private final String galleryPath;
|
||||
private String picturesDirectory;
|
||||
private String galleryFileName;
|
||||
|
||||
public GalleryPathVO(String picturesDirectory, String galleryFileName) {
|
||||
this.picturesDirectory = picturesDirectory;
|
||||
this.galleryFileName = galleryFileName;
|
||||
this.galleryPath = this.picturesDirectory + "/" + this.galleryFileName;
|
||||
}
|
||||
|
||||
public String getGalleryPath() {
|
||||
return galleryPath;
|
||||
}
|
||||
|
||||
public String getPicturesDirectory() {
|
||||
return picturesDirectory;
|
||||
}
|
||||
|
||||
public String getGalleryFileName() {
|
||||
return galleryFileName;
|
||||
}
|
||||
}
|
||||
@@ -17,5 +17,5 @@
|
||||
-->
|
||||
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<cache-path name="cache_files" path="." />
|
||||
<external-path name="external_files" path="."/>
|
||||
</paths>
|
||||
@@ -112,7 +112,7 @@ function capture (success, errorCallback, opts) {
|
||||
};
|
||||
|
||||
if (navigator.getUserMedia) {
|
||||
navigator.getUserMedia({ video: true, audio: false }, successCallback, errorCallback);
|
||||
navigator.getUserMedia({video: true, audio: false}, successCallback, errorCallback);
|
||||
} else {
|
||||
alert('Browser does not support camera :(');
|
||||
}
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
|
||||
enum CDVDestinationType {
|
||||
DestinationTypeDataUrl = 0,
|
||||
DestinationTypeFileUri
|
||||
DestinationTypeFileUri,
|
||||
DestinationTypeNativeUri
|
||||
};
|
||||
typedef NSUInteger CDVDestinationType;
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ static NSString* toBase64(NSData* data) {
|
||||
[weakSelf sendNoPermissionResult:command.callbackId];
|
||||
}]];
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Settings", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
||||
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil];
|
||||
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
|
||||
[weakSelf sendNoPermissionResult:command.callbackId];
|
||||
}]];
|
||||
[weakSelf.viewController presentViewController:alertController animated:YES completion:nil];
|
||||
@@ -185,16 +185,16 @@ static NSString* toBase64(NSData* data) {
|
||||
|
||||
- (void)showCameraPicker:(NSString*)callbackId withOptions:(CDVPictureOptions *) pictureOptions
|
||||
{
|
||||
CDVCameraPicker* cameraPicker = [CDVCameraPicker createFromPictureOptions:pictureOptions];
|
||||
self.pickerController = cameraPicker;
|
||||
|
||||
cameraPicker.delegate = self;
|
||||
cameraPicker.callbackId = callbackId;
|
||||
// we need to capture this state for memory warnings that dealloc this object
|
||||
cameraPicker.webView = self.webView;
|
||||
|
||||
// Perform UI operations on the main thread
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
CDVCameraPicker* cameraPicker = [CDVCameraPicker createFromPictureOptions:pictureOptions];
|
||||
self.pickerController = cameraPicker;
|
||||
|
||||
cameraPicker.delegate = self;
|
||||
cameraPicker.callbackId = callbackId;
|
||||
// we need to capture this state for memory warnings that dealloc this object
|
||||
cameraPicker.webView = self.webView;
|
||||
|
||||
// If a popover is already open, close it; we only want one at a time.
|
||||
if (([[self pickerController] pickerPopoverController] != nil) && [[[self pickerController] pickerPopoverController] isPopoverVisible]) {
|
||||
[[[self pickerController] pickerPopoverController] dismissPopoverAnimated:YES];
|
||||
@@ -374,7 +374,7 @@ static NSString* toBase64(NSData* data) {
|
||||
self.metadata = [[NSMutableDictionary alloc] init];
|
||||
|
||||
NSMutableDictionary* EXIFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyExifDictionary]mutableCopy];
|
||||
if (EXIFDictionary) {
|
||||
if (EXIFDictionary) {
|
||||
[self.metadata setObject:EXIFDictionary forKey:(NSString*)kCGImagePropertyExifDictionary];
|
||||
}
|
||||
|
||||
@@ -383,7 +383,6 @@ static NSString* toBase64(NSData* data) {
|
||||
}
|
||||
[[self locationManager] startUpdatingLocation];
|
||||
}
|
||||
data = nil;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -397,10 +396,14 @@ static NSString* toBase64(NSData* data) {
|
||||
- (NSString*)tempFilePath:(NSString*)extension
|
||||
{
|
||||
NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath];
|
||||
// unique file name
|
||||
NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
|
||||
NSNumber *timeStampObj = [NSNumber numberWithDouble: timeStamp];
|
||||
NSString* filePath = [NSString stringWithFormat:@"%@/%@%ld.%@", docsPath, CDV_PHOTO_PREFIX, [timeStampObj longValue], extension];
|
||||
NSFileManager* fileMgr = [[NSFileManager alloc] init]; // recommended by Apple (vs [NSFileManager defaultManager]) to be threadsafe
|
||||
NSString* filePath;
|
||||
|
||||
// generate unique file name
|
||||
int i = 1;
|
||||
do {
|
||||
filePath = [NSString stringWithFormat:@"%@/%@%03d.%@", docsPath, CDV_PHOTO_PREFIX, i++, extension];
|
||||
} while ([fileMgr fileExistsAtPath:filePath]);
|
||||
|
||||
return filePath;
|
||||
}
|
||||
@@ -440,16 +443,33 @@ static NSString* toBase64(NSData* data) {
|
||||
UIImage* image = nil;
|
||||
|
||||
switch (options.destinationType) {
|
||||
case DestinationTypeDataUrl:
|
||||
case DestinationTypeNativeUri:
|
||||
{
|
||||
image = [self retrieveImage:info options:options];
|
||||
NSData* data = [self processImage:image info:info options:options];
|
||||
if (data) {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:toBase64(data)];
|
||||
NSURL* url = [info objectForKey:UIImagePickerControllerReferenceURL];
|
||||
saveToPhotoAlbum = NO;
|
||||
// If, for example, we use sourceType = Camera, URL might be nil because image is stored in memory.
|
||||
// In this case we must save image to device before obtaining an URI.
|
||||
if (url == nil) {
|
||||
image = [self retrieveImage:info options:options];
|
||||
ALAssetsLibrary* library = [ALAssetsLibrary new];
|
||||
[library writeImageToSavedPhotosAlbum:image.CGImage orientation:(ALAssetOrientation)(image.imageOrientation) completionBlock:^(NSURL *assetURL, NSError *error) {
|
||||
CDVPluginResult* resultToReturn = nil;
|
||||
if (error) {
|
||||
resultToReturn = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]];
|
||||
} else {
|
||||
NSString* nativeUri = [[self urlTransformer:assetURL] absoluteString];
|
||||
resultToReturn = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:nativeUri];
|
||||
}
|
||||
completion(resultToReturn);
|
||||
}];
|
||||
return;
|
||||
} else {
|
||||
NSString* nativeUri = [[self urlTransformer:url] absoluteString];
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:nativeUri];
|
||||
}
|
||||
}
|
||||
break;
|
||||
default: // DestinationTypeFileUri
|
||||
case DestinationTypeFileUri:
|
||||
{
|
||||
image = [self retrieveImage:info options:options];
|
||||
NSData* data = [self processImage:image info:info options:options];
|
||||
@@ -468,10 +488,22 @@ static NSString* toBase64(NSData* data) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DestinationTypeDataUrl:
|
||||
{
|
||||
image = [self retrieveImage:info options:options];
|
||||
NSData* data = [self processImage:image info:info options:options];
|
||||
if (data) {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:toBase64(data)];
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
if (saveToPhotoAlbum && image) {
|
||||
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
|
||||
ALAssetsLibrary* library = [ALAssetsLibrary new];
|
||||
[library writeImageToSavedPhotosAlbum:image.CGImage orientation:(ALAssetOrientation)(image.imageOrientation) completionBlock:nil];
|
||||
}
|
||||
|
||||
completion(result);
|
||||
@@ -480,22 +512,9 @@ static NSString* toBase64(NSData* data) {
|
||||
- (CDVPluginResult*)resultForVideo:(NSDictionary*)info
|
||||
{
|
||||
NSString* moviePath = [[info objectForKey:UIImagePickerControllerMediaURL] absoluteString];
|
||||
// On iOS 13 the movie path becomes inaccessible, create and return a copy
|
||||
if (IsAtLeastiOSVersion(@"13.0")) {
|
||||
moviePath = [self createTmpVideo:[[info objectForKey:UIImagePickerControllerMediaURL] path]];
|
||||
}
|
||||
return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:moviePath];
|
||||
}
|
||||
|
||||
- (NSString *) createTmpVideo:(NSString *) moviePath {
|
||||
NSString* moviePathExtension = [moviePath pathExtension];
|
||||
NSString* copyMoviePath = [self tempFilePath:moviePathExtension];
|
||||
NSFileManager* fileMgr = [[NSFileManager alloc] init];
|
||||
NSError *error;
|
||||
[fileMgr copyItemAtPath:moviePath toPath:copyMoviePath error:&error];
|
||||
return [[NSURL fileURLWithPath:copyMoviePath] absoluteString];
|
||||
}
|
||||
|
||||
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info
|
||||
{
|
||||
__weak CDVCameraPicker* cameraPicker = (CDVCameraPicker*)picker;
|
||||
@@ -547,8 +566,10 @@ static NSString* toBase64(NSData* data) {
|
||||
|
||||
dispatch_block_t invoke = ^ (void) {
|
||||
CDVPluginResult* result;
|
||||
if (picker.sourceType == UIImagePickerControllerSourceTypeCamera && [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] != AVAuthorizationStatusAuthorized) {
|
||||
if (picker.sourceType == UIImagePickerControllerSourceTypeCamera && [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] != ALAuthorizationStatusAuthorized) {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"has no access to camera"];
|
||||
} else if (picker.sourceType != UIImagePickerControllerSourceTypeCamera && ! IsAtLeastiOSVersion(@"11.0") && [ALAssetsLibrary authorizationStatus] != ALAuthorizationStatusAuthorized) {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"has no access to assets"];
|
||||
} else {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No Image Selected"];
|
||||
}
|
||||
@@ -565,15 +586,15 @@ static NSString* toBase64(NSData* data) {
|
||||
|
||||
- (CLLocationManager*)locationManager
|
||||
{
|
||||
if (locationManager != nil) {
|
||||
return locationManager;
|
||||
}
|
||||
if (locationManager != nil) {
|
||||
return locationManager;
|
||||
}
|
||||
|
||||
locationManager = [[CLLocationManager alloc] init];
|
||||
[locationManager setDesiredAccuracy:kCLLocationAccuracyNearestTenMeters];
|
||||
[locationManager setDelegate:self];
|
||||
locationManager = [[CLLocationManager alloc] init];
|
||||
[locationManager setDesiredAccuracy:kCLLocationAccuracyNearestTenMeters];
|
||||
[locationManager setDelegate:self];
|
||||
|
||||
return locationManager;
|
||||
return locationManager;
|
||||
}
|
||||
|
||||
- (void)locationManager:(CLLocationManager*)manager didUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*)oldLocation
|
||||
@@ -651,26 +672,19 @@ static NSString* toBase64(NSData* data) {
|
||||
CDVPluginResult* result = nil;
|
||||
|
||||
if (self.metadata) {
|
||||
NSData* dataCopy = [self.data mutableCopy];
|
||||
CGImageSourceRef sourceImage = CGImageSourceCreateWithData((__bridge CFDataRef)dataCopy, NULL);
|
||||
CGImageSourceRef sourceImage = CGImageSourceCreateWithData((__bridge CFDataRef)self.data, NULL);
|
||||
CFStringRef sourceType = CGImageSourceGetType(sourceImage);
|
||||
|
||||
CGImageDestinationRef destinationImage = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)self.data, sourceType, 1, NULL);
|
||||
CGImageDestinationAddImageFromSource(destinationImage, sourceImage, 0, (__bridge CFDictionaryRef)self.metadata);
|
||||
CGImageDestinationFinalize(destinationImage);
|
||||
|
||||
dataCopy = nil;
|
||||
CFRelease(sourceImage);
|
||||
CFRelease(destinationImage);
|
||||
}
|
||||
|
||||
switch (options.destinationType) {
|
||||
case DestinationTypeDataUrl:
|
||||
{
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:toBase64(self.data)];
|
||||
}
|
||||
break;
|
||||
default: // DestinationTypeFileUri
|
||||
case DestinationTypeFileUri:
|
||||
{
|
||||
NSError* err = nil;
|
||||
NSString* extension = self.pickerController.pictureOptions.encodingType == EncodingTypePNG ? @"png":@"jpg";
|
||||
@@ -685,6 +699,14 @@ static NSString* toBase64(NSData* data) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DestinationTypeDataUrl:
|
||||
{
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:toBase64(self.data)];
|
||||
}
|
||||
break;
|
||||
case DestinationTypeNativeUri:
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
if (result) {
|
||||
@@ -697,7 +719,8 @@ static NSString* toBase64(NSData* data) {
|
||||
self.metadata = nil;
|
||||
|
||||
if (options.saveToPhotoAlbum) {
|
||||
UIImageWriteToSavedPhotosAlbum([[UIImage alloc] initWithData:self.data], nil, nil, nil);
|
||||
ALAssetsLibrary *library = [ALAssetsLibrary new];
|
||||
[library writeImageDataToSavedPhotosAlbum:self.data metadata:self.metadata completionBlock:nil];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
|
||||
enum CDVDestinationType {
|
||||
DestinationTypeDataUrl = 0,
|
||||
DestinationTypeFileUri
|
||||
DestinationTypeFileUri,
|
||||
DestinationTypeNativeUri
|
||||
};
|
||||
typedef NSUInteger CDVDestinationType;
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ static NSMutableArray *cleanUpFiles;
|
||||
|
||||
/*!
|
||||
Returns to JavaScript a URI.
|
||||
Called when Camera.DestinationType.FILE_URI.
|
||||
Called when Camera.DestinationType.FILE_URI or Camera.DestinationType.NATIVE_URI.
|
||||
*/
|
||||
- (void)returnUri:(NSString *)path command:(CDVInvokedUrlCommand *)command options:(CDVPictureOptions *)pictureOptions {
|
||||
NSString *protocol = (pictureOptions.destinationType == DestinationTypeFileUri) ? @"file://" : @"";
|
||||
|
||||
@@ -190,13 +190,17 @@ function takePictureFromFileWP (successCallback, errorCallback, args) {
|
||||
webUIApp.removeEventListener('activated', filePickerActivationHandler);
|
||||
return;
|
||||
}
|
||||
if (destinationType === Camera.DestinationType.FILE_URI) {
|
||||
if (destinationType === Camera.DestinationType.FILE_URI || destinationType === Camera.DestinationType.NATIVE_URI) {
|
||||
if (targetHeight > 0 && targetWidth > 0) {
|
||||
resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType);
|
||||
} else {
|
||||
var storageFolder = getAppData().localFolder;
|
||||
file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).done(function (storageFile) {
|
||||
successCallback(URL.createObjectURL(storageFile));
|
||||
if (destinationType === Camera.DestinationType.NATIVE_URI) {
|
||||
successCallback('ms-appdata:///local/' + storageFile.name);
|
||||
} else {
|
||||
successCallback(URL.createObjectURL(storageFile));
|
||||
}
|
||||
}, function () {
|
||||
errorCallback("Can't access localStorage folder.");
|
||||
});
|
||||
@@ -255,13 +259,17 @@ function takePictureFromFileWindows (successCallback, errorCallback, args) {
|
||||
errorCallback("User didn't choose a file.");
|
||||
return;
|
||||
}
|
||||
if (destinationType === Camera.DestinationType.FILE_URI) {
|
||||
if (destinationType === Camera.DestinationType.FILE_URI || destinationType === Camera.DestinationType.NATIVE_URI) {
|
||||
if (targetHeight > 0 && targetWidth > 0) {
|
||||
resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType);
|
||||
} else {
|
||||
var storageFolder = getAppData().localFolder;
|
||||
file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).done(function (storageFile) {
|
||||
successCallback(URL.createObjectURL(storageFile));
|
||||
if (destinationType === Camera.DestinationType.NATIVE_URI) {
|
||||
successCallback('ms-appdata:///local/' + storageFile.name);
|
||||
} else {
|
||||
successCallback(URL.createObjectURL(storageFile));
|
||||
}
|
||||
}, function () {
|
||||
errorCallback("Can't access localStorage folder.");
|
||||
});
|
||||
@@ -366,6 +374,7 @@ function takePictureFromCameraWP (successCallback, errorCallback, args) {
|
||||
|
||||
return capture.initializeAsync(captureSettings);
|
||||
}).then(function () {
|
||||
|
||||
// create focus control if available
|
||||
var VideoDeviceController = capture.videoDeviceController;
|
||||
var FocusControl = VideoDeviceController.focusControl;
|
||||
@@ -468,6 +477,7 @@ function takePictureFromCameraWP (successCallback, errorCallback, args) {
|
||||
}
|
||||
|
||||
function captureAction () {
|
||||
|
||||
var encodingProperties;
|
||||
var fileName;
|
||||
var tempFolder = getAppData().temporaryFolder;
|
||||
@@ -705,7 +715,7 @@ function takePictureFromCameraWindows (successCallback, errorCallback, args) {
|
||||
|
||||
if (targetWidth === -1 && targetHeight === -1) {
|
||||
maxRes = UIMaxRes.highestAvailable;
|
||||
// Temp fix for CB-10539
|
||||
// Temp fix for CB-10539
|
||||
/* else if (totalPixels <= 320 * 240) {
|
||||
maxRes = UIMaxRes.verySmallQvga;
|
||||
} */
|
||||
@@ -771,7 +781,7 @@ function takePictureFromCameraWindows (successCallback, errorCallback, args) {
|
||||
function savePhoto (picture, options, successCallback, errorCallback) {
|
||||
// success callback for capture operation
|
||||
var success = function (picture) {
|
||||
if (options.destinationType === Camera.DestinationType.FILE_URI) {
|
||||
if (options.destinationType === Camera.DestinationType.FILE_URI || options.destinationType === Camera.DestinationType.NATIVE_URI) {
|
||||
if (options.targetHeight > 0 && options.targetWidth > 0) {
|
||||
resizeImage(successCallback, errorCallback, picture, options.targetWidth, options.targetHeight, options.encodingType);
|
||||
} else {
|
||||
@@ -802,6 +812,7 @@ function savePhoto (picture, options, successCallback, errorCallback) {
|
||||
|
||||
if (!options.saveToPhotoAlbum) {
|
||||
success(picture);
|
||||
|
||||
} else {
|
||||
var savePicker = new Windows.Storage.Pickers.FileSavePicker();
|
||||
var saveFile = function (file) {
|
||||
|
||||
39
tests/ios/doc/de/README.md
Normal file
39
tests/ios/doc/de/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
<!---
|
||||
license: 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.
|
||||
-->
|
||||
|
||||
# iOS-Tests für CDVCamera
|
||||
|
||||
Sie müssen installieren `node.js` in `Cordova-Ios` zu ziehen.
|
||||
|
||||
Installieren Sie Cordova-Ios zum ersten Mal:
|
||||
|
||||
npm install
|
||||
|
||||
|
||||
... im aktuellen Ordner.
|
||||
|
||||
# Testen von Xcode
|
||||
|
||||
1. Starten Sie die Datei `CDVCameraTest.xcworkspace` .
|
||||
2. Wählen Sie im Dropdown-Schema "CDVCameraLibTests"
|
||||
3. Klicken Sie und halten Sie auf den `Play` -Button und wählen Sie das `Schraubenschlüssel` -Symbol zum Ausführen der tests
|
||||
|
||||
# Tests von der Befehlszeile aus
|
||||
|
||||
npm test
|
||||
39
tests/ios/doc/es/README.md
Normal file
39
tests/ios/doc/es/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
<!---
|
||||
license: 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.
|
||||
-->
|
||||
|
||||
# Pruebas de iOS para CDVCamera
|
||||
|
||||
Necesita instalar `node.js` en `Córdoba-ios`.
|
||||
|
||||
Primero instalar cordova-ios:
|
||||
|
||||
npm install
|
||||
|
||||
|
||||
... en la carpeta actual.
|
||||
|
||||
# Prueba de Xcode
|
||||
|
||||
1. Iniciar el archivo `CDVCameraTest.xcworkspace` .
|
||||
2. Elija "CDVCameraLibTests" en el menú de lista desplegable esquema
|
||||
3. Haga clic y mantenga el botón de `Play` y elegir el icono de `llave inglesa` para ejecutar las pruebas
|
||||
|
||||
# Pruebas desde la línea de comandos
|
||||
|
||||
npm test
|
||||
39
tests/ios/doc/fr/README.md
Normal file
39
tests/ios/doc/fr/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
<!---
|
||||
license: 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.
|
||||
-->
|
||||
|
||||
# Tests d'iOS pour CDVCamera
|
||||
|
||||
Vous devez installer `node.js` à `cordova-ios`.
|
||||
|
||||
Commencez par installer cordova-ios :
|
||||
|
||||
npm install
|
||||
|
||||
|
||||
... dans le dossier actuel.
|
||||
|
||||
# Tests de Xcode
|
||||
|
||||
1. Lancez le fichier `CDVCameraTest.xcworkspace` .
|
||||
2. Choisissez « CDVCameraLibTests » dans le menu déroulant de régime
|
||||
3. Cliquez et maintenez sur la touche `Play` et cliquez sur l'icône de `clé` pour exécuter les tests
|
||||
|
||||
# Test de la ligne de commande
|
||||
|
||||
npm test
|
||||
39
tests/ios/doc/it/README.md
Normal file
39
tests/ios/doc/it/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
<!---
|
||||
license: 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.
|
||||
-->
|
||||
|
||||
# Test di iOS per CDVCamera
|
||||
|
||||
È necessario installare `node. js` per tirare in `cordova-ios`.
|
||||
|
||||
In primo luogo installare cordova-ios:
|
||||
|
||||
npm install
|
||||
|
||||
|
||||
... nella cartella corrente.
|
||||
|
||||
# Test da Xcode
|
||||
|
||||
1. Lanciare il file `CDVCameraTest.xcworkspace` .
|
||||
2. Scegli "CDVCameraLibTests" dal menu a discesa Schema
|
||||
3. Fare clic e tenere premuto il pulsante `Play` e scegliere l'icona della `chiave inglese` per eseguire i test
|
||||
|
||||
# Test dalla riga di comando
|
||||
|
||||
npm test
|
||||
39
tests/ios/doc/ja/README.md
Normal file
39
tests/ios/doc/ja/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
<!---
|
||||
license: 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.
|
||||
-->
|
||||
|
||||
# CDVCamera の iOS のテスト
|
||||
|
||||
`Node.js` `コルドバ`ios をプルするをインストールする必要があります。.
|
||||
|
||||
コルドバ ios をインストールします。
|
||||
|
||||
npm install
|
||||
|
||||
|
||||
現在のフォルダーに.
|
||||
|
||||
# Xcode からテスト
|
||||
|
||||
1. `CDVCameraTest.xcworkspace`ファイルを起動します。
|
||||
2. スキーム] ドロップダウン メニューから"CDVCameraLibTests"を選択します。
|
||||
3. クリックし、`再生`ボタンを押し、テストを実行する`レンチ`のアイコンを選択
|
||||
|
||||
# コマンドラインからテスト
|
||||
|
||||
npm test
|
||||
39
tests/ios/doc/ko/README.md
Normal file
39
tests/ios/doc/ko/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
<!---
|
||||
license: 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.
|
||||
-->
|
||||
|
||||
# CDVCamera에 대 한 iOS 테스트
|
||||
|
||||
`Node.js` `코르도바` ios에서를 설치 해야.
|
||||
|
||||
코르도바-ios를 설치 하는 첫번째는:
|
||||
|
||||
npm install
|
||||
|
||||
|
||||
현재 폴더에....
|
||||
|
||||
# Xcode에서 테스트
|
||||
|
||||
1. `CDVCameraTest.xcworkspace` 파일을 시작 합니다.
|
||||
2. 구성표 드롭 다운 메뉴에서 "CDVCameraLibTests"를 선택
|
||||
3. 클릭 하 고 `재생` 버튼에는 테스트를 실행 하려면 `공구 모양` 아이콘을 선택
|
||||
|
||||
# 명령줄에서 테스트
|
||||
|
||||
npm test
|
||||
39
tests/ios/doc/pl/README.md
Normal file
39
tests/ios/doc/pl/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
<!---
|
||||
license: 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.
|
||||
-->
|
||||
|
||||
# iOS testy dla CDVCamera
|
||||
|
||||
Musisz zainstalować `node.js` ciągnąć w `cordova-ios`.
|
||||
|
||||
Najpierw zainstalować cordova-ios:
|
||||
|
||||
npm install
|
||||
|
||||
|
||||
... w folderze bieżącym.
|
||||
|
||||
# Badania z Xcode
|
||||
|
||||
1. Uruchom plik `CDVCameraTest.xcworkspace` .
|
||||
2. Wybierz z menu rozwijanego systemu "CDVCameraLibTests"
|
||||
3. Kliknij i przytrzymaj przycisk `Play` i wybrać ikonę `klucz` do testów
|
||||
|
||||
# Badania z wiersza polecenia
|
||||
|
||||
npm test
|
||||
39
tests/ios/doc/zh/README.md
Normal file
39
tests/ios/doc/zh/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
<!---
|
||||
license: 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.
|
||||
-->
|
||||
|
||||
# CDVCamera 的 iOS 測試
|
||||
|
||||
您需要安裝`node.js`拉`科爾多瓦 ios`中.
|
||||
|
||||
第一次安裝科爾多瓦 ios:
|
||||
|
||||
npm install
|
||||
|
||||
|
||||
在當前資料夾中。
|
||||
|
||||
# 從 Xcode 測試
|
||||
|
||||
1. 啟動`CDVCameraTest.xcworkspace`檔。
|
||||
2. 從方案下拉式功能表中選擇"CDVCameraLibTests"
|
||||
3. 按一下並堅持`播放`按鈕,然後選擇要運行的測試的`扳手`圖示
|
||||
|
||||
# 從命令列測試
|
||||
|
||||
npm test
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cordova-plugin-camera-tests",
|
||||
"version": "5.0.2",
|
||||
"version": "2.4.1-dev",
|
||||
"description": "",
|
||||
"cordova": {
|
||||
"id": "cordova-plugin-camera-tests",
|
||||
@@ -10,5 +10,5 @@
|
||||
"ecosystem:cordova"
|
||||
],
|
||||
"author": "",
|
||||
"license": "Apache-2.0"
|
||||
"license": "Apache 2.0"
|
||||
}
|
||||
|
||||
@@ -22,10 +22,12 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:rim="http://www.blackberry.com/ns/widgets"
|
||||
id="cordova-plugin-camera-tests"
|
||||
version="5.0.2">
|
||||
version="4.1.0-dev">
|
||||
<name>Cordova Camera Plugin Tests</name>
|
||||
<license>Apache 2.0</license>
|
||||
|
||||
<dependency id="cordova-plugin-file-transfer" />
|
||||
|
||||
<js-module src="tests.js" name="tests">
|
||||
</js-module>
|
||||
</plugin>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
/* globals Camera, resolveLocalFileSystemURL, FileEntry, CameraPopoverOptions, LocalFileSystem, MSApp */
|
||||
/* globals Camera, resolveLocalFileSystemURL, FileEntry, CameraPopoverOptions, FileTransfer, FileUploadOptions, LocalFileSystem, MSApp */
|
||||
/* eslint-env jasmine */
|
||||
|
||||
exports.defineAutoTests = function () {
|
||||
@@ -42,8 +42,10 @@ exports.defineAutoTests = function () {
|
||||
it('camera.spec.2 should contain three DestinationType constants', function () {
|
||||
expect(Camera.DestinationType.DATA_URL).toBe(0);
|
||||
expect(Camera.DestinationType.FILE_URI).toBe(1);
|
||||
expect(Camera.DestinationType.NATIVE_URI).toBe(2);
|
||||
expect(navigator.camera.DestinationType.DATA_URL).toBe(0);
|
||||
expect(navigator.camera.DestinationType.FILE_URI).toBe(1);
|
||||
expect(navigator.camera.DestinationType.NATIVE_URI).toBe(2);
|
||||
});
|
||||
|
||||
it('camera.spec.3 should contain two EncodingType constants', function () {
|
||||
@@ -138,7 +140,7 @@ 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) {
|
||||
if (pictureUrl.indexOf('file:') === 0 || pictureUrl.indexOf('content:') === 0 || pictureUrl.indexOf('ms-appdata:') === 0 || pictureUrl.indexOf('assets-library:') === 0) {
|
||||
resolveLocalFileSystemURL(data, function (e) {
|
||||
fileEntry = e;
|
||||
logCallback('resolveLocalFileSystemURL()', true)(e.toURL());
|
||||
@@ -165,6 +167,26 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
};
|
||||
}
|
||||
|
||||
function uploadImage () {
|
||||
var ft = new FileTransfer();
|
||||
var options = new FileUploadOptions();
|
||||
options.fileKey = 'photo';
|
||||
options.fileName = 'test.jpg';
|
||||
options.mimeType = 'image/jpeg';
|
||||
ft.onprogress = function (progressEvent) {
|
||||
console.log('progress: ' + progressEvent.loaded + ' of ' + progressEvent.total);
|
||||
};
|
||||
var server = 'http://sheltered-retreat-43956.herokuapp.com';
|
||||
|
||||
ft.upload(pictureUrl, server + '/upload', win, fail, options);
|
||||
function win (information_back) {
|
||||
log('upload complete');
|
||||
}
|
||||
function fail (message) {
|
||||
log('upload failed: ' + JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
function logCallback (apiName, success) {
|
||||
return function () {
|
||||
log('Call to ' + apiName + (success ? ' success: ' : ' failed: ') + JSON.stringify([].slice.call(arguments)));
|
||||
@@ -172,7 +194,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Select image from library
|
||||
* Select image from library using a NATIVE_URI destination type
|
||||
* This calls FileEntry.getMetadata, FileEntry.setMetadata, FileEntry.getParent, FileEntry.file, and FileReader.readAsDataURL.
|
||||
*/
|
||||
function readFile () {
|
||||
@@ -215,7 +237,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy image from library
|
||||
* Copy image from library using a NATIVE_URI destination type
|
||||
* This calls FileEntry.copyTo and FileEntry.moveTo.
|
||||
*/
|
||||
function copyImage () {
|
||||
@@ -249,7 +271,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Write image to library
|
||||
* Write image to library using a NATIVE_URI destination type
|
||||
* This calls FileEntry.createWriter, FileWriter.write, and FileWriter.truncate.
|
||||
*/
|
||||
function writeImage () {
|
||||
@@ -283,7 +305,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove image from library
|
||||
* Remove image from library using a NATIVE_URI destination type
|
||||
* This calls FileEntry.remove.
|
||||
*/
|
||||
function removeImage () {
|
||||
@@ -356,7 +378,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
|
||||
var options = '';
|
||||
if (typeof values === 'boolean') {
|
||||
values = { true: 1, false: 0 };
|
||||
values = { 'true': 1, 'false': 0 };
|
||||
}
|
||||
for (var k in values) {
|
||||
var isSelected = '';
|
||||
@@ -387,9 +409,9 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
createOptionsEl('destinationType', Camera.DestinationType, camDestinationTypeDefault) +
|
||||
createOptionsEl('encodingType', Camera.EncodingType, camEncodingTypeDefault) +
|
||||
createOptionsEl('mediaType', Camera.MediaType, camMediaTypeDefault) +
|
||||
createOptionsEl('quality', { 0: 0, 50: 50, 80: 80, 100: 100 }, camQualityDefault) +
|
||||
createOptionsEl('targetWidth', { 50: 50, 200: 200, 800: 800, 2048: 2048 }) +
|
||||
createOptionsEl('targetHeight', { 50: 50, 200: 200, 800: 800, 2048: 2048 }) +
|
||||
createOptionsEl('quality', { '0': 0, '50': 50, '80': 80, '100': 100 }, camQualityDefault) +
|
||||
createOptionsEl('targetWidth', { '50': 50, '200': 200, '800': 800, '2048': 2048 }) +
|
||||
createOptionsEl('targetHeight', { '50': 50, '200': 200, '800': 800, '2048': 2048 }) +
|
||||
createOptionsEl('allowEdit', true, camAllowEditDefault) +
|
||||
createOptionsEl('correctOrientation', true, camCorrectOrientationDefault) +
|
||||
createOptionsEl('saveToPhotoAlbum', true, camSaveToPhotoAlbumDefault) +
|
||||
@@ -476,6 +498,10 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
writeImage();
|
||||
}, 'write');
|
||||
|
||||
createActionButton('Upload Image', function () {
|
||||
uploadImage();
|
||||
}, 'upload');
|
||||
|
||||
createActionButton('Draw Using Canvas', function () {
|
||||
displayImageUsingCanvas();
|
||||
}, 'draw_canvas');
|
||||
|
||||
3
types/index.d.ts
vendored
3
types/index.d.ts
vendored
@@ -51,6 +51,8 @@ interface CameraOptions {
|
||||
* Defined in navigator.camera.DestinationType. Default is FILE_URI.
|
||||
* DATA_URL : 0, Return image as base64-encoded string
|
||||
* FILE_URI : 1, Return image file URI
|
||||
* NATIVE_URI : 2 Return image native URI
|
||||
* (e.g., assets-library:// on iOS or content:// on Android)
|
||||
*/
|
||||
destinationType?: number;
|
||||
/**
|
||||
@@ -147,6 +149,7 @@ declare var Camera: {
|
||||
DestinationType: {
|
||||
DATA_URL: number;
|
||||
FILE_URI: number;
|
||||
NATIVE_URI: number
|
||||
}
|
||||
Direction: {
|
||||
BACK: number;
|
||||
|
||||
@@ -26,14 +26,20 @@ module.exports = {
|
||||
/**
|
||||
* @description
|
||||
* Defines the output format of `Camera.getPicture` call.
|
||||
* _Note:_ On iOS passing `DestinationType.NATIVE_URI` along with
|
||||
* `PictureSourceType.PHOTOLIBRARY` or `PictureSourceType.SAVEDPHOTOALBUM` will
|
||||
* disable any image modifications (resize, quality change, cropping, etc.) due
|
||||
* to implementation specific.
|
||||
*
|
||||
* @enum {number}
|
||||
*/
|
||||
DestinationType: {
|
||||
/** Return base64 encoded string. DATA_URL can be very memory intensive and cause app crashes or out of memory errors. Use FILE_URI if possible */
|
||||
/** 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
|
||||
FILE_URI: 1,
|
||||
/** Return native uri (eg. asset-library://... for iOS) */
|
||||
NATIVE_URI: 2
|
||||
},
|
||||
/**
|
||||
* @enum {number}
|
||||
@@ -58,6 +64,9 @@ module.exports = {
|
||||
/**
|
||||
* @description
|
||||
* Defines the output format of `Camera.getPicture` call.
|
||||
* _Note:_ On iOS passing `PictureSourceType.PHOTOLIBRARY` or `PictureSourceType.SAVEDPHOTOALBUM`
|
||||
* along with `DestinationType.NATIVE_URI` will disable any image modifications (resize, quality
|
||||
* change, cropping, etc.) due to implementation specific.
|
||||
*
|
||||
* @enum {number}
|
||||
*/
|
||||
|
||||
@@ -58,7 +58,7 @@ var CameraPopoverHandle = function () {
|
||||
* @param {module:CameraPopoverOptions} popoverOptions
|
||||
*/
|
||||
this.setPosition = function (popoverOptions) {
|
||||
var args = [popoverOptions];
|
||||
var args = [ popoverOptions ];
|
||||
exec(null, null, 'Camera', 'repositionPopover', args);
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user