You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

510 lines
23 KiB

  1. /*
  2. *
  3. * Licensed to the Apache Software Foundation (ASF) under one
  4. * or more contributor license agreements. See the NOTICE file
  5. * distributed with this work for additional information
  6. * regarding copyright ownership. The ASF licenses this file
  7. * to you under the Apache License, Version 2.0 (the
  8. * "License"); you may not use this file except in compliance
  9. * with the License. You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing,
  14. * software distributed under the License is distributed on an
  15. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  16. * KIND, either express or implied. See the License for the
  17. * specific language governing permissions and limitations
  18. * under the License.
  19. *
  20. */
  21. /* globals Camera, resolveLocalFileSystemURL, FileEntry, CameraPopoverOptions, FileTransfer, FileUploadOptions, LocalFileSystem, MSApp */
  22. /* jshint jasmine: true */
  23. exports.defineAutoTests = function () {
  24. describe('Camera (navigator.camera)', function () {
  25. it("should exist", function () {
  26. expect(navigator.camera).toBeDefined();
  27. });
  28. it("should contain a getPicture function", function () {
  29. expect(navigator.camera.getPicture).toBeDefined();
  30. expect(typeof navigator.camera.getPicture == 'function').toBe(true);
  31. });
  32. });
  33. describe('Camera Constants (window.Camera + navigator.camera)', function () {
  34. it("camera.spec.1 window.Camera should exist", function () {
  35. expect(window.Camera).toBeDefined();
  36. });
  37. it("camera.spec.2 should contain three DestinationType constants", function () {
  38. expect(Camera.DestinationType.DATA_URL).toBe(0);
  39. expect(Camera.DestinationType.FILE_URI).toBe(1);
  40. expect(Camera.DestinationType.NATIVE_URI).toBe(2);
  41. expect(navigator.camera.DestinationType.DATA_URL).toBe(0);
  42. expect(navigator.camera.DestinationType.FILE_URI).toBe(1);
  43. expect(navigator.camera.DestinationType.NATIVE_URI).toBe(2);
  44. });
  45. it("camera.spec.3 should contain two EncodingType constants", function () {
  46. expect(Camera.EncodingType.JPEG).toBe(0);
  47. expect(Camera.EncodingType.PNG).toBe(1);
  48. expect(navigator.camera.EncodingType.JPEG).toBe(0);
  49. expect(navigator.camera.EncodingType.PNG).toBe(1);
  50. });
  51. it("camera.spec.4 should contain three MediaType constants", function () {
  52. expect(Camera.MediaType.PICTURE).toBe(0);
  53. expect(Camera.MediaType.VIDEO).toBe(1);
  54. expect(Camera.MediaType.ALLMEDIA).toBe(2);
  55. expect(navigator.camera.MediaType.PICTURE).toBe(0);
  56. expect(navigator.camera.MediaType.VIDEO).toBe(1);
  57. expect(navigator.camera.MediaType.ALLMEDIA).toBe(2);
  58. });
  59. it("camera.spec.5 should contain three PictureSourceType constants", function () {
  60. expect(Camera.PictureSourceType.PHOTOLIBRARY).toBe(0);
  61. expect(Camera.PictureSourceType.CAMERA).toBe(1);
  62. expect(Camera.PictureSourceType.SAVEDPHOTOALBUM).toBe(2);
  63. expect(navigator.camera.PictureSourceType.PHOTOLIBRARY).toBe(0);
  64. expect(navigator.camera.PictureSourceType.CAMERA).toBe(1);
  65. expect(navigator.camera.PictureSourceType.SAVEDPHOTOALBUM).toBe(2);
  66. });
  67. });
  68. };
  69. /******************************************************************************/
  70. /******************************************************************************/
  71. /******************************************************************************/
  72. exports.defineManualTests = function (contentEl, createActionButton) {
  73. var pictureUrl = null;
  74. var fileObj = null;
  75. var fileEntry = null;
  76. var pageStartTime = +new Date();
  77. //default camera options
  78. var camQualityDefault = ['50', 50];
  79. var camDestinationTypeDefault = ['FILE_URI', 1];
  80. var camPictureSourceTypeDefault = ['CAMERA', 1];
  81. var camAllowEditDefault = ['allowEdit', false];
  82. var camEncodingTypeDefault = ['JPEG', 0];
  83. var camMediaTypeDefault = ['mediaType', 0];
  84. var camCorrectOrientationDefault = ['correctOrientation', false];
  85. var camSaveToPhotoAlbumDefault = ['saveToPhotoAlbum', true];
  86. function log(value) {
  87. console.log(value);
  88. document.getElementById('camera_status').textContent += (new Date() - pageStartTime) / 1000 + ': ' + value + '\n';
  89. }
  90. function clearStatus() {
  91. document.getElementById('camera_status').innerHTML = '';
  92. document.getElementById('camera_image').src = 'about:blank';
  93. var canvas = document.getElementById('canvas');
  94. canvas.width = canvas.height = 1;
  95. pictureUrl = null;
  96. fileObj = null;
  97. fileEntry = null;
  98. }
  99. function setPicture(url, callback) {
  100. try {
  101. window.atob(url);
  102. // if we got here it is a base64 string (DATA_URL)
  103. url = "data:image/jpeg;base64," + url;
  104. } catch (e) {
  105. // not DATA_URL
  106. }
  107. log('URL: "' + url.slice(0, 90) + '"');
  108. pictureUrl = url;
  109. var img = document.getElementById('camera_image');
  110. var startTime = new Date();
  111. img.src = url;
  112. img.onload = function () {
  113. log('Img size: ' + img.naturalWidth + 'x' + img.naturalHeight);
  114. log('Image tag load time: ' + (new Date() - startTime));
  115. if (callback) {
  116. callback();
  117. }
  118. };
  119. }
  120. function onGetPictureError(e) {
  121. log('Error getting picture: ' + (e.code || e));
  122. }
  123. function getPictureWin(data) {
  124. setPicture(data);
  125. // TODO: Fix resolveLocalFileSystemURI to work with native-uri.
  126. if (pictureUrl.indexOf('file:') === 0 || pictureUrl.indexOf('content:') === 0 || pictureUrl.indexOf('ms-appdata:') === 0 || pictureUrl.indexOf('assets-library:') === 0) {
  127. resolveLocalFileSystemURL(data, function (e) {
  128. fileEntry = e;
  129. logCallback('resolveLocalFileSystemURL()', true)(e.toURL());
  130. readFile();
  131. }, logCallback('resolveLocalFileSystemURL()', false));
  132. } else if (pictureUrl.indexOf('data:image/jpeg;base64') === 0) {
  133. // do nothing
  134. } else {
  135. var path = pictureUrl.replace(/^file:\/\/(localhost)?/, '').replace(/%20/g, ' ');
  136. fileEntry = new FileEntry('image_name.png', path);
  137. }
  138. }
  139. function getPicture() {
  140. clearStatus();
  141. var options = extractOptions();
  142. log('Getting picture with options: ' + JSON.stringify(options));
  143. var popoverHandle = navigator.camera.getPicture(getPictureWin, onGetPictureError, options);
  144. // Reposition the popover if the orientation changes.
  145. window.onorientationchange = function () {
  146. var newPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, 0);
  147. popoverHandle.setPosition(newPopoverOptions);
  148. };
  149. }
  150. function uploadImage() {
  151. var ft = new FileTransfer(),
  152. options = new FileUploadOptions();
  153. options.fileKey = "photo";
  154. options.fileName = 'test.jpg';
  155. options.mimeType = "image/jpeg";
  156. ft.onprogress = function (progressEvent) {
  157. console.log('progress: ' + progressEvent.loaded + ' of ' + progressEvent.total);
  158. };
  159. var server = "http://sheltered-retreat-43956.herokuapp.com";
  160. ft.upload(pictureUrl, server + '/upload', win, fail, options);
  161. function win(information_back) {
  162. log('upload complete');
  163. }
  164. function fail(message) {
  165. log('upload failed: ' + JSON.stringify(message));
  166. }
  167. }
  168. function logCallback(apiName, success) {
  169. return function () {
  170. log('Call to ' + apiName + (success ? ' success: ' : ' failed: ') + JSON.stringify([].slice.call(arguments)));
  171. };
  172. }
  173. /**
  174. * Select image from library using a NATIVE_URI destination type
  175. * This calls FileEntry.getMetadata, FileEntry.setMetadata, FileEntry.getParent, FileEntry.file, and FileReader.readAsDataURL.
  176. */
  177. function readFile() {
  178. function onFileReadAsDataURL(evt) {
  179. var img = document.getElementById('camera_image');
  180. img.style.visibility = "visible";
  181. img.style.display = "block";
  182. img.src = evt.target.result;
  183. log("FileReader.readAsDataURL success");
  184. }
  185. function onFileReceived(file) {
  186. log('Got file: ' + JSON.stringify(file));
  187. fileObj = file;
  188. var reader = new FileReader();
  189. reader.onload = function () {
  190. log('FileReader.readAsDataURL() - length = ' + reader.result.length);
  191. };
  192. reader.onerror = logCallback('FileReader.readAsDataURL', false);
  193. reader.onloadend = onFileReadAsDataURL;
  194. reader.readAsDataURL(file);
  195. }
  196. // Test out onFileReceived when the file object was set via a native <input> elements.
  197. if (fileObj) {
  198. onFileReceived(fileObj);
  199. } else {
  200. fileEntry.file(onFileReceived, logCallback('FileEntry.file', false));
  201. }
  202. }
  203. function getFileInfo() {
  204. // Test FileEntry API here.
  205. fileEntry.getMetadata(logCallback('FileEntry.getMetadata', true), logCallback('FileEntry.getMetadata', false));
  206. fileEntry.setMetadata(logCallback('FileEntry.setMetadata', true), logCallback('FileEntry.setMetadata', false), { "com.apple.MobileBackup": 1 });
  207. fileEntry.getParent(logCallback('FileEntry.getParent', true), logCallback('FileEntry.getParent', false));
  208. fileEntry.getParent(logCallback('FileEntry.getParent', true), logCallback('FileEntry.getParent', false));
  209. }
  210. /**
  211. * Copy image from library using a NATIVE_URI destination type
  212. * This calls FileEntry.copyTo and FileEntry.moveTo.
  213. */
  214. function copyImage() {
  215. var onFileSystemReceived = function (fileSystem) {
  216. var destDirEntry = fileSystem.root;
  217. var origName = fileEntry.name;
  218. // Test FileEntry API here.
  219. fileEntry.copyTo(destDirEntry, 'copied_file.png', logCallback('FileEntry.copyTo', true), logCallback('FileEntry.copyTo', false));
  220. fileEntry.moveTo(destDirEntry, 'moved_file.png', logCallback('FileEntry.moveTo', true), logCallback('FileEntry.moveTo', false));
  221. //cleanup
  222. //rename moved file back to original name so other tests can reference image
  223. resolveLocalFileSystemURL(destDirEntry.nativeURL+'moved_file.png', function(fileEntry) {
  224. fileEntry.moveTo(destDirEntry, origName, logCallback('FileEntry.moveTo', true), logCallback('FileEntry.moveTo', false));
  225. console.log('Cleanup: successfully renamed file back to original name');
  226. }, function () {
  227. console.log('Cleanup: failed to rename file back to original name');
  228. });
  229. //remove copied file
  230. resolveLocalFileSystemURL(destDirEntry.nativeURL+'copied_file.png', function(fileEntry) {
  231. fileEntry.remove(logCallback('FileEntry.remove', true), logCallback('FileEntry.remove', false));
  232. console.log('Cleanup: successfully removed copied file');
  233. }, function () {
  234. console.log('Cleanup: failed to remove copied file');
  235. });
  236. };
  237. window.requestFileSystem(LocalFileSystem.TEMPORARY, 0, onFileSystemReceived, null);
  238. }
  239. /**
  240. * Write image to library using a NATIVE_URI destination type
  241. * This calls FileEntry.createWriter, FileWriter.write, and FileWriter.truncate.
  242. */
  243. function writeImage() {
  244. var onFileWriterReceived = function (fileWriter) {
  245. fileWriter.onwrite = logCallback('FileWriter.write', true);
  246. fileWriter.onerror = logCallback('FileWriter.write', false);
  247. fileWriter.write("some text!");
  248. };
  249. var onFileTruncateWriterReceived = function (fileWriter) {
  250. fileWriter.onwrite = logCallback('FileWriter.truncate', true);
  251. fileWriter.onerror = logCallback('FileWriter.truncate', false);
  252. fileWriter.truncate(10);
  253. };
  254. fileEntry.createWriter(onFileWriterReceived, logCallback('FileEntry.createWriter', false));
  255. fileEntry.createWriter(onFileTruncateWriterReceived, null);
  256. }
  257. function displayImageUsingCanvas() {
  258. var canvas = document.getElementById('canvas');
  259. var img = document.getElementById('camera_image');
  260. var w = img.width;
  261. var h = img.height;
  262. h = 100 / w * h;
  263. w = 100;
  264. canvas.width = w;
  265. canvas.height = h;
  266. var context = canvas.getContext('2d');
  267. context.drawImage(img, 0, 0, w, h);
  268. }
  269. /**
  270. * Remove image from library using a NATIVE_URI destination type
  271. * This calls FileEntry.remove.
  272. */
  273. function removeImage() {
  274. fileEntry.remove(logCallback('FileEntry.remove', true), logCallback('FileEntry.remove', false));
  275. }
  276. function testInputTag(inputEl) {
  277. clearStatus();
  278. // iOS 6 likes to dead-lock in the onchange context if you
  279. // do any alerts or try to remote-debug.
  280. window.setTimeout(function () {
  281. testNativeFile2(inputEl);
  282. }, 0);
  283. }
  284. function testNativeFile2(inputEl) {
  285. if (!inputEl.value) {
  286. alert('No file selected.');
  287. return;
  288. }
  289. fileObj = inputEl.files[0];
  290. if (!fileObj) {
  291. alert('Got value but no file.');
  292. return;
  293. }
  294. var URLApi = window.URL || window.webkitURL;
  295. if (URLApi) {
  296. var blobURL = URLApi.createObjectURL(fileObj);
  297. if (blobURL) {
  298. setPicture(blobURL, function () {
  299. URLApi.revokeObjectURL(blobURL);
  300. });
  301. } else {
  302. log('URL.createObjectURL returned null');
  303. }
  304. } else {
  305. log('URL.createObjectURL() not supported.');
  306. }
  307. }
  308. function extractOptions() {
  309. var els = document.querySelectorAll('#image-options select');
  310. var ret = {};
  311. /*jshint -W084 */
  312. for (var i = 0, el; el = els[i]; ++i) {
  313. var value = el.value;
  314. if (value === '') continue;
  315. value = +value;
  316. if (el.isBool) {
  317. ret[el.getAttribute("name")] = !!value;
  318. } else {
  319. ret[el.getAttribute("name")] = value;
  320. }
  321. }
  322. /*jshint +W084 */
  323. return ret;
  324. }
  325. function createOptionsEl(name, values, selectionDefault) {
  326. var openDiv = '<div style="display: inline-block">' + name + ': ';
  327. var select = '<select name=' + name + ' id="' + name + '">';
  328. var defaultOption = '';
  329. if (selectionDefault === undefined) {
  330. defaultOption = '<option value="">default</option>';
  331. }
  332. var options = '';
  333. if (typeof values == 'boolean') {
  334. values = { 'true': 1, 'false': 0 };
  335. }
  336. for (var k in values) {
  337. var isSelected = '';
  338. if (selectionDefault) {
  339. if (selectionDefault[0] == k) {
  340. isSelected = 'selected';
  341. }
  342. }
  343. options += '<option value="' + values[k] + '" ' + isSelected + '>' + k + '</option>';
  344. }
  345. var closeDiv = '</select></div>';
  346. return openDiv + select + defaultOption + options + closeDiv;
  347. }
  348. /******************************************************************************/
  349. var info_div = '<h1>Camera</h1>' +
  350. '<div id="info">' +
  351. '<b>Status:</b> <div id="camera_status"></div>' +
  352. 'img: <img width="100" id="camera_image">' +
  353. 'canvas: <canvas id="canvas" width="1" height="1"></canvas>' +
  354. '</div>',
  355. options_div = '<h2>Cordova Camera API Options</h2>' +
  356. '<div id="image-options">' +
  357. createOptionsEl('sourceType', Camera.PictureSourceType, camPictureSourceTypeDefault) +
  358. createOptionsEl('destinationType', Camera.DestinationType, camDestinationTypeDefault) +
  359. createOptionsEl('encodingType', Camera.EncodingType, camEncodingTypeDefault) +
  360. createOptionsEl('mediaType', Camera.MediaType, camMediaTypeDefault) +
  361. createOptionsEl('quality', { '0': 0, '50': 50, '80': 80, '100': 100 }, camQualityDefault) +
  362. createOptionsEl('targetWidth', { '50': 50, '200': 200, '800': 800, '2048': 2048 }) +
  363. createOptionsEl('targetHeight', { '50': 50, '200': 200, '800': 800, '2048': 2048 }) +
  364. createOptionsEl('allowEdit', true, camAllowEditDefault) +
  365. createOptionsEl('correctOrientation', true, camCorrectOrientationDefault) +
  366. createOptionsEl('saveToPhotoAlbum', true, camSaveToPhotoAlbumDefault) +
  367. createOptionsEl('cameraDirection', Camera.Direction) +
  368. '</div>',
  369. getpicture_div = '<div id="getpicture"></div>',
  370. test_procedure = '<h4>Recommended Test Procedure</h4>' +
  371. 'Options not specified should be the default value' +
  372. '<br>Status box should update with image and info whenever an image is taken or selected from library' +
  373. '</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">' +
  374. '<ol> <li>All default options. Should be able to edit once picture is taken and will be saved to library.</li>' +
  375. '</p><li>sourceType=PHOTOLIBRARY<br>Should be able to see picture that was just taken in previous test and edit when selected</li>' +
  376. '</p><li>sourceType=Camera<br>allowEdit=false<br>saveToPhotoAlbum=false<br>Should not be able to edit when taken and will not save to library</li>' +
  377. '</p><li>encodingType=PNG<br>allowEdit=true<br>saveToPhotoAlbum=true<br>cameraDirection=FRONT<br>Should bring up front camera. Verify in status box info URL that image is encoded as PNG.</li>' +
  378. '</p><li>sourceType=SAVEDPHOTOALBUM<br>mediaType=VIDEO<br>Should only be able to select a video</li>' +
  379. '</p><li>sourceType=SAVEDPHOTOALBUM<br>mediaType=PICTURE<br>allowEdit=false<br>Should only be able to select a picture and not edit</li>' +
  380. '</p><li>sourceType=PHOTOLIBRARY<br>mediaType=ALLMEDIA<br>allowEdit=true<br>Should be able to select pics and videos and edit picture if selected</li>' +
  381. '</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>' +
  382. '</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>' +
  383. '</ol></div>',
  384. inputs_div = '<h2>Native File Inputs</h2>' +
  385. 'For the following tests, status box should update with file selected' +
  386. '</p><div>input type=file <input type="file" class="testInputTag"></div>' +
  387. '<div>capture=camera <input type="file" accept="image/*;capture=camera" class="testInputTag"></div>' +
  388. '<div>capture=camcorder <input type="file" accept="video/*;capture=camcorder" class="testInputTag"></div>' +
  389. '<div>capture=microphone <input type="file" accept="audio/*;capture=microphone" class="testInputTag"></div>',
  390. actions_div = '<h2>Actions</h2>' +
  391. 'For the following tests, ensure that an image is set in status box' +
  392. '</p><div id="metadata"></div>' +
  393. '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"' +
  394. '</p><div id="reader"></div>' +
  395. 'Expected result: Read contents of file.<br>Status box will show "Got file: {some metadata}, FileReader.readAsDataURL() - length = someNumber"' +
  396. '</p><div id="copy"></div>' +
  397. 'Expected result: Copy image to new location and move file to different location.<br>Status box will show "Call to FileEntry.copyTo success:{some metadata}, Call to FileEntry.moveTo success:{some metadata}"' +
  398. '</p><div id="write"></div>' +
  399. 'Expected result: Write image to library.<br>Status box will show "Call to FileWriter.write success:{some metadata}, Call to FileWriter.truncate success:{some metadata}"' +
  400. '</p><div id="upload"></div>' +
  401. 'Expected result: Upload image to server.<br>Status box may print out progress. Once finished will show "upload complete"' +
  402. '</p><div id="draw_canvas"></div>' +
  403. 'Expected result: Display image using canvas.<br>Image will be displayed in status box under "canvas:"' +
  404. '</p><div id="remove"></div>' +
  405. 'Expected result: Remove image from library.<br>Status box will show "FileEntry.remove success:["OK"]';
  406. // We need to wrap this code due to Windows security restrictions
  407. // see http://msdn.microsoft.com/en-us/library/windows/apps/hh465380.aspx#differences for details
  408. if (window.MSApp && window.MSApp.execUnsafeLocalFunction) {
  409. MSApp.execUnsafeLocalFunction(function() {
  410. contentEl.innerHTML = info_div + options_div + getpicture_div + test_procedure + inputs_div + actions_div;
  411. });
  412. } else {
  413. contentEl.innerHTML = info_div + options_div + getpicture_div + test_procedure + inputs_div + actions_div;
  414. }
  415. var elements = document.getElementsByClassName("testInputTag");
  416. var listener = function (e) {
  417. testInputTag(e.target);
  418. };
  419. for (var i = 0; i < elements.length; ++i) {
  420. var item = elements[i];
  421. item.addEventListener("change", listener, false);
  422. }
  423. createActionButton('Get picture', function () {
  424. getPicture();
  425. }, 'getpicture');
  426. createActionButton('Clear Status', function () {
  427. clearStatus();
  428. }, 'getpicture');
  429. createActionButton('Get File Metadata', function () {
  430. getFileInfo();
  431. }, 'metadata');
  432. createActionButton('Read with FileReader', function () {
  433. readFile();
  434. }, 'reader');
  435. createActionButton('Copy Image', function () {
  436. copyImage();
  437. }, 'copy');
  438. createActionButton('Write Image', function () {
  439. writeImage();
  440. }, 'write');
  441. createActionButton('Upload Image', function () {
  442. uploadImage();
  443. }, 'upload');
  444. createActionButton('Draw Using Canvas', function () {
  445. displayImageUsingCanvas();
  446. }, 'draw_canvas');
  447. createActionButton('Remove Image', function () {
  448. removeImage();
  449. }, 'remove');
  450. };