/* * * 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. * */ /* globals Camera, resolveLocalFileSystemURL, FileEntry, CameraPopoverOptions, FileTransfer, FileUploadOptions, LocalFileSystem, MSApp */ /* jshint jasmine: true */ exports.defineAutoTests = function () { describe('Camera (navigator.camera)', function () { it("should exist", function () { expect(navigator.camera).toBeDefined(); }); it("should contain a getPicture function", function () { expect(navigator.camera.getPicture).toBeDefined(); expect(typeof navigator.camera.getPicture == 'function').toBe(true); }); }); describe('Camera Constants (window.Camera + navigator.camera)', function () { it("camera.spec.1 window.Camera should exist", function () { expect(window.Camera).toBeDefined(); }); it("camera.spec.2 should contain three DestinationType constants", function () { expect(Camera.DestinationType.DATA_URL).toBe(0); expect(Camera.DestinationType.FILE_URI).toBe(1); expect(Camera.DestinationType.NATIVE_URI).toBe(2); expect(navigator.camera.DestinationType.DATA_URL).toBe(0); expect(navigator.camera.DestinationType.FILE_URI).toBe(1); expect(navigator.camera.DestinationType.NATIVE_URI).toBe(2); }); it("camera.spec.3 should contain two EncodingType constants", function () { expect(Camera.EncodingType.JPEG).toBe(0); expect(Camera.EncodingType.PNG).toBe(1); expect(navigator.camera.EncodingType.JPEG).toBe(0); expect(navigator.camera.EncodingType.PNG).toBe(1); }); it("camera.spec.4 should contain three MediaType constants", function () { expect(Camera.MediaType.PICTURE).toBe(0); expect(Camera.MediaType.VIDEO).toBe(1); expect(Camera.MediaType.ALLMEDIA).toBe(2); expect(navigator.camera.MediaType.PICTURE).toBe(0); expect(navigator.camera.MediaType.VIDEO).toBe(1); expect(navigator.camera.MediaType.ALLMEDIA).toBe(2); }); it("camera.spec.5 should contain three PictureSourceType constants", function () { expect(Camera.PictureSourceType.PHOTOLIBRARY).toBe(0); expect(Camera.PictureSourceType.CAMERA).toBe(1); expect(Camera.PictureSourceType.SAVEDPHOTOALBUM).toBe(2); expect(navigator.camera.PictureSourceType.PHOTOLIBRARY).toBe(0); expect(navigator.camera.PictureSourceType.CAMERA).toBe(1); expect(navigator.camera.PictureSourceType.SAVEDPHOTOALBUM).toBe(2); }); }); }; /******************************************************************************/ /******************************************************************************/ /******************************************************************************/ exports.defineManualTests = function (contentEl, createActionButton) { var pictureUrl = null; var fileObj = null; var fileEntry = null; var 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]; function log(value) { console.log(value); document.getElementById('camera_status').textContent += (new Date() - pageStartTime) / 1000 + ': ' + value + '\n'; } function clearStatus() { document.getElementById('camera_status').innerHTML = ''; document.getElementById('camera_image').src = 'about:blank'; var canvas = document.getElementById('canvas'); canvas.width = canvas.height = 1; pictureUrl = null; fileObj = null; fileEntry = null; } function setPicture(url, callback) { try { window.atob(url); // if we got here it is a base64 string (DATA_URL) url = "data:image/jpeg;base64," + url; } catch (e) { // not DATA_URL } log('URL: "' + url.slice(0, 90) + '"'); pictureUrl = url; var img = document.getElementById('camera_image'); var startTime = new Date(); img.src = url; img.onload = function () { log('Img size: ' + img.naturalWidth + 'x' + img.naturalHeight); log('Image tag load time: ' + (new Date() - startTime)); if (callback) { callback(); } }; } function onGetPictureError(e) { log('Error getting picture: ' + (e.code || e)); } function getPictureWin(data) { setPicture(data); // TODO: Fix resolveLocalFileSystemURI to work with native-uri. if (pictureUrl.indexOf('file:') === 0 || pictureUrl.indexOf('content:') === 0 || pictureUrl.indexOf('ms-appdata:') === 0 || pictureUrl.indexOf('assets-library:') === 0) { resolveLocalFileSystemURL(data, function (e) { fileEntry = e; logCallback('resolveLocalFileSystemURL()', true)(e.toURL()); readFile(); }, logCallback('resolveLocalFileSystemURL()', false)); } else if (pictureUrl.indexOf('data:image/jpeg;base64') === 0) { // do nothing } else { var path = pictureUrl.replace(/^file:\/\/(localhost)?/, '').replace(/%20/g, ' '); fileEntry = new FileEntry('image_name.png', path); } } function getPicture() { clearStatus(); var options = extractOptions(); log('Getting picture with options: ' + JSON.stringify(options)); var 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); popoverHandle.setPosition(newPopoverOptions); }; } function uploadImage() { var ft = new FileTransfer(), options = new FileUploadOptions(); options.fileKey = "photo"; options.fileName = 'test.jpg'; options.mimeType = "image/jpeg"; ft.onprogress = function (progressEvent) { console.log('progress: ' + progressEvent.loaded + ' of ' + progressEvent.total); }; var server = "http://cordova-filetransfer.jitsu.com"; ft.upload(pictureUrl, server + '/upload', win, fail, options); function win(information_back) { log('upload complete'); } function fail(message) { log('upload failed: ' + JSON.stringify(message)); } } function logCallback(apiName, success) { return function () { log('Call to ' + apiName + (success ? ' success: ' : ' failed: ') + JSON.stringify([].slice.call(arguments))); }; } /** * Select image from library using a NATIVE_URI destination type * This calls FileEntry.getMetadata, FileEntry.setMetadata, FileEntry.getParent, FileEntry.file, and FileReader.readAsDataURL. */ function readFile() { function onFileReadAsDataURL(evt) { var img = document.getElementById('camera_image'); img.style.visibility = "visible"; img.style.display = "block"; img.src = evt.target.result; log("FileReader.readAsDataURL success"); } function onFileReceived(file) { log('Got file: ' + JSON.stringify(file)); fileObj = file; var reader = new FileReader(); reader.onload = function () { log('FileReader.readAsDataURL() - length = ' + reader.result.length); }; reader.onerror = logCallback('FileReader.readAsDataURL', false); reader.onloadend = onFileReadAsDataURL; reader.readAsDataURL(file); } // Test out onFileReceived when the file object was set via a native elements. if (fileObj) { onFileReceived(fileObj); } else { fileEntry.file(onFileReceived, logCallback('FileEntry.file', false)); } } function getFileInfo() { // Test FileEntry API here. fileEntry.getMetadata(logCallback('FileEntry.getMetadata', true), logCallback('FileEntry.getMetadata', false)); fileEntry.setMetadata(logCallback('FileEntry.setMetadata', true), logCallback('FileEntry.setMetadata', false), { "com.apple.MobileBackup": 1 }); fileEntry.getParent(logCallback('FileEntry.getParent', true), logCallback('FileEntry.getParent', false)); fileEntry.getParent(logCallback('FileEntry.getParent', true), logCallback('FileEntry.getParent', false)); } /** * Copy image from library using a NATIVE_URI destination type * This calls FileEntry.copyTo and FileEntry.moveTo. */ function copyImage() { var onFileSystemReceived = function (fileSystem) { var destDirEntry = fileSystem.root; var origName = fileEntry.name; // Test FileEntry API here. fileEntry.copyTo(destDirEntry, 'copied_file.png', logCallback('FileEntry.copyTo', true), logCallback('FileEntry.copyTo', false)); fileEntry.moveTo(destDirEntry, 'moved_file.png', logCallback('FileEntry.moveTo', true), logCallback('FileEntry.moveTo', false)); //cleanup //rename moved file back to original name so other tests can reference image resolveLocalFileSystemURL(destDirEntry.nativeURL+'moved_file.png', function(fileEntry) { fileEntry.moveTo(destDirEntry, origName, logCallback('FileEntry.moveTo', true), logCallback('FileEntry.moveTo', false)); console.log('Cleanup: successfully renamed file back to original name'); }, function () { console.log('Cleanup: failed to rename file back to original name'); }); //remove copied file resolveLocalFileSystemURL(destDirEntry.nativeURL+'copied_file.png', function(fileEntry) { fileEntry.remove(logCallback('FileEntry.remove', true), logCallback('FileEntry.remove', false)); console.log('Cleanup: successfully removed copied file'); }, function () { console.log('Cleanup: failed to remove copied file'); }); }; window.requestFileSystem(LocalFileSystem.TEMPORARY, 0, onFileSystemReceived, null); } /** * Write image to library using a NATIVE_URI destination type * This calls FileEntry.createWriter, FileWriter.write, and FileWriter.truncate. */ function writeImage() { var onFileWriterReceived = function (fileWriter) { fileWriter.onwrite = logCallback('FileWriter.write', true); fileWriter.onerror = logCallback('FileWriter.write', false); fileWriter.write("some text!"); }; var onFileTruncateWriterReceived = function (fileWriter) { fileWriter.onwrite = logCallback('FileWriter.truncate', true); fileWriter.onerror = logCallback('FileWriter.truncate', false); fileWriter.truncate(10); }; fileEntry.createWriter(onFileWriterReceived, logCallback('FileEntry.createWriter', false)); fileEntry.createWriter(onFileTruncateWriterReceived, null); } function displayImageUsingCanvas() { var canvas = document.getElementById('canvas'); var img = document.getElementById('camera_image'); var w = img.width; var h = img.height; h = 100 / w * h; w = 100; canvas.width = w; canvas.height = h; var context = canvas.getContext('2d'); context.drawImage(img, 0, 0, w, h); } /** * Remove image from library using a NATIVE_URI destination type * This calls FileEntry.remove. */ function removeImage() { fileEntry.remove(logCallback('FileEntry.remove', true), logCallback('FileEntry.remove', false)); } function testInputTag(inputEl) { clearStatus(); // iOS 6 likes to dead-lock in the onchange context if you // do any alerts or try to remote-debug. window.setTimeout(function () { testNativeFile2(inputEl); }, 0); } function testNativeFile2(inputEl) { if (!inputEl.value) { alert('No file selected.'); return; } fileObj = inputEl.files[0]; if (!fileObj) { alert('Got value but no file.'); return; } var URLApi = window.URL || window.webkitURL; if (URLApi) { var blobURL = URLApi.createObjectURL(fileObj); if (blobURL) { setPicture(blobURL, function () { URLApi.revokeObjectURL(blobURL); }); } else { log('URL.createObjectURL returned null'); } } else { log('URL.createObjectURL() not supported.'); } } function extractOptions() { var els = document.querySelectorAll('#image-options select'); var ret = {}; /*jshint -W084 */ for (var i = 0, el; el = els[i]; ++i) { var value = el.value; if (value === '') continue; value = +value; if (el.isBool) { ret[el.getAttribute("name")] = !!value; } else { ret[el.getAttribute("name")] = value; } } /*jshint +W084 */ return ret; } function createOptionsEl(name, values, selectionDefault) { var openDiv = '
' + name + ': '; var select = '
'; return openDiv + select + defaultOption + options + closeDiv; } /******************************************************************************/ var info_div = '

Camera

' + '
' + 'Status:
' + 'img: ' + 'canvas: ' + '
', options_div = '

Cordova Camera API Options

' + '
' + createOptionsEl('sourceType', Camera.PictureSourceType, camPictureSourceTypeDefault) + createOptionsEl('destinationType', Camera.DestinationType, camDestinationTypeDefault) + createOptionsEl('encodingType', Camera.EncodingType, camEncodingTypeDefault) + createOptionsEl('mediaType', Camera.MediaType, camMediaTypeDefault) + createOptionsEl('quality', { '0': 0, '50': 50, '80': 80, '100': 100 }, camQualityDefault) + createOptionsEl('targetWidth', { '50': 50, '200': 200, '800': 800, '2048': 2048 }) + createOptionsEl('targetHeight', { '50': 50, '200': 200, '800': 800, '2048': 2048 }) + createOptionsEl('allowEdit', true, camAllowEditDefault) + createOptionsEl('correctOrientation', true, camCorrectOrientationDefault) + createOptionsEl('saveToPhotoAlbum', true, camSaveToPhotoAlbumDefault) + createOptionsEl('cameraDirection', Camera.Direction) + '
', getpicture_div = '
', test_procedure = '

Recommended Test Procedure

' + 'Options not specified should be the default value' + '
Status box should update with image and info whenever an image is taken or selected from library' + '

' + '
  1. All default options. Should be able to edit once picture is taken and will be saved to library.
  2. ' + '

  3. sourceType=PHOTOLIBRARY
    Should be able to see picture that was just taken in previous test and edit when selected
  4. ' + '

  5. sourceType=Camera
    allowEdit=false
    saveToPhotoAlbum=false
    Should not be able to edit when taken and will not save to library
  6. ' + '

  7. encodingType=PNG
    allowEdit=true
    saveToPhotoAlbum=true
    cameraDirection=FRONT
    Should bring up front camera. Verify in status box info URL that image is encoded as PNG.
  8. ' + '

  9. sourceType=SAVEDPHOTOALBUM
    mediaType=VIDEO
    Should only be able to select a video
  10. ' + '

  11. sourceType=SAVEDPHOTOALBUM
    mediaType=PICTURE
    allowEdit=false
    Should only be able to select a picture and not edit
  12. ' + '

  13. sourceType=PHOTOLIBRARY
    mediaType=ALLMEDIA
    allowEdit=true
    Should be able to select pics and videos and edit picture if selected
  14. ' + '

  15. sourceType=CAMERA
    targetWidth & targetHeight=50
    allowEdit=false
    Do Get File Metadata test below and take note of size
    Repeat test but with width and height=800. Size should be significantly larger.
  16. ' + '

  17. quality=0
    targetWidth & targetHeight=default
    allowEdit=false
    Do Get File Metadata test below and take note of size
    Repeat test but with quality=80. Size should be significantly larger.
  18. ' + '
', inputs_div = '

Native File Inputs

' + 'For the following tests, status box should update with file selected' + '

input type=file
' + '
capture=camera
' + '
capture=camcorder
' + '
capture=microphone
', actions_div = '

Actions

' + 'For the following tests, ensure that an image is set in status box' + '

' + 'Expected result: Get metadata about file selected.
Status box will show, along with the metadata, "Call to FileEntry.getMetadata success, Call to FileEntry.setMetadata success, Call to FileEntry.getParent success"' + '

' + 'Expected result: Read contents of file.
Status box will show "Got file: {some metadata}, FileReader.readAsDataURL() - length = someNumber"' + '

' + 'Expected result: Copy image to new location and move file to different location.
Status box will show "Call to FileEntry.copyTo success:{some metadata}, Call to FileEntry.moveTo success:{some metadata}"' + '

' + 'Expected result: Write image to library.
Status box will show "Call to FileWriter.write success:{some metadata}, Call to FileWriter.truncate success:{some metadata}"' + '

' + 'Expected result: Upload image to server.
Status box may print out progress. Once finished will show "upload complete"' + '

' + 'Expected result: Display image using canvas.
Image will be displayed in status box under "canvas:"' + '

' + 'Expected result: Remove image from library.
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; } var elements = document.getElementsByClassName("testInputTag"); var listener = function (e) { testInputTag(e.target); }; for (var i = 0; i < elements.length; ++i) { var item = elements[i]; item.addEventListener("change", listener, false); } createActionButton('Get picture', function () { getPicture(); }, 'getpicture'); createActionButton('Clear Status', function () { clearStatus(); }, 'getpicture'); createActionButton('Get File Metadata', function () { getFileInfo(); }, 'metadata'); createActionButton('Read with FileReader', function () { readFile(); }, 'reader'); createActionButton('Copy Image', function () { copyImage(); }, 'copy'); createActionButton('Write Image', function () { writeImage(); }, 'write'); createActionButton('Upload Image', function () { uploadImage(); }, 'upload'); createActionButton('Draw Using Canvas', function () { displayImageUsingCanvas(); }, 'draw_canvas'); createActionButton('Remove Image', function () { removeImage(); }, 'remove'); };