Compare commits

..

35 Commits
5.0.3 ... 7.0.0

Author SHA1 Message Date
Erisu
24839eb71f release(camera-v7.0.0): updated version and RELEASENOTES.md 2023-09-06 16:55:23 +09:00
エリス
64bd32d641 ci(gh-action): sync with paramedic configs (#851) 2023-09-06 02:27:41 +09:00
jcesarmobile
0796f784c1 chore: remove windows/osx from plugin.xml (#850) 2023-09-01 02:12:00 +02:00
jcesarmobile
20293f3d64 fix!: remove deprecated platforms (#848) 2023-09-01 00:34:30 +02:00
jcesarmobile
8cb34e1175 chore: Update SUPPORT_QUESTION.md template (#849) 2023-08-30 23:35:39 +02:00
エリス
505ccefb4c feat(android)!: Android 13 support (#844)
* feat(android)!: Android 13 support
* refactor(android): simplify getPermissions logic
* feat(android)!: bump cordova-android requirement to >=12.0.0
* feat(android): update saveAlbumPermission to include Android 9 and below use case

---------

Co-authored-by: ochakov <evgeny@ochakov.com>
2023-08-26 20:21:32 +09:00
エリス
61a6e9bb44 dep(dev)!: bump @cordova/eslint-config@5.0.0 (#846)
* dep(dev)!: bump @cordova/eslint-config@5.0.0
* chore: apply automatic lint fix
2023-08-18 00:59:25 +09:00
エリス
c2eb21d201 chore: bump plugin version 7.0.0-dev (#845) 2023-08-17 21:43:13 +09:00
Norman Breau
23642f09b5 ci(android): Drop API 22 & 31. Added API 24 & 33 (#835)
Co-authored-by: エリス <erisu@users.noreply.github.com>
2023-06-05 22:50:49 +09:00
Norman Breau
84166f6355 chore(android): Cleanup obsolete BuildConfig comments (#831) 2023-04-14 08:48:12 -03:00
seamlink-aalves
2c09ade500 fix(android): set applicationId (#827)
Co-authored-by: Alexandre Alves <aalves@seamlink.com>
2023-04-14 08:06:30 -03:00
エリス
827bb611ee ci: sync workflow with paramedic (#804) 2022-10-01 13:28:27 +09:00
jcesarmobile
d0545c879f fix(browser): use navigator.mediaDevices.getUserMedia (#810) 2022-09-30 00:59:27 +02:00
jcesarmobile
d0d46c151c docs(README): Document ANDROIDX_CORE_VERSION variable (#808) 2022-09-25 05:03:51 +02:00
エリス
a18fda7ddf dep(npm): bump package-lock v2 w/ rebuild (#800) 2022-08-10 16:49:43 +09:00
エリス
3e548770b7 ci(android): update java requirement for cordova-android@11 (#798) 2022-07-17 13:30:47 +09:00
Scott Murphy
4608f8ef80 fix(ios): preserving EXIF data (#712) 2022-03-22 16:23:56 +09:00
Marcus Abrahamsson
53223c3df2 fix(android): update queries in plugin.xml (#780)
Solves the issue with duplicate queries elements in AppManifest.xml

https://github.com/apache/cordova-plugin-camera/issues/779
2022-03-18 09:08:13 +09:00
エリス
bf12b39d18 ci(ios): update workflow w/ iOS 15 (#770) 2021-10-18 21:13:02 +09:00
エリス
ed216ce714 ci: remove old ci workflow (#766) 2021-09-25 16:02:04 +09:00
エリス
879712028a ci: add action-badge (#765) 2021-09-25 01:04:05 +09:00
エリス
204234b1b9 ci: remove travis & appveyor (#764) 2021-09-25 00:07:07 +09:00
エリス
0fba37cac3 ci: add gh-actions workflows (#762) 2021-09-16 22:17:11 +09:00
Erisu
5b8263732a Increment package version to 6.0.1-dev 2021-08-23 14:25:23 +09:00
Erisu
869f02da1a Updated version and RELEASENOTES.md for release 6.0.0 (camera-v6.0.0) 2021-08-19 17:14:44 +09:00
Dave Alden
e9db20e381 fix(android): return exception message (where it exists) (#687)
Co-authored-by: エリス <erisu@users.noreply.github.com>
Co-authored-by: jcesarmobile <jcesarmobile@gmail.com>
2021-08-11 16:56:42 +09:00
Francis
c7971d9f63 fix(android): file path correction if Uri authority is FileProvider (#585)
Co-authored-by: Tim Brust <ratchet.player@gmx.de>
Co-authored-by: Francis Monier <contactpro@francismonier.com>
Co-authored-by: Norman Breau <norman@normanbreau.com>
2021-08-11 16:34:20 +09:00
Niklas Merz
3112e5fb15 chore: add release notify action (#654)
Co-authored-by: Erisu <ellis.bryan@gmail.com>
2021-08-11 16:04:14 +09:00
Jesse MacFadyen
c56a255fe8 ci(gh-action): added workflow to run tests (#745)
Co-authored-by: Erisu <ellis.bryan@gmail.com>
2021-08-11 16:03:34 +09:00
エリス
0227cdcf14 feat(android)!: support AndroidX (#751)
* feat: migrate FileProvider to androidx
* feat: add androidx.core:core with variable override ANDROIDX_CORE_VERSION
2021-08-09 23:09:29 +09:00
Pieter Van Poyer
75bf807261 Bugfix issue 711 heic format (#731)
* Android - issue/711 - support .heic format
2021-08-09 15:36:58 +02:00
エリス
abfbbd35d5 fix(android): supports sdk-30 package visibility (#684) 2021-08-09 20:57:12 +09:00
エリス
59cf76d1da feat: bump cordova-android requirements for 10.x (#750) 2021-08-09 20:56:15 +09:00
エリス
4ee90a84f3 feat: bump plugin version for next major (#749)
Co-authored-by: Niklas Merz <nmerz@gedys-intraware.de>
2021-08-09 20:52:50 +09:00
Niklas Merz
5587bec320 Bump 5.0.4-dev 2021-08-09 10:53:00 +02:00
29 changed files with 4376 additions and 2717 deletions

View File

@@ -1,33 +0,0 @@
# appveyor file
# http://www.appveyor.com/docs/appveyor-yml
max_jobs: 1
shallow_clone: true
init:
- git config --global core.autocrlf true
image:
- Visual Studio 2017
environment:
matrix:
- nodejs_version: "10"
- nodejs_version: "12"
- nodejs_version: "14"
platform:
- x86
- x64
install:
- ps: Install-Product node $env:nodejs_version
- node --version
- npm install -g github:apache/cordova-paramedic
- npm install -g cordova
build: off
test_script:
- cordova-paramedic --config pr\windows-10-store --plugin . --justBuild

View File

@@ -13,7 +13,8 @@ For usage and support questions, please check out the resources below. Thanks!
You can get answers to your usage and support questions about **Apache Cordova** on:
* Slack Community Chat: https://cordova.slack.com (you can sign-up at http://slack.cordova.io/)
* GitHub Discussions: https://github.com/apache/cordova/discussions
* Slack Community Chat: https://cordova.slack.com (you can sign-up at https://s.apache.org/cordova-slack)
* StackOverflow: https://stackoverflow.com/questions/tagged/cordova using the tag `cordova`
---
@@ -22,6 +23,4 @@ If you are using a tool that uses Cordova internally, like e.g. Ionic, check the
* **Ionic Framework**
* [Ionic Community Forum](https://forum.ionicframework.com/)
* [Ionic Worldwide Slack](https://ionicworldwide.herokuapp.com/)
* **PhoneGap**
* [PhoneGap Developer Community](https://forums.adobe.com/community/phonegap)
* [Ionic Discord](https://ionic.link/discord)

137
.github/workflows/android.yml vendored Normal file
View File

@@ -0,0 +1,137 @@
# 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.
name: Android Testsuite
on:
push:
paths-ignore:
- '**.md'
- 'LICENSE'
- '.eslint*'
pull_request:
paths-ignore:
- '**.md'
- 'LICENSE'
- '.eslint*'
jobs:
test:
name: Android ${{ matrix.versions.android }} Test
runs-on: macos-latest
continue-on-error: true
# hoist configurations to top that are expected to be updated
env:
# Storing a copy of the repo
repo: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
node-version: 16
# These are the default Java configurations used by most tests.
# To customize these options, add "java-distro" or "java-version" to the strategy matrix with its overriding value.
default_java-distro: temurin
default_java-version: 11
# These are the default Android System Image configurations used by most tests.
# To customize these options, add "system-image-arch" or "system-image-target" to the strategy matrix with its overriding value.
default_system-image-arch: x86_64
default_system-image-target: google_apis # Most system images have a google_api option. Set this as default.
# configurations for each testing strategy (test matrix)
strategy:
matrix:
versions:
# Test the lowest minimum supported APIs
- android: 7
android-api: 24
# Test the last 3-4 supported APIs
- android: 10
android-api: 29
- android: 11
android-api: 30
- android: 12L
android-api: 32
- android: 13
android-api: 33
timeout-minutes: 60
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ env.node-version }}
- uses: actions/setup-java@v3
env:
java-version: ${{ matrix.versions.java-version == '' && env.default_java-version || matrix.versions.java-version }}
java-distro: ${{ matrix.versions.java-distro == '' && env.default_java-distro || matrix.versions.java-distro }}
with:
distribution: ${{ env.java-distro }}
java-version: ${{ env.java-version }}
- name: Run Environment Information
run: |
node --version
npm --version
java -version
- name: Run npm install
run: |
export PATH="/usr/local/lib/android/sdk/platform-tools":$PATH
export JAVA_HOME=$JAVA_HOME_11_X64
npm i -g cordova@latest
npm ci
- name: Run paramedic install
if: ${{ endswith(env.repo, '/cordova-paramedic') != true }}
run: npm i -g github:apache/cordova-paramedic
- uses: reactivecircus/android-emulator-runner@d94c3fbe4fe6a29e4a5ba47c12fb47677c73656b
env:
system-image-arch: ${{ matrix.versions.system-image-arch == '' && env.default_system-image-arch || matrix.versions.system-image-arch }}
system-image-target: ${{ matrix.versions.system-image-target == '' && env.default_system-image-target || matrix.versions.system-image-target }}
with:
api-level: ${{ matrix.versions.android-api }}
target: ${{ env.system-image-target }}
arch: ${{ env.system-image-arch }}
force-avd-creation: false
disable-animations: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim
script: echo "Pregenerate the AVD before running Paramedic"
- name: Run paramedic tests
uses: reactivecircus/android-emulator-runner@d94c3fbe4fe6a29e4a5ba47c12fb47677c73656b
env:
system-image-arch: ${{ matrix.versions.system-image-arch == '' && env.default_system-image-arch || matrix.versions.system-image-arch }}
system-image-target: ${{ matrix.versions.system-image-target == '' && env.default_system-image-target || matrix.versions.system-image-target }}
test_config: 'android-${{ matrix.versions.android }}.config.json'
# Generally, this should automatically work for cordova-paramedic & plugins. If the path is unique, this can be manually changed.
test_plugin_path: ${{ endswith(env.repo, '/cordova-paramedic') && './spec/testable-plugin/' || './' }}
paramedic: ${{ endswith(env.repo, '/cordova-paramedic') && 'node main.js' || 'cordova-paramedic' }}
with:
api-level: ${{ matrix.versions.android-api }}
target: ${{ env.system-image-target }}
arch: ${{ env.system-image-arch }}
force-avd-creation: false
disable-animations: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim
script: ${{ env.paramedic }} --config ./pr/local/${{ env.test_config }} --plugin ${{ env.test_plugin_path }}

73
.github/workflows/chrome.yml vendored Normal file
View File

@@ -0,0 +1,73 @@
# 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.
name: Chrome Testsuite
on:
push:
paths-ignore:
- '**.md'
- 'LICENSE'
- '.eslint*'
pull_request:
paths-ignore:
- '**.md'
- 'LICENSE'
- '.eslint*'
jobs:
test:
name: Chrome Latest Test
runs-on: ubuntu-latest
# hoist configurations to top that are expected to be updated
env:
# Storing a copy of the repo
repo: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
node-version: 16
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ env.node-version }}
- name: Run install xvfb
run: sudo apt-get install xvfb
- name: Run Environment Information
run: |
node --version
npm --version
- name: Run npm install
run: |
npm i -g cordova@latest
npm ci
- name: Run paramedic install
if: ${{ endswith(env.repo, '/cordova-paramedic') != true }}
run: npm i -g github:apache/cordova-paramedic
- name: Run paramedic tests
env:
test_config: 'browser.config.json'
# Generally, this should automatically work for cordova-paramedic & plugins. If the path is unique, this can be manually changed.
test_plugin_path: ${{ endswith(env.repo, '/cordova-paramedic') && './spec/testable-plugin/' || './' }}
paramedic: ${{ endswith(env.repo, '/cordova-paramedic') && 'node main.js' || 'cordova-paramedic' }}
run: xvfb-run --auto-servernum ${{ env.paramedic }} --config ./pr/local/${{ env.test_config }} --plugin ${{ env.test_plugin_path }}

101
.github/workflows/ios.yml vendored Normal file
View File

@@ -0,0 +1,101 @@
# 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.
name: iOS Testsuite
on:
push:
paths-ignore:
- '**.md'
- 'LICENSE'
- '.eslint*'
pull_request:
paths-ignore:
- '**.md'
- 'LICENSE'
- '.eslint*'
jobs:
test:
name: iOS ${{ matrix.versions.ios-version }} Test
runs-on: ${{ matrix.versions.os-version }}
continue-on-error: true
# hoist configurations to top that are expected to be updated
env:
# Storing a copy of the repo
repo: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
node-version: 16
# > Starting April 26, 2021, all iOS and iPadOS apps submitted to the App Store must be built with Xcode 12 and the iOS 14 SDK.
# Because of Apple's requirement, listed above, We will only be using the latest Xcode release for testing.
# To customize these options, add "xcode-version" to the strategy matrix with its overriding value.
default_xcode-version: latest-stable
strategy:
matrix:
versions:
- os-version: macos-11
ios-version: 13.x
xcode-version: 11.x
- os-version: macos-11
ios-version: 14.x
xcode-version: 12.x
- os-version: macos-11
ios-version: 15.x
xcode-version: 13.x
- os-version: macos-12
ios-version: 16.x
xcode-version: 14.x
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ env.node-version }}
- uses: maxim-lobanov/setup-xcode@9a697e2b393340c3cacd97468baa318e4c883d98
env:
xcode-version: ${{ matrix.versions.xcode-version == '' && env.default_xcode-version || matrix.versions.xcode-version }}
with:
xcode-version: ${{ env.xcode-version }}
- name: Run Environment Information
run: |
node --version
npm --version
xcodebuild -version
- name: Run npm install
run: |
npm i -g cordova@latest ios-deploy@latest
npm ci
- name: Run paramedic install
if: ${{ endswith(env.repo, '/cordova-paramedic') != true }}
run: npm i -g github:apache/cordova-paramedic
- name: Run paramedic tests
env:
test_config: 'ios-${{ matrix.versions.ios-version }}.config.json'
# Generally, this should automatically work for cordova-paramedic & plugins. If the path is unique, this can be manually changed.
test_plugin_path: ${{ endswith(env.repo, '/cordova-paramedic') && './spec/testable-plugin/' || './' }}
paramedic: ${{ endswith(env.repo, '/cordova-paramedic') && 'node main.js' || 'cordova-paramedic' }}
run: ${{ env.paramedic }} --config ./pr/local/${{ env.test_config }} --plugin ${{ env.test_plugin_path }}

56
.github/workflows/lint.yml vendored Normal file
View File

@@ -0,0 +1,56 @@
# 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.
name: Lint Test
on:
push:
paths:
- '**.js'
- '.eslint*'
- '.github/workflow/lint.yml'
pull_request:
paths:
- '**.js'
- '.eslint*'
- '.github/workflow/lint.yml'
jobs:
test:
name: Lint Test
runs-on: ubuntu-latest
env:
node-version: 16
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ env.node-version }}
- name: Run Environment Information
run: |
node --version
npm --version
- name: Run npm install
run: |
npm ci
- name: Run lint test
run: |
npm run lint

13
.github/workflows/release-notify.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
name: Close issue asking for release
on:
issues:
types: [opened]
jobs:
action-test:
runs-on: ubuntu-latest
steps:
- uses: niklasmerz/release-notify@master
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,3 +1,2 @@
.*
appveyor.yml
tests

View File

@@ -1,118 +0,0 @@
# 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
secure: QivPLlqTVvOo3TJeHxuBOfxU6lho1I0IxQ3b68yntkEQQJko6kzleXHfgjf0a8aw8m38E3+fxaBWF1bGyucGwOLDWY8Ddt2P2xg44zdXH5EXHd9oIqAgngIdzLvUtH3Db2TbQEtIGOkrnNR2STovjqB7vHGLASQrgs4oL7r32/s=
env:
global:
- SAUCE_USERNAME=snay
- TRAVIS_NODE_VERSION=12
- ANDROID_API_LEVEL=29
- ANDROID_BUILD_TOOLS_VERSION=29.0.2
language: node_js
node_js: 14
# yaml anchor/alias: https://medium.com/@tommyvn/travis-yml-dry-with-anchors-8b6a3ac1b027
_ios: &_ios
os: osx
osx_image: xcode11.6
_android: &_android
language: android
os: linux
jdk: oraclejdk8
android:
components:
- tools
- build-tools-$ANDROID_BUILD_TOOLS_VERSION
- android-$ANDROID_API_LEVEL
licenses:
- "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
# local tests, without saucelabs
- env: PLATFORM=local/browser
<<: *_ios
- env: PLATFORM=local/ios-10.0
<<: *_ios
# many tests with saucelabs
- env: PLATFORM=browser-chrome
- env: PLATFORM=browser-firefox
- env: PLATFORM=browser-safari
- env: PLATFORM=browser-edge
- env: PLATFORM=ios-11.3
<<: *_ios
- env: PLATFORM=ios-12.0
<<: *_ios
- env: PLATFORM=ios-12.2
<<: *_ios
- env: PLATFORM=android-5.1
<<: *_android
- env: PLATFORM=android-6.0
<<: *_android
- env: PLATFORM=android-7.0
<<: *_android
- env: PLATFORM=android-7.1
<<: *_android
- env: PLATFORM=android-8.0
<<: *_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
- 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
install:
- npm install
before_script:
- |
if [[ "$TRAVIS_REPO_SLUG" =~ cordova-paramedic ]]; then
# when used in the cordova-paramedic repo
TEST_COMMAND="npm run eslint"
PARAMEDIC_PLUGIN_TO_TEST="./spec/testable-plugin/"
PARAMEDIC_COMMAND="node main.js"
else
# when used in any other (plugin) repo
TEST_COMMAND="npm test"
PARAMEDIC_PLUGIN_TO_TEST=$(pwd)
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;
fi

View File

@@ -21,12 +21,10 @@ description: Take pictures with the device camera.
# under the License.
-->
|AppVeyor|Travis CI|
|:-:|:-:|
|[![Build status](https://ci.appveyor.com/api/projects/status/github/apache/cordova-plugin-camera?branch=master)](https://ci.appveyor.com/project/ApacheSoftwareFoundation/cordova-plugin-camera)|[![Build Status](https://travis-ci.org/apache/cordova-plugin-camera.svg?branch=master)](https://travis-ci.org/apache/cordova-plugin-camera)|
# cordova-plugin-camera
[![Android Testsuite](https://github.com/apache/cordova-plugin-camera/actions/workflows/android.yml/badge.svg)](https://github.com/apache/cordova-plugin-camera/actions/workflows/android.yml) [![Chrome Testsuite](https://github.com/apache/cordova-plugin-camera/actions/workflows/chrome.yml/badge.svg)](https://github.com/apache/cordova-plugin-camera/actions/workflows/chrome.yml) [![iOS Testsuite](https://github.com/apache/cordova-plugin-camera/actions/workflows/ios.yml/badge.svg)](https://github.com/apache/cordova-plugin-camera/actions/workflows/ios.yml) [![Lint Test](https://github.com/apache/cordova-plugin-camera/actions/workflows/lint.yml/badge.svg)](https://github.com/apache/cordova-plugin-camera/actions/workflows/lint.yml)
This plugin defines a global `navigator.camera` object, which provides an API for taking pictures and for choosing images from
the system's image library.
@@ -40,16 +38,20 @@ Although the object is attached to the global scoped `navigator`, it is not avai
## Installation
This requires cordova 5.0+
cordova plugin add cordova-plugin-camera
Older versions of cordova can still install via the __deprecated__ id
cordova plugin add org.apache.cordova.camera
It is also possible to install via repo url directly ( unstable )
cordova plugin add https://github.com/apache/cordova-plugin-camera.git
## Plugin variables
The plugin uses the `ANDROIDX_CORE_VERSION` variable to configure `androidx.core:core` dependency. This allows to avoid conflicts with other plugins that have the dependency hardcoded.
If no value is passed, it will use `1.6.+` as the default value.
The variable is configured on install time
cordova plugin add cordova-plugin-camera --variable ANDROIDX_CORE_VERSION=1.8.0
## How to Contribute
@@ -170,8 +172,6 @@ __Supported Platforms__
- Android
- Browser
- iOS
- Windows
- OSX
More examples [here](#camera-getPicture-examples). Quirks [here](#camera-getPicture-quirks).
@@ -474,16 +474,6 @@ displays:
// do your thing here!
}, 0);
#### Windows quirks
On Windows Phone 8.1 using `SAVEDPHOTOALBUM` or `PHOTOLIBRARY` as a source type causes application to suspend until file picker returns the selected image and
then restore with start page as defined in app's `config.xml`. In case when `camera.getPicture` was called from different page, this will lead to reloading
start page from scratch and success and error callbacks will never be called.
To avoid this we suggest using SPA pattern or call `camera.getPicture` only from your app's start page.
More information about Windows Phone 8.1 picker APIs is here: [How to continue your Windows Phone app after calling a file picker](https://msdn.microsoft.com/en-us/library/windows/apps/dn720490.aspx)
## `CameraOptions` Errata <a name="CameraOptions-quirks"></a>
#### Android Quirks
@@ -569,12 +559,6 @@ function displayImage(imgUri) {
}
```
To display the image on some platforms, you might need to include the main part of the URI in the Content-Security-Policy `<meta>` element in index.html. For example, on Windows 10, you can include `ms-appdata:` in your `<meta>` element. Here is an example.
```html
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: ms-appdata: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *">
```
## Take a Picture and Return Thumbnails (Resize the Picture) <a name="getThumbnails"></a>
To get smaller images, you can return a resized image by passing both `targetHeight` and `targetWidth` values with your CameraOptions object. In this example, you resize the returned image to fit in a 100px by 100px box (the aspect ratio is maintained, so 100px is either the height or width, whichever is greater in the source).

View File

@@ -20,6 +20,61 @@
-->
# Release Notes
### 7.0.0 (Sep 06, 2023)
**Breaking Changes:**
* [GH-848](https://github.com/apache/cordova-plugin-camera/pull/848) fix!: remove deprecated platforms
* [GH-844](https://github.com/apache/cordova-plugin-camera/pull/844) feat(android)!: Android 13 support
**Fixes:**
* [GH-827](https://github.com/apache/cordova-plugin-camera/pull/827) fix(android): set `applicationId`
* [GH-810](https://github.com/apache/cordova-plugin-camera/pull/810) fix(browser): use `navigator.mediaDevices.getUserMedia`
* [GH-712](https://github.com/apache/cordova-plugin-camera/pull/712) fix(ios): preserving `EXIF` data
* [GH-780](https://github.com/apache/cordova-plugin-camera/pull/780) fix(android): update queries in `plugin.xml`
**Chores, Dependencies, Docs:**
* [GH-850](https://github.com/apache/cordova-plugin-camera/pull/850) chore: remove windows/osx from `plugin.xml`
* [GH-849](https://github.com/apache/cordova-plugin-camera/pull/849) chore: Update `SUPPORT_QUESTION.md` template
* [GH-831](https://github.com/apache/cordova-plugin-camera/pull/831) chore(android): Cleanup obsolete `BuildConfig` comments
* [GH-846](https://github.com/apache/cordova-plugin-camera/pull/846) dep(dev)!: bump `@cordova/eslint-config@5.0`
* [GH-800](https://github.com/apache/cordova-plugin-camera/pull/800) dep(npm): bump package-lock v2 w/ rebuild
* [GH-808](https://github.com/apache/cordova-plugin-camera/pull/808) docs(README): Document `ANDROIDX_CORE_VERSION` variable
**CI:**
* [GH-851](https://github.com/apache/cordova-plugin-camera/pull/851) ci(gh-action): sync with `paramedic` configs
* [GH-835](https://github.com/apache/cordova-plugin-camera/pull/835) ci(android): Drop API 22 & 31. Added API 24 & 33
* [GH-804](https://github.com/apache/cordova-plugin-camera/pull/804) ci: sync workflow with `paramedic`
* [GH-798](https://github.com/apache/cordova-plugin-camera/pull/798) ci(android): update java requirement for `cordova-android@11`
* [GH-770](https://github.com/apache/cordova-plugin-camera/pull/770) ci(ios): update workflow w/ iOS 15
* [GH-766](https://github.com/apache/cordova-plugin-camera/pull/766) ci: remove old ci workflow
* [GH-765](https://github.com/apache/cordova-plugin-camera/pull/765) ci: add action-badge
* [GH-764](https://github.com/apache/cordova-plugin-camera/pull/764) ci: remove `travis` & `appveyor`
* [GH-762](https://github.com/apache/cordova-plugin-camera/pull/762) ci: add `gh-actions` workflows
### 6.0.0 (Aug 19, 2021)
**Feature:**
* [GH-751](https://github.com/apache/cordova-plugin-camera/pull/751) feat(android)!: support **AndroidX**
* [GH-750](https://github.com/apache/cordova-plugin-camera/pull/750) feat(android): bump `cordova-android` requirements for `10.x`
* [GH-731](https://github.com/apache/cordova-plugin-camera/pull/731) feat(android): encode `heic` format to `EncodingType` for webview display [#711](https://github.com/apache/cordova-plugin-camera/issues/711)
* [GH-684](https://github.com/apache/cordova-plugin-camera/pull/684) feat(android): `sdk-30` package visibility support
**Fix:**
* [GH-687](https://github.com/apache/cordova-plugin-camera/pull/687) fix(android): return exception message (where it exists)
* [GH-585](https://github.com/apache/cordova-plugin-camera/pull/585) fix(android): file path correction if `Uri` authority is `FileProvider`
**Chore & CI:**
* [GH-749](https://github.com/apache/cordova-plugin-camera/pull/749) chore: bump plugin version for next major
* [GH-654](https://github.com/apache/cordova-plugin-camera/pull/654) chore: add release notify action
* [GH-745](https://github.com/apache/cordova-plugin-camera/pull/745) ci(gh-action): added workflow to run tests
### 5.0.3 (Aug 04, 2021)
* [GH-754](https://github.com/apache/cordova-plugin-camera/pull/754) chore: rebuilt `package-lock.json`

4524
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "cordova-plugin-camera",
"version": "5.0.3",
"version": "7.0.0",
"description": "Cordova Camera Plugin",
"types": "./types/index.d.ts",
"cordova": {
@@ -8,9 +8,7 @@
"platforms": [
"android",
"ios",
"browser",
"windows",
"osx"
"browser"
]
},
"repository": "github:apache/cordova-plugin-camera",
@@ -21,9 +19,7 @@
"ecosystem:cordova",
"cordova-android",
"cordova-ios",
"cordova-browser",
"cordova-windows",
"cordova-osx"
"cordova-browser"
],
"scripts": {
"test": "npm run lint",
@@ -45,17 +41,27 @@
"cordova-ios": ">=5.1.0",
"cordova": ">=9.0.0"
},
"5.0.3": {
"5.0.4-dev": {
"cordova-android": "<10.0.0",
"cordova-ios": ">=5.1.0",
"cordova": ">=9.0.0"
},
"6.0.0": {
"cordova-android": ">=10.0.0",
"cordova-ios": ">=5.1.0",
"cordova": ">=9.0.0"
},
"7.0.0": {
"cordova-android": ">=12.0.0",
"cordova-ios": ">=5.1.0",
"cordova": ">=9.0.0"
},
"8.0.0": {
"cordova": ">100"
}
}
},
"devDependencies": {
"@cordova/eslint-config": "^3.0.0"
"@cordova/eslint-config": "^5.0.0"
}
}

View File

@@ -21,7 +21,7 @@
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
xmlns:android="http://schemas.android.com/apk/res/android"
id="cordova-plugin-camera"
version="5.0.3">
version="7.0.0">
<name>Camera</name>
<description>Cordova Camera Plugin</description>
<license>Apache 2.0</license>
@@ -31,7 +31,7 @@
<engines>
<engine name="cordova" version=">=9.0.0"/>
<engine name="cordova-android" version="<10.0.0" />
<engine name="cordova-android" version=">=12.0.0" />
<engine name="cordova-ios" version=">=5.1.0" />
</engines>
@@ -55,7 +55,9 @@
</feature>
</config-file>
<config-file target="AndroidManifest.xml" parent="/*">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
</config-file>
<config-file target="AndroidManifest.xml" parent="application">
<provider
@@ -69,6 +71,22 @@
</provider>
</config-file>
<config-file target="AndroidManifest.xml" parent="queries">
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
<intent>
<action android:name="android.intent.action.GET_CONTENT" />
</intent>
<intent>
<action android:name="android.intent.action.PICK" />
</intent>
<intent>
<action android:name="com.android.camera.action.CROP" />
<data android:scheme="content" android:mimeType="image/*"/>
</intent>
</config-file>
<source-file src="src/android/CameraLauncher.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" />
@@ -76,13 +94,12 @@
<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" />
<preference name="ANDROIDX_CORE_VERSION" default="1.6.+"/>
<framework src="androidx.core:core:$ANDROIDX_CORE_VERSION" />
<js-module src="www/CameraPopoverHandle.js" name="CameraPopoverHandle">
<clobbers target="CameraPopoverHandle" />
</js-module>
<preference name="ANDROID_SUPPORT_V4_VERSION" default="27.+"/>
<framework src="com.android.support:support-v4:$ANDROID_SUPPORT_V4_VERSION"/>
</platform>
<!-- ios -->
@@ -128,36 +145,4 @@
</js-module>
</platform>
<!-- windows -->
<platform name="windows">
<config-file target="package.appxmanifest" parent="/Package/Capabilities">
<DeviceCapability Name="webcam" />
</config-file>
<js-module src="www/CameraPopoverHandle.js" name="CameraPopoverHandle">
<clobbers target="CameraPopoverHandle" />
</js-module>
<js-module src="src/windows/CameraProxy.js" name="CameraProxy">
<runs />
</js-module>
</platform>
<!-- osx -->
<platform name="osx">
<config-file target="config.xml" parent="/*">
<feature name="Camera">
<param name="osx-package" value="CDVCamera"/>
</feature>
</config-file>
<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>

View File

@@ -19,6 +19,7 @@
package org.apache.cordova.camera;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
@@ -39,7 +40,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v4.content.FileProvider;
import androidx.core.content.FileProvider;
import android.util.Base64;
import org.apache.cordova.BuildHelper;
@@ -59,6 +60,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
/**
@@ -87,6 +89,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
private static final String PNG_EXTENSION = "." + PNG_TYPE;
private static final String PNG_MIME_TYPE = "image/png";
private static final String JPEG_MIME_TYPE = "image/jpeg";
private static final String HEIC_MIME_TYPE = "image/heic";
private static final String GET_PICTURE = "Get Picture";
private static final String GET_VIDEO = "Get Video";
private static final String GET_All = "Get All";
@@ -121,8 +124,6 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
private boolean orientationCorrected; // Has the picture's orientation been corrected
private boolean allowEdit; // Should we allow the user to crop the image.
protected final static String[] permissions = { Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE };
public CallbackContext callbackContext;
private int numPics;
@@ -144,11 +145,9 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
*/
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
this.callbackContext = callbackContext;
//Adding an API to CoreAndroid to get the BuildConfigValue
//This allows us to not make this a breaking change to embedding
this.applicationId = (String) BuildHelper.getBuildConfigValue(cordova.getActivity(), "APPLICATION_ID");
this.applicationId = preferences.getString("applicationId", this.applicationId);
this.applicationId = cordova.getContext().getPackageName();
this.applicationId = preferences.getString("applicationId", this.applicationId);
if (action.equals(TAKE_PICTURE_ACTION)) {
this.srcType = CAMERA;
@@ -194,10 +193,11 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
else if ((this.srcType == PHOTOLIBRARY) || (this.srcType == SAVEDPHOTOALBUM)) {
// FIXME: Stop always requesting the permission
if(!PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
PermissionHelper.requestPermission(this, SAVE_TO_ALBUM_SEC, Manifest.permission.READ_EXTERNAL_STORAGE);
String[] permissions = getPermissions(true, mediaType);
if(!hasPermissions(permissions)) {
PermissionHelper.requestPermissions(this, SAVE_TO_ALBUM_SEC, permissions);
} else {
this.getImage(this.srcType, destType, encodingType);
this.getImage(this.srcType, destType);
}
}
}
@@ -222,6 +222,37 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// LOCAL METHODS
//--------------------------------------------------------------------------
private String[] getPermissions(boolean storageOnly, int mediaType) {
ArrayList<String> permissions = new ArrayList<>();
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// Android API 33 and higher
switch (mediaType) {
case PICTURE:
permissions.add(Manifest.permission.READ_MEDIA_IMAGES);
break;
case VIDEO:
permissions.add(Manifest.permission.READ_MEDIA_VIDEO);
break;
default:
permissions.add(Manifest.permission.READ_MEDIA_IMAGES);
permissions.add(Manifest.permission.READ_MEDIA_VIDEO);
break;
}
} else {
// Android API 32 or lower
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
if (!storageOnly) {
// Add camera permission when not storage.
permissions.add(Manifest.permission.CAMERA);
}
return permissions.toArray(new String[0]);
}
private String getTempDirectoryPath() {
File cache = cordova.getActivity().getCacheDir();
// Create the cache directory if it doesn't exist
@@ -244,8 +275,13 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
* @param encodingType Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
*/
public void callTakePicture(int returnType, int encodingType) {
boolean saveAlbumPermission = PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
&& PermissionHelper.hasPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
String[] storagePermissions = getPermissions(true, mediaType);
boolean saveAlbumPermission;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
saveAlbumPermission = this.saveToPhotoAlbum ? hasPermissions(storagePermissions) : true;
} else {
saveAlbumPermission = hasPermissions(storagePermissions);
}
boolean takePicturePermission = PermissionHelper.hasPermission(this, Manifest.permission.CAMERA);
// CB-10120: The CAMERA permission does not need to be requested unless it is declared
@@ -273,13 +309,12 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
if (takePicturePermission && saveAlbumPermission) {
takePicture(returnType, encodingType);
} else if (saveAlbumPermission && !takePicturePermission) {
} else if (saveAlbumPermission) {
PermissionHelper.requestPermission(this, TAKE_PIC_SEC, Manifest.permission.CAMERA);
} else if (!saveAlbumPermission && takePicturePermission) {
PermissionHelper.requestPermissions(this, TAKE_PIC_SEC,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE});
} else if (takePicturePermission) {
PermissionHelper.requestPermissions(this, TAKE_PIC_SEC, storagePermissions);
} else {
PermissionHelper.requestPermissions(this, TAKE_PIC_SEC, permissions);
PermissionHelper.requestPermissions(this, TAKE_PIC_SEC, getPermissions(false, mediaType));
}
}
@@ -356,11 +391,10 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
*
* @param srcType The album to get image from.
* @param returnType Set the type of image to return.
* @param encodingType
*/
// TODO: Images selected from SDCARD don't display correctly, but from CAMERA ALBUM do!
// TODO: Images from kitkat filechooser not going into crop function
public void getImage(int srcType, int returnType, int encodingType) {
public void getImage(int srcType, int returnType) {
Intent intent = new Intent();
String title = GET_PICTURE;
croppedUri = null;
@@ -420,7 +454,6 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// set crop properties
cropIntent.putExtra("crop", "true");
// indicate output X and Y
if (targetWidth > 0) {
cropIntent.putExtra("outputX", targetWidth);
@@ -439,7 +472,6 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
cropIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
cropIntent.putExtra("output", croppedUri);
// start the activity - we handle returning in onActivityResult
if (this.cordova != null) {
@@ -450,9 +482,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
LOG.e(LOG_TAG, "Crop operation not supported on this device");
try {
processResultFromCamera(destType, cameraIntent);
}
catch (IOException e)
{
} catch (IOException e) {
e.printStackTrace();
LOG.e(LOG_TAG, "Unable to write to file");
}
@@ -475,7 +505,6 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
this.croppedFilePath :
this.imageFilePath;
if (this.encodingType == JPEG) {
try {
//We don't support PNG, so let's not pretend we do
@@ -610,7 +639,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
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));
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, getMimetypeForEncodingType());
Uri galleryOutputUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
InputStream fileStream = org.apache.cordova.camera.FileHelper.getInputStreamFromUriString(imageUri.toString(), cordova);
@@ -623,7 +652,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
private GalleryPathVO getPicturesPath() {
String timeStamp = new SimpleDateFormat(TIME_FORMAT).format(new Date());
String imageFileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? JPEG_EXTENSION : PNG_EXTENSION);
String imageFileName = "IMG_" + timeStamp + getExtensionForEncodingType();
File storageDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
storageDir.mkdirs();
@@ -639,28 +668,20 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
/**
* Converts output image format int value to string value of mime type.
* @param outputFormat int Output format of camera API.
* Must be value of either JPEG or PNG constant
* @return String String value of mime type or empty string if mime type is not supported
*/
private String getMimetypeForFormat(int outputFormat) {
if (outputFormat == PNG) return PNG_MIME_TYPE;
if (outputFormat == JPEG) return JPEG_MIME_TYPE;
private String getMimetypeForEncodingType() {
if (encodingType == PNG) return PNG_MIME_TYPE;
if (encodingType == JPEG) return JPEG_MIME_TYPE;
return "";
}
private String outputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
private String outputModifiedBitmap(Bitmap bitmap, Uri uri, String mimeTypeOfOriginalFile) throws IOException {
// Some content: URIs do not map to file paths (e.g. picasa).
String realPath = FileHelper.getRealPath(uri, this.cordova);
String fileName = calculateModifiedBitmapOutputFileName(mimeTypeOfOriginalFile, realPath);
// Get filename from uri
String fileName = realPath != null ?
realPath.substring(realPath.lastIndexOf('/') + 1) :
"modified." + (this.encodingType == JPEG ? JPEG_TYPE : PNG_TYPE);
String timeStamp = new SimpleDateFormat(TIME_FORMAT).format(new Date());
//String fileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? ".jpg" : ".png");
String modifiedPath = getTempDirectoryPath() + "/" + fileName;
OutputStream os = new FileOutputStream(modifiedPath);
@@ -684,6 +705,23 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
return modifiedPath;
}
private String calculateModifiedBitmapOutputFileName(String mimeTypeOfOriginalFile, String realPath) {
if (realPath == null) {
return "modified" + getExtensionForEncodingType();
}
String fileName = realPath.substring(realPath.lastIndexOf('/') + 1);
if (getMimetypeForEncodingType().equals(mimeTypeOfOriginalFile)) {
return fileName;
}
// if the picture is not a jpeg or png, (a .heic for example) when processed to a bitmap
// the file extension is changed to the output format, f.e. an input file my_photo.heic could become my_photo.jpg
return fileName.substring(fileName.lastIndexOf(".") + 1) + getExtensionForEncodingType();
}
private String getExtensionForEncodingType() {
return this.encodingType == JPEG ? JPEG_EXTENSION : PNG_EXTENSION;
}
/**
* Applies all needed transformation to the image received from the gallery.
@@ -707,24 +745,22 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
String uriString = uri.toString();
String finalLocation = fileLocation != null ? fileLocation : uriString;
String mimeType = FileHelper.getMimeType(uriString, this.cordova);
String mimeTypeOfGalleryFile = 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))) {
// If you ask for video or the selected file cannot be processed
// there will be no attempt to resize any returned data.
if (this.mediaType == VIDEO || !isImageMimeTypeProcessable(mimeTypeOfGalleryFile)) {
this.callbackContext.success(finalLocation);
}
else {
} 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)))
getMimetypeForEncodingType().equalsIgnoreCase(mimeTypeOfGalleryFile))
{
this.callbackContext.success(finalLocation);
} else {
@@ -750,17 +786,17 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// Did we modify the image?
if ( (this.targetHeight > 0 && this.targetWidth > 0) ||
(this.correctOrientation && this.orientationCorrected) ||
!mimeType.equalsIgnoreCase(getMimetypeForFormat(encodingType)))
!mimeTypeOfGalleryFile.equalsIgnoreCase(getMimetypeForEncodingType()))
{
try {
String modifiedPath = this.outputModifiedBitmap(bitmap, uri);
String modifiedPath = this.outputModifiedBitmap(bitmap, uri, mimeTypeOfGalleryFile);
// 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.");
this.failPicture("Error retrieving image: "+e.getLocalizedMessage());
}
} else {
this.callbackContext.success(finalLocation);
@@ -777,6 +813,18 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
/**
* JPEG, PNG and HEIC mime types (images) can be scaled, decreased in quantity, corrected by orientation.
* But f.e. an image/gif cannot be scaled, but is can be selected through the PHOTOLIBRARY.
*
* @param mimeType The mimeType to check
* @return if the mimeType is a processable image mime type
*/
private boolean isImageMimeTypeProcessable(String mimeType) {
return JPEG_MIME_TYPE.equalsIgnoreCase(mimeType) || PNG_MIME_TYPE.equalsIgnoreCase(mimeType)
|| HEIC_MIME_TYPE.equalsIgnoreCase(mimeType);
}
/**
* Called when the camera view exits.
*
@@ -830,7 +878,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
} catch (IOException e) {
e.printStackTrace();
this.failPicture("Error capturing image.");
this.failPicture("Error capturing image: "+e.getLocalizedMessage());
}
}
@@ -974,7 +1022,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
if (fileStream != null) {
// Generate a temporary file
String timeStamp = new SimpleDateFormat(TIME_FORMAT).format(new Date());
String fileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? JPEG_EXTENSION : PNG_EXTENSION);
String fileName = "IMG_" + timeStamp + (getExtensionForEncodingType());
localFile = new File(getTempDirectoryPath() + fileName);
galleryUri = Uri.fromFile(localFile);
writeUncompressedImage(fileStream, galleryUri);
@@ -998,15 +1046,11 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
rotate = 0;
}
}
}
catch (Exception e)
{
} catch (Exception e) {
LOG.e(LOG_TAG,"Exception while getting input stream: "+ e.toString());
return null;
}
try {
// figure out the original width and height of the image
BitmapFactory.Options options = new BitmapFactory.Options();
@@ -1052,7 +1096,6 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// determine the correct aspect ratio
int[] widthHeight = calculateAspectRatio(rotatedWidth, rotatedHeight);
// Load in the smallest bitmap possible that is closest to the size we want
options.inJustDecodeBounds = false;
options.inSampleSize = calculateSampleSize(rotatedWidth, rotatedHeight, widthHeight[0], widthHeight[1]);
@@ -1092,8 +1135,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
}
return scaledBitmap;
}
finally {
} finally {
// delete the temporary copy
if (localFile != null) {
localFile.delete();
@@ -1226,6 +1268,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// delete the duplicate file if the difference is 2 for file URI or 1 for Data URL
if ((currentNumOfImages - numPics) == diff) {
cursor.moveToLast();
@SuppressLint("Range")
int id = Integer.valueOf(cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media._ID)));
if (diff == 2) {
id--;
@@ -1269,7 +1312,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
code = null;
}
} catch (Exception e) {
this.failPicture("Error compressing image.");
this.failPicture("Error compressing image: "+e.getLocalizedMessage());
}
jpeg_data = null;
}
@@ -1305,9 +1348,8 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
this.conn.disconnect();
}
public void onRequestPermissionResult(int requestCode, String[] permissions,
int[] grantResults) throws JSONException {
int[] grantResults) {
for (int r : grantResults) {
if (r == PackageManager.PERMISSION_DENIED) {
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, PERMISSION_DENIED_ERROR));
@@ -1319,7 +1361,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
takePicture(this.destType, this.encodingType);
break;
case SAVE_TO_ALBUM_SEC:
this.getImage(this.srcType, this.destType, this.encodingType);
this.getImage(this.srcType, this.destType);
break;
}
}
@@ -1381,9 +1423,18 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
if (state.containsKey(IMAGE_FILE_PATH_KEY)) {
this.imageFilePath = state.getString(IMAGE_FILE_PATH_KEY);
this.imageFilePath = state.getString(IMAGE_FILE_PATH_KEY);
}
this.callbackContext = callbackContext;
}
private boolean hasPermissions(String[] permissions) {
for (String permission: permissions) {
if (!PermissionHelper.hasPermission(this, permission)) {
return false;
}
}
return true;
}
}

View File

@@ -19,7 +19,6 @@ package org.apache.cordova.camera;
import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.content.CursorLoader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
@@ -29,8 +28,8 @@ import android.provider.MediaStore;
import android.webkit.MimeTypeMap;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.LOG;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -44,7 +43,7 @@ public class FileHelper {
* Returns the real path of the given URI string.
* If the given URI string represents a content:// URI, the real path is retrieved from the media store.
*
* @param uriString the URI string of the audio/image/video
* @param uri the URI of the audio/image/video
* @param cordova the current application context
* @return the full path to the file
*/
@@ -57,7 +56,7 @@ public class FileHelper {
* Returns the real path of the given URI.
* If the given URI is a content:// URI, the real path is retrieved from the media store.
*
* @param uri the URI of the audio/image/video
* @param uriString the URI string from which to obtain the input stream
* @param cordova the current application context
* @return the full path to the file
*/
@@ -132,6 +131,9 @@ public class FileHelper {
if (isGooglePhotosUri(uri))
return uri.getLastPathSegment();
if (isFileProviderUri(context, uri))
return getFileProviderPath(context, uri);
return getDataColumn(context, uri, null, null);
}
// File
@@ -142,22 +144,6 @@ public class FileHelper {
return null;
}
public static String getRealPathFromURI_BelowAPI11(Context context, Uri contentUri) {
String[] proj = { MediaStore.Images.Media.DATA };
String result = null;
try {
Cursor cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
result = cursor.getString(column_index);
} catch (Exception e) {
result = null;
}
return result;
}
/**
* Returns an input stream based on given URI string.
*
@@ -177,6 +163,7 @@ public class FileHelper {
if (question > -1) {
uriString = uriString.substring(0, question);
}
if (uriString.startsWith("file:///android_asset/")) {
Uri uri = Uri.parse(uriString);
String relativePath = uri.getPath().substring(15);
@@ -206,6 +193,7 @@ public class FileHelper {
* @return a path without the "file://" prefix
*/
public static String stripFileProtocol(String uriString) {
if (uriString.startsWith("file://")) {
uriString = uriString.substring(7);
}
@@ -225,7 +213,7 @@ public class FileHelper {
}
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
/**
* Returns the mime type of the data specified by the given URI string.
*
@@ -233,7 +221,7 @@ public class FileHelper {
* @return the mime type of the specified data
*/
public static String getMimeType(String uriString, CordovaInterface cordova) {
String mimeType = null;
String mimeType;
Uri uri = Uri.parse(uriString);
if (uriString.startsWith("content://")) {
@@ -316,4 +304,28 @@ public class FileHelper {
public static boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}
/**
* @param context The Application context
* @param uri The Uri is checked by functions
* @return Whether the Uri authority is FileProvider
*/
public static boolean isFileProviderUri(final Context context, final Uri uri) {
final String packageName = context.getPackageName();
final String authority = new StringBuilder(packageName).append(".provider").toString();
return authority.equals(uri.getAuthority());
}
/**
* @param context The Application context
* @param uri The Uri is checked by functions
* @return File path or null if file is missing
*/
public static String getFileProviderPath(final Context context, final Uri uri)
{
final File appDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
final File file = new File(appDir, uri.getLastPathSegment());
return file.exists() ? file.toString(): null;
}
}

View File

@@ -18,4 +18,4 @@
*/
package org.apache.cordova.camera;
public class FileProvider extends android.support.v4.content.FileProvider {}
public class FileProvider extends androidx.core.content.FileProvider {}

View File

@@ -19,13 +19,13 @@
*
*/
var HIGHEST_POSSIBLE_Z_INDEX = 2147483647;
const HIGHEST_POSSIBLE_Z_INDEX = 2147483647;
function takePicture (success, error, opts) {
if (opts && opts[2] === 1) {
capture(success, error, opts);
} else {
var input = document.createElement('input');
const input = document.createElement('input');
input.style.position = 'relative';
input.style.zIndex = HIGHEST_POSSIBLE_Z_INDEX;
input.className = 'cordova-camera-select';
@@ -33,11 +33,11 @@ function takePicture (success, error, opts) {
input.name = 'files[]';
input.onchange = function (inputEvent) {
var reader = new FileReader(); /* eslint no-undef : 0 */
const reader = new FileReader(); /* eslint no-undef : 0 */
reader.onload = function (readerEvent) {
input.parentNode.removeChild(input);
var imageData = readerEvent.target.result;
const imageData = readerEvent.target.result;
return success(imageData.substr(imageData.indexOf(',') + 1));
};
@@ -50,16 +50,16 @@ function takePicture (success, error, opts) {
}
function capture (success, errorCallback, opts) {
var localMediaStream;
var targetWidth = opts[3];
var targetHeight = opts[4];
let localMediaStream;
let targetWidth = opts[3];
let targetHeight = opts[4];
targetWidth = targetWidth === -1 ? 320 : targetWidth;
targetHeight = targetHeight === -1 ? 240 : targetHeight;
var video = document.createElement('video');
var button = document.createElement('button');
var parent = document.createElement('div');
const video = document.createElement('video');
const button = document.createElement('button');
const parent = document.createElement('div');
parent.style.position = 'relative';
parent.style.zIndex = HIGHEST_POSSIBLE_Z_INDEX;
parent.className = 'cordova-camera-capture';
@@ -72,13 +72,13 @@ function capture (success, errorCallback, opts) {
button.onclick = function () {
// create a canvas and capture a frame from video stream
var canvas = document.createElement('canvas');
const canvas = document.createElement('canvas');
canvas.width = targetWidth;
canvas.height = targetHeight;
canvas.getContext('2d').drawImage(video, 0, 0, targetWidth, targetHeight);
// convert image stored in canvas to base64 encoded image
var imageData = canvas.toDataURL('image/png');
let imageData = canvas.toDataURL('image/png');
imageData = imageData.replace('data:image/png;base64,', '');
// stop video stream, remove video and button.
@@ -100,7 +100,7 @@ function capture (success, errorCallback, opts) {
navigator.mozGetUserMedia ||
navigator.msGetUserMedia;
var successCallback = function (stream) {
const successCallback = function (stream) {
localMediaStream = stream;
if ('srcObject' in video) {
video.srcObject = localMediaStream;
@@ -111,7 +111,11 @@ function capture (success, errorCallback, opts) {
document.body.appendChild(parent);
};
if (navigator.getUserMedia) {
if (navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia({ video: true, audio: false })
.then(successCallback)
.catch(errorCallback);
} else if (navigator.getUserMedia) {
navigator.getUserMedia({ video: true, audio: false }, successCallback, errorCallback);
} else {
alert('Browser does not support camera :(');
@@ -119,7 +123,7 @@ function capture (success, errorCallback, opts) {
}
module.exports = {
takePicture: takePicture,
takePicture,
cleanup: function () {}
};

View File

@@ -29,6 +29,7 @@
#import <ImageIO/CGImageDestination.h>
#import <MobileCoreServices/UTCoreTypes.h>
#import <objc/message.h>
#import <Photos/Photos.h>
#ifndef __CORDOVA_4_0_0
#import <Cordova/NSData+Base64.h>
@@ -159,7 +160,7 @@ static NSString* toBase64(NSData* data) {
if (pictureOptions.sourceType == UIImagePickerControllerSourceTypeCamera) {
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted)
{
if(!granted)
if (!granted)
{
// Denied; show an alert
dispatch_async(dispatch_get_main_queue(), ^{
@@ -174,11 +175,32 @@ static NSString* toBase64(NSData* data) {
[weakSelf.viewController presentViewController:alertController animated:YES completion:nil];
});
} else {
[weakSelf showCameraPicker:command.callbackId withOptions:pictureOptions];
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf showCameraPicker:command.callbackId withOptions:pictureOptions];
});
}
}];
} else {
[weakSelf showCameraPicker:command.callbackId withOptions:pictureOptions];
[weakSelf options:pictureOptions requestPhotoPermissions:^(BOOL granted) {
if (!granted) {
// Denied; show an alert
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"] message:NSLocalizedString(@"Access to the camera roll has been prohibited; please enable it in the Settings to continue.", nil) preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[weakSelf sendNoPermissionResult:command.callbackId];
}]];
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Settings", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
[weakSelf sendNoPermissionResult:command.callbackId];
}]];
[weakSelf.viewController presentViewController:alertController animated:YES completion:nil];
});
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf showCameraPicker:command.callbackId withOptions:pictureOptions];
});
}
}];
}
}];
}
@@ -367,24 +389,51 @@ static NSString* toBase64(NSData* data) {
data = UIImageJPEGRepresentation(image, [options.quality floatValue] / 100.0f);
}
if (options.usesGeolocation) {
NSDictionary* controllerMetadata = [info objectForKey:@"UIImagePickerControllerMediaMetadata"];
if (pickerController.sourceType == UIImagePickerControllerSourceTypeCamera) {
if (options.usesGeolocation) {
NSDictionary* controllerMetadata = [info objectForKey:@"UIImagePickerControllerMediaMetadata"];
if (controllerMetadata) {
self.data = data;
self.metadata = [[NSMutableDictionary alloc] init];
NSMutableDictionary* EXIFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyExifDictionary]mutableCopy];
if (EXIFDictionary) {
[self.metadata setObject:EXIFDictionary forKey:(NSString*)kCGImagePropertyExifDictionary];
}
if (IsAtLeastiOSVersion(@"8.0")) {
[[self locationManager] performSelector:NSSelectorFromString(@"requestWhenInUseAuthorization") withObject:nil afterDelay:0];
}
[[self locationManager] startUpdatingLocation];
}
data = nil;
}
} else if (pickerController.sourceType == UIImagePickerControllerSourceTypePhotoLibrary) {
PHAsset* asset = [info objectForKey:@"UIImagePickerControllerPHAsset"];
NSDictionary* controllerMetadata = [self getImageMetadataFromAsset:asset];
self.data = data;
if (controllerMetadata) {
self.data = data;
self.metadata = [[NSMutableDictionary alloc] init];
NSMutableDictionary* EXIFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyExifDictionary]mutableCopy];
if (EXIFDictionary) {
[self.metadata setObject:EXIFDictionary forKey:(NSString*)kCGImagePropertyExifDictionary];
}
if (IsAtLeastiOSVersion(@"8.0")) {
[[self locationManager] performSelector:NSSelectorFromString(@"requestWhenInUseAuthorization") withObject:nil afterDelay:0];
NSMutableDictionary* TIFFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyTIFFDictionary
]mutableCopy];
if (TIFFDictionary) {
[self.metadata setObject:TIFFDictionary forKey:(NSString*)kCGImagePropertyTIFFDictionary];
}
NSMutableDictionary* GPSDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyGPSDictionary
]mutableCopy];
if (GPSDictionary) {
[self.metadata setObject:GPSDictionary forKey:(NSString*)kCGImagePropertyGPSDictionary
];
}
[[self locationManager] startUpdatingLocation];
}
data = nil;
}
}
break;
default:
@@ -394,6 +443,78 @@ static NSString* toBase64(NSData* data) {
return data;
}
/* --------------------------------------------------------------
-- get the metadata of the image from a PHAsset
-------------------------------------------------------------- */
- (NSDictionary*)getImageMetadataFromAsset:(PHAsset*)asset {
if(asset == nil) {
return nil;
}
// get photo info from this asset
__block NSDictionary *dict = nil;
PHImageRequestOptions *imageRequestOptions = [[PHImageRequestOptions alloc] init];
imageRequestOptions.synchronous = YES;
[[PHImageManager defaultManager]
requestImageDataForAsset:asset
options:imageRequestOptions
resultHandler: ^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
dict = [self convertImageMetadata:imageData]; // as this imageData is in NSData format so we need a method to convert this NSData into NSDictionary
}];
return dict;
}
-(NSDictionary*)convertImageMetadata:(NSData*)imageData {
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)(imageData), NULL);
if (imageSource) {
NSDictionary *options = @{(NSString *)kCGImageSourceShouldCache : [NSNumber numberWithBool:NO]};
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);
if (imageProperties) {
NSDictionary *metadata = (__bridge NSDictionary *)imageProperties;
CFRelease(imageProperties);
CFRelease(imageSource);
NSLog(@"Metadata of selected image%@", metadata);// image metadata after converting NSData into NSDictionary
return metadata;
}
CFRelease(imageSource);
}
NSLog(@"Can't read image metadata");
return nil;
}
- (void)options:(CDVPictureOptions*)options requestPhotoPermissions:(void (^)(BOOL auth))completion
{
if((unsigned long)options.sourceType == 1){
completion(YES);
}
else{
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
switch (status) {
case PHAuthorizationStatusAuthorized:
completion(YES);
break;
case PHAuthorizationStatusNotDetermined: {
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus authorizationStatus) {
if (authorizationStatus == PHAuthorizationStatusAuthorized) {
completion(YES);
} else {
completion(NO);
}
}];
break;
}
default:
completion(NO);
break;
}
}
}
- (NSString*)tempFilePath:(NSString*)extension
{
NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath];
@@ -454,17 +575,48 @@ static NSString* toBase64(NSData* data) {
image = [self retrieveImage:info options:options];
NSData* data = [self processImage:image info:info options:options];
if (data) {
if (pickerController.sourceType == UIImagePickerControllerSourceTypePhotoLibrary) {
NSMutableData *imageDataWithExif = [NSMutableData data];
if (self.metadata) {
CGImageSourceRef sourceImage = CGImageSourceCreateWithData((__bridge CFDataRef)self.data, NULL);
CFStringRef sourceType = CGImageSourceGetType(sourceImage);
NSString* extension = options.encodingType == EncodingTypePNG? @"png" : @"jpg";
NSString* filePath = [self tempFilePath:extension];
NSError* err = nil;
CGImageDestinationRef destinationImage = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageDataWithExif, sourceType, 1, NULL);
CGImageDestinationAddImageFromSource(destinationImage, sourceImage, 0, (__bridge CFDictionaryRef)self.metadata);
CGImageDestinationFinalize(destinationImage);
// save file
if (![data writeToFile:filePath options:NSAtomicWrite error:&err]) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
} else {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[self urlTransformer:[NSURL fileURLWithPath:filePath]] absoluteString]];
CFRelease(sourceImage);
CFRelease(destinationImage);
} else {
imageDataWithExif = [self.data mutableCopy];
}
NSError* err = nil;
NSString* extension = self.pickerController.pictureOptions.encodingType == EncodingTypePNG ? @"png":@"jpg";
NSString* filePath = [self tempFilePath:extension];
// save file
if (![imageDataWithExif writeToFile:filePath options:NSAtomicWrite error:&err]) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
}
else {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[self urlTransformer:[NSURL fileURLWithPath:filePath]] absoluteString]];
}
} else if (pickerController.sourceType != UIImagePickerControllerSourceTypeCamera || !options.usesGeolocation) {
// No need to save file if usesGeolocation is true since it will be saved after the location is tracked
NSString* extension = options.encodingType == EncodingTypePNG? @"png" : @"jpg";
NSString* filePath = [self tempFilePath:extension];
NSError* err = nil;
// save file
if (![data writeToFile:filePath options:NSAtomicWrite error:&err]) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
} else {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[self urlTransformer:[NSURL fileURLWithPath:filePath]] absoluteString]];
}
}
}
}
break;
@@ -649,19 +801,23 @@ static NSString* toBase64(NSData* data) {
{
CDVPictureOptions* options = self.pickerController.pictureOptions;
CDVPluginResult* result = nil;
NSMutableData *imageDataWithExif = [NSMutableData data];
if (self.metadata) {
NSData* dataCopy = [self.data mutableCopy];
CGImageSourceRef sourceImage = CGImageSourceCreateWithData((__bridge CFDataRef)dataCopy, NULL);
CFStringRef sourceType = CGImageSourceGetType(sourceImage);
CGImageDestinationRef destinationImage = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)self.data, sourceType, 1, NULL);
CGImageDestinationRef destinationImage = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageDataWithExif, sourceType, 1, NULL);
CGImageDestinationAddImageFromSource(destinationImage, sourceImage, 0, (__bridge CFDictionaryRef)self.metadata);
CGImageDestinationFinalize(destinationImage);
dataCopy = nil;
CFRelease(sourceImage);
CFRelease(destinationImage);
} else {
imageDataWithExif = [self.data mutableCopy];
}
switch (options.destinationType) {
@@ -695,7 +851,7 @@ static NSString* toBase64(NSData* data) {
self.pickerController = nil;
self.data = nil;
self.metadata = nil;
imageDataWithExif = nil;
if (options.saveToPhotoAlbum) {
UIImageWriteToSavedPhotosAlbum([[UIImage alloc] initWithData:self.data], nil, nil, nil);
}

View File

@@ -1,80 +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.
*/
#import <Quartz/Quartz.h>
#import <AppKit/AppKit.h>
#import <Cordova/CDVPlugin.h>
enum CDVDestinationType {
DestinationTypeDataUrl = 0,
DestinationTypeFileUri
};
typedef NSUInteger CDVDestinationType;
enum CDVSourceType {
SourceTypePhotoLibrary = 0,
SourceTypeCamera,
SourceTypePhotoAlbum
};
typedef NSUInteger CDVSourceType;
enum CDVEncodingType {
EncodingTypeJPEG = 0,
EncodingTypePNG
};
typedef NSUInteger CDVEncodingType;
enum CDVMediaType {
MediaTypePicture = 0,
MediaTypeVideo,
MediaTypeAll
};
typedef NSUInteger CDVMediaType;
// ======================================================================= //
@interface CDVPictureOptions : NSObject
@property (strong) NSNumber *quality;
@property (assign) CDVDestinationType destinationType;
@property (assign) CDVSourceType sourceType;
@property (assign) CGSize targetSize;
@property (assign) CDVEncodingType encodingType;
@property (assign) CDVMediaType mediaType;
@property (assign) BOOL allowsEditing;
@property (assign) BOOL correctOrientation;
@property (assign) BOOL saveToPhotoAlbum;
+ (instancetype) createFromTakePictureArguments:(CDVInvokedUrlCommand *)command;
@end
// ======================================================================= //
@interface CDVCamera : CDVPlugin
- (void)takePicture:(CDVInvokedUrlCommand *)command;
- (void)cleanup:(CDVInvokedUrlCommand *)command;
@end

View File

@@ -1,258 +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.
*/
#import "CDVCamera.h"
@implementation CDVPictureOptions
+ (instancetype) createFromTakePictureArguments:(CDVInvokedUrlCommand*)command {
CDVPictureOptions *pictureOptions = [[CDVPictureOptions alloc] init];
pictureOptions.quality = [command argumentAtIndex:0 withDefault:@(50)];
pictureOptions.destinationType = [[command argumentAtIndex:1 withDefault:@(DestinationTypeFileUri)] unsignedIntegerValue];
pictureOptions.sourceType = [[command argumentAtIndex:2 withDefault:@(SourceTypeCamera)] unsignedIntegerValue];
NSNumber *targetWidth = [command argumentAtIndex:3 withDefault:nil];
NSNumber *targetHeight = [command argumentAtIndex:4 withDefault:nil];
pictureOptions.targetSize = CGSizeMake(0, 0);
if ((targetWidth != nil) && (targetHeight != nil)) {
pictureOptions.targetSize = CGSizeMake([targetWidth floatValue], [targetHeight floatValue]);
}
pictureOptions.encodingType = [[command argumentAtIndex:5 withDefault:@(EncodingTypeJPEG)] unsignedIntegerValue];
pictureOptions.mediaType = [[command argumentAtIndex:6 withDefault:@(MediaTypePicture)] unsignedIntegerValue];
pictureOptions.allowsEditing = [[command argumentAtIndex:7 withDefault:@(NO)] boolValue];
pictureOptions.correctOrientation = [[command argumentAtIndex:8 withDefault:@(NO)] boolValue];
pictureOptions.saveToPhotoAlbum = [[command argumentAtIndex:9 withDefault:@(NO)] boolValue];
return pictureOptions;
}
@end
// ======================================================================= //
@implementation CDVCamera
/*!
Static array that stores the temporary created files allowing to delete them when calling navigator.camera.cleanup(...)
*/
static NSMutableArray *cleanUpFiles;
+ (void)initialize {
cleanUpFiles = [NSMutableArray array];
}
- (void)takePicture:(CDVInvokedUrlCommand *)command {
CDVPictureOptions *pictureOptions = [CDVPictureOptions createFromTakePictureArguments:command];
if (pictureOptions.sourceType == SourceTypeCamera) {
[self takePictureFromCamera:command withOptions:pictureOptions];
} else {
[self takePictureFromFile:command withOptions:pictureOptions];
}
}
- (void)cleanup:(CDVInvokedUrlCommand*)command {
[self.commandDelegate runInBackground:^{
if (cleanUpFiles.count > 0) {
for (int i=0; i<cleanUpFiles.count; i++) {
NSString *path = [cleanUpFiles objectAtIndex:i];
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
}
[cleanUpFiles removeAllObjects];
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
}
}];
}
#pragma mark - Camera
/*!
Takes a picture from the iSight camera using the default OS dialog.
@see https://developer.apple.com/documentation/quartz/ikpicturetaker
*/
- (void)takePictureFromCamera:(CDVInvokedUrlCommand *)command withOptions:(CDVPictureOptions *)pictureOptions {
IKPictureTaker *pictureTaker = [IKPictureTaker pictureTaker];
[pictureTaker setValue:[NSNumber numberWithBool:YES] forKey:IKPictureTakerAllowsVideoCaptureKey];
[pictureTaker setValue:[NSNumber numberWithBool:NO] forKey:IKPictureTakerAllowsFileChoosingKey];
[pictureTaker setValue:[NSNumber numberWithBool:pictureOptions.allowsEditing] forKey:IKPictureTakerShowEffectsKey];
[pictureTaker setValue:[NSNumber numberWithBool:pictureOptions.allowsEditing] forKey:IKPictureTakerAllowsEditingKey];
NSDictionary *contextInfo = @{ @"command": command, @"pictureOptions" : pictureOptions};
[pictureTaker beginPictureTakerSheetForWindow:self.viewController.contentView.window withDelegate:self didEndSelector:@selector(pictureTakerDidEnd:returnCode:contextInfo:) contextInfo:(void *)CFBridgingRetain(contextInfo)];
}
- (void)pictureTakerDidEnd:(IKPictureTaker *)pictureTaker returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
if (returnCode == NSOKButton) {
NSDictionary *contextInfoDictionary = (NSDictionary *)CFBridgingRelease(contextInfo);
CDVInvokedUrlCommand *command = [contextInfoDictionary valueForKey:@"command"];
CDVPictureOptions *pictureOptions = [contextInfoDictionary valueForKey:@"pictureOptions"];
[self returnImage:pictureTaker.outputImage command:command options:pictureOptions];
}
}
#pragma mark - File
/*!
Allows to select an image or video using the system native dialog.
*/
- (void)takePictureFromFile:(CDVInvokedUrlCommand *)command withOptions:(CDVPictureOptions *)pictureOptions {
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
openPanel.canChooseFiles = YES;
openPanel.canChooseDirectories = NO;
openPanel.canCreateDirectories = YES;
openPanel.allowsMultipleSelection = NO;
NSMutableArray *allowedTypes = [NSMutableArray array];
if (pictureOptions.mediaType == MediaTypePicture || pictureOptions.mediaType == MediaTypeAll) {
[allowedTypes addObjectsFromArray:[NSImage imageTypes]];
}
if (pictureOptions.mediaType == MediaTypeVideo || pictureOptions.mediaType == MediaTypeAll) {
[allowedTypes addObjectsFromArray:@[(NSString *)kUTTypeMovie]];
}
[openPanel setAllowedFileTypes:allowedTypes];
[openPanel beginSheetModalForWindow:self.viewController.contentView.window completionHandler:^(NSInteger result) {
if (result == NSOKButton) {
NSURL *fileURL = [openPanel.URLs objectAtIndex:0];
if ([self fileIsImage:fileURL]) {
NSImage *image = [[NSImage alloc] initWithContentsOfFile:fileURL.path];
[self returnImage:image command:command options:pictureOptions];
} else {
if (pictureOptions.destinationType == DestinationTypeDataUrl) {
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Camera.DestinationType.DATA_URL is only available with image files"];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
} else {
[self returnUri:fileURL.path command:command options:pictureOptions];
}
}
}
}];
}
#pragma mark - Common
/*!
Returns to JavaScript a URI.
Called when Camera.DestinationType.FILE_URI.
*/
- (void)returnUri:(NSString *)path command:(CDVInvokedUrlCommand *)command options:(CDVPictureOptions *)pictureOptions {
NSString *protocol = (pictureOptions.destinationType == DestinationTypeFileUri) ? @"file://" : @"";
NSString *uri = [NSString stringWithFormat:@"%@%@", protocol, path];
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:uri];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
}
/*!
Returns to JavaScript a base64 encoded image.
Called when Camera.DestinationType.DATA_URL.
*/
- (void)returnImage:(NSImage *)image command:(CDVInvokedUrlCommand *)command options:(CDVPictureOptions *)pictureOptions {
[self.commandDelegate runInBackground:^{
NSData *processedImageData = [self processImage:image options:pictureOptions];
if (pictureOptions.destinationType == DestinationTypeDataUrl) {
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[processedImageData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
} else {
NSString *tempFilePath = [self uniqueImageName:pictureOptions];
[processedImageData writeToFile:tempFilePath atomically:YES];
[cleanUpFiles addObject:tempFilePath];
[self returnUri:tempFilePath command:command options:pictureOptions];
}
}];
}
/*!
Top level method to apply the size and quality required changes to an image.
*/
- (NSData *)processImage:(NSImage *)image options:(CDVPictureOptions *)pictureOptions {
NSImage *sourceImage = image;
if (pictureOptions.targetSize.width > 0 && pictureOptions.targetSize.height > 0) {
sourceImage = [self resizeImage:sourceImage toSize:pictureOptions.targetSize];
}
CGImageRef cgRef = [sourceImage CGImageForProposedRect:NULL context:nil hints:nil];
NSBitmapImageRep *imageRepresentation = [[NSBitmapImageRep alloc] initWithCGImage:cgRef];
NSData *data = (pictureOptions.encodingType == EncodingTypeJPEG)
? [imageRepresentation representationUsingType:NSJPEGFileType properties:@{NSImageCompressionFactor: [NSNumber numberWithFloat:pictureOptions.quality.floatValue/100.f]}]
: [imageRepresentation representationUsingType:NSPNGFileType properties:@{NSImageCompressionFactor: @1.0}];
return data;
}
/*!
Auxiliar method to resize an image.
*/
- (NSImage *)resizeImage:(NSImage *)image toSize:(CGSize)newSize {
CGFloat aspectWidth = newSize.width / image.size.width;
CGFloat aspectHeight = newSize.height / image.size.height;
CGFloat aspectRatio = MIN(aspectWidth, aspectHeight);
CGSize scaledSize = NSMakeSize(image.size.width*aspectRatio, image.size.height*aspectRatio);
NSImage *smallImage = [[NSImage alloc] initWithSize: scaledSize];
[smallImage lockFocus];
[image setSize: scaledSize];
[[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
[image drawAtPoint:NSZeroPoint fromRect:CGRectMake(0, 0, scaledSize.width, scaledSize.height) operation:NSCompositeCopy fraction:1.0];
[smallImage unlockFocus];
return smallImage;
}
/*!
Auxiliar method to know if a given file is an image or not.
*/
- (BOOL)fileIsImage:(NSURL *)fileURL {
NSString *type;
BOOL isImage = NO;
if ([fileURL getResourceValue:&type forKey:NSURLTypeIdentifierKey error:nil]) {
isImage = [[NSImage imageTypes] containsObject:type];
}
return isImage;
}
/*!
Auxiliar method that generates an unique filename for an image in the temporary directory.
*/
- (NSString *)uniqueImageName:(CDVPictureOptions *)pictureOptions {
NSString *tempDir = NSTemporaryDirectory();
NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString] ;
NSString *extension = (pictureOptions.encodingType == EncodingTypeJPEG) ? @"jpeg" : @"png";
NSString *uniqueFileName = [NSString stringWithFormat:@"%@%@.%@", tempDir, guid, extension];
return uniqueFileName;
}
@end

View File

@@ -1,861 +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.
*
*/
/* global Windows:true, URL:true, module:true, require:true, WinJS:true */
var Camera = require('./Camera');
var getAppData = function () {
return Windows.Storage.ApplicationData.current;
};
var encodeToBase64String = function (buffer) {
return Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
};
var OptUnique = Windows.Storage.CreationCollisionOption.generateUniqueName;
var CapMSType = Windows.Media.Capture.MediaStreamType;
var webUIApp = Windows.UI.WebUI.WebUIApplication;
var fileIO = Windows.Storage.FileIO;
var pickerLocId = Windows.Storage.Pickers.PickerLocationId;
module.exports = {
// args will contain :
// ... it is an array, so be careful
// 0 quality:50,
// 1 destinationType:Camera.DestinationType.FILE_URI,
// 2 sourceType:Camera.PictureSourceType.CAMERA,
// 3 targetWidth:-1,
// 4 targetHeight:-1,
// 5 encodingType:Camera.EncodingType.JPEG,
// 6 mediaType:Camera.MediaType.PICTURE,
// 7 allowEdit:false,
// 8 correctOrientation:false,
// 9 saveToPhotoAlbum:false,
// 10 popoverOptions:null
// 11 cameraDirection:0
takePicture: function (successCallback, errorCallback, args) {
var sourceType = args[2];
if (sourceType !== Camera.PictureSourceType.CAMERA) {
takePictureFromFile(successCallback, errorCallback, args);
} else {
takePictureFromCamera(successCallback, errorCallback, args);
}
}
};
// https://msdn.microsoft.com/en-us/library/windows/apps/ff462087(v=vs.105).aspx
var windowsVideoContainers = ['.avi', '.flv', '.asx', '.asf', '.mov', '.mp4', '.mpg', '.rm', '.srt', '.swf', '.wmv', '.vob'];
var windowsPhoneVideoContainers = ['.avi', '.3gp', '.3g2', '.wmv', '.3gp', '.3g2', '.mp4', '.m4v'];
// Default aspect ratio 1.78 (16:9 hd video standard)
var DEFAULT_ASPECT_RATIO = '1.8';
// Highest possible z-index supported across browsers. Anything used above is converted to this value.
var HIGHEST_POSSIBLE_Z_INDEX = 2147483647;
// Resize method
function resizeImage (successCallback, errorCallback, file, targetWidth, targetHeight, encodingType) {
var tempPhotoFileName = '';
var targetContentType = '';
if (encodingType === Camera.EncodingType.PNG) {
tempPhotoFileName = 'camera_cordova_temp_return.png';
targetContentType = 'image/png';
} else {
tempPhotoFileName = 'camera_cordova_temp_return.jpg';
targetContentType = 'image/jpeg';
}
var storageFolder = getAppData().localFolder;
file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting)
.then(function (storageFile) {
return fileIO.readBufferAsync(storageFile);
})
.then(function (buffer) {
var strBase64 = encodeToBase64String(buffer);
var imageData = 'data:' + file.contentType + ';base64,' + strBase64;
var image = new Image(); /* eslint no-undef : 0 */
image.src = imageData;
image.onload = function () {
var ratio = Math.min(targetWidth / this.width, targetHeight / this.height);
var imageWidth = ratio * this.width;
var imageHeight = ratio * this.height;
var canvas = document.createElement('canvas');
var storageFileName;
canvas.width = imageWidth;
canvas.height = imageHeight;
canvas.getContext('2d').drawImage(this, 0, 0, imageWidth, imageHeight);
var fileContent = canvas.toDataURL(targetContentType).split(',')[1];
var storageFolder = getAppData().localFolder;
storageFolder.createFileAsync(tempPhotoFileName, OptUnique)
.then(function (storagefile) {
var content = Windows.Security.Cryptography.CryptographicBuffer.decodeFromBase64String(fileContent);
storageFileName = storagefile.name;
return fileIO.writeBufferAsync(storagefile, content);
})
.done(function () {
successCallback('ms-appdata:///local/' + storageFileName);
}, errorCallback);
};
})
.done(null, function (err) {
errorCallback(err);
});
}
// Because of asynchronous method, so let the successCallback be called in it.
function resizeImageBase64 (successCallback, errorCallback, file, targetWidth, targetHeight) {
fileIO.readBufferAsync(file).done(function (buffer) {
var strBase64 = encodeToBase64String(buffer);
var imageData = 'data:' + file.contentType + ';base64,' + strBase64;
var image = new Image(); /* eslint no-undef : 0 */
image.src = imageData;
image.onload = function () {
var ratio = Math.min(targetWidth / this.width, targetHeight / this.height);
var imageWidth = ratio * this.width;
var imageHeight = ratio * this.height;
var canvas = document.createElement('canvas');
canvas.width = imageWidth;
canvas.height = imageHeight;
var ctx = canvas.getContext('2d');
ctx.drawImage(this, 0, 0, imageWidth, imageHeight);
// The resized file ready for upload
var finalFile = canvas.toDataURL(file.contentType);
// Remove the prefix such as "data:" + contentType + ";base64," , in order to meet the Cordova API.
var arr = finalFile.split(',');
var newStr = finalFile.substr(arr[0].length + 1);
successCallback(newStr);
};
}, function (err) { errorCallback(err); });
}
function takePictureFromFile (successCallback, errorCallback, args) {
// Detect Windows Phone
if (navigator.appVersion.indexOf('Windows Phone 8.1') >= 0) {
takePictureFromFileWP(successCallback, errorCallback, args);
} else {
takePictureFromFileWindows(successCallback, errorCallback, args);
}
}
function takePictureFromFileWP (successCallback, errorCallback, args) {
var mediaType = args[6];
var destinationType = args[1];
var targetWidth = args[3];
var targetHeight = args[4];
var encodingType = args[5];
/*
Need to add and remove an event listener to catch activation state
Using FileOpenPicker will suspend the app and it's required to catch the PickSingleFileAndContinue
https://msdn.microsoft.com/en-us/library/windows/apps/xaml/dn631755.aspx
*/
var filePickerActivationHandler = function (eventArgs) {
if (eventArgs.kind === Windows.ApplicationModel.Activation.ActivationKind.pickFileContinuation) {
var file = eventArgs.files[0];
if (!file) {
errorCallback("User didn't choose a file.");
webUIApp.removeEventListener('activated', filePickerActivationHandler);
return;
}
if (destinationType === Camera.DestinationType.FILE_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));
}, function () {
errorCallback("Can't access localStorage folder.");
});
}
} else {
if (targetHeight > 0 && targetWidth > 0) {
resizeImageBase64(successCallback, errorCallback, file, targetWidth, targetHeight);
} else {
fileIO.readBufferAsync(file).done(function (buffer) {
var strBase64 = encodeToBase64String(buffer);
successCallback(strBase64);
}, errorCallback);
}
}
webUIApp.removeEventListener('activated', filePickerActivationHandler);
}
};
var fileOpenPicker = new Windows.Storage.Pickers.FileOpenPicker();
if (mediaType === Camera.MediaType.PICTURE) {
fileOpenPicker.fileTypeFilter.replaceAll(['.png', '.jpg', '.jpeg']);
fileOpenPicker.suggestedStartLocation = pickerLocId.picturesLibrary;
} else if (mediaType === Camera.MediaType.VIDEO) {
fileOpenPicker.fileTypeFilter.replaceAll(windowsPhoneVideoContainers);
fileOpenPicker.suggestedStartLocation = pickerLocId.videosLibrary;
} else {
fileOpenPicker.fileTypeFilter.replaceAll(['*']);
fileOpenPicker.suggestedStartLocation = pickerLocId.documentsLibrary;
}
webUIApp.addEventListener('activated', filePickerActivationHandler);
fileOpenPicker.pickSingleFileAndContinue();
}
function takePictureFromFileWindows (successCallback, errorCallback, args) {
var mediaType = args[6];
var destinationType = args[1];
var targetWidth = args[3];
var targetHeight = args[4];
var encodingType = args[5];
var fileOpenPicker = new Windows.Storage.Pickers.FileOpenPicker();
if (mediaType === Camera.MediaType.PICTURE) {
fileOpenPicker.fileTypeFilter.replaceAll(['.png', '.jpg', '.jpeg']);
fileOpenPicker.suggestedStartLocation = pickerLocId.picturesLibrary;
} else if (mediaType === Camera.MediaType.VIDEO) {
fileOpenPicker.fileTypeFilter.replaceAll(windowsVideoContainers);
fileOpenPicker.suggestedStartLocation = pickerLocId.videosLibrary;
} else {
fileOpenPicker.fileTypeFilter.replaceAll(['*']);
fileOpenPicker.suggestedStartLocation = pickerLocId.documentsLibrary;
}
fileOpenPicker.pickSingleFileAsync().done(function (file) {
if (!file) {
errorCallback("User didn't choose a file.");
return;
}
if (destinationType === Camera.DestinationType.FILE_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));
}, function () {
errorCallback("Can't access localStorage folder.");
});
}
} else {
if (targetHeight > 0 && targetWidth > 0) {
resizeImageBase64(successCallback, errorCallback, file, targetWidth, targetHeight);
} else {
fileIO.readBufferAsync(file).done(function (buffer) {
var strBase64 = encodeToBase64String(buffer);
successCallback(strBase64);
}, errorCallback);
}
}
}, function () {
errorCallback("User didn't choose a file.");
});
}
function takePictureFromCamera (successCallback, errorCallback, args) {
// Check if necessary API available
if (!Windows.Media.Capture.CameraCaptureUI) {
takePictureFromCameraWP(successCallback, errorCallback, args);
} else {
takePictureFromCameraWindows(successCallback, errorCallback, args);
}
}
function takePictureFromCameraWP (successCallback, errorCallback, args) {
// We are running on WP8.1 which lacks CameraCaptureUI class
// so we need to use MediaCapture class instead and implement custom UI for camera
var destinationType = args[1];
var targetWidth = args[3];
var targetHeight = args[4];
var encodingType = args[5];
var saveToPhotoAlbum = args[9];
var cameraDirection = args[11];
var capturePreview = null;
var cameraCaptureButton = null;
var cameraCancelButton = null;
var capture = null;
var captureSettings = null;
var CaptureNS = Windows.Media.Capture;
var sensor = null;
function createCameraUI () {
// create style for take and cancel buttons
var buttonStyle = 'width:45%;padding: 10px 16px;font-size: 18px;line-height: 1.3333333;color: #333;background-color: #fff;border-color: #ccc; border: 1px solid transparent;border-radius: 6px; display: block; margin: 20px; z-index: 1000;border-color: #adadad;';
// Create fullscreen preview
// z-order style element for capturePreview and cameraCancelButton elts
// is necessary to avoid overriding by another page elements, -1 sometimes is not enough
capturePreview = document.createElement('video');
capturePreview.style.cssText = 'position: fixed; left: 0; top: 0; width: 100%; height: 100%; z-index: ' + (HIGHEST_POSSIBLE_Z_INDEX - 1) + ';';
// Create capture button
cameraCaptureButton = document.createElement('button');
cameraCaptureButton.innerText = 'Take';
cameraCaptureButton.style.cssText = buttonStyle + 'position: fixed; left: 0; bottom: 0; margin: 20px; z-index: ' + HIGHEST_POSSIBLE_Z_INDEX + ';';
// Create cancel button
cameraCancelButton = document.createElement('button');
cameraCancelButton.innerText = 'Cancel';
cameraCancelButton.style.cssText = buttonStyle + 'position: fixed; right: 0; bottom: 0; margin: 20px; z-index: ' + HIGHEST_POSSIBLE_Z_INDEX + ';';
capture = new CaptureNS.MediaCapture();
captureSettings = new CaptureNS.MediaCaptureInitializationSettings();
captureSettings.streamingCaptureMode = CaptureNS.StreamingCaptureMode.video;
}
function continueVideoOnFocus () {
// if preview is defined it would be stuck, play it
if (capturePreview) {
capturePreview.play();
}
}
function startCameraPreview () {
// Search for available camera devices
// This is necessary to detect which camera (front or back) we should use
var DeviceEnum = Windows.Devices.Enumeration;
var expectedPanel = cameraDirection === 1 ? DeviceEnum.Panel.front : DeviceEnum.Panel.back;
// Add focus event handler to capture the event when user suspends the app and comes back while the preview is on
window.addEventListener('focus', continueVideoOnFocus);
DeviceEnum.DeviceInformation.findAllAsync(DeviceEnum.DeviceClass.videoCapture).then(function (devices) {
if (devices.length <= 0) {
destroyCameraPreview();
errorCallback('Camera not found');
return;
}
devices.forEach(function (currDev) {
if (currDev.enclosureLocation.panel && currDev.enclosureLocation.panel === expectedPanel) {
captureSettings.videoDeviceId = currDev.id;
}
});
captureSettings.photoCaptureSource = Windows.Media.Capture.PhotoCaptureSource.photo;
return capture.initializeAsync(captureSettings);
}).then(function () {
// create focus control if available
var VideoDeviceController = capture.videoDeviceController;
var FocusControl = VideoDeviceController.focusControl;
if (FocusControl.supported === true) {
capturePreview.addEventListener('click', function () {
// Make sure function isn't called again before previous focus is completed
if (this.getAttribute('clicked') === '1') {
return false;
} else {
this.setAttribute('clicked', '1');
}
var preset = Windows.Media.Devices.FocusPreset.autoNormal;
var parent = this;
FocusControl.setPresetAsync(preset).done(function () {
// set the clicked attribute back to '0' to allow focus again
parent.setAttribute('clicked', '0');
});
});
}
// msdn.microsoft.com/en-us/library/windows/apps/hh452807.aspx
capturePreview.msZoom = true;
capturePreview.src = URL.createObjectURL(capture);
capturePreview.play();
// Bind events to controls
sensor = Windows.Devices.Sensors.SimpleOrientationSensor.getDefault();
if (sensor !== null) {
sensor.addEventListener('orientationchanged', onOrientationChange);
}
// add click events to capture and cancel buttons
cameraCaptureButton.addEventListener('click', onCameraCaptureButtonClick);
cameraCancelButton.addEventListener('click', onCameraCancelButtonClick);
// Change default orientation
if (sensor) {
setPreviewRotation(sensor.getCurrentOrientation());
} else {
setPreviewRotation(Windows.Graphics.Display.DisplayInformation.getForCurrentView().currentOrientation);
}
// Get available aspect ratios
var aspectRatios = getAspectRatios(capture);
// Couldn't find a good ratio
if (aspectRatios.length === 0) {
destroyCameraPreview();
errorCallback('There\'s not a good aspect ratio available');
return;
}
// add elements to body
document.body.appendChild(capturePreview);
document.body.appendChild(cameraCaptureButton);
document.body.appendChild(cameraCancelButton);
if (aspectRatios.indexOf(DEFAULT_ASPECT_RATIO) > -1) {
return setAspectRatio(capture, DEFAULT_ASPECT_RATIO);
} else {
// Doesn't support 16:9 - pick next best
return setAspectRatio(capture, aspectRatios[0]);
}
}).done(null, function (err) {
destroyCameraPreview();
errorCallback('Camera intitialization error ' + err);
});
}
function destroyCameraPreview () {
// If sensor is available, remove event listener
if (sensor !== null) {
sensor.removeEventListener('orientationchanged', onOrientationChange);
}
// Pause and dispose preview element
capturePreview.pause();
capturePreview.src = null;
// Remove event listeners from buttons
cameraCaptureButton.removeEventListener('click', onCameraCaptureButtonClick);
cameraCancelButton.removeEventListener('click', onCameraCancelButtonClick);
// Remove the focus event handler
window.removeEventListener('focus', continueVideoOnFocus);
// Remove elements
[capturePreview, cameraCaptureButton, cameraCancelButton].forEach(function (elem) {
if (elem /* && elem in document.body.childNodes */) {
document.body.removeChild(elem);
}
});
// Stop and dispose media capture manager
if (capture) {
capture.stopRecordAsync();
capture = null;
}
}
function captureAction () {
var encodingProperties;
var fileName;
var tempFolder = getAppData().temporaryFolder;
if (encodingType === Camera.EncodingType.PNG) {
fileName = 'photo.png';
encodingProperties = Windows.Media.MediaProperties.ImageEncodingProperties.createPng();
} else {
fileName = 'photo.jpg';
encodingProperties = Windows.Media.MediaProperties.ImageEncodingProperties.createJpeg();
}
tempFolder.createFileAsync(fileName, OptUnique)
.then(function (tempCapturedFile) {
return new WinJS.Promise(function (complete) {
var photoStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
var finalStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
capture.capturePhotoToStreamAsync(encodingProperties, photoStream)
.then(function () {
return Windows.Graphics.Imaging.BitmapDecoder.createAsync(photoStream);
})
.then(function (dec) {
finalStream.size = 0; // BitmapEncoder requires the output stream to be empty
return Windows.Graphics.Imaging.BitmapEncoder.createForTranscodingAsync(finalStream, dec);
})
.then(function (enc) {
// We need to rotate the photo wrt sensor orientation
enc.bitmapTransform.rotation = orientationToRotation(sensor.getCurrentOrientation());
return enc.flushAsync();
})
.then(function () {
return tempCapturedFile.openAsync(Windows.Storage.FileAccessMode.readWrite);
})
.then(function (fileStream) {
return Windows.Storage.Streams.RandomAccessStream.copyAndCloseAsync(finalStream, fileStream);
})
.done(function () {
photoStream.close();
finalStream.close();
complete(tempCapturedFile);
}, function () {
photoStream.close();
finalStream.close();
throw new Error('An error has occured while capturing the photo.');
});
});
})
.done(function (capturedFile) {
destroyCameraPreview();
savePhoto(capturedFile, {
destinationType: destinationType,
targetHeight: targetHeight,
targetWidth: targetWidth,
encodingType: encodingType,
saveToPhotoAlbum: saveToPhotoAlbum
}, successCallback, errorCallback);
}, function (err) {
destroyCameraPreview();
errorCallback(err);
});
}
function getAspectRatios (capture) {
var videoDeviceController = capture.videoDeviceController;
var photoAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.photo).map(function (element) {
return (element.width / element.height).toFixed(1);
}).filter(function (element, index, array) { return (index === array.indexOf(element)); });
var videoAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoRecord).map(function (element) {
return (element.width / element.height).toFixed(1);
}).filter(function (element, index, array) { return (index === array.indexOf(element)); });
var videoPreviewAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoPreview).map(function (element) {
return (element.width / element.height).toFixed(1);
}).filter(function (element, index, array) { return (index === array.indexOf(element)); });
var allAspectRatios = [].concat(photoAspectRatios, videoAspectRatios, videoPreviewAspectRatios);
var aspectObj = allAspectRatios.reduce(function (map, item) {
if (!map[item]) {
map[item] = 0;
}
map[item]++;
return map;
}, {});
return Object.keys(aspectObj).filter(function (k) {
return aspectObj[k] === 3;
});
}
function setAspectRatio (capture, aspect) {
// Max photo resolution with desired aspect ratio
var videoDeviceController = capture.videoDeviceController;
var photoResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.photo)
.filter(function (elem) {
return ((elem.width / elem.height).toFixed(1) === aspect);
})
.reduce(function (prop1, prop2) {
return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2;
});
// Max video resolution with desired aspect ratio
var videoRecordResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoRecord)
.filter(function (elem) {
return ((elem.width / elem.height).toFixed(1) === aspect);
})
.reduce(function (prop1, prop2) {
return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2;
});
// Max video preview resolution with desired aspect ratio
var videoPreviewResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoPreview)
.filter(function (elem) {
return ((elem.width / elem.height).toFixed(1) === aspect);
})
.reduce(function (prop1, prop2) {
return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2;
});
return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.photo, photoResolution)
.then(function () {
return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.videoPreview, videoPreviewResolution);
})
.then(function () {
return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.videoRecord, videoRecordResolution);
});
}
/**
* When Capture button is clicked, try to capture a picture and return
*/
function onCameraCaptureButtonClick () {
// Make sure user can't click more than once
if (this.getAttribute('clicked') === '1') {
return false;
} else {
this.setAttribute('clicked', '1');
}
captureAction();
}
/**
* When Cancel button is clicked, destroy camera preview and return with error callback
*/
function onCameraCancelButtonClick () {
// Make sure user can't click more than once
if (this.getAttribute('clicked') === '1') {
return false;
} else {
this.setAttribute('clicked', '1');
}
destroyCameraPreview();
errorCallback('no image selected');
}
/**
* When the phone orientation change, get the event and change camera preview rotation
* @param {Object} e - SimpleOrientationSensorOrientationChangedEventArgs
*/
function onOrientationChange (e) {
setPreviewRotation(e.orientation);
}
/**
* Converts SimpleOrientation to a VideoRotation to remove difference between camera sensor orientation
* and video orientation
* @param {number} orientation - Windows.Devices.Sensors.SimpleOrientation
* @return {number} - Windows.Media.Capture.VideoRotation
*/
function orientationToRotation (orientation) {
// VideoRotation enumerable and BitmapRotation enumerable have the same values
// https://msdn.microsoft.com/en-us/library/windows/apps/windows.media.capture.videorotation.aspx
// https://msdn.microsoft.com/en-us/library/windows/apps/windows.graphics.imaging.bitmaprotation.aspx
switch (orientation) {
// portrait
case Windows.Devices.Sensors.SimpleOrientation.notRotated:
return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
// landscape
case Windows.Devices.Sensors.SimpleOrientation.rotated90DegreesCounterclockwise:
return Windows.Media.Capture.VideoRotation.none;
// portrait-flipped (not supported by WinPhone Apps)
case Windows.Devices.Sensors.SimpleOrientation.rotated180DegreesCounterclockwise:
// Falling back to portrait default
return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
// landscape-flipped
case Windows.Devices.Sensors.SimpleOrientation.rotated270DegreesCounterclockwise:
return Windows.Media.Capture.VideoRotation.clockwise180Degrees;
// faceup & facedown
default:
// Falling back to portrait default
return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
}
}
/**
* Rotates the current MediaCapture's video
* @param {number} orientation - Windows.Devices.Sensors.SimpleOrientation
*/
function setPreviewRotation (orientation) {
capture.setPreviewRotation(orientationToRotation(orientation));
}
try {
createCameraUI();
startCameraPreview();
} catch (ex) {
errorCallback(ex);
}
}
function takePictureFromCameraWindows (successCallback, errorCallback, args) {
var destinationType = args[1];
var targetWidth = args[3];
var targetHeight = args[4];
var encodingType = args[5];
var allowCrop = !!args[7];
var saveToPhotoAlbum = args[9];
var WMCapture = Windows.Media.Capture;
var cameraCaptureUI = new WMCapture.CameraCaptureUI();
cameraCaptureUI.photoSettings.allowCropping = allowCrop;
if (encodingType === Camera.EncodingType.PNG) {
cameraCaptureUI.photoSettings.format = WMCapture.CameraCaptureUIPhotoFormat.png;
} else {
cameraCaptureUI.photoSettings.format = WMCapture.CameraCaptureUIPhotoFormat.jpeg;
}
// decide which max pixels should be supported by targetWidth or targetHeight.
var maxRes = null;
var UIMaxRes = WMCapture.CameraCaptureUIMaxPhotoResolution;
var totalPixels = targetWidth * targetHeight;
if (targetWidth === -1 && targetHeight === -1) {
maxRes = UIMaxRes.highestAvailable;
// Temp fix for CB-10539
/* else if (totalPixels <= 320 * 240) {
maxRes = UIMaxRes.verySmallQvga;
} */
} else if (totalPixels <= 640 * 480) {
maxRes = UIMaxRes.smallVga;
} else if (totalPixels <= 1024 * 768) {
maxRes = UIMaxRes.mediumXga;
} else if (totalPixels <= 3 * 1000 * 1000) {
maxRes = UIMaxRes.large3M;
} else if (totalPixels <= 5 * 1000 * 1000) {
maxRes = UIMaxRes.veryLarge5M;
} else {
maxRes = UIMaxRes.highestAvailable;
}
cameraCaptureUI.photoSettings.maxResolution = maxRes;
var cameraPicture;
// define focus handler for windows phone 10.0
var savePhotoOnFocus = function () {
window.removeEventListener('focus', savePhotoOnFocus);
// call only when the app is in focus again
savePhoto(cameraPicture, {
destinationType: destinationType,
targetHeight: targetHeight,
targetWidth: targetWidth,
encodingType: encodingType,
saveToPhotoAlbum: saveToPhotoAlbum
}, successCallback, errorCallback);
};
// if windows phone 10, add and delete focus eventHandler to capture the focus back from cameraUI to app
if (navigator.appVersion.indexOf('Windows Phone 10.0') >= 0) {
window.addEventListener('focus', savePhotoOnFocus);
}
cameraCaptureUI.captureFileAsync(WMCapture.CameraCaptureUIMode.photo).done(function (picture) {
if (!picture) {
errorCallback("User didn't capture a photo.");
// Remove the focus handler if present
window.removeEventListener('focus', savePhotoOnFocus);
return;
}
cameraPicture = picture;
// If not windows 10, call savePhoto() now. If windows 10, wait for the app to be in focus again
if (navigator.appVersion.indexOf('Windows Phone 10.0') < 0) {
savePhoto(cameraPicture, {
destinationType: destinationType,
targetHeight: targetHeight,
targetWidth: targetWidth,
encodingType: encodingType,
saveToPhotoAlbum: saveToPhotoAlbum
}, successCallback, errorCallback);
}
}, function () {
errorCallback('Fail to capture a photo.');
window.removeEventListener('focus', savePhotoOnFocus);
});
}
function savePhoto (picture, options, successCallback, errorCallback) {
// success callback for capture operation
var success = function (picture) {
if (options.destinationType === Camera.DestinationType.FILE_URI) {
if (options.targetHeight > 0 && options.targetWidth > 0) {
resizeImage(successCallback, errorCallback, picture, options.targetWidth, options.targetHeight, options.encodingType);
} else {
// CB-11714: check if target content-type is PNG to just rename as *.jpg since camera is captured as JPEG
if (options.encodingType === Camera.EncodingType.PNG) {
picture.name = picture.name.replace(/\.png$/, '.jpg');
}
picture.copyAsync(getAppData().localFolder, picture.name, OptUnique).done(function (copiedFile) {
successCallback('ms-appdata:///local/' + copiedFile.name);
}, errorCallback);
}
} else {
if (options.targetHeight > 0 && options.targetWidth > 0) {
resizeImageBase64(successCallback, errorCallback, picture, options.targetWidth, options.targetHeight);
} else {
fileIO.readBufferAsync(picture).done(function (buffer) {
var strBase64 = encodeToBase64String(buffer);
picture.deleteAsync().done(function () {
successCallback(strBase64);
}, function (err) {
errorCallback(err);
});
}, errorCallback);
}
}
};
if (!options.saveToPhotoAlbum) {
success(picture);
} else {
var savePicker = new Windows.Storage.Pickers.FileSavePicker();
var saveFile = function (file) {
if (file) {
// Prevent updates to the remote version of the file until we're done
Windows.Storage.CachedFileManager.deferUpdates(file);
picture.moveAndReplaceAsync(file)
.then(function () {
// Let Windows know that we're finished changing the file so
// the other app can update the remote version of the file.
return Windows.Storage.CachedFileManager.completeUpdatesAsync(file);
})
.done(function (updateStatus) {
if (updateStatus === Windows.Storage.Provider.FileUpdateStatus.complete) {
success(picture);
} else {
errorCallback('File update status is not complete.');
}
}, errorCallback);
} else {
errorCallback('Failed to select a file.');
}
};
savePicker.suggestedStartLocation = pickerLocId.picturesLibrary;
if (options.encodingType === Camera.EncodingType.PNG) {
savePicker.fileTypeChoices.insert('PNG', ['.png']);
savePicker.suggestedFileName = 'photo.png';
} else {
savePicker.fileTypeChoices.insert('JPEG', ['.jpg']);
savePicker.suggestedFileName = 'photo.jpg';
}
// If Windows Phone 8.1 use pickSaveFileAndContinue()
if (navigator.appVersion.indexOf('Windows Phone 8.1') >= 0) {
/*
Need to add and remove an event listener to catch activation state
Using FileSavePicker will suspend the app and it's required to catch the pickSaveFileContinuation
https://msdn.microsoft.com/en-us/library/windows/apps/xaml/dn631755.aspx
*/
var fileSaveHandler = function (eventArgs) {
if (eventArgs.kind === Windows.ApplicationModel.Activation.ActivationKind.pickSaveFileContinuation) {
var file = eventArgs.file;
saveFile(file);
webUIApp.removeEventListener('activated', fileSaveHandler);
}
};
webUIApp.addEventListener('activated', fileSaveHandler);
savePicker.pickSaveFileAndContinue();
} else {
savePicker.pickSaveFileAsync()
.done(saveFile, errorCallback);
}
}
}
require('cordova/exec/proxy').add('Camera', module.exports);

View File

@@ -1,6 +1,6 @@
{
"name": "cordova-plugin-camera-tests",
"version": "5.0.3",
"version": "7.0.0",
"description": "",
"cordova": {
"id": "cordova-plugin-camera-tests",

View File

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

View File

@@ -19,7 +19,7 @@
*
*/
/* globals Camera, resolveLocalFileSystemURL, FileEntry, CameraPopoverOptions, LocalFileSystem, MSApp */
/* globals Camera, resolveLocalFileSystemURL, FileEntry, CameraPopoverOptions, LocalFileSystem */
/* eslint-env jasmine */
exports.defineAutoTests = function () {
@@ -78,20 +78,20 @@ exports.defineAutoTests = function () {
/******************************************************************************/
exports.defineManualTests = function (contentEl, createActionButton) {
var pictureUrl = null;
var fileObj = null;
var fileEntry = null;
var pageStartTime = +new Date();
let pictureUrl = null;
let fileObj = null;
let fileEntry = null;
const pageStartTime = +new Date();
// default camera options
var camQualityDefault = ['50', 50];
var camDestinationTypeDefault = ['FILE_URI', 1];
var camPictureSourceTypeDefault = ['CAMERA', 1];
var camAllowEditDefault = ['allowEdit', false];
var camEncodingTypeDefault = ['JPEG', 0];
var camMediaTypeDefault = ['mediaType', 0];
var camCorrectOrientationDefault = ['correctOrientation', false];
var camSaveToPhotoAlbumDefault = ['saveToPhotoAlbum', true];
const camQualityDefault = ['50', 50];
const camDestinationTypeDefault = ['FILE_URI', 1];
const camPictureSourceTypeDefault = ['CAMERA', 1];
const camAllowEditDefault = ['allowEdit', false];
const camEncodingTypeDefault = ['JPEG', 0];
const camMediaTypeDefault = ['mediaType', 0];
const camCorrectOrientationDefault = ['correctOrientation', false];
const camSaveToPhotoAlbumDefault = ['saveToPhotoAlbum', true];
function log (value) {
console.log(value);
@@ -101,7 +101,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
function clearStatus () {
document.getElementById('camera_status').innerHTML = '';
document.getElementById('camera_image').src = 'about:blank';
var canvas = document.getElementById('canvas');
const canvas = document.getElementById('canvas');
canvas.width = canvas.height = 1;
pictureUrl = null;
fileObj = null;
@@ -119,8 +119,8 @@ exports.defineManualTests = function (contentEl, createActionButton) {
log('URL: "' + url.slice(0, 90) + '"');
pictureUrl = url;
var img = document.getElementById('camera_image');
var startTime = new Date();
const img = document.getElementById('camera_image');
const startTime = new Date();
img.src = url;
img.onload = function () {
log('Img size: ' + img.naturalWidth + 'x' + img.naturalHeight);
@@ -147,20 +147,20 @@ exports.defineManualTests = function (contentEl, createActionButton) {
} else if (pictureUrl.indexOf('data:image/jpeg;base64') === 0) {
// do nothing
} else {
var path = pictureUrl.replace(/^file:\/\/(localhost)?/, '').replace(/%20/g, ' ');
const path = pictureUrl.replace(/^file:\/\/(localhost)?/, '').replace(/%20/g, ' ');
fileEntry = new FileEntry('image_name.png', path);
}
}
function getPicture () {
clearStatus();
var options = extractOptions();
const options = extractOptions();
log('Getting picture with options: ' + JSON.stringify(options));
var popoverHandle = navigator.camera.getPicture(getPictureWin, onGetPictureError, options);
const popoverHandle = navigator.camera.getPicture(getPictureWin, onGetPictureError, options);
// Reposition the popover if the orientation changes.
window.onorientationchange = function () {
var newPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, 0, 300, 400);
const newPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, 0, 300, 400);
popoverHandle.setPosition(newPopoverOptions);
};
}
@@ -177,7 +177,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
*/
function readFile () {
function onFileReadAsDataURL (evt) {
var img = document.getElementById('camera_image');
const img = document.getElementById('camera_image');
img.style.visibility = 'visible';
img.style.display = 'block';
img.src = evt.target.result;
@@ -188,7 +188,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
log('Got file: ' + JSON.stringify(file));
fileObj = file;
/* eslint-disable no-undef */
var reader = new FileReader();
const reader = new FileReader();
/* eslint-enable no-undef */
reader.onload = function () {
log('FileReader.readAsDataURL() - length = ' + reader.result.length);
@@ -219,9 +219,9 @@ exports.defineManualTests = function (contentEl, createActionButton) {
* This calls FileEntry.copyTo and FileEntry.moveTo.
*/
function copyImage () {
var onFileSystemReceived = function (fileSystem) {
var destDirEntry = fileSystem.root;
var origName = fileEntry.name;
const onFileSystemReceived = function (fileSystem) {
const destDirEntry = fileSystem.root;
const origName = fileEntry.name;
// Test FileEntry API here.
fileEntry.copyTo(destDirEntry, 'copied_file.png', logCallback('FileEntry.copyTo', true), logCallback('FileEntry.copyTo', false));
@@ -253,13 +253,13 @@ exports.defineManualTests = function (contentEl, createActionButton) {
* This calls FileEntry.createWriter, FileWriter.write, and FileWriter.truncate.
*/
function writeImage () {
var onFileWriterReceived = function (fileWriter) {
const onFileWriterReceived = function (fileWriter) {
fileWriter.onwrite = logCallback('FileWriter.write', true);
fileWriter.onerror = logCallback('FileWriter.write', false);
fileWriter.write('some text!');
};
var onFileTruncateWriterReceived = function (fileWriter) {
const onFileTruncateWriterReceived = function (fileWriter) {
fileWriter.onwrite = logCallback('FileWriter.truncate', true);
fileWriter.onerror = logCallback('FileWriter.truncate', false);
fileWriter.truncate(10);
@@ -270,15 +270,15 @@ exports.defineManualTests = function (contentEl, createActionButton) {
}
function displayImageUsingCanvas () {
var canvas = document.getElementById('canvas');
var img = document.getElementById('camera_image');
var w = img.width;
var h = img.height;
const canvas = document.getElementById('canvas');
const img = document.getElementById('camera_image');
let w = img.width;
let h = img.height;
h = 100 / w * h;
w = 100;
canvas.width = w;
canvas.height = h;
var context = canvas.getContext('2d');
const context = canvas.getContext('2d');
context.drawImage(img, 0, 0, w, h);
}
@@ -311,9 +311,9 @@ exports.defineManualTests = function (contentEl, createActionButton) {
return;
}
/* eslint-enable no-undef */
var URLApi = window.URL || window.webkitURL;
const URLApi = window.URL || window.webkitURL;
if (URLApi) {
var blobURL = URLApi.createObjectURL(fileObj);
const blobURL = URLApi.createObjectURL(fileObj);
if (blobURL) {
setPicture(blobURL, function () {
URLApi.revokeObjectURL(blobURL);
@@ -327,11 +327,11 @@ exports.defineManualTests = function (contentEl, createActionButton) {
}
function extractOptions () {
var els = document.querySelectorAll('#image-options select');
var ret = {};
const els = document.querySelectorAll('#image-options select');
const ret = {};
/* eslint-disable no-cond-assign */
for (var i = 0, el; el = els[i]; ++i) {
var value = el.value;
for (let i = 0, el; el = els[i]; ++i) {
let value = el.value;
if (value === '') continue;
value = +value;
@@ -346,20 +346,20 @@ exports.defineManualTests = function (contentEl, createActionButton) {
}
function createOptionsEl (name, values, selectionDefault) {
var openDiv = '<div style="display: inline-block">' + name + ': ';
var select = '<select name=' + name + ' id="' + name + '">';
const openDiv = '<div style="display: inline-block">' + name + ': ';
const select = '<select name=' + name + ' id="' + name + '">';
var defaultOption = '';
let defaultOption = '';
if (selectionDefault === undefined) {
defaultOption = '<option value="">default</option>';
}
var options = '';
let options = '';
if (typeof values === 'boolean') {
values = { true: 1, false: 0 };
}
for (var k in values) {
var isSelected = '';
for (const k in values) {
let isSelected = '';
if (selectionDefault) {
if (selectionDefault[0] === k) {
isSelected = 'selected';
@@ -368,20 +368,20 @@ exports.defineManualTests = function (contentEl, createActionButton) {
options += '<option value="' + values[k] + '" ' + isSelected + '>' + k + '</option>';
}
var closeDiv = '</select></div>';
const closeDiv = '</select></div>';
return openDiv + select + defaultOption + options + closeDiv;
}
/******************************************************************************/
var info_div = '<h1>Camera</h1>' +
const info_div = '<h1>Camera</h1>' +
'<div id="info">' +
'<b>Status:</b> <div id="camera_status"></div>' +
'img: <img width="100" id="camera_image">' +
'canvas: <canvas id="canvas" width="1" height="1"></canvas>' +
'</div>';
var options_div = '<h2>Cordova Camera API Options</h2>' +
const options_div = '<h2>Cordova Camera API Options</h2>' +
'<div id="image-options">' +
createOptionsEl('sourceType', Camera.PictureSourceType, camPictureSourceTypeDefault) +
createOptionsEl('destinationType', Camera.DestinationType, camDestinationTypeDefault) +
@@ -395,8 +395,8 @@ exports.defineManualTests = function (contentEl, createActionButton) {
createOptionsEl('saveToPhotoAlbum', true, camSaveToPhotoAlbumDefault) +
createOptionsEl('cameraDirection', Camera.Direction) +
'</div>';
var getpicture_div = '<div id="getpicture"></div>';
var test_procedure = '<h4>Recommended Test Procedure</h4>' +
const getpicture_div = '<div id="getpicture"></div>';
const test_procedure = '<h4>Recommended Test Procedure</h4>' +
'Options not specified should be the default value' +
'<br>Status box should update with image and info whenever an image is taken or selected from library' +
'</p><div style="background:#B0C4DE;border:1px solid #FFA07A;margin:15px 6px 0px;min-width:295px;max-width:97%;padding:4px 0px 2px 10px;min-height:160px;max-height:200px;overflow:auto">' +
@@ -410,13 +410,13 @@ exports.defineManualTests = function (contentEl, createActionButton) {
'</p><li>sourceType=CAMERA<br>targetWidth & targetHeight=50<br>allowEdit=false<br>Do Get File Metadata test below and take note of size<br>Repeat test but with width and height=800. Size should be significantly larger.</li>' +
'</p><li>quality=0<br>targetWidth & targetHeight=default<br>allowEdit=false<br>Do Get File Metadata test below and take note of size<br>Repeat test but with quality=80. Size should be significantly larger.</li>' +
'</ol></div>';
var inputs_div = '<h2>Native File Inputs</h2>' +
const inputs_div = '<h2>Native File Inputs</h2>' +
'For the following tests, status box should update with file selected' +
'</p><div>input type=file <input type="file" class="testInputTag"></div>' +
'<div>capture=camera <input type="file" accept="image/*;capture=camera" class="testInputTag"></div>' +
'<div>capture=camcorder <input type="file" accept="video/*;capture=camcorder" class="testInputTag"></div>' +
'<div>capture=microphone <input type="file" accept="audio/*;capture=microphone" class="testInputTag"></div>';
var actions_div = '<h2>Actions</h2>' +
const actions_div = '<h2>Actions</h2>' +
'For the following tests, ensure that an image is set in status box' +
'</p><div id="metadata"></div>' +
'Expected result: Get metadata about file selected.<br>Status box will show, along with the metadata, "Call to FileEntry.getMetadata success, Call to FileEntry.setMetadata success, Call to FileEntry.getParent success"' +
@@ -433,22 +433,14 @@ exports.defineManualTests = function (contentEl, createActionButton) {
'</p><div id="remove"></div>' +
'Expected result: Remove image from library.<br>Status box will show "FileEntry.remove success:["OK"]';
// We need to wrap this code due to Windows security restrictions
// see http://msdn.microsoft.com/en-us/library/windows/apps/hh465380.aspx#differences for details
if (window.MSApp && window.MSApp.execUnsafeLocalFunction) {
MSApp.execUnsafeLocalFunction(function () {
contentEl.innerHTML = info_div + options_div + getpicture_div + test_procedure + inputs_div + actions_div;
});
} else {
contentEl.innerHTML = info_div + options_div + getpicture_div + test_procedure + inputs_div + actions_div;
}
contentEl.innerHTML = info_div + options_div + getpicture_div + test_procedure + inputs_div + actions_div;
var elements = document.getElementsByClassName('testInputTag');
var listener = function (e) {
const elements = document.getElementsByClassName('testInputTag');
const listener = function (e) {
testInputTag(e.target);
};
for (var i = 0; i < elements.length; ++i) {
var item = elements[i];
for (let i = 0; i < elements.length; ++i) {
const item = elements[i];
item.addEventListener('change', listener, false);
}

View File

@@ -19,9 +19,9 @@
*
*/
var argscheck = require('cordova/argscheck');
var exec = require('cordova/exec');
var Camera = require('./Camera');
const argscheck = require('cordova/argscheck');
const exec = require('cordova/exec');
const Camera = require('./Camera');
// XXX: commented out
// CameraPopoverHandle = require('./CameraPopoverHandle');
@@ -32,10 +32,10 @@ var Camera = require('./Camera');
/**
* @exports camera
*/
var cameraExport = {};
const cameraExport = {};
// Tack on the Camera Constants to the base camera plugin.
for (var key in Camera) {
for (const key in Camera) {
cameraExport[key] = Camera[key];
}
@@ -114,14 +114,8 @@ for (var key in Camera) {
* __Supported Platforms__
*
* - Android
* - BlackBerry
* - Browser
* - Firefox
* - FireOS
* - iOS
* - Windows
* - WP8
* - Ubuntu
*
* More examples [here](#camera-getPicture-examples). Quirks [here](#camera-getPicture-quirks).
*
@@ -134,22 +128,22 @@ for (var key in Camera) {
cameraExport.getPicture = function (successCallback, errorCallback, options) {
argscheck.checkArgs('fFO', 'Camera.getPicture', arguments);
options = options || {};
var getValue = argscheck.getValue;
const getValue = argscheck.getValue;
var quality = getValue(options.quality, 50);
var destinationType = getValue(options.destinationType, Camera.DestinationType.FILE_URI);
var sourceType = getValue(options.sourceType, Camera.PictureSourceType.CAMERA);
var targetWidth = getValue(options.targetWidth, -1);
var targetHeight = getValue(options.targetHeight, -1);
var encodingType = getValue(options.encodingType, Camera.EncodingType.JPEG);
var mediaType = getValue(options.mediaType, Camera.MediaType.PICTURE);
var allowEdit = !!options.allowEdit;
var correctOrientation = !!options.correctOrientation;
var saveToPhotoAlbum = !!options.saveToPhotoAlbum;
var popoverOptions = getValue(options.popoverOptions, null);
var cameraDirection = getValue(options.cameraDirection, Camera.Direction.BACK);
const quality = getValue(options.quality, 50);
const destinationType = getValue(options.destinationType, Camera.DestinationType.FILE_URI);
const sourceType = getValue(options.sourceType, Camera.PictureSourceType.CAMERA);
const targetWidth = getValue(options.targetWidth, -1);
const targetHeight = getValue(options.targetHeight, -1);
const encodingType = getValue(options.encodingType, Camera.EncodingType.JPEG);
const mediaType = getValue(options.mediaType, Camera.MediaType.PICTURE);
const allowEdit = !!options.allowEdit;
const correctOrientation = !!options.correctOrientation;
const saveToPhotoAlbum = !!options.saveToPhotoAlbum;
const popoverOptions = getValue(options.popoverOptions, null);
const cameraDirection = getValue(options.cameraDirection, Camera.Direction.BACK);
var args = [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType,
const args = [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType,
mediaType, allowEdit, correctOrientation, saveToPhotoAlbum, popoverOptions, cameraDirection];
exec(successCallback, errorCallback, 'Camera', 'takePicture', args);

View File

@@ -23,7 +23,7 @@
* @ignore in favour of iOS' one
* A handle to an image picker popover.
*/
var CameraPopoverHandle = function () {
const CameraPopoverHandle = function () {
this.setPosition = function (popoverOptions) {
console.log('CameraPopoverHandle.setPosition is only supported on iOS.');
};

View File

@@ -19,7 +19,7 @@
*
*/
var Camera = require('./Camera');
const Camera = require('./Camera');
/**
* @namespace navigator
@@ -42,7 +42,7 @@ var Camera = require('./Camera');
* @param {Number} [popoverWidth=0] - width of the popover (0 or not specified will use apple's default width).
* @param {Number} [popoverHeight=0] - height of the popover (0 or not specified will use apple's default height).
*/
var CameraPopoverOptions = function (x, y, width, height, arrowDir, popoverWidth, popoverHeight) {
const CameraPopoverOptions = function (x, y, width, height, arrowDir, popoverWidth, popoverHeight) {
// information of rectangle that popover should be anchored to
this.x = x || 0;
this.y = y || 32;

View File

@@ -19,7 +19,7 @@
*
*/
var exec = require('cordova/exec');
const exec = require('cordova/exec');
/**
* @namespace navigator
@@ -48,7 +48,7 @@ var exec = require('cordova/exec');
* }
* @module CameraPopoverHandle
*/
var CameraPopoverHandle = function () {
const CameraPopoverHandle = function () {
/**
* Can be used to reposition the image selection dialog,
* for example, when the device orientation changes.
@@ -58,7 +58,7 @@ var CameraPopoverHandle = function () {
* @param {module:CameraPopoverOptions} popoverOptions
*/
this.setPosition = function (popoverOptions) {
var args = [popoverOptions];
const args = [popoverOptions];
exec(null, null, 'Camera', 'repositionPopover', args);
};
};