diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..46ed441
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,26 @@
+_archive/
+_devdocs/
+_docs/*.jpg
+_docs/ads.txt
+_docs/app-ads.txt
+_docs/gplay.html
+_other/
+_saved/
+
+.gradle/
+
+.idea/*
+!.idea/inspectionProfiles
+
+build/
+app/release/
+gfx/
+testdata/
+
+*.db
+*.iml
+*.apk
+*.ap_
+
+local.properties
+uninstall.bat
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..136b777
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/_docs/.htaccess b/_docs/.htaccess
new file mode 100644
index 0000000..1f8de97
--- /dev/null
+++ b/_docs/.htaccess
@@ -0,0 +1,3 @@
+RewriteEngine On
+RewriteCond %{HTTP:X-Forwarded-Proto} !https
+RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
diff --git a/_docs/baseline_add_a_photo_white_48.png b/_docs/baseline_add_a_photo_white_48.png
new file mode 100644
index 0000000..17a587b
Binary files /dev/null and b/_docs/baseline_add_a_photo_white_48.png differ
diff --git a/_docs/baseline_delete_white_48.png b/_docs/baseline_delete_white_48.png
new file mode 100644
index 0000000..318f8e7
Binary files /dev/null and b/_docs/baseline_delete_white_48.png differ
diff --git a/_docs/baseline_filter_vintage_white_48.png b/_docs/baseline_filter_vintage_white_48.png
new file mode 100644
index 0000000..5359e45
Binary files /dev/null and b/_docs/baseline_filter_vintage_white_48.png differ
diff --git a/_docs/credits.html b/_docs/credits.html
new file mode 100644
index 0000000..d2dd47f
--- /dev/null
+++ b/_docs/credits.html
@@ -0,0 +1,97 @@
+
+
+
+Open Camera
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Open Camera Credits
+
+
+< Main Page.
+
+Open Camera is written by Mark Harman. Additional credits:
+
+App icon by Adam Lapinski.
+Improvements/help for video frame rates (including high speed) by George Joseph.
+Support for the video picture profiles implementation and custom Log profiles JTVideo, JTLog and JTLog2 design by JT Haapala.
+Smart Housing Bluetooth LE support by Edouard Lafargue
+Improved selfie stick button support by Lau Keat Hwa.
+Option to storing yaw/pitch/roll in Exif user comment by Joshua.
+Option for filenames to be based on UTC (Zulu) time by David Pletcher ( lpm_sourceforge AT cathedralcanyon DOT net , https://www.cathedralcanyon.net ).
+Support for manual ISO for old camera API on Asus Zenphone 2 Z00A and Z008 by Flávio Keglevich ( fkeglevich AT gmail DOT com ).
+Changing icons for pause/resume video by Johan Ejdemark ( johanejdemark AT hotmail DOT com).
+Various improvements (including for lock screen behaviour) by Daniel Zhang.
+Option to use milliseconds in filenames by Rob Emery ( opencam AT mintsoft DOT net).
+Azerbaijani translation by Eldost ( l-dost AT mail DOT ru ).
+Brazilian tranlation by Kaio Duarte.
+Catalan translation by Cambrells.
+Chinese Simplified translation by Michael Lu ( yeskky AT gmail DOT com ), tumuyan ( tumuyan AT gmail DOT com ) and Tommy He.
+Chinese Traditional translation by You-Cheng Hsieh ( yochenhsieh AT gmail DOT com ) and Hsiu-Ming Chang.
+Belarusian translation by Zmicer Turok.
+Czech translation by Jaroslav Svoboda ( multi DOT flexi AT gmail DOT com , http://jaroslavsvoboda.eu ).
+French translation by Olivier Seiler ( oseiler AT nebuka DOT net ) and Eric Lassauge ( lassauge AT users DOT sf DOT net ).
+German translation by Ronny Steiner, Sebastian Ahlborn, Carsten Schlote, Wilhelm Stein, Jochen Wiesel.
+Greek translation by Wasilis Mandratzis-Walz.
+Hungarian translation by Báthory Péter.
+Italian tranlation by Valerio Bozzolan, Stefano Gualmo ( s DOT gualmo AT gmail DOT com ), Renato Giliberti.
+Japanese translation by Mitsuse and Yanagimoto Yoshiaki.
+Korean translation by Halcyonest.
+Norwegian Bokmål translation by Imre Kristoffer Eilertsen ( imreeil42 AT gmail DOT com ).
+Polish translation by Jacek Buczyński and Grzegorz Koryga.
+
+Russian translation by maksnogin ( maksnogin AT gmail DOT com ), Grigorii Chirkov, Dmitry Vahnin aka JSBmanD, Aleksey Khlybov, Ilya Pogrebenko.
+Slovenian translation by Peter Klofutar.
+Spanish translation by Mario Sanoguera ( sanogueralorenzo AT gmail DOT com , https://play.google.com/store/apps/developer?id=Mario+Sanoguera ; Sebastian05067, https://forum.xda-developers.com/member.php?u=6302705 ) and Gonzalo Prieto Vega.
+Turkish translation by Serdar Erkoc ( serdarerkoc2004 AT yahoo DOT com ).
+Ukranian translation by Olexandr ( https://sourceforge.net/u/olexn13/ ).
+Vietnamese translation by Khánh Trần ( crhanh AT gmail DOT com ).
+Earlier versions (pre-Material Design) have an icon/logo by Cosmin Saveanu ( http://aboutfoto.wordpress.com/ ).
+
+
+Also see licence for third party files.
+
+
+Open Camera Privacy Policy.
+This website uses icons from third party sources, see licences.
+Open Camera on Sourceforge.
+
+
+
+
diff --git a/_docs/devices.html b/_docs/devices.html
new file mode 100644
index 0000000..075ba97
--- /dev/null
+++ b/_docs/devices.html
@@ -0,0 +1,221 @@
+
+
+
+Open Camera Device Compatibility
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Open Camera Device Compatibility
+
+
+< Main Page.
+
+This page provides some guidance on possible issues of Open Camera on various Android™ devices. Please note the following:
+
+ Sometimes behaviour can be affected by things like operating system version or can differ between variants of the same
+ model. Behaviour may also change over time depending on different versions of Open Camera, and different
+ versions of device operating system. So something listed here isn't a guarantee of that behaviour on a
+ particular model.
+ This information is provided "as is" with no warranties - if you need to be certain of how Open Camera works on a
+ particular device, ultimately you'll have to test it yourself.
+
+
+General notes
+
+If you're just interested in taking photos with non-advanced features (without using "Camera2 API"), then most things
+ should work on most devices, as far as I can tell. I do occasionally get bug reports of things which seem to be device
+ specific, but not enough to draw conclusions about things not working on particular devices. The most commonly reported
+ issues seems to be:
+
+ Video is one of the most difficult things working across different Android devices - on some devices recording comes out
+ corrupted. In some cases this is only on some resolutions, or it may be
+ all.
+ If you're wanting to save to external SD cards, you'll need to follow the advice at
+ the FAQ . Note that in some cases, it seems that SD cards
+ can't be selected even when using the Storage Access Framework option - this is a device issue, and
+ something out of my control. If you're wanting to have lots
+ of storage for taking photos or videos, it's probably better to make sure you get a device with plenty of
+ internal storage (internal storage is faster anyway, so means faster taking of photos, and more reliable high
+ resolution video recording).
+
+
+If you're interested in enabling Camera2 support for advanced features (manual focus, manual exposure, expo bracketing, HDR),
+ be aware that some devices have poor support for Camera2 (even if they support the API, the
+ implementations seem to have problems). Thankfully things seem to be improving on that front with newer
+ devices. Please read the details below on what I know about different devices.
+
+Also note that just because a manufacturer advertises a particular camera feature, it doesn't mean that Open Camera can use
+ it. Unfortunately some manufacturers limit some features to the "stock" camera application, and don't make it available
+ through to third party cameras. This tends to be more advanced features - 4K video, high photo resolutions, high frame
+ rate video, RAW.
+
+Device specific notes
+
+Google Nexuses/Pixels
+
+In general, Google Nexuses and Pixels have worked well for Open Camera.
+
+Camera2 API on the Nexus 6 works well (there are some minor issues, e.g., manual exposure doesn't work well when recording
+ video). It's hard to be sure about other Nexuses though.
+
+Similarly Camera2 API works well on the Pixel 6 Pro. Open Camera also supports Google's HDR+ mode on the Pixels with Pixel Visual Core
+ (including the Pixel 6 Pro). As of Open Camera 1.50, Night Sight on the Pixel 6 Pro is available via the photo mode X-Night.
+ As of Open Camera 1.50, all of the Pixel 6 Pro's cameras are available to use by zooming in or out.
+ As of Open Camera 1.54, you can also select individual cameras directly (tested on the Pixel 6 Pro).
+
+Color effects don't work on the Nexus 7.
+
+Huawei
+
+I've had reports of expo bracketing and HDR in Camera2 mode not working properly on some Huawei devices
+ (Huawei P8 lite 2017, P9). See here for details.
+
+I've also had reports of RAW/DNG images being saved with red/blue swapped. See
+here for details.
+
+Nokia
+
+I've tested Open Camera with the Nokia 8. Everything seems to work as far as I can tell, including Camera2 API with full
+manual controls, RAW and 120fps video.
+
+OnePlus
+
+I've tested Open Camera with the OnePlus Pad. Everything seems to work as far as I can tell, including Camera2 API with full
+ manual controls, and RAW.
+
+The OnePlus 3T had problems related to Camera2 API and manual exposure:
+
+ Manual exposure sometimes fails (the preview corrupts or the device may freeze for a few moments), this also
+ includes HDR.
+ Low light scenes show the wrong ISO and shutter speed, both on-screen and in the saved Exif info (although the
+ photos themselves still come out okay); also a knock on effect of this bug is that HDR and expo bracketing
+ don't work in low light. Manual ISO also doesn't work above 799 (the images still come out with ISO at 799).
+
+See this thread for more
+ details.
+
+The OnePlus 5 seems to have the same problems with Camera2 API as the OnePlus 3T (see above). Issues with RAW images have been
+ reported for third party camera applications - see
+ here ,
+ here and
+ here . As of August 2017,
+ this
+ seems to have been fixed .
+ But as of December 2017, there seem to be
+ additional
+ problems with RAW on Android 8.
+
+
+The OnePlus 3T and 5 are rather old devices - as I say, the more recent OnePlus Pad does not seem to have these problems.
+
+Samsung
+
+I have tested Open Camera on a Samsung Galaxy S24+ (Exynos SM-S926B) and Galaxy S10e (Exynos SM-G970F). Mostly things work, including with Camera2
+ API. Known issues are:
+
+ Night mode is available via X-Night.
+ Slow motion and high speed frame rate video doesn't work on the Galaxy S10e (see below for more details), but fine on the Galaxy S24+.
+ The "Image quality" setting has no effect for JPEGs on the Galaxy S10e (unless post-processing options such as auto-level or
+ photo stamp are applied). This has also been reported for other Samsung devices; I also have the same
+ issue with other third party camera applications on my S10e. Howevever the image quality setting does work on the Galaxy S24+. See
+ this thread
+ for details.
+ The photo shutter sound always plays at maximum volume on Camera2 API, this seems to be
+ a Samsung issue - a workaround is to turn off the shutter sound
+ via Settings/More camera controls/"Shutter sound".
+
+
+All the Galaxy S10e and Galaxy S24+ rear cameras are available (including telephoto for the S24+), you can switch by zooming in or out.
+ As of Open Camera 1.54, you can also select individual cameras directly.
+ Also the two modes for the front camera
+ ("cropped" and "wide") are available to Open Camera.
+
+At least some Samsung Galaxy devices support the camera extension modes (X-Night, X-Bokeh, X-Bty) (including the Galaxy S10e and Galaxy S24+;
+in general this is more likely to be available for the flagship S devices running Android 12+).
+
+The Samsung Galaxy S24+ at least supports Ultra HDR JPEG image format.
+
+More generally I have occasionally tested on various Samsung devices using their remote test labs - although useful, this is limited
+ compared to owning a real device (especially when the test labs are dark!)
+
+I've had reports of the audio being out of sync with video on the Galaxy S7 and S8 when in Camera2 API mode.
+
+Older Samsung devices (e.g., Galaxy S5) didn't have 4K video recording available for third party camera applications. In some
+ cases it could be enabled with the "Force 4K" option, but this only works on some devices (in some cases whether it works
+ depends on which variant of a device). As of the Galaxy S10e at least, 4K video is available.
+
+Some Samsung devices do not have any "scene modes" (in some cases this can depend on even which variant of a device is
+ used).
+
+
+
+At least some Samsung devices don't seem to have support for high speed frame rates for video for third party camera applications.
+ Filmic have documented issues for the
+ S9 and S9+ , and
+ Note 9 (these articles
+ are for Filmic Pro, but the issues faced likely affect all third party camera applications, including Open Camera).
+
+On a related note, the Galaxy Note 4 and 5 were used with Open Camera to film
+ the
+ world's first 4K feature film shot on a phone .
+
+Sony Xperia
+
+To enable the 23MP photo resolution, you need to set Settings/"Camera API" to "Camera2 API". Someone has reported to me this works on the
+ Sony XA1 (G3123) (Android 8), I'm not sure about older devices.
+
+
+Sony devices don't seem to support
+ RAW/DNG at the time of writing.
+
+I've had a report that manual white balance temperature doesn't work (Sony Xperia X Compact).
+
+
+Open Camera Privacy Policy.
+This website uses icons from third party sources, see licences.
+Open Camera on Sourceforge.
+
+
+
+
diff --git a/_docs/exposure_locked.png b/_docs/exposure_locked.png
new file mode 100644
index 0000000..3b2aa4b
Binary files /dev/null and b/_docs/exposure_locked.png differ
diff --git a/_docs/exposure_unlocked.png b/_docs/exposure_unlocked.png
new file mode 100644
index 0000000..e784ce1
Binary files /dev/null and b/_docs/exposure_unlocked.png differ
diff --git a/_docs/focus_mode_auto.png b/_docs/focus_mode_auto.png
new file mode 100644
index 0000000..7e48c33
Binary files /dev/null and b/_docs/focus_mode_auto.png differ
diff --git a/_docs/focus_mode_continuous_picture.png b/_docs/focus_mode_continuous_picture.png
new file mode 100644
index 0000000..51d42c1
Binary files /dev/null and b/_docs/focus_mode_continuous_picture.png differ
diff --git a/_docs/focus_mode_edof.png b/_docs/focus_mode_edof.png
new file mode 100644
index 0000000..4e01fb6
Binary files /dev/null and b/_docs/focus_mode_edof.png differ
diff --git a/_docs/focus_mode_fixed.png b/_docs/focus_mode_fixed.png
new file mode 100644
index 0000000..420daef
Binary files /dev/null and b/_docs/focus_mode_fixed.png differ
diff --git a/_docs/focus_mode_infinity.png b/_docs/focus_mode_infinity.png
new file mode 100644
index 0000000..6491103
Binary files /dev/null and b/_docs/focus_mode_infinity.png differ
diff --git a/_docs/focus_mode_locked.png b/_docs/focus_mode_locked.png
new file mode 100644
index 0000000..bcf7034
Binary files /dev/null and b/_docs/focus_mode_locked.png differ
diff --git a/_docs/focus_mode_manual.png b/_docs/focus_mode_manual.png
new file mode 100644
index 0000000..c434167
Binary files /dev/null and b/_docs/focus_mode_manual.png differ
diff --git a/_docs/google_material_design_icons_LICENSE-2.0.txt b/_docs/google_material_design_icons_LICENSE-2.0.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/_docs/google_material_design_icons_LICENSE-2.0.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed 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.
diff --git a/_docs/help.html b/_docs/help.html
new file mode 100644
index 0000000..a204cad
--- /dev/null
+++ b/_docs/help.html
@@ -0,0 +1,1546 @@
+
+
+
+Open Camera Help
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Open Camera Help
+
+
+< Main Page.
+
+Contents:
+
+
+
+
+
+
+
+
+
+
+
+
+Quickstart
+
+Simply point, and press the blue circular camera icon to take a photo.
+If your device supports focus areas, you can touch the part of the screen you want to focus. Touching an area will also
+(if your device supports it) control the exposure level (e.g., so clicking on a bright area will adjust the exposure
+so that it becomes less bright). Double tapping will reset the focus and exposure area.
+
+To zoom, use the slider next to the take photo button, or do a multi-touch "pinch"
+gesture, or a double-tap-drag gesture. You can also control via the volume keys on your phone or tablet - by default, pressing them will take a
+photo, but you can change this to zoom in/out from the Settings .
+
+You can press the gallery icon to view your photos
+(by default saved in the OpenCamera folder) - it will show the most recent image or video. The gallery button's
+icon will also show a thumbnail for the most recent image/video.
+
+The screen display is kept on when Open Camera is running as the foreground app (if
+you want to switch off the display, do so on your device manually).
+
+On-Screen User Interface
+
+Shutter button - Click the blue circular icon to take
+a photo. In some cases, you can also hold (long press) for a continuous burst:
+
+ Photo mode must be Standard or Fast Burst
+ Settings/"Camera API" must be set to "Camera2 API".
+ Continuous burst only supported on some devices and resolutions.
+
+
+Switch to video mode - Clicking the
+ smaller video icon next to the shutter button will switch to video mode. The shutter button will now show a smaller
+ blue dot in a white circle to indicate video mode. Pressing the shutter button will now start and stop video recording.
+ You can return to photo mode by clicking the
+ smaller photo icon .
+
+ When recording video, the main shutter button will turn red to indicate this. A smaller blue shutter button will show,
+ pressing this will take a photo while recording video. Note that different
+ photo modes are not supported when taking photos while recording video, nor is RAW supported. The photo resolution will not
+ in general match the resolution set in preferences, rather it will be set automatically (usually to match the aspect ratio
+ of the video resolution).
+
+
+
+Switch camera - Switches between front and back camera (if your
+device has both front and back cameras). If your device has more than one front and/or back camera, then this will switch
+between the first front and back camera.
+
+Switch multi-camera icon -
+ This icon only shows on devices with more than one front and/or back cameras, and allows you to switch between those
+ cameras. For example, a device might have two back cameras, one standard and one ultra-wide, this icon will switch between
+ the standard and ultra-wide camera.
+ Some devices also support cameras that are made up of multiple lenses. The device will automatically switch cameras as required
+ when zooming in or out, but this menu allows you to choose a specific physical lens (e.g. telephoto) to use (requires Camera2 API).
+ If Settings/On screen GUI/"Multiple cameras icon" is disabled, then this icon will not show; instead the "Switch camera"
+ icon can by used to cycle through all the cameras.
+ Note that some other devices do not allow third party applications to access their multiple cameras at all, in which case Open Camera isn't
+ able to use them.
+
+Exposure lock - Click to lock or unlock the exposure.
+When locked, the icon will change to . Locking the exposure means
+the exposure doesn't change based on the brightness of the current scene (though you can still adjust the exposure
+compensation). Note that this isn't guaranteed to work on all devices (doesn't seem to work on Galaxy S3, Nexus 6).
+
+Exposure compensation, ISO and Manual White Balance - Clicking this will bring
+a panel with various controls:
+
+ ISO (top row) - (Not supported on all devices.) A higher ISO setting means the camera is more sensitive to light, though may
+ also result in more noise. This mimics the film speed on traditional film cameras. Select "AUTO" to switch back to
+ automatic ISO mode.
+ If Camera2 API is used, then selecting a non-auto ISO will bring up sliders allowing direct control over the ISO and exposure
+ time (in place of the exposure compensation slider). You can also select "M" to switch straight to manual mode, keeping to the
+ current ISO value.
+
+
+ Exposure compensation (slider) - A higher value increases the exposure, so that pictures come out brighter in low light; a
+ lower value makes pictures darker. One unit of EV changes the brightness of the captured image by a factor of two. +1 EV
+ doubles the image brightness, while -1 EV halves the image brightness. Set to 0 for the default exposure.
+ See Exposure compensation .
+ (Only available if the camera supports control of the exposure.)
+
+
+ ISO and shutter speed sliders - If Camera2 API is used, and a non-auto ISO mode is selected, instead of the exposure
+ compensation sliders, two sliders will appear allowing you to control the ISO more finely, and (if supported) the exposure
+ (shutter speed) time.
+
+
+ Manual white balance - If Camera2 API is used, and manual white balance is enabled (from the popup menu), then this
+ will also show a slider allowing you to control the white balance temperature.
+
+
+To get rid of this panel, either click the Exposure button again, or click elsewhere on the screen.
+
+Popup menu - Opens the popup menu for quick access to changing
+various options:
+Flash - Typical options are off, auto, on and torch mode. For front cameras without a real flash, flash and torch options
+will be available which instead work by making the screen light up (note, front screen flash "auto" is only available with Camera2 API).
+Focus - Controls the focusing method used. Available options depend on your device. These may include:
+ Continuous (default) -
+ The camera focuses continuously. This is generally the best mode - the preview will usually always be in focus, and taking photos
+ is quick when the preview is already in focus. Note that you can still manually focus by touching the screen.
+
+ Auto -
+ The camera focuses when you take a photo, or when you touch to focus.
+
+ Infinity -
+ The camera focus will be fixed for objects far away.
+
+ Macro -
+ This behaves similarly to auto focus, but is optimised for close-up objects.
+
+ Locked -
+ The camera does not focus, unless you manually touch to focus.
+
+ Manual -
+ A slider appears allowing you to manually control the focus distance (only available if Camera2
+ API is used). Also see the options "Focus assist" and "Focus peaking" under Settings/Camera preview/
+ which may be useful when using manual focus.
+
+ Fixed -
+ The focus remains fixed.
+
+ EDOF -
+ Extended depth of field (EDOF). Focusing is done digitally and continuously.
+
+
+Photo Mode - You can also choose different photo modes:
+ STD - Standard operation: takes a single photo.
+ NR - Enables Noise Reduction mode. (Only available on some devices,
+ and if Camera2 API is used.)
+ DRO - Enables Dynamic Range Optimisation mode.
+ HDR - Enables High Dynamic Range mode.
+ Pano - Enables Panorama mode.
+ []]] - Enables Fast Burst mode. Takes multiple images in quick succession. (Only available on some
+ devices, and if Camera2 API is used.) In this mode, you can change the number of photos to take from the
+ popup menu.
+ Expo {} - Enables Auto Exposure Bracketing (AEB) mode. Takes multiple images at different exposure
+ levels. See Settings/"Photo settings" for options to control the number of images and stops. These images can
+ be processed in other applications to create HDR images. For Android, you can try my own
+ Vibrance HDR .
+ Focus {} - Enables Focus Bracketing
+ mode. (Only available on some devices, and if Camera2 API is
+ used.) This mode takes a series of photos each with a different focus distance. Two sliders appear, allowing you
+ to change the "source" and "target" focus distance. In this mode, you can change the number of photos to take from
+ the popup menu, and the option "Add infinite distance" if enabled will mean an extra photo is taken, at infinite focus distance.
+ Also on the popup menu, enabling "Auto source" will mean that the "source" focus distance will be set automatically via
+ continuous focus (or touching to focus will also select the source focus distance in this mode). Manually adjusting the
+ source focus distance slider will exit "Auto source" mode.
+ Focus bracketing is typically used with
+ Focus stacking software to merge the images into a single
+ photo. Note that whilst taking a set of focus bracketed photos, you can cancel the set by pressing the "take photo"
+ button again. Also see the options "Focus assist" and "Focus peaking" under Settings/Camera preview/
+ which may be useful when adjusting the focus distances.
+ X- modes - These extension modes enable device specific algorithms or effects that manufacturers have exposed to
+ third party applications (via Android's camera extensions API) (requires Android 12; only available on some devices,
+ and if Camera2 API is used).
+
+ X-Auto: Allows the device to choose which algorithm to use based on the current scene. Note this differs to
+ "STD" mode in that it allows the use of the other camera extensions e.g. Night mode for low light scenes.
+ X-Night: Improves image quality under low light conditions.
+ X-Bokeh: Blurs the background of photos. This is typically intended when taking portraits of people.
+ X-Bty: Face retouch or "beauty", applies cosmetic effects to people's faces.
+
+ Note many features may be unavailable when using an extension mode, including flash and manual controls.
+
+
+Auto-level - Enable the auto-level feature for photos (see
+below ). (Only available if the device has enough memory.)
+Aperture - Allows changing the current camera aperture. Large numbers mean smaller aperture, which means less
+ light is allowed into the camera. (Only available on some devices, and if Camera2 API is used.)
+Camera resolution - Change the photo resolution (also available under Settings/Photo
+Settings/"Camera resolution".
+Video resolution - Change the photo resolution (also available under Settings/Video
+Settings/"Video resolution".
+Speed - This allows you to record video at either a faster rate (time lapse) or slower
+ rate (slow motion). Note that slow motion is only available on some devices, and requires
+ Camera2 API to be enabled. Also note that sound is not recorded in time lapse or slow motion
+ modes.
+Timer - Set a timer for taking photos or recording video. Also available under
+Settings/Timer.
+Repeat - Take a repeated set of photos when the take photo button is pressed. The time
+interval between each repeated photo can be set under Settings/"Repeat mode interval".
+Grid - Whether to display one of a choice of grids on the camera preview. Also available
+under Settings/Camera preview/"Show a grid".
+White balance - Choose a method to control how the white balance is set.
+(Only available if the camera supports different white balance
+settings.) If Camera2 API is enabled, then you can also set "manual". In this mode, manual
+control over the white balance temperature is available from the exposure compensation icon
+ .
+Scene mode - Choose a scene mode to apply. (Only available if the camera supports scene
+modes.)
+Color effect - Choose a color effect to apply. (Only available if the camera supports
+color effects.)
+
+
+Settings - Click to open the Settings . (If your phone/tablet
+has a hardware menu button, pressing that should also open the settings.)
+
+Gallery - Click to launch the Gallery app, to view the most recent
+photo/video (by default saved in the OpenCamera folder). If you get the message "No Gallery app available", then you should install
+a Gallery app.
+You can also "long press" on the Gallery icon - this will let you switch between the recent save locations, or take you straight to a
+dialog to choose a save location if additional locations have yet been defined. See
+Save location under Settings/More camera controls for more details.
+
+ Pause video - When recording
+video, this icon allows you to pause and then resume video recording. (Requires Android 7.0 or higher.)
+
+In continuous focus mode, a white circle shows to indicate that the camera is focusing. When touching to focus, or in
+ autofocus mode, a white square is shown (depending on the focus mode), which turns green to indicate that the auto-focus was
+ successful, or red if it was not.
+
+The on-screen display also shows the remaining battery left (green/red status bar in the corner), and optionally
+the zoom level (if zoomed in), the remaining free storage space on the device, and the current angle orientation
+of the camera. If "Store location data" is enabled (off by default), then a small GPS icon
+ will appear at the top left
+when the location is available (you can still take photos when the GPS icon doesn't show,
+it's just that location data won't be stored in the photo). A dot shows to the top-right of the GPS icon to
+indicate the accuracy (green for accurate, yellow for less accurate, grey if waiting for location but using a recently
+cached location). If the location isn't available, a dash will be shown through the gps icon.
+
+Auto-level feature
+
+All mobile phone cameras will rotate the photo depending on the orientation of the camera, but only to the nearest 90 degrees - so the
+photos look right whether you hold the device in "portrait" or "landscape" mode. But Open Camera has the option to rotate the
+photos so they are perfectly level, so your shots come out looking perfectly level every time!
+
+
+
+
+
+The above shows a rather exaggerated example - in practice, you can probably take better photos, but this feature ensures they
+come out perfectly level, without you having to edit them afterwards in a photo editor. Of course you won't always want this -
+perhaps you're going for artistic 45-degree shots - so this is an option. By default it is disabled. To enable, open the
+Popup menu , and enable "Auto-level". Note that this feature is memory intensive - it will not be available
+on devices with low memory. Even where it is available, the performance of taking photos will be
+slower.
+
+Also note that the feature reduces the available space in the image - because rotating an image
+ makes it no longer fit into a rectangular image, so we have to crop it. So it's still advisable
+ to try to hold the camera reasonably level when using this feature.
+ When auto-level is enabled, an on-screen rectangle will display the frame of the resultant
+ rotated photo (note that this won't necessarily be 100% accurate depending on device/resolution,
+ in the same way that normally the preview frame may not perfectly match the resultant photo -
+ this will especially be true if Settings/Camera preview/"Preview size" is set to
+ "Maximise preview size".
+
+Note that auto-stabilising will not occur if the device is pointing up or down.
+
+Noise Reduction
+
+In Noise Reduction photo mode, Open Camera takes a burst of photos which are then automatically merged, to improve photo quality.
+This is particularly useful in low light scenes to reduce noise.
+
+
+
+Some things to note about Open Camera's NR feature:
+
+In dark scenes, NR will also apply pixel binning, merging 4 pixels into 1 to reduce noise. Therefore in such cases , the
+resultant photo resolution will be halved in width and height.
+In bright scenes where there is a high dynamic range, NR mode will perform better than Standard at capturing the dynamic range.
+This is similar to what Open Camera's HDR mode can achieve. Also see NR vs HDR .
+Taking photos with NR is significantly slower than regular photos.
+Although Open Camera applies auto-alignment to the images, it's still best to try to hold your device steady while the photo is
+being taken. Aside from reducing the risk of ghosting effects, the more steady the camera is, the more effective the noise reduction
+algorithm is in reducing noise and enhancing detail.
+If you have a Google Pixel with Pixel Visual Core, you should get Google's HDR+ photos when using Open Camera's Standard photo mode, so there
+is generally little benefit to using Open Camera's NR mode on these devices. See "Does Open Camera support HDR+" in the
+FAQ for more details.
+If the photo mode "X-Night" is available, this may get better results for low light scenes.
+
+
+In Noise Reduction photo mode, an additional "NR mode" option will appear on the popup mode. This defaults to Normal, but
+you can change to "Low Light" mode, which further improves results in dark scenes. If Open Camera detects poor light in this
+mode, it will take a burst of images for a duration of around 5 seconds. For best results, use a tripod, or try to hold the
+camera as steady as possible.
+
+DRO
+
+Dynamic Range Optimisation (DRO) is a technique that optimises the dynamic range available in the image. In particular, dark
+regions will have their brightness boosted to bring out the detail. This mode is useful for capturing scenes with a wide range
+of brightness (e.g., on a bright sunny day) as well as being useful to automatically optimise photos in low light scenes. Also
+see DRO vs HDR .
+
+HDR
+
+High Dynamic Range Imaging (HDR) is a technique where the camera takes multiple shots at different exposures, and combines them
+into a single image. A typical problem in photography is that a scene may contain a brightness range that is wider than what can be
+captured in a single shot. Varying the exposure (whether by touching on the screen, or exposure compensation or manual exposure) might
+make darker regions brighter, but leave other areas over-exposed. Or reducing the exposure to prevent over-exposure may result in
+the rest of the scene being too dark. HDR uses an algorithm to combine the best parts of each image, and adjusts the colors so that
+the full range of brightness values are captured in the scene:
+
+
+
+
+
+The left set of three images show the individual exposures, the right the final HDR image.
+
+Some things to note about Open Camera's HDR feature:
+
+Taking photos with HDR is significantly slower than regular photos (although note that HDR will give much faster performance if Camera2 API is enabled, see Settings - not all devices support this).
+HDR isn't so good for scenes with movement, due to combining an image from multiple shots. Open Camera does apply "deghosting"
+to reduce the problem, but this isn't perfect.
+Although Open Camera applies auto-alignment to the images, it's still best to try to hold your device steady while the photo is
+being taken.
+HDR can sometimes result in less accurate color reproduction (due to trying to estimate the colors from multiple exposures).
+Some camera apps use "HDR" to mean "apply a whacky-looking filter". Whilst HDR filters can be used to apply a rather
+unreal or vivid image, for now Open Camera's HDR is more geared towards capturing the range of exposures. Also note that many camera
+apps that advertise "HDR" don't even make use of multiple images - this is more a case of filtering, than HDR.
+If you have a Google Pixel with Pixel Visual Core, you should get Google's HDR+ photos when using Open Camera's Standard photo mode, so there
+is generally little benefit to using Open Camera's HDR mode on these devices. See "Does Open Camera support HDR+" in the
+FAQ for more details.
+
+
+DRO vs HDR
+
+Both DRO and HDR share in common that they are ways to handle wide ranges of brightness. They each have their pros and cons:
+
+DRO requires only a single image from the sensor, so shots are fast to take, and fine for scenes with movement, unlike HDR.
+Photos will still take longer to process compared with Standard photo mode, but it will still be faster than HDR.
+HDR will in general be better at scenes with a high range of brightness values. It can take advantage of the information from
+three separate images, rather than reprocessing a single image.
+
+
+NR vs HDR
+
+In scenes with high dynamic range, HDR should do better at capturing the high dynamic range (although NR should still be better
+than Standard photo mode). Though NR still has the advantage that it is less prone to ghosting and misalignment. NR is also better
+suited to working in a wide range of scenes.
+
+In summary: NR is better if you just want a "works best in most cases" option. HDR may be a better choice specifically in
+scenes with high dynamic range, that also don't have movement in the scene.
+
+Panorama
+
+Panorama photo mode allows creating wide photos, by stitching together multiple separate photos.
+
+To take a panorama image, hold your device in portrait orientation, and click to take a photo to start the panorama. Two blue circular dots
+will then appear. Rotate your device about the device's axis, either to the left or right, to move the centred white circle over one of the
+blue dots, which will allow the camera to capture another image. After each new image is captured, keep rotating your device to cover each new
+blue dot that appears in turn. If the device is tilted to one side too much, an icon will appear to indicate you need to rotate the device
+clockwise or anti-clockwise.
+
+Click the tick icon to save the panorama, or the cross icon to cancel. Or the panorama will automatically save after 10 images have been
+captured.
+
+For best results, try to keep your device steady, apart from rotating about the device's axis. Note that panorama photos can take time to process and save.
+
+Note that the following options are not supported with panorama:
+
+Timer.
+Auto-repeat.
+Immersive mode.
+Pause after taking photo.
+
+
+Settings
+
+
+
+
+
+
+
+
+
+Camera Controls:
+
+Face detection - If this is enabled, then the camera will automatically try to detect faces, and use
+them for the focus, metering (exposure) and white balance. Faces will be drawn as yellow squares when detected.
+
+ If this option is enabled, then you will not be able to touch to manually select the focus area, and
+ the white balance option will have no effect (since the face recognition will be used to determine these).
+ If Camera2 API is enabled, then the scene mode option will also have no effect (again since the face recognition
+ option overrides this).
+ If you are using an accessibility service such as Google Talkback, information about the number and
+ location of detected faces will be reported when face detection is enabled.
+
+
+Timer - Set a timer for taking photos or recording video. Press the take photo/video button again to
+cancel the timer.
+
+
+
+Repeat mode interval - Specify the delay (if any) between photos in repeat mode. Note that if a delay
+is selected, note that this does not include the time taken to auto-focus and take each photo. Similarly, "No
+delay" still means there will be some time between each photo. If you want to take a burst of photos, use the
+Fast Burst photo mode, or long press the "take photo" button in Standard or Fast Burst photo modes.
+(Burst mode only supported on some devices; requires Camera2 API to be enabled).
+
+More camera controls... - Select to access the following controls:
+
+Touch to capture - This option allows you to take a photo, or start and stop video recording,
+ just by either touching or double-tapping on the preview screen. Note that "Single touch" means
+ you will no longer be able to touch to select a focus or metering area; while "Double tap" means
+ you will not be able to reset focus/metering areas by double tapping.
+
+
+Pause after taking photo - If ticked, after taking a photo the display will pause, with options to share
+ or delete
+ the
+photo. To keep the photo and continue, touch the screen, press back, or take another photo. Note that this isn't supported
+when holding the shutter button to take a continuous burst of photos.
+
+Shutter sound - Whether to play a sound after taking a photo. (Not supported on all devices.)
+ For Camera2 API, this also controls whether to play a sound for start/stop video
+recording.
+
+Timer beep - Whether to beep when the timer is counting down, or for the repeat mode delay (see below).
+
+Voice timer countdown - Whether to give a voice countdown when the timer is counting down, or for the repeat mode
+delay (see below).
+
+Volume keys - You can set what happens when your device's volume keys are pressed:
+Take photo or start/stop video (depending on photo/video mode). On Android 7+, volume down will instead pause/resume video when recording video.
+Trigger an autofocus - or if in manual mode, change the focus distance in/out. In this mode, holding down both volume keys
+will take a photo (or start/stop video). This makes your volume keys behave more like a physical camera button - hold down one
+key to focus, then both to take a photo.
+Zoom in/out.
+Change the exposure compensation (or if in manual ISO mode and using Camera2 API, change the ISO).
+Switch auto-level on/off.
+Change the device's volume as normal.
+Do nothing.
+
+
+Audio control options - If enabled, this allows taking a photo (or starting video recording, depending on the mode)
+by making a noise. An on-screen microphone button will appear, to
+start/stop listening. The "loud noise" option will listen for any noise (so you can remotely take a photo by saying "cheese",
+whistling, or whatever you prefer). Note that leaving the listening turned on may use additional battery.
+Note that this can't be used to stop video recording - if you want to have some remote control on video recording,
+see the "Max duration of video" option.
+
+Audio control sensitivity - This controls how sensitive Open Camera is to noises, if "Audio control" is set to "Loud noise".
+If you find it's taking photos too often unintentionally, or isn't responding to your sounds, try adjusting this option.
+
+Bluetooth LE remote control - Open Camera supports connecting to some specific "smart housing" cases via the
+ options in these settings. See "Remote device type" for supported types. At the time of writing, only one make/model
+ is supported. Once connected via Bluetooth, it should be possible to control Open Camera from the device.
+ The on-screen display of Open Camera will also display information from the housing (temperature and depth).
+
+Lock photo/video orientation - Normally the orientation of the photo/video will be rotated by some multiple of
+90 degree such that the orientation looks right - e.g. if your device is held in portrait, the resultant image/video will
+be in portrait. This option allows fixing the camera to either be in portrait or landscape. Note that if
+auto-level is also enabled, it will have the effect of aligning photos to the nearest
+90 degrees.
+
+Save location - Select the folder to store the resultant photos or videos in.
+
+ On Android 9 or earlier: This opens a file dialog. Click on a folder (or "Parent Folder") to navigate through
+ the filesystem. Select "New Folder" to create a new folder in the currently displayed folder. Select "Use Folder"
+ to choose the currently displayed folder. Note that on Android, there are some folders that cannot be written
+ to - Open Camera will display a message if you try to use one of these folders.
+ On Android 10 or later: This opens a dialog to type the name of the folder. This
+ will be a subfolder of DCIM on your internal storage. You can specify subfolders with the "/"
+ character. For example, specifying Camera/holiday will save inside DCIM/Camera/holiday/
+ on your internal storage.
+ If "Storage Access Framework" is enabled : Then on any Android version, this option
+ will show up the Android standard file chooser - navigate to the desired folder, and click "SELECT" or
+ "ALLOW ACCESS" (wording varies depending on Android version).
+
+Once you have specified a new save location, you can long press on the Gallery icon to quickly switch between recent save
+ locations. If you want to save to an SD card, see "How can I save to my external SD card?" under the FAQ .
+
+Storage Access Framework - If selected, Open Camera will instead use the Android
+Storage Access Framework . This
+has some advantages, such as using the standard Android file picker, and being the only way to save to SD cards on Android 5+.
+In some cases it may allow you to save to cloud or local storage providers provided by other apps or services.
+Furthermore on Android 10+, it is the only way to save outside of the DCIM/ folder.
+
+
+
+Save photo prefix - This option allows you to customise save filenames for photos.
+
+Save video prefix - This option allows you to customise save filenames for videos.
+
+Time format for filename - By default, Open Camera uses the local timezone for the save filenames, but you
+can also select UTC (Coordinated Universal Time / Zulu Time) .
+For the latter option, a "Z" will be appended to the filename (e.g., "IMG_20160524_155116Z.jpg"). The Z (Zulu) suffix
+is a standard convention for identifying UTC timestamps.
+
+Use milliseconds in filename - If enabled, the datestamp used for filenames will include
+ milliseconds (e.g. "IMG_20250312_210531.350.jpg")
+
+Allow vibration feedback - Whether to allow haptic vibration feedback, for example when
+ adjusting some controls. Note this also requires touch interactions to be enabled in your
+ device's settings, this is usually under the "Sound and vibration" settings or similar.
+
+Show camera when locked - If you have a lock screen on your device (e.g., PIN to unlock), if this option
+ is enabled Open Camera will show above the lock screen - i.e., if locked, you won't have to enter the PIN to
+ use Open Camera. The device still needs to be unlocked in order to go to the Settings or Gallery. If you would
+ prefer Open Camera to always be unavailable when your device is locked, this option should be disabled.
+
+Perform auto-focus on startup - Whether Open Camera should auto-focus when starting the camera. Some devices
+have a bug where the flash turns on when this happens, so a workaround is to disable this option.
+
+Allow long press actions - Some icons support a "long press" action (touching and holding on the icon). For example,
+long press on the gallery icon to change the save location, or in some cases long press on the shutter icon will enable a
+burst. If you prefer, you can disable these long press actions.
+
+Calibrate level angle - The options Auto-level , "Show angle" and "Show angle line"
+rely on your device's ability to detect which orientation it's being held (the accelerometer). On some device's this might not
+be calibated correctly. If so, you can use this option to calibrate the acceleromer (or reset the calibration back to the
+default behaviour).
+
+Camera preview... - Select to access the following controls:
+
+Preview size - By default, Open Camera matches the aspect ratio of the preview (the image that is
+ displayed on the phone/tablet's display) with that of the photo resolution ("Match photo size (WYSIWYG)" mode). The
+ advantage is that what you see in the preview will match what will be in the resultant photo
+ ("What-You-See-Is-What-You-Get") - though this may mean you will have "black bars" on the display in order to do
+ this. If instead you select "Maximise preview size", then the camera preview will be as large as possible, trying to
+ fill the available space. However if the resolution of the photo is a different aspect ratio to that of your device,
+ this will result in the preview being cropped. In video mode, the preview is always in WYSIWYG mode.
+
+ Note that Android 4.4.3 introduced a bug
+ which means that the aspect ratio will be wrong if you select "Maximise preview size" (i.e., the preview will look
+ squished). To avoid this problem, stick with "Match photo size".
+ Also note that even in "Match photo size" mode, on some devices and resolutions, it may not be possible to match
+ the preview to the resultant photo/video exactly; in such cases, Open Camera will try to match as closely as
+ possible.
+
+
+Ghost image - You can overlay a previous photo. This is useful for aligning photos (e.g., for stop
+ motion animation). This can also be used to create a custom "grid", by selecting an image with transparency.
+
+ Last photo taken - When you take a photo, a ghost of that photo will be displayed. The ghost is
+ reset either by leaving and returning to Open Camera, switching between photo/video modes, or turning
+ this option off.
+ Selected image - Select a specific image on your device to be displayed as a ghost
+ Note that for this option, the ghost image will not be displayed if Open Camera is running
+ when your device is locked. If the image doesn't show at all, it may be that Open Camera was unable to
+ load the selected image.
+
+
+Ghost image opacity - If a "Ghost image" is selected, this option controls how transparent/opaque the ghost
+image should be drawn with.
+
+Focus assist - If enabled, this will show a zoomed in view on the camera preview when in manual focus mode, and you
+ are changing the manual focus distance. Similarly in focus bracketing mode, the preview will zoom in when changing the focus
+ distances to bracket between. Note not supported when recording video.
+
+Show zoom - Whether to display the current zoom level of the camera (when zoomed in).
+
+Show angle - Whether to display the orientation angle of the device's camera.
+
+Show angle line - Whether to display a horizontal "level" line that visually indicates the orientation of the device.
+
+Show pitch lines - Whether to display horizontal pitch lines that visually indicate the pitch of the device.
+
+Angle highlight color - This controls the color to be used for the angle display and "level" line when the camera is nearly
+ level; and also for the 0 degree pitch line when the camera is held at nearly zero pitch.
+
+Show compass direction - Whether to display the compass direction of the device's camera.
+
+Show compass direction lines - Whether to display vertical lines that visually indicate the compass direction of the device.
+
+Show battery - Whether to display the current battery level.
+
+Show time - Whether to display the current time.
+
+Show camera ID - For devices with multiple front/back cameras. Whether to display the currently used camera ID.
+ This will be a value starting from 0, that distinguishes between the different cameras on your device.
+
+Show free memory - Whether to display the remaining storage space of the device.
+
+Show ISO - If selected, the current ISO, exposure time and frame rate (FPS) will be displayed (only available if Camera2
+ API is used). The text will be shown in red when the auto-exposure routine is scanning.
+
+Show a histogram - Allows displaying an on-screen histogram (only available if Camera2 API is used). Note that the
+ histogram reflects the currently display on-screen preview, and will not necessarily be accurate for the final resultant photograph,
+ especially if modes such as NR, DRO, HDR are used. Also note that displaying a histogram may use more battery.
+ The follow options are available:
+
+ RGB Colors - Displays a histogram for each color channel.
+ Luminance - The brightness is computed as a weighted average of the RGB components: 0.299*R + 0.587G + 0.114*B.
+ Value - The brightness is computed as the maximum of the RGB components.
+ Intensity - The brightness is computed as an unweighted average of the RGB components.
+ Lightness - The brightness is computed as an average of the minimum and maximum of the RGB components.
+
+
+Show zebra stripes - Allows displaying on-screen zebra stripes (also known as
+ zebra patterning ) to show whether the
+ image is over-exposed (only available if Camera2 API is used). Note when using photo modes NR and HDR,
+ a region that is shown as over-exposed on-screen may not necessarily be over-exposed in the resultant image,
+ since these modes are able to better handle a high dynamic range. Also note that enabling zebra stripes
+ may use more battery.
+
+Zebra stripes foreground/background colour - If "Show zebra stripes" is enabled, these options allow you to
+choose the colours of the stripes.
+
+Focus peaking - Allows displaying on-screen highlights to indicate edges (contours) that are in-focus
+ (only available if Camera2 API is used). This is particularly useful in conjunction with manual focus mode, to help show
+ which regions of the image are in focus. Note that enabling focus peaking may use more battery.
+
+Focus peaking color - This controls the color to be used for the highlights when "Focus peaking" is enabled.
+
+Show audio level meter - If selected, when recording video an on-screen meter will display the current audio level.
+
+Show a grid - Whether to display one of a choice of grids on the camera preview. Grids are useful in photography to help
+ compose your image. Options are:
+
+ 3x3 - helps with applying the rule of thirds .
+ Phi 3x3 - 3x3 grid with ratios 1:0.618:1.
+ 4x2
+ Crosshair
+ Golden - displays a Golden spiral (or technically,
+ a Fibonacci spiral). You can use this to improve your photography.
+ Golden Triangles
+ Diagonals
+
+
+Show a crop guide - A crop guide is a rectangle displayed on-screen, which has the specified aspect ratio
+ (if different to the photo/video aspect ratio). This is useful if you plan to crop the resultant photos or videos
+ to a particular aspect ratio. For photos, the crop-guide requires "Preview size" to be set to WYSIWYG mode.
+
+Show thumbnail animation - Whether to display the moving thumbnail animation when taking a photo.
+
+Show border when taking photo - Whether to display a border effect when taking a photo.
+
+Rotate preview - this option rotates the on-screen preview by 180 degrees (i.e., upside down). Most users
+ won't ever need this, but this option can be useful if you are using Open Camera with equipment such as zoom lenses
+ which invert the image. Note that this doesn't rotate the resultant photos/videos - you'll still have to rotate those
+ yourself afterwards - but this will correct the preview so that you can still see what you are shooting properly.
+
+On screen GUI... - Select to access the following controls:
+
+UI placement - Allows you to choose between various layouts for the on-screen user interface icons.
+
+Immersive mode - Allows you to choose between various modes which affect the behaviour of the user-interface, in order to
+make it more immersive:
+
+Off (default) - Don't use immersive mode, and on-screen virtual navigation buttons are always visible.
+Hide on-screen virtual navigation buttons - After a short delay, any on-screen virtual navigation buttons will disappear.
+ Hide GUI - After a short delay, any on-screen virtual navigation buttons will disappear, along with most of the GUI buttons.
+To exit this immersive mode, either touch on-screen, or swipe in from the top/bottom sides when the device is held in landscape (or
+left/right sides if held in portrait).
+Hide everything - Same as "Hide GUI", but when in immersive mode everything will disappear except the camera preview. This
+mode probably isn't useful for most people (since you need to exit immersive mode even to take a photo), but is available if you wish
+to only have the preview showing.
+
+
+Show face detection icon - Whether to display an on-screen icon for enabling or disabling face detection. See
+ Auto-level feature above for more details.
+
+Show flash icon - Whether to display an on-screen icon for cycling through flash modes. If this is enabled, then
+flash modes won't show on the popup menu. Also note that it is not possible to enable the torch with this method.
+
+Show focus peaking icon - Whether to display an on-screen icon for enabling or disabling focus peaking. See
+ Settings/Camera preview/"Focus peaking" above for more details.
+
+Show auto-level icon - Whether to display an on-screen icon for enabling or disabling auto-level. See
+ Settings/"Face detection" above for more details.
+
+Show stamp photo icon - Whether to display an on-screen icon for enabling or disabling photo stamp. See
+ Settings/Photo settings/"Stamp photos" for more details.
+
+Show custom text stamp photo icon - Whether to display an on-screen icon for setting a custom text to stamp onto
+ resultant photos. See
+ Settings/Photo settings/"Custom text" for more details.
+
+Show store location data icon - Whether to display an on-screen icon for enabling or disabling storing location
+ data (geotagging). See Settings/Location settings/"Store location data (geotagging)"
+ for more details.
+
+Show RAW icon - Whether to display an on-screen icon for cycling through RAW modes (Standard, Standard+RAW, RAW only).
+ See Settings/Photo settings/"RAW" for more details.
+
+Show auto white balance lock icon - Whether to display an on-screen icon for locking or unlocking auto white
+ balance.
+
+Show auto exposure lock icon - Whether to display an on-screen icon for locking or unlocking auto exposure.
+
+Show zoom slider controls - Whether to display to a slider to control zoom.
+
+Show "Take Photo" icon - Whether to display the shutter button
+for taking a photo (or recording video). Uncheck if you'd
+rather take photos by other methods (e.g., if your device has a hardware shutter button, or using the volume keys).
+
+Show on-screen messages - Open Camera will sometimes display temporary on-screen text with information (in some
+ cases using "toasts"). This option can be used to disable them.
+
+Show What's New dialog - When updating to a major new version, Open Camera displays a dialog explaining the new
+features and other options. You can disable this dialog from being displayed if you prefer.
+
+Multiple cameras icon - This option only shows on devices that allow explicitly switching between more than
+one front and/or back cameras. When
+enabled (the default), such devices will have two on-screen icons: one to switch between front/back cameras, and another
+to switch between the multiple front or back cameras (and if supported, selecting a physical lens). If this option is disabled, only a single icon will show, which
+will cycle between all cameras (selecting a specific lens is not possible with this method).
+
+Keep display on - Whether to force keeping the screen display on, while the main Open Camera UI is active.
+
+Force maximum brightness - Whether to force the screen display to maximum brightness.
+
+Photo and Video Settings:
+
+Photo settings - Select to access the following controls:
+
+Camera resolution - Select the resolution of photo images taken by the camera.
+
+ If auto-level is enabled, images will in general come out as a
+ slightly lower resolution (due to the rotation and cropping that's required).
+
+ Note that in some photo modes, the actual photo mode may be different to that selected here.
+ For example, advanced photo modes such as HDR, NR, Fast Burst, Expo Bracketing may have a
+ maximum supported resolution - if the resolution selected here is too high, the largest
+ allowed resolution will instead be used. To see the resolution currently used (or to change
+ it), see the "Camera resolution" setting on the popup menu.
+
+ The resolution setting is ignored altogether in Panorama mode.
+
+
+
+Optimise focus for... - How to optimise the behaviour when using continuous focus mode.
+ Latency means take the photo as soon as possible - on modern devices, the result will typically
+ already be in focus. If this isn't the case, select "Quality" to ensure that the scene is focused.
+ (Requires Android 12+ and Camera2 API; on other devices this will default to "Quality".)
+
+Save preview shots - When enabled, a short video will be saved alongside each photo,
+ containing shots from the moment before the photo was taken. Not supported for expo bracketing,
+ focus bracketing or panorama modes. Typically the video will contain 12 shots, over approximately
+ just over a second. Note that this is not intended to be a 30fps (or better) video, rather the intent
+ is to save a burst of photos. You can use various gallery apps to export individual frames (usually by
+ selecting "Edit" when viewing the video).
+ (Requires Android 8+ and Camera2 API.)
+
+
+Image quality - The image quality of saved JPEG (including Ultra HDR) or WebP images. Higher means better quality, but the image files will take up
+more storage space. Note that 100% does not necessarily mean there is no lossy compression, rather that there is minimum
+compression. Also note this option has no effect if "Image format" is set to PNG.
+
+Image format - The image file format for saving photos:
+
+ JPEG - This is the fastest option. Also note that only JPEG and Ultra HDR JPEG support saving various photo
+ metadata (Exif), including camera details, location, and artist/copyright tags.
+ Ultra HDR JPEG - This is a format that saves additional high dynamic range data, for viewing on
+ HDR displays, but is backwards compatible with JPEG (so resultant images can still be viewed by older
+ JPEG viewers, and on standard non-HDR displays). (Requires Android 14+ and Camera2 API, and even then,
+ only available on devices that support Ultra HDR for third party applications.) This is not supported for
+ NR, HDR, Pano or "X-" extension photo modes - in these modes, images will be saved as regular JPEG.
+ WebP - This is an alternative (lossy) file format to JPEG. It offers smaller file sizes,
+ but saving takes longer, and some applications may not support WebP images. Note that WebP
+ images are generated by converting from a JPEG with 100% quality (since the Android camera API does not
+ have native WebP output).
+ PNG - This is a lossless format, but note that file sizes will be large, and saving will
+ take significantly longer. Note that this doesn't mean there will be absolute no loss in quality:
+ cameras on Android offer images in JPEG or RAW (DNG) file formats. When using PNG, Open Camera
+ receives the JPEG with the quality set to 100%, and converts to PNG - this means that there is minimal
+ lossy compression. The only way to have truly lossless is via RAW (see option below).
+
+
+RAW - Only available if Camera2 API is used. If set to "Standard and DNG (RAW)", then photos
+will also be saved in RAW (DNG) format. If set to "DNG (RAW) only" (requires Android 7), then photos
+will only be saved in DNG format. DNG stands for
+"digital negative" , and contains the full
+uncompressed and unprocessed information from your camera. Please note the following points:
+
+ Some gallery applications don't recognise DNG files - if this is the case, you won't even see them listed.
+ Either install a gallery application that does, or use specialised RAW viewer or editing tools.
+ Or transfer to a PC to use DNG editing applications there. Note that newer versions of Android
+ (7 onwards) seem to have better support for DNG images.
+ If your gallery application doesn't show DNG files, a file explorer application may also be useful,
+ to delete DNGs if you don't want them (DNGs take up a lot of space - and won't be deleted when you delete
+ the corresponding JPEG from a Gallery application).
+ Note that various processing options such as DRO photo mode, "Stamp photos" and "Auto-level" will only apply
+ to the JPEG images, and not the DNG images. This is not a bug - the DNGs represent the RAW output
+ of the camera device. This also means that these options have no relevance for photos in "DNG (RAW) only" mode, therefore such
+ options will not show on the popup menu.
+ Options to store extra Exif tags ("Store compass direction", "Artist", "Copyright") are only supported for
+ JPEG formats.
+ Some devices only support saving RAW images in the Standard or DRO photo modes. Higher end devices will also support saving RAW
+ images also in Expo Bracketing and Focus Bracketing modes, as well as HDR where "Save all images for HDR mode" is enabled.
+ The X- modes do not support saving RAW images.
+ The on-screen "RAW" icon will show if RAW is enabled for the current photo mode.
+
+
+Allow RAW for expo bracketing - If this option is disabled, the RAW images won't ever be saved in expo bracketing or HDR
+ photo modes.
+
+Allow RAW for focus bracketing - If this option is disabled, the RAW images won't ever be saved in focus bracketing photo
+ mode.
+
+Noise Reduction original images - When using Noise Reduction mode, if
+ this option is enabled, then the input images from the burst of images taken will be saved, as well as the
+ final NR photo. You can choose to save either a single image from the burst, or all images. This may be useful
+ if you want to use external image stacking applications to merge the images. Note that these images will
+ typically be noisier than a typical photo from Standard mode, because the device's noise reduction algorithms
+ are disabled (this seems counter-intuitive, but in order to improve quality by merging multiple images, noise
+ reduction algorithms need to be applied after the merging, not before).
+
+
+Save all images for HDR mode - When using HDR mode, if this option is enabled,
+then the three base exposure images will be saved as well as the final HDR photo. This is useful if you want
+to use external HDR applications (such as my own
+Vibrance HDR for Android,
+or various HDR applications for PC) to
+create the final HDR image (although if you don't want Open Camera's HDR mode at all, you can instead use the
+Exposure Bracketing Photo Mode). Note this will make saving slower, especially if options like "Stamp photos" or
+Auto-level are also used.
+
+HDR tonemapping - When using HDR mode, the high dynamic range image needs to be converted
+back to a regular image, using a process called tonemapping. This option allows you to choose some different
+tonemapping algorithms.
+
+HDR contrast enhancement - When using HDR mode, in some (bright) scenes a local contrast
+enhancement algorithm is applied to improve the look of the image. It also gives such images a look that is stereotypically
+associated with "HDR". If you prefer not to apply this at all, you can change this option from "Smart" to "Off". Or you can
+choose "Always" to have it applied in all cases.
+
+Exposure Bracketing - Specifies the total number of images to save in Exposure Bracketing Photo Mode
+(Camera2 API only).
+
+Exposure Bracketing Stops - Specifies the number of stops to subtract/add from the "base"
+exposure level to the darkest/brightest image, in Exposure Bracketing Photo Mode. An increase of 1
+stop means a doubling of the amount of light. So a setting of "2", with 3 images, will produce
+images with exposure set to [-2, 0, +2]. For 5 images, this will produce [-2, -1, 0, +1, +2].
+
+Panorama auto-crop - Normally panoramas are cropped to give a rectangular result.
+Disabling this option will show the full extent of the panorama, but will leave wavy black borders.
+
+Panorama original images - Whether to save the original shots in Panorama mode. This can be useful
+if you want to use a third party application to stitch the images together to create a panorama. Note that these original
+images are always saved in PNG format. This option can also be useful for debugging purposes, to send me example images when
+reporting problems with panorama - for this, it's best to select to include the debug XML file. Note the XML files will be
+saved inside Android/data/net.sourceforge.opencamera/files/ , and you'll typically need
+a third party File Explorer application to see and delete XML files.
+
+Front camera mirror - Normally for front cameras, the preview will behave like a mirror, but resultant
+photos will still be as the camera (or other people) view the scene. This option can be used to mirror the
+resultant photo, so the resultant photo matches the mirrored image you see on the screen.
+
+Remove device EXIF data - Whether to remove device EXIF metadata from JPEG photos. Note that
+ this will not remove exif tags applied by other Open Camera settings that apply EXIF metadata (e.g.
+ location/geotagging, artist, copyright etc). Those other options are independent and will override this
+ setting. Also note that RAW/DNG and videos are not affected.
+
+Artist - If text is entered in this setting, then the text will be stored in the image's Exif metadata as the
+ "Artist" tag. Only supported for JPEG formats. Not supported for RAW photos (DNG format).
+
+Copyright - If text is entered in this setting, then the text will be stored in the image's Exif metadata as the
+ "Copyright" tag. Only supported for JPEG formats. Not supported for RAW photos (DNG format).
+
+Stamp photos - Option to add a date and timestamp to the resultant photos. If "Store
+location data" is enabled (see "Location settings" below), then the current location latitude
+and longitude coordinates, and altitude, will also be stamped on the resultant photos (if the location is known).
+Similarly for "Store compass direction". Note that if this option is enabled, then it will take
+longer to save the photo. Also see "Video subtitles".
+
+Datestamp format, Timestamp format - If "Stamp photos" is enabled, these options allow
+extra control over the date and time formatting. Also used for Video settings/"Video subtitles".
+
+GPS stamp format - If "Stamp photos" is enabled, this allows extra control over the GPS
+formatting. Also used for Video settings/"Video subtitles".
+
+
+
+Distance unit - If "Stamp photos" is enabled, this controls whether to use metres (m) or
+feet (ft) when recording the GPS altitude. Also used for Video settings/"Video subtitles".
+
+Custom text - Here you can enter some text to be stamped onto resultant photos (e.g.,
+this could be used for a copyright image). Note that if this option is enabled, then it will take
+longer to save the photo. Also note that this option is only supported for photos, not video.
+
+Font size - Sets the font size used for text for options "Stamp photos" or
+"Custom text".
+
+Font color - Sets the font color used for text for options "Stamp photos" or
+"Custom text".
+
+Text style - Whether to render the text on the image with a shadow background effect, for
+options "Stamp photos" or "Custom text".
+
+Use alternative flash method - (Camera2 API only.) Unfortunately many devices have poor support for the
+Camera2 API. A common issue is poor flash behaviour (either flash doesn't fire, or photos are over or under exposed).
+If so, enabling this option may help - this uses an alternative algorithm for flash (using the torch to simulate flash
+as a workaround). Note that this is enabled by default for Samsung devices.
+
+Enable dummy capture HDR/expo fix - (Camera2 API only.) Enable this option if your device has problems taking photos
+in HDR or Exposure Bracketing photo modes, specifically if some expo images come out with the same exposures. This option
+takes an additional "dummy" image which may resolve such problems. Note that "Enable fast HDR/expo burst" (below) must
+be enabled for this option to have an effect.
+
+Enable fast HDR/expo burst - (Camera2 API only.) Disable this option if your device has problems taking photos
+in HDR or Exposure Bracketing photo modes (disabling this option will result in a longer delay between the photos being
+taken, but may give more stable behaviour if your device is having problems with this).
+
+Allow photos whilst recording video - (Camera2 API only.) Some devices support taking photos whilst recording video,
+but there's the problem that enabling such functionality make cause problems with regular video recording. If you have problems
+recording video with Camera2 API enabled, try disabling this option.
+
+
+
+Video settings - Select to access the following controls:
+
+Video resolution - Select the resolution of videos taken by the camera.
+
+Enable digital video stabilization - Video stabilization reduces the shaking due to the motion of the camera in
+both the preview and in recorded videos. This enables a digital method in the camera driver, and may be unnecessary if
+your device supports optical image stabilization (OIS).
+
+Video format - Allows choice of various video file formats and codecs. Please test before using, as some may
+not work properly on all devices! Also note:
+ WebM does not support recording audio (at the time of writing, it seems encoding in Vorbis audio format is
+ not supported on
+ Android).
+ WebM does not support storing location data ("Store location data" option).
+ If using 3GPP, then restarting video when hitting maximum filesize will not be seamless (even on Android 8).
+
+
+Video picture profiles - Enables different color profiles for recording video. Only available if Camera2
+ API is used, and only supported on some devices. Additional notes:
+ The various "log" profiles enable a "flat" color profile, and are intended to create videos
+ for further editing in post production.
+ Note that it may be necessary to increase the video bitrate when shooting with a "log" profile
+ or with high custom gamma.
+ When "Gamma" is selected, you can specify the gamma value used with the next option "Video gamma value".
+ This feature is somewhat experimental, please test if it fits your need before shooting your masterpiece!
+
+
+Max duration of video - This option can be used to set a maximum duration of the video. If set, video recording
+will stop after the specified time (unless already stopped earlier).
+
+Restart video after max duration - If a max duration has been set (see above), this option can be used to make
+the video automatically stop and restart the specified number of times. So this can be used to take a video for a long
+period, broken up into multiple video files. If a max duration has not been set, then this option has no effect.
+
+Maximum file size of video - This allows to set a maximum file size for videos. Note that many Android devices
+set a maximum file size (typically around 2GB or 4GB), and there is no way Open Camera can work around such a limitation (and
+using exFAT doesn't get round it). This option allows you to set a smaller maximum file size - note that it can only
+be used to reduce the device's maximum file size (so if a device has a 2GB limit, you can't increase it by setting this option to
+a larger value). Note that the value is approximate - typically
+the resultant videos may be slightly smaller. Note that if you using this option together
+with "Max duration of video", then - if "Restart on maximum file size" is enabled - hitting the maximum file size will cause a
+restart that doesn't reset the max duration timer, nor does it count as one of the number of restarts. E.g., if you requested a
+maximum duration of 30m, with 1 restart, but the video hits the maximum file size after 20m, rather than getting two times 30m
+videos, you'd get four videos, of lengths 20m, 10m, 20m, 10m (i.e., the 30m videos are split at the maximum file sizes). If
+"Restart on maximum file size" is disabled, then hitting the maximum file size will always cause the video to end without
+restarting, even if you've set "Restart video after max duration".
+
+Restart on maximum file size - Whether to automatically restart if the maximum file size is met. As noted above, almost all
+Android devices have a maximum file size for videos, even if you don't explicitly set one. So it's advisable to keep this option to true,
+so that Open Camera will restart as soon as possible if you're recording video, and hit this limit. Note that on devices that
+are not running Android 8 or later, there will still be a loss of a few seconds while the video stops and restarts. On Android 8
+or later, the resume should be seamless (unless using 3GPP video file format).
+
+Record audio - Whether to record audio when recording a video.
+
+Audio source - Select the audio source for recording video. The effect of this depends on your device -
+if it supports an external microphone, you may be able to use this by selecting "External mic". The other options may
+provide different settings affecting the resultant audio (e.g., automatic gain control), though this behaviour is
+device specific. These options are just controls for the Android MediaRecorder API, and so the exact behaviour is
+up to the device.
+
+Audio channels - If recording audio with video, this option allows you to specify mono or stereo recording.
+Note that most devices do not support stereo recording. Even for devices that do support this, you may need to modify
+the "Audio source" option to another value for this to work.
+
+Lock screen when recording video - If enabled, the GUI will be locked when recording video (i.e., the GUI
+won't respond to touch presses). You can use this to prevent accidental presses that might change settings or stop
+recording. To unlock the GUI, swipe the screen (in any direction). Note that this won't prevent the video being
+stopped if you press your device's Home, Recent Apps or Power button (it is not possible for apps to override the
+behaviour of these buttons).
+
+Video subtitles - This option is analogous to the "Stamp photos" option, but rather than embedding text into
+the video itself, it stores the text in a separate subtitles
+(".SRT" ) file. Most decent video players should support
+SRT files, and use them to display the information as subtitles. The subtitles will record the date and time. If "Store location data" is enabled (see
+"Location settings" below), then the current location latitude and longitude coordinates will also be recorded (if the location is
+known). Similarly for "Store compass direction". Note that you can control the formatting style for date, time and location using
+ the options under the "Photo settings" menu (Datestamp format, Timestamp format, GPS stamp format, Distance unit).
+
+ Note that on Android 10, using this option means the ".SRT" files will show in most gallery apps as separate unplayable video
+ files. A workaround is to enable Settings/More camera controls/"Storage Access Framework".
+ On Android 11+, this option is only available if Settings/More camera controls/"Storage Access Framework" is
+ enabled. This is due to changes in Android 11 which affect how applications are able to save files.
+
+
+Video bitrate (approx) - If set to a value other than "default", the default video bitrate is overridden. Higher values mean better
+ quality video, but the files take up more disk space. Note that some values may be unsupported by your device, and may
+ cause the recording to fail - in some cases, this can cause problems with the camera that require a reboot to fix. So
+ please test before using. Also note that the bitrate setting is approximate - the resultant video file will typically be slightly different
+ to that requested.
+
+Video frame rate (approx) - If set to a value other than "default", the camera will try to match this frame
+ rate.
+
+ This is very approximate, as frame rate depends on many factors such as your device and lighting
+ conditions, and this is only a "recommendation" to the camera driver, so there is no guarantee that the
+ resultant video's frame rate will match with the requested value.
+ Some frame rate values may be unsupported by your device, and cause the recording to fail, so please test before
+ using.
+ Even if video recording is successful, if achieving a specific FPS is desired, please check the resultant video's
+ frame rate rather than assuming that it was achieved.
+ For best results (especially for 120fps or higher), please set Settings/"Camera API" to "Camera2 API".
+ This setting is ignored in slow motion mode, where instead Open Camera will choose an appropriate high speed frame
+ rate.
+
+
+Force 4K UHD video (may not work on all devices) - Enable recording in 4K UHD (3840x2160) on the back camera (if ticked,
+this overrides the setting in "Video resolution"). This is provided for some phones that don't properly expose their 4K video resolution to 3rd party camera apps
+(and so 4K resolution doesn't show in the Video resolution option above).
+It turns out that some such devices can be made to record in 4K resolution if it's requested, but on other devices this won't work.
+If you enable this on a device that doesn't support it, you may either get an error
+message when you try to record, or it may succeed but create a video where the resolution isn't 4K, or may even result in a crash! So
+please test this out first. I've tested this successfully on a Samsung Galaxy S5 and Note 3, but even there it only works on some
+variants of those devices. If this doesn't work, it isn't a bug in Open Camera, it's because your device doesn't support 4K recording
+for third party camera apps. (If this option doesn't show up at all, it's either because your device already lists 4K in the Video
+resolutions options above, or because Open Camera thinks this is a device that probably doesn't support 4K video.)
+
+Critical battery check - If a device runs out of power while recording video, in theory the video should safely stop in time.
+However in some cases this doesn't happen in time (if the video file is large, post-processing may still be occurring when the device
+switches off), causing the entire video file to be corrupted! To reduce this risk, Open Camera will stop video recording when the
+battery level is low (3%), but before the device is about to shut off. If for some reason you don't want this behaviour, you can
+switch this option off.
+
+Flash while recording video - If enabled, the camera flash will flash every second while recording video. This
+isn't something most people will need, but it can be useful if the phone is being operated remotely, as a signal that the
+video is still recording.
+
+Location settings - Select to access the following controls:
+
+Store location data (Geotagging) - If selected, then photos will be tagged with the current
+location. Location data will also be stored in videos (though only for devices that record in MPEG4 or
+3GPP formats).
+
+Store compass direction - If selected, then photos will be tagged with the compass direction.
+Only supported for JPEG formats. Not supported for RAW photos (DNG format) or videos.
+
+Store yaw, pitch and roll - If selected, then photos will be tagged with the device's yaw, pitch and roll.
+ Note that Exif data does not have direct support for this, instead it will be written as a string in the Exif data's
+ User Comment for the image. Only supported for JPEG formats. Not supported for RAW photos (DNG format) or videos.
+
+Require location data - If "Store location data" is enabled, then also enabling this option means that
+photos and videos can only be taken if location data is present (this can be useful if you need pictures/videos to
+have location data in them).
+
+Processing settings - Select to access the following controls:
+
+Anti-banding - Some kinds of lighting, such as some fluorescent lights, flicker at the rate of the power supply
+frequency (60Hz or 50Hz, depending on country). While this is typically not noticeable to a person, it can be visible to
+a camera. If a camera sets its exposure time to the wrong value, the flicker may become visible in the viewfinder as flicker
+or in a final captured image, as a set of variable-brightness bands across the image. Therefore, the auto-exposure routines
+of the camera include antibanding routines that ensure that the chosen exposure value will not cause such banding. The choice
+of exposure time depends on the rate of flicker, which the camera can detect automatically, or the expected rate can be
+specified by using this option.
+
+Edge mode algorithm - Only available if Camera2 API is used. Allows control over the algorithm used by the camera
+ driver for applying edge enhancement. Edge enhancement improves sharpness and details in the captured image, though on
+ some devices you may prefer to turn it off if it introduces undesirable effects. Note that this setting is ignored in
+ Noise Reduction (NR) photo mode.
+
+Noise reduction mode algorithm - Only available if Camera2 API is used. Allows control over the algorithm used by the camera
+ driver for applying noise reduction. On some devices you may prefer to turn it off if it introduces undesirable effects.
+ Note that this setting is not related to Open Camera's Noise Reduction (NR) photo mode (and in fact this setting is ignored
+ in NR photo mode).
+
+Misc:
+
+Online help - Load this web page.
+
+Camera API - If set to "Camera2 API", this enables support for the Camera2 API that was introduced
+in Android 5. Changing this setting will cause Open Camera to restart. Camera2 API enables more advanced features
+(including manual ISO/exposure, manual focus, HDR, exposure bracketing).
+Note that not all Android 5+ devices have full support for the Camera2 API (Open Camera will only show this
+option if at least one camera reports either "LIMITED" or "FULL" support for the API; "LEGACY" only devices are not supported).
+Also note that even if devices support Camera2 API, some
+devices have poor support.
+These are not necessarily bugs in Open Camera, but problems with manufacturer support for Camera2 API. If you have
+problems with flash behaviour, try the "Use alternative flash method" setting under "Photo Settings".
+Please see here for more details on device compatibility.
+
+About - Provides various debug information about the app and your device's camera. You can also copy
+this information to the clipboard.
+
+Privacy policy - Open Camera's privacy policy.
+
+Open Source licences - Licences for files used in Open Camera.
+
+Settings manager - Select to access the following controls:
+
+Save settings - Open Camera supports saving all of its settings to a file, so that you can
+ restore them later. This could potentially be used for saving different profiles or presets.
+ The files are saved inside Android/data/net.sourceforge.opencamera/files/backups/ .
+ Please note that all backup files are removed in Open Camera is uninstalled (or you clear Open
+ Camera's data in your device's Settings/Apps), unless you manually copy them elsewhere.
+
+Restore settings - Restores all settings from a previously saved settings file (see
+ "Save settings"). A file dialog will appear allowing you to choose the settings file. Be warned
+ that selecting a file will mean all of Open Camera's settings will be overwritten with the
+ saved version! Also beware of using this to transfer settings between different devices - there
+ is the risk that settings on some devices may be incompatible with other devices. Also if the
+ saved settings file specified a save location, this may not be valid on the new device (or if
+ using "Storage Access Framework", you may have to reselect the folder in Open Camera, to grant
+ write permission for the folder). Note that on Android 10+, the file dialog will only let you
+ select a file inside Android/data/net.sourceforge.opencamera/files/ .
+
+Reset settings - Resets all Open Camera settings to their default. Selecting this option will cause
+Open Camera to restart. Note that this will not delete any saved settings (see above options).
+
+Widgets and tiles
+
+Open Camera comes with a "Take Photo" widget. You can place this on your homescreen. When clicked
+on, it lauches Open Camera and takes a photo immediately.
+
+On Android 7, Open Camera supports Quick Settings Tiles, to launch Open Camera in photo mode
+("Camera"), video mode ("Record video") or front camera mode ("selfie").
+
+On Android 7.1, Open Camera supports application shortcuts. Press and hold the Open Camera icon
+to show additional shortcut options.
+
+Remote control
+
+Some remote control buttons and selfie sticks work by transmitting a volume key command, which
+by default will take a photo, but you can change this from the Settings .
+
+Open Camera also supports some remote control via a Bluetooth or USB keyboard:
+
+ Function key or numeric keypad "*": toggle popup menu
+ "/": toggle exposure menu
+ Space or numeric keypad "5": Activate shutter (take photo/video), unless the exposure or popup menu is open,
+ and you are in highlight selection mode (see below).
+ Up/down arrows, or numeric keypad "8" and "2": if the exposure or popup menu is open, this enters highlight selection
+ mode:
+ For the popup menu, move the highlighted row up or down to select a row, then press Space/"5", then you can change
+ the selected icon in that row with the up/down arrows, then press Space/"5" again to select the button.
+ For the exposure menu, move the highlighted row up or down to select a row, then press Space/"5", then you
+ can change the selected value or slider with the up/down arrows.
+
+
+
+ "+" or "-": Zoom in or out.
+
+
+Frequently Asked Questions
+
+How can I save to my external SD card? - This depends on your Android version:
+
+Android 5.0 onwards - enable Settings/More camera controls/"Storage Access Framework", and this should allow you
+to save to external SD cards. If when choosing a folder, you only see "Recent", you may need to click on the
+three dots at the top right to open the menu, to enable showing the drives (e.g., "Show internal storage").
+Android 6.0 onwards - From Android 6, some devices support "Adoptable Storage" allowing you to
+select to use
+an SD card as internal storage. Note that not all devices support this, even if running Android 6 or later. If your device
+doesn't support this, or you want to instead use an SD card as "portable storage", you'll have to use
+the Storage Access Framework method as with Android 5.
+
+
+Can you implement disabling shutter sound for my phone? -
+ If Open Camera shows the option Settings/"Camera API", then changing to "Camera2 API" means you'll be able to disable
+ shutter sounds under "Settings/More camera controls...".
+ When not using Camera2 API, if the option "Shutter sound" under "More camera controls..." isn't shown,
+ then it's not available. There
+ are possible workarounds for some of these devices (which is why some third party camera applications
+ may be able to silence the shutter), though the issue is these don't work on all devices, and tend
+ to use methods that Google now discourage. The fault is with the device for not supporting
+ standard method for cameras to disable the shutter sound on Android. In particular, if under Settings/About
+ you see that "Can disable shutter sound?" says No, it means the device's camera API is telling 3rd
+ party camera apps that shutter sound can't be disabled (so either it can't do it, or the API is lying
+ - either way, this should be reported to your manufacturer).
+
+Photos or videos fail to save! - Firstly, if you're trying to save to an
+external SD card, see "How can I save to my external SD card?" above. Otherwise:
+
+ If Settings/More camera controls/"Storage Access Framework" is enabled,
+ in some cases the permission may be lost, try rechoosing the save location (from
+ Settings/More camera controls/"Save location").
+ If not using Storage Access Framework, but you have changed the save location,
+ it may be you've chosen somewhere where applications don't have permission to save
+ files.
+ Or sometimes simply restarting the devices fixes such problems.
+
+
+I switched to a new phone, and now something doesn't work! - Google's auto-backup will
+typically transfer settings to a new phone, but this may mean a camera-specific setting is no
+longer relevant. In particular, if you set a non-default save location, it may be that the path is
+not valid on the new device, or if using Settings/More camera controls/"Storage Access Framework",
+you may need to rechoose the save location (from Settings/More camera controls/"Save location") to
+grant permission to the new device. You can use Settings/Settings manager/"Reset settings" to reset
+Open Camera to its original state, to rule out any issues from an Android backup from another
+device.
+
+My pictures are being rotated/cropped! - This likely means the auto-level
+option is on. (If they're being rotated even when the phone is held level, it may mean the accelerometer sensor on your
+device isn't calibrated.) It's off by default, but you may have accidentally switched it on. To turn off, go to the "popup" menu
+and untick Auto-level.
+
+Why doesn't Open Camera support dual / multiple cameras? - Open Camera supports cameras
+ that are made available to third party applications, although you may need to set Settings/"Camera API"
+ to "Camera2 API". When using Camera2 API, many devices expose multiple cameras via the zoom - zooming out to less
+ than 1x switches to the ultra-wide camera, zooming in automatically switches to the telephoto when
+ required. On other devices, the cameras can be manually switched by using the
+ switch multi-camera icon.
+ Note that some devices don't allow third party applications to use their extra cameras, either
+ via zoom or by explicitly switching to the camera. In such cases Open Camera cannot access them.
+
+But another third party camera app can access the extra cameras on my device, why can't Open Camera? -
+ On some devices, it may be possible to access the camera by ignoring what the device claims, and trying to
+ access the camera IDs anyway. This is a hack - on other devices, this will lead to buggy behaviour where
+ cameras are exposed that hang or otherwise don't work. The problem here is that the device does not
+ support exposing the cameras to third party camera applications via the Android camera API.
+
+But can't you use the hacky method to access the extra cameras anyway? - Put it this way:
+ you paid hundreds of pounds for a device from a large company with lots of resources, but you
+ want the free application to do the extra work to workaround the device's limitation, even when it's a hacky
+ method? Sometimes I do implement workarounds for device limitations - but it is risky to do so here.
+
+Why doesn't Open Camera support the maximum video resolution on my device? - If you
+are using Camera2 API, make sure that you're not in slow motion mode (see "Speed" under
+the popup menu), and you don't have a non-default frame rate set (under Settings/Video settings). If
+a high speed frame rate is in use, then this usually limits the maximum video resolution. If this
+isn't the case, then it may be that the device isn't exposing the highest video resolution to third
+party applications (e.g., this is common for 4K/UHD video resolutions on older devices, especially
+if Camera2 API isn't enabled).
+
+Why doesn't the FPS/bitrate setting for video work? - These settings only give "recommendations" to the
+camera, and there is no guarantee that they will be met. For best chance of success, try setting Settings/"Camera API" to "Camera2 API".
+
+But my camera can do 60/120FPS, so why can't Open Camera? - High frame rates often are achieved only by the
+"stock" camera app (or "mods" of it) because these are written for a specific device and don't have to go through
+the standard Android camera API. Some devices do now support high speed frame rates when Camera2 API is enabled.
+
+Why doesn't Open Camera show 23MP resolution on my Sony Xperia, only 8MP? - This was a problem on older devices
+ and/or with the old Camera API because of Sony not making this available to third party camera applications. On newer Sony
+ devices, this should become available if you set Settings/"Camera API" to "Camera2 API".
+
+Why does the resolution of my photos not match the specified camera resolution? - This happens if
+auto-level is enabled. The image is rotated to be level, which means the
+resolution (and aspect-ratio) will change.
+
+Why can't I change the ISO? - Even if your device supports ISO, this may not be made available through the
+standard Android API for 3rd party camera apps to use.
+
+Why doesn't touch to focus work? - Touching the screen should allow you to choose a particular region to focus
+on. If this doesn't work:
+
+ Check the focus mode (under the popup menu), for best results you usually want
+ Continuous or
+ Auto focus modes.
+ Check that Settings/"Face detection" is disabled (focus regions aren't possible in face detection mode).
+ Some devices or cameras (especially front cameras) don't support touch to focus.
+
+
+I get "FAILED TO OPEN CAMERA" - In some cases this is fixed by restarting
+Open Camera, or otherwise make sure nothing else is using the camera (including the
+torch being enabled). In some cases a phone gets into a state where the camera can't
+be opened, which is fixed by rebooting. If it persists even after restarting your
+device, try other camera applications in case the camera is faulty.
+
+Why has Open Camera stopped working properly? - If something stops working in Open Camera
+first try a reboot of your device. If that doesn't resolve the problem, try resetting
+the settings to the defaults (under Settings/Settings manager/"Reset settings"), or try reinstalling
+the app (or go to your device's App Settings and select "Clear data" for Open Camera) to
+reset it to its initial conditions. Obviously ideally this shouldn't happen, but can be a way of
+working around any unresolved bugs that appear. If something stops working in an upgrade to a new
+version of Open Camera, and the problem isn't resolved by a reinstall/Clear data, please let me
+know, but in the meantime you can install the older versions from
+
+https://sourceforge.net/projects/opencamera/files/ .
+
+Why doesn't the preview display match the resultant photo/video? One of them is cropped. - Firstly, make
+sure that Settings/Camera preview/Preview size is set to "Match photo size (WYSIWYG)". However if that doesn't fix
+the problem, this is a limitation on some devices and photo/video resolutions (it happens if the device doesn't offer
+a "preview" with the same aspect ratio as the chosen photo/video resolution). A workaround may be to try a different
+resolution for photos and/or videos.
+
+Why isn't Open Camera available in my language? - I can only speak English I'm afraid. Please contact me
+if you're willing to do a translation (this doesn't require any knowledge of Android programming, it's just a case
+of translating a set of strings in a text file).
+
+Why is the non-English translation of my language incomplete? - Scene modes and color effects aren't
+currently translated, as these are just strings returned by the camera. Also note that even if I get someone to
+translate Open Camera, when I later add new features/options, this may require additional strings which aren't
+translated. I don't have a team of paid translators, so it's not always possible to keep translations up to date :)
+
+The non-English translation is wrong! - I can only speak English, and am dependent on other people to
+offer translations. If you think a particular translation is inaccurate, please let me know.
+
+Why is the screen forced to maximum brightness? - If you don't like this feature, you can switch it off
+by going to Settings/On screen GUI/Force maximum brightness.
+
+Does Open Camera support selfie sticks / bluetooth remotes? - Open Camera has support for some selfie sticks
+though different sticks work in different ways, so it's hard to guarantee this. Sticks and bluetooth remote controls
+which work by triggering a volume key press should work (and the behaviour can be configured by
+Settings/More camera controls/"Volume keys").
+
+Why is auto-level slow? - This feature requires doing a decompress of the JPEG data, followed by a
+rotation of a multi-megapixel image, then recompressing, which typically results in a short pause on most devices.
+And as devices get faster CPUs, they typically come with cameras with even bigger megapixels! This is why I've made
+it optional (and you can set the volume control to quickly switch it on and off if you like).
+
+Why is auto-level for photos only? - Doing auto-level for video is a massively harder
+problem. This wouldn't be possible in real-time - rotating images causes a noticeable pause as it is, imagine
+having to do that for every frame. Also the rotation angle wouldn't be constant, so it's a much harder problem
+figuring out what the correct result should actually be.
+
+Can I launch a different gallery app when I press the gallery icon? Why doesn't Open Camera
+have its own gallery app? - If you have more than one Gallery app on your device, you should be
+given the choice which to use when you press the gallery icon. If one app is already set up as the
+default and you want to change it, then go to the App Settings for that app, and under
+"Launch by default" (or something like that) it will list if it is set as the defaults for any
+actions, with an option to clear them. There are plenty of gallery apps for Android, and it seems
+better for users to have this choice, rather than Open Camera having its own custom gallery.
+
+Clicking on the thumbnail icon only shows the photo briefly? - This can happen if you've changed the save location
+for photos/videos to one that is not typical (e.g., not inside DCIM/ ). Some gallery applications will not show a photo in
+such cases.
+
+Why does Open Camera have ads? - Open Camera does not have ads in the application (there may be ads on the online
+webpage you're reading now, but not in the app). There are however some clones on Google Play with ads inserted.
+Please ensure that you've downloaded from one of the places listed above
+on this page.
+
+Does Open Camera support features like manual controls for exposure/ISO/focus, and RAW? -
+Set Settings/"Camera API" to "Camera2 API" for such features (if the option isn't there, it's not supported on your device).
+This is turned off by default as some devices have poor behaviour.
+
+Why can't I select the Camera2 API? - Some devices only have
+"legacy" Camera2 support, which means it isn't any better than the original Camera API. Open Camera doesn't support
+enabling Camera2 on such devices.
+
+Does Open Camera support HDR+, or use the Pixel's Visual Core? If you have a Google Pixel with Pixel Visual Core, then Open Camera
+should be using HDR+ when the following settings are set: Photo Mode Standard, flash off (or auto if the flash doesn't fire),
+white balance auto, color effect none, no exposure compensation, no manual ISO/exposure, no RAW. HDR+ should be
+supported in both old and Camera2 API. See
+this thread for more discussion.
+Note that some Google Pixels (e.g., 3a) do not have a Pixel Visual Core chip, and may not support HDR+ in third party
+camera applications (see this thread ).
+
+Why isn't Panorama supported on my device? - To support panorama in Open Camera, this requires
+a gyroscope and compass, and at least 256MB of "large heap" memory (note, this isn't the same as the device's RAM).
+Bear in mind that even if your device supports panorama, with Open Camera I have to support thousands of Android devices,
+and I don't have the luxury of targetting functionality towards one particular device.
+
+Why doesn't Open Camera's HDR images look like other HDR camera apps? - There are a great many different ways
+of applying a HDR algorithm, some may do better or worse in different circumstances; some may look more or less pleasing
+depending on what you are after. Also note that some camera apps use "HDR" to mean "apply a whacky-looking filter".
+Whilst HDR filters can be used to apply a rather unreal or vivid image, for now Open Camera's HDR is more geared towards
+capturing the range of exposures.
+
+Why doesn't Open Camera's HDR photos look as good as other HDR photos I see? - Firstly, see the previous question.
+Beyond that, many HDR photos you see on the Internet may have been manually processed in HDR software that allows the
+user to tweak settings for optimal results for a given image. Such photos may have had additional processing done. Open
+Camera's HDR algorithm tries to get the best results for most purposes automatically, but isn't going to beat manual
+editing.
+
+I ran out of space while recording video, and the video is corrupted, how can I get it back? - Firstly, this sadly
+isn't an Open Camera bug - it's an issue that the video recording API doesn't stop or report an error when storage
+runs out. As a workaround, Open Camera does try to stop videos when storage space is low (although note this workaround
+may not be feasible in some cases if saving to non-default locations). In order to recover a file, you can try
+"MP4Fix Video Repair Tool" by Smamolot (com.smamolot.mp4fix) (not affiliated with me or Open Camera!).
+
+My device ran out of power while recording video, and the video is corrupt! How can I get it back? - This can happen if
+the device is very slow at processing the video file after stopping, and this doesn't finishing when the device powers off. Open
+Camera will stop the video in advance of the device shutting down to help reduce this risk (see "Critical battery check" option),
+but if this still happens, the best hope is to try MP4Fix, see the previous question. Note that if this happens, it isn't an
+Open Camera bug - it's a problem that will happen on any device where the device shuts off before the video can be processed.
+
+Does Open Camera support external USB cameras? - Unfortunately Open Camera does not support USB cameras. These don't
+seem to use the standard Android camera APIs, and it would be a lot of work to add support for these.
+
+I don't like the UI! - The UI for Open Camera has improved significantly over the versions (both in terms
+of having a consistent look, and the operation), so this criticism seems to have gone down, but there's always room
+for improvement! However, in order for me to improve, please be specific: comments like this could mean all sorts of
+things, such as the style of the icons, the arrangement of the icons, wanting more things on the main screen, wanting
+less things on the main screen, preferring swipes to icons, wanting it easier to change certain options, or even that
+some devices may have a bug that I'm not aware of. Also bear in mind that some preferences may be a matter of opinion
+and it's not possible to get an app that satisfies everyone (e.g., some camera apps hide everything behind popup menus
+that you swipe to enable; others have as much on screen as possible - I try to achieve a balance in Open Camera).
+
+Why is the UI cluttered? - Under Settings/"On screen GUI", there are options to disable various controls and
+so on from the main view.
+
+Can I use the Open Camera source code in my app? - The Open Camera source is available under the GPL (see
+Licence ), and can be used for free, including commercially, if you follow the terms of that
+licence (this means making the source of your app available under a GPL-compatible licence).
+
+Contacting me (bugs etc)
+
+If you experience a crash, and Google offers to "Report", please do so (if you've installed via F-Droid,
+please see here ).
+
+If you have a question or what seems to be a bug, please first read FAQ . Also if something
+no longer seems to work properly, try a reboot of your device, or if that fails to fix, try resetting Open Camera
+settings to the defaults (under Settings/Settings manager/"Reset settings").
+
+If there's still a problem, please check other third party camera applications to see if they have the same
+ problem or not. (It's not enough to try your device's "stock" camera - in some cases, devices may have bugs for
+ third party camera applications that don't affect the stock camera.)
+
+If you find a bug, please
+report it here (please check for existing tickets first).
+It is helpful to supply the "About" information - please go to Settings/About, then click "Copy to clipboard", then you
+can paste the information into your web browser, email or whatever.
+
+For more general questions or things like feature suggestions, please use the
+forums .
+For some enquiries you may prefer to use email.
+Please contact me at mark.harman.apps@gmail.com .
+
+Please note that I get a lot of emails
+for Open Camera these days - I try to reply as many as I can, but this is not always feasible. I do however
+read every email and forum post.
+
+Note that whilst I welcome reviews/ratings, they are not a good way for reporting bugs (I may
+miss it, there's only limited number of characters for me to reply).
+
+
+Open Camera Privacy Policy.
+This website uses icons from third party sources, see licences.
+Open Camera on Sourceforge.
+
+
+
+
diff --git a/_docs/history.html b/_docs/history.html
new file mode 100644
index 0000000..ea17dc8
--- /dev/null
+++ b/_docs/history.html
@@ -0,0 +1,1946 @@
+
+
+
+Open Camera History
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Open Camera History
+
+
+< Main Page.
+
+
+Version 1.55 (2025/08/18)
+
+FIXED Crash on some devices when starting preview using camera vendor extensions, now fail
+ gracefully instead.
+FIXED Crash related to zoom on some older devices when starting with Camera2 API.
+FIXED Fixed possible crash related to focusing with Camera2 API.
+FIXED Store geotagging properly on devices that always had longitude set to 0.0 (e.g.
+ Fairphone 5).
+FIXED Dialog for poor magnetic sensor dialog wasn't showing any text.
+FIXED Ensure the info text for capture progress in x- extension modes remains visible until 100%.
+FIXED Zoom wasn't available in video mode after restarting, if photo mode was set to Panorama.
+ADDED Announce current camera info for accessibility (e.g. Talkback) on startup and when switching
+ camera.
+ADDED More crop guides: 65:24 and 3:1.
+ADDED Catalan translation (thanks to Cambrells).
+UPDATED Shutter button now changes to a red square when recording video.
+UPDATED Smooth zoom transition when using zoom seekbar (for Camera2 API).
+UPDATED Removed -/+ controls for zoom and exposure compensation (these "zoom controls" are now
+ deprecated in Android).
+UPDATED Also hide settings icon when taking a photo.
+UPDATED Show current save location in settings.
+UPDATED Don't block UI thread when first starting camera preview (for Camera2 API with Android 14+).
+
+Version 1.54.1 (2025/04/07)
+
+FIXED Crash in 1.54 due to trying to change exposure compensation beyond min or max values.
+
+Version 1.54 (2025/04/03)
+
+FIXED Device specific crashes when starting with Camera2 API (related to camera vendor
+ extensions).
+FIXED Manual white balance for Pixel devices.
+FIXED Returning from manual to auto white balance mode had incorrect colours until preview or
+ camera was restarted.
+FIXED REC709 and sRGB video picture profiles gave errors on some devices (e.g. Pixel).
+FIXED Device specific fixes for slow motion and high speed video.
+FIXED Some devices didn't support highest photo resolutions for focus bracketing mode.
+FIXED Don't enter immersive mode when in background.
+FIXED Some devices (e.g. Galaxy S24+) didn't layout UI correctly when switching directly between
+ landscape and reversed landscape orientation.
+FIXED Placement on of-screen text (e.g. zoom indicator) when in reversed landscape orientation and
+ focus seekbar was visible.
+FIXED Sometimes didn't layout UI correctly for landscape vs reversed landscape when in
+ split-screen mode.
+FIXED Shifted positions of icons (when "along top") to avoid camera privacy indicator on some devices
+ e.g. Samsung Galaxy.
+FIXED Icons weren't smoothly rotating the first time the device was rotated (for Camera2 API).
+FIXED Duplicate entry of 0.1s for manual exposure time.
+ADDED New Settings/Photo settings/"Save preview shots". Enables saving a short video alongside
+ photos, containing shots from the moment before the photo was taken (requires Android 8+ and
+ Camera2 API).
+ADDED Support for choosing a specific physical lens (e.g., specifically choosing telephoto lens)
+ on supported devices (requires Android 9+ and Camera2 API).
+ADDED Support for Ultra HDR on supported devices, under Settings/Photo settings/"Image format"
+ (requires Android 14+ and Camera2 API).
+ADDED New "Auto source" mode for focus bracketing: in this mode, the source focus distance will be
+ automatically set via continuous focus or touch to focus.
+ADDED Haptic/vibration feedback for adjusting some seekbars. Can be disabled under
+ Settings/More camera controls/"Allow vibration feedback". Note this also requires touch
+ interactions to be enabled in your device's settings.
+ADDED New option Settings/More camera controls/"Use milliseconds in filename" (thanks to
+ Rob Emery).
+UPDATED Now requires Android 5+, Android 4.x no longer supported (sorry to anyone still on those
+ devices - but latest AndroidX libraries now require Android 5+).
+UPDATED Improved performance for Camera2 API (on Android 12+) for taking photos with continuous
+ focus mode, by optimising for latency. If this results in out of focus shots on your device,
+ change the new option Settings/Photo settings/"Optimise focus for..." to Quality.
+UPDATED Improved performance for resuming application with Camera2 API on devices that support
+ camera vendor extensions.
+UPDATED Double tapping will now cancel focus and metering area.
+UPDATED Single handed touch zoom by double tap and drag up/down.
+UPDATED Improvements for edge-to-edge mode support on Android 15.
+UPDATED Allow some more dialogs to run while still showing camera preview.
+UPDATED Support longer exposure time (0.5s) on Samsung Galaxy S devices.
+UPDATED OnePlus devices on Android 14+ now default to Camera2 API for new installs.
+UPDATED No longer force max brightness by default for new installs.
+UPDATED Removed "dim on-screen virtual navigation buttons" option for "Immersive mode" (this is now
+ deprecated on Android, and few devices support it anymore).
+UPDATED Changed shutter sound on Samsung devices with Camera2 API (workaround for Samsung specific
+ bug where Camera2 API sounds are always 100% volume).
+UPDATED Made upright detection in Panorama photo mode less strict.
+UPDATED Improved exposure compensation seekbar so it's easier to return to an exposure compensation
+ of 0.
+UPDATED No longer using renderscript for image processing algorithms.
+
+Version 1.53.1 (2024/06/04)
+
+FIXED Bug in 1.53 where video subtitles option was incorrectly disabled when SAF enabled, also
+ fixed a related crash when recreating fragment.
+
+Version 1.53 (2024/05/28)
+
+FIXED Device specific crashes when starting with Camera2 API (related to camera vendor
+ extensions).
+FIXED Crash when saving images if unable to create thumbnail.
+FIXED ANRs and poor performance if using Storage Access Framework when save folder had lots of
+ files (due to trying to measure free memory).
+FIXED HDR images coming out green on Samsung Qualcomm devices.
+FIXED If force destroyed when in settings, the camera would be incorrectly opened when application
+ was recreated (camera should only be reopened when leaving settings).
+FIXED Long pressing on the shutter button in video mode meant nothing happened when releasing
+ touch.
+FIXED Clicking on gallery icon when using Storage Access Framework would open contacts on some
+ devices.
+FIXED Thumbnail wasn't ignoring deleted files when using Storage Access Framework (thanks to
+ Daniel Zhang).
+ADDED Camera vendor extensions show percentage progress on supported Android 14 devices.
+ADDED Long press on switch camera icons now bring up a menu to jump to any camera (for devices
+ that expose multiple cameras).
+ADDED New option for on-screen icon to enable or disable focus peaking.
+ADDED Support for themed/monochrome application icon (Android 13).
+UPDATED Improved user interface icons for shutter, switch photo/video and switch camera.
+UPDATED Smoother zoom for Camera2 API.
+UPDATED Improvements for loading thumbnails for gallery icon (including fixing orientation for
+ X-NIGHT portrait images on Pixel 6 Pro).
+UPDATED Improvements to lock screen behaviour when running above lock screen (thanks to
+ Daniel Zhang).
+UPDATED Improvements for popup menu and exposure UI when using large font sizes.
+UPDATED Made user's font size preference apply to on-screen text.
+UPDATED Changes in preparation for back button behaviour for future Android versions.
+UPDATED Better compatibility when debug option Settings/Photo settings/"Enable fast HDR/expo burst"
+ is disabled: also change the preview exposure. Turning this option off fixes HDR/expo for
+ Samsung Galaxy devices. New installs on Samsung Galaxy devices now disable fast HDR/expo
+ burst by default.
+UPDATED Updated Chinese Simplified translation (thanks to tumuyan).
+
+Version 1.52 (2023/08/13)
+
+FIXED Crash related to multi-camera devices.
+FIXED Possible crash when failing to save with Storage Access Framework.
+FIXED Jittery zoom when using multitouch pinch but pinching slowly.
+FIXED Don't show zebra stripes, focus peaking or histogram, when displaying resultant photo for
+ "Pause after taking photo" option.
+FIXED Problem where clicking on gallery icon would sometimes go to a "base" image instead of HDR
+ image, when saving HDR photos with base images (for Android 10+).
+FIXED Collapse notification panel when launching from a quick settings tile.
+FIXED Some info toasts weren't showing (e.g., when cancelling SAF dialog, or denying location
+ permission).
+FIXED Problem where if setting Video Picture Profiles to non-default value caused camera to fail
+ to open, the Video Picture Profiles setting would no longer show to be able to set back to
+ default.
+FIXED Allow trying to switch between photo and video mode if camera fails to open (in some case
+ the failure may be specific to the mode).
+FIXED Aspect ratio and other fixes in split-screen and multi-window modes.
+FIXED Problem on some tablets where zoom seekbar showed under navigation bar in landscape
+ orientation in some circumstances.
+ADDED Support for zoom with camera vendor extensions (for supported Android 13+ devices).
+ADDED Support for displaying on-screen ISO and exposure time with camera vendor extensions (for
+ supported Android 13+ devices).
+UPDATED Made pinch zoom more sensitive (better support for modern devices with higher zoom levels).
+UPDATED "Touch to capture" option now supports starting and stopping video.
+UPDATED Applied a timeout of 2 second for focusing with original camera API.
+UPDATED Improved performance for NR photo mode.
+UPDATED Drop support for notifications for background saving, due to Android 13 permissions faff.
+UPDATED No longer allow a screenshot of the camera preview to show in "recent apps" view (for
+ Android 13+).
+UPDATED No longer cancel panorama when moving device orientation too far in wrong direction.
+UPDATED Made more text scale according to user's font size preference.
+
+Version 1.51.1 (2023/01/02)
+
+FIXED Fix crashes for Camera2 API.
+
+Version 1.51 (2022/12/21)
+
+FIXED Gallery thumbnail had incorrect orientation on some Android 10+ devices.
+FIXED Focus bracketing images came out underexposed on some devices since
+ version 1.50 (e.g. Pixel 6 Pro).
+FIXED Problems with NR, fast burst and long manual exposures on some devices (e.g., Pixel 6 Pro).
+FIXED Face detection on-screen icon shouldn't show in camera vendor extension modes (as not
+ supported).
+FIXED For Camera2 API, red eye flash was incorrectly being shown even on devices that didn't
+ support it.
+FIXED Not saving location exif information for Camera2 API on some devices (e.g., Pixel 6 Pro).
+FIXED Crashed recording video on some devices and resolutions (e.g. Pixel 6 Pro at 1920x1440) if
+ those resolutions didn't support the same frame rate as other resolutions.
+FIXED Don't display error message if using volume keys to turn auto-level on or off in RAW or
+ Panorama mode (unless device doesn't support auto-level at all).
+ADDED New option Settings/Photo settings/"Remove device EXIF data" to remove device metadata from
+ JPEG photos.
+ADDED Shading for auto-level and crop guides, to darken the preview outside of the region of
+ interest.
+ADDED Display message to hold device steady when using X-Night photo mode.
+ADDED New option Settings/Photo settings/"HDR tonemapping" to choose tonemapping algorithm used
+ for HDR photo mode.
+UPDATED Applied a timeout of 1 second for focusing with Camera2 API.
+UPDATED Made it easier to zoom to 1x zoom using seekbar on devices that can zoom out to ultra-wide
+ camera.
+UPDATED Make zoom seekbar snap to powers of two (for Camera2 API).
+UPDATED No longer switch to manual mode in DRO and NR photo modes, as on some devices this meant
+ losing the benefit of manufacturer algorithms.
+UPDATED Volume key down now supports pause/resume video on Android 7+ when recording video, if
+ option for volume keys is set to "Take photo (or start/stop video recording)".
+UPDATED Apply a dimmed effect when reopening camera or switching modes (for Camera2 API).
+UPDATED Improved look of on-screen level line.
+UPDATED On-screen pitch and compass lines now show smaller intervals as camera zooms in.
+UPDATED Improvement to HDR algorithm for dark scenes.
+UPDATED Camera2 extension night mode now adds "_Night" to filename.
+UPDATED Default to Camera2 API for some devices (will only take affect for new installs or if
+ resetting settings).
+UPDATED Default to flash off instead of flash auto.
+UPDATED DRO, HDR, NR modes no longer activate showing full on-screen info toast when opening camera.
+UPDATED Use system toasts without custom views when appropriate.
+UPDATED Display current value for photo stamp font size and colour in preference summary.
+
+Version 1.50.1 (2022/06/08)
+
+FIXED Crash on OPPO devices for old camera API introduced in 1.50.
+
+Version 1.50 (2022/06/04)
+
+FIXED HDR photos came out black on some Samsung Galaxy devices with Android 12.
+FIXED Problems with flash on Camera2 API (Samsung Galaxy, OnePlus, Pixel 6 Pro). Galaxy and
+ OnePlus devices therefore no longer default to using the "alternative flash method".
+FIXED Problems with expo, HDR and long manual exposures on some devices (e.g., Pixel 6 Pro).
+FIXED Granting only approximate location permission on Android 12 would turn geotagging option
+ back off.
+FIXED On-screen text looked strange on Android 12.
+FIXED Gallery icon overlapped with navigation bar if using widescreen resolution with UI in left
+ or right handed mode.
+ADDED Support for Android 12's camera extensions API. When using Camera2 API, on selected devices
+ advanced photo modes are now available (e.g., Night on Pixel 6 Pro; Night, Bokeh and Beauty
+ on some Galaxy devices).
+ADDED Improved support for devices with multiple camera devices, where extra cameras are exposed
+ via zooming in and out (e.g., Pixel 5/6).
+ADDED New debug option Settings/Photo settings/"Enable dummy capture HDR/expo fix". Please enable
+ this if you are having problems with HDR or expo bracketing mode on Samsung Galaxy devices
+ with Android 11+ (specifically if some expo images come out with the same exposures).
+UPDATED Removed "use addresses" and "say cheese" options. Sorry about that, but
+ this is due to new data privacy requirements on Google Play: although
+ these used standard Android APIs, information was not available for these APIs to satisfy
+ data privacy requirements.
+UPDATED Now targetting Android 12. For remote control device options, new bluetooth permissions are
+ used instead of requiring location permission.
+UPDATED Move gallery icon slightly to avoid overlapping with Android 12 camera privacy icon.
+UPDATED Made pinch zoom smoother, to allow finer control.
+
+Version 1.49.2 (2022/01/13)
+
+FIXED Dialog for "Save settings" shouldn't allow multiple lines.
+FIXED Crash for NR photo mode on some devices since version 1.49.
+UPDATED Switched to using AppCompat AppCompatActivity.
+UPDATED Photo stamp custom text now uses AppCompat libraries to support latest emoji.
+UPDATED Made appearance of info "toasts" more consistent.
+
+Version 1.49.1 (2021/09/20)
+
+FIXED Crop guides weren't drawn correctly in portrait orientation in 1.49.
+FIXED Diagonals grid wasn't drawn correctly in portrait orientation in 1.49.
+
+Version 1.49 (2021/09/07)
+
+FIXED Crash when failing to save photos/videos with mediastore (Android 10+ if not using Storage
+ Access Framework).
+FIXED Crash related to original camera API.
+FIXED Crash when using photo stamp with auto-level when angle is zero.
+FIXED Couldn't exit immersive mode on Android 11.
+FIXED Behaviour where widescreen preview aspect ratios show under on-screen navigation bar wasn't
+ working properly on Android 11.
+FIXED Manual white balance had inverted effect.
+FIXED Video subtitles file didn't work properly when video file restarted due to max filesize.
+FIXED Taking a photo in RAW only, then clicking on the gallery thumbnail would sometimes
+ incorrectly open an earlier non-RAW photo or video.
+FIXED Corrected pitch and compass line lengths for portrait vs landscape orientations.
+FIXED Single and double tap options to take photo weren't working correctly in panorama mode.
+FIXED When using manual ISO seekbar, sometimes incorrect ISO button would be highlighted.
+UPDATED Now supports portrait and landscape system orientations, rather than being locked to
+ landscape system orientation.
+UPDATED Double tap to take photo option no longer performs a touch to focus, this now only happens
+ from a single tap.
+UPDATED Improved performance when opening camera and clicking on gallery icon (mainly relevant for
+ using Storage Access Framework with Android 10+ when save folder has large number of files).
+UPDATED Set max preview exposure time to be 1/5s instead of 1/12s, for when using manual exposure.
+UPDATED Support longer exposure time (1/5s) on some Samsung Galaxy S devices.
+UPDATED Improvements to brightness levels for Noise Reduction, DRO and HDR photo modes (images
+ coming out too dark in some cases).
+UPDATED Improvement to Noise Reduction photo mode quality (improved ability to distinguish noise
+ from ghosting effects).
+UPDATED Improvement to Noise Reduction photo mode to avoid overexposing in lower light scenes.
+UPDATED Improved choosing when to use 8 images for Noise Reduction photo mode.
+UPDATED Optimisations for DRO and NR photo modes on Samsung devices.
+UPDATED Accessibility improvement, set hints for EditTexts.
+UPDATED Now targetting Android 11. Due to changes in Android 11 this means "video subtitles" option
+ is now only available when saving with
+ Settings/More camera controls/"Storage Access Framework" enabled.
+UPDATED Updated some user interface icons.
+
+Version 1.48.3 (2020/11/20)
+
+FIXED Possible crash for panorama if failing to crop due to poor transformations; now fails
+ gracefully.
+FIXED Crash on EXTERNAL devices with Camera2 API that didn't support querying the view angles.
+FIXED Photos would sometimes fail to save on some devices with Storage Access Framework, when some
+ options were enabled (options like DRO, HDR, auto-level, photostamp that require
+ post-processing; custom Exif tags like artist or copyright; or when using geotagging with
+ Camera2 API).
+FIXED Fix for HDR scenes with both very bright and very dark regions, result would be over
+ exposed.
+FIXED Fixed possible misalignment for HDR scenes with very bright or very dark images.
+FIXED Corrupt videos could be left over if video failed to start.
+FIXED Possible problem taking photos on some devices with LIMITED Camera2 API support.
+FIXED Possible problem with default edge mode and noise reduction mode behaviours on some devices
+ with LIMITED Camera2 API support.
+FIXED UI would become sluggish if camera or storage permission denied with "Don't ask again".
+UPDATED Now supporting "scoped storage" on Android 10+. This means storage permission is no longer
+ required on Android 10+. However this means the following changes:
+ * Saving outside of DCIM/ is no longer possible unless using the Storage Access Framework
+ option. If you had set up a custom save folder outside of DCIM/ and are on Android 10+,
+ it will be reset to the default DCIM/OpenCamera/ folder. If you want to continue saving
+ outside of DCIM/, you can enable
+ Settings/More camera controls/"Use Storage Access Framework" and choose a new folder.
+ * If using Video subtitles option, then the .SRT files will show up in gallery
+ applications, unless Settings/More camera controls/"Use Storage Access Framework" is
+ enabled.
+ Note that these changes are required due to changes being made in Android that applications
+ are required to support.
+UPDATED Use seekbar for more settings (audio control sensitivity, image quality, photo stamp font
+ size).
+UPDATED Debug XML files for panorama now saved in Android/data/net.sourceforge.opencamera/files/.
+UPDATED Camera now closed when in settings or preview otherwise in background.
+
+Version 1.48.2 (2020/07/12)
+
+FIXED Manual focus and focus bracketing seekbars weren't being hidden when in immersive mode.
+FIXED Video subtitles would stop before end of video on some devices when using Storage Access
+ Framework.
+UPDATED Switched to AndroidX support library.
+UPDATED Artist, Copyright exif tags option now supported for devices running Android 6 or earlier.
+UPDATED Selecting remote device type for Bluetooth remote control now calls Open Camera's
+ DeviceScanner directly; DeviceScanner activity no longer exported.
+
+Version 1.48.1 (2020/05/02)
+
+FIXED Crash on devices with Camera2 API where camera reports no picture, video or preview
+ resolutions, instead fail to open camera gracefully instead.
+FIXED Fix switch camera buttons behaviour if a camera with ID greater than 0 failed to open.
+FIXED Some devices lost custom video profiles in 1.48.
+ADDED If camera fails to open, display ID of current camera that we tried to open.
+
+Version 1.48 (2020/04/22)
+
+FIXED Taking front camera photos with frontscreen torch was slow.
+FIXED When using "Pause after taking photo", touching to unpause no longer
+ triggers auto focus, or taking another photo for "Touch to capture".
+FIXED Take photo widget issue.
+FIXED Camera specific hardware keys such as volume keys shouldn't take effect in settings etc.
+FIXED Don't set optical image stabilization if video digital stabilization is enabled in video
+ mode.
+FIXED Seamless video restart on maximum filesize (for Android 8+) wasn't broadcasting video files
+ except the last one, meaning they were taking longer to show up in mediastore gallery.
+FIXED Recording video on Android 8+ could leave zero-size files if size approached the maximum
+ filesize, but a restart did not occur.
+FIXED Problem of on-screen level angle overlapping with shutter icon when using a widescreen
+ preview aspect ratio.
+FIXED Incorrect layout for on-screen text when using "icons along top" with wide-screen aspect
+ ratio and device held in upside-down landscape orientation.
+FIXED Focus seekbars overlapped with histogram in widescreen aspect ratio when using "Icons along
+ top" UI placement.
+FIXED Was incorrectly offering manual white balance even if camera didn't support this
+ (inconsistency that the manual white balance option showed, even though the manual white
+ balance temperature seekbar wasn't shown).
+FIXED Optional on-screen icons (such as flash, RAW) weren't updating correctly if switching to a
+ camera that didn't support that feature.
+FIXED Don't show on-screen flash icon in video mode (since this icon doesn't support torch, and
+ flash auto/on not supported in video mode).
+FIXED Preview texture buffer size (for Camera2 API) could be set incorrectly after changing aspect
+ ratios.
+FIXED Auto-level photos could never be full resolution (for when angle was 0).
+FIXED Update on-screen time format more often when changing device settings.
+FIXED USB/bluetooth keyboard control bug when navigating popup menu, if icons were displayed with a
+ horizontal scrollbar.
+ADDED New icon for switching between multiple cameras. If your device has multiple front and/or
+ back cameras, then the existing icon to switch cameras will switch between the first front
+ and back camera; the new icon will instead cycle between the multiple front or back cameras.
+ If you prefer the old behaviour, then disable
+ Settings/On screen GUI/"Multiple cameras icon".
+ADDED Current camera ID now displayed on-screen (next to date/time) for devices with multiple
+ front/back cameras. This can be disabled under Settings/Camera preview/"Show camera ID".
+ADDED Aperture control, for devices that support this. (Camera2 API only.)
+ADDED Flash on and torch now supported for manual ISO/exposure.
+ADDED Option to specify REC709 or sRGB profile for video recording.
+ADDED New custom gamma profile option for video recording.
+ADDED New video profiles JTVideo, JTLog and JTLog2 (thanks to JT Haapala).
+ADDED New option for alpha value to use for ghost image option.
+ADDED More zebra stripe values 93-99%.
+ADDED Options to control zebra stripe colours.
+ADDED Option for storing device's current yaw/pitch/roll in Exif user comment for photos (thanks
+ to Joshua).
+ADDED New option Settings/More camera controls/"Allow long press actions" to disable long press
+ actions.
+UPDATED Auto-level feature now shows on-screen rectangle to show the frame of the resultant photo.
+UPDATED Improvements for log profiles for video recording. Please note that this means the behaviour
+ of these profiles has changed!
+UPDATED On devices with on-screen navigation buttons, camera preview can now display under these
+ buttons if required for wide aspect ratio (requires Android 5+).
+UPDATED New immersive mode option to hide navigation buttons only when in immersive mode; existing
+ option for hiding navigation buttons now renamed to say "dim".
+UPDATED Show toast with camera id on startup if camera isn't set to the default camera for front or
+ back facing. Toast for cameras also displays whether ultra-wide, when using Camera2 API.
+UPDATED HDR and NR photo modes now limited to maximum resolution of 22 megapixels (to avoid risk of
+ running out of memory on devices with large camera resolutions).
+UPDATED Improved performance when displaying ghost image larger than device's resolution.
+UPDATED Popup menu now displays extra information for resolutions (MP for photos, descriptive name
+ like FullHD, VGA etc for video).
+UPDATED Don't set video digital stabilization when in photo mode.
+UPDATED Some preferences are now showed disabled if only relevant for another nearby option that
+ isn't currently enabled.
+UPDATED Moved video bitrate and frame rate options to debugging section.
+UPDATED Improved UI support for "external" cameras (if detected/supported with Camera2 API).
+UPDATED Improved placement of on-screen text (zoom, video recording time etc) to avoid focus
+ seekbars in landscape mode.
+UPDATED Improved look of on-screen text for manual/exposure sliders.
+UPDATED Exposure icon now highlights red when exposure UI is open.
+UPDATED Exposure UI now auto-opens when switching to manual white balance (as exposure UI contains
+ the manual white balance temperature seekbar).
+UPDATED More repeat mode options (100x, 200x, 500x).
+UPDATED Optimisation for reading most recent photo/video for thumbnail.
+
+Version 1.47.3 (2019/10/20)
+
+FIXED Grids were being drawing too faintly.
+UPDATED Camera2 API is now a "list" selection rather than a boolean switch.
+UPDATED Updated some icons for newer more consistent material design look.
+UPDATED Changed notification icon to be white.
+UPDATED Minor accessibility improvements.
+UPDATED Clarify in settings that PNG image format is not truly lossless.
+UPDATED Moved licences from about to separate preference category.
+
+Version 1.47.2 (2019/09/04)
+
+FIXED Some devices (e.g., Samsung) played shutter sound etc when Camera2 API enabled even when
+ phone was on silent/vibrate.
+FIXED When using "Icons along top" UI placement on devices with notches/cutouts, on-screen text
+ could be covered up by icons in widescreen aspect ratios.
+FIXED Don't display ghost image whilst frontscreen flash is enabled.
+FIXED Info toast string for video mode could be too long in some cases for high speed frame rates.
+ADDED Support for EXTRA_DURATION_LIMIT, EXTRA_SIZE_LIMIT, EXTRA_VIDEO_QUALITY for when called from
+ video (ACTION_VIDEO_CAPTURE) intent.
+UPDATED Minor performance improvement for panorama.
+
+Version 1.47.1 (2019/08/17)
+
+FIXED Crash in 1.47 when saving photo with SAF and geotagging, when using Camera2 API.
+FIXED Crash in 1.47 for panorama photo mode if a new photo is taken when a previous panorama is
+ still being processed.
+FIXED Crash for panorama if no matches found when aligning images.
+
+Version 1.47 (2019/08/14)
+
+FIXED Disallow switching between auto and manual mode while recording video (this previously
+ crashed).
+FIXED Crashes related to histogram, zebra stripes, focus peaking - don't crash if we have
+ problems.
+FIXED Don't close Open Camera if bluetooth service (for remote control) fails to initialise.
+FIXED Crash related to focus assist if camera closed.
+FIXED Crashes related to multiple instances of Open Camera and Renderscript.releaseAllContexts().
+FIXED Some devices (e.g., LG G6) defaulted to front camera, hopefully fixed to default to back
+ camera on all devices that have a back camera.
+ADDED New panorama photo mode (requires Android 5+, note not all devices are supported).
+ADDED New option Settings/Photo settings/Text style/"Text with shaded background" for photo stamp
+ option, to draw with a rectangular shaded background (i.e., bringing back the old behaviour
+ that was replaced by the newer outline shadow text).
+UPDATED Notifications now displayed on Android 8+ when Open Camera is in background when images are
+ still being processed/saved.
+UPDATED Improved exposure panel UI, and made on-screen text (zoom, video timer etc) more compact.
+UPDATED Camera ID is now displayed when switching cameras, on devices with more than two cameras.
+UPDATED Set navigation bar colour to black (needed for some devices, e.g., Samsung).
+UPDATED Improved performance on Android 7+ when saving photos when using Storage Access Framework
+ (no longer need to save to a temporary file to handle Exif tags).
+UPDATED When saving "base images" for Noise Reduction mode, keep JPEG quality at 100% even if using
+ post processing options.
+UPDATED Updated Renderscript to 64-bit code.
+
+Version 1.46 (2019/05/18)
+
+FIXED Problems with toasts being displayed for seekbars on Android 9.
+FIXED Face detection stopped working when recording video (on old Camera API).
+FIXED Wasn't displaying crop guides in some cases (when the crop guide aspect ratio matched the
+ photo/video resolution's aspect ratio, but this didn't match the preview's aspect ratio).
+FIXED Improvement to on-screen placement of pitch and compass lines.
+FIXED Toast for enabling/disabling face detection (via on-screen icon) was hidden by subsequent
+ info toast from restarting the camera.
+FIXED Fixed some crashes for old camera API.
+FIXED Problem with Camera2 API where with manual exposure time, preview would display a maximum
+ exposure time of 1/12s rather than what the manual exposure time had been set to.
+FIXED When using "red eye" flash, on-screen flash icon (with Camera2 API) was being shown all the
+ time, when this actually behaves similar to flash auto.
+FIXED Avoid "capturing" text from being covered by focus bracketing sliders in focus bracketing
+ photo mode and landscape orientation.
+FIXED Don't allow continuous fast burst mode (when holding down shutter button) in standard photo
+ mode with RAW only (previously this appeared to work, but no images were saved).
+FIXED Crashes with Camera2 API related to multithreading issues.
+FIXED Problem with camera preview and UI orientation if user switched to reverse landscape
+ orientation whilst in settings or an on-screen dialog was displayed.
+FIXED When using "Icons along top" UI placement, on-screen text could appear below the icons
+ if icons showed above the camera preview, when popup menu opened.
+FIXED Layout problem on Nexus 7 with "Icons along top" UI placement when going to settings and
+ back.
+FIXED Free storage space should now be correct for external SD cards when using Storage Access
+ Framework.
+ADDED New option Settings/Camera preview/"Show a histogram" to display an on-screen histogram.
+ (Camera2 API only.)
+ADDED New option Settings/Camera preview/"Show zebra stripes" to display on-screen zebra stripes
+ to indicate over-exposed regions. (Camera2 API only.)
+ADDED New option Settings/Camera preview/"Focus peaking" to display on-screen highlights to
+ indicate in-focus edges. (Camera2 API only.)
+ADDED RAW/DNG now enabled on higher end devices for expo bracketing and focus bracketing photo
+ modes, as well as HDR when saving the base expo images. Use the new options Settings/Photo
+ settings/"Allow RAW for expo bracketing" and "Allow RAW for focus bracketing" if you want to
+ enable RAW for standard photo mode, but not for expo/focus bracketing modes.
+ADDED New option Settings/Photo settings/"Use addresses" to display GPS coordinates in the form
+ of an address (for photo stamp and video subtitles).
+ADDED Support for remote control via bluetooth/USB keyboards.
+ADDED New option under Settings/On screen GUI/ to enable on-screen icon for enabling location
+ data.
+ADDED New option under Settings/On screen GUI/ to enable on-screen icon for cycling through flash
+ modes (instead of on the popup menu).
+ADDED New option under Settings/On screen GUI/ to enable on-screen icon for cycling through RAW
+ modes.
+ADDED Support for the Kraken Smart Housing (thanks to Edouard Lafargue). See Settings/More camera
+ controls/"Bluetooth LE remote control...".
+ADDED Added some more common manual shutter speeds (1/10 to 1/50 now changes in steps of 1/5).
+UPDATED Default UI layout is now "icons along top" (see Settings/On screen GUI/UI placement to
+ change back to old layouts.
+UPDATED Improved DRO algorithm for some scenes (e.g. stop making clouds so unnatural).
+UPDATED Noise Reduction "NR Mode" (normal or low light) is now preserved; the on-screen "NR" icon is
+ now highlighted in yellow in low light mode.
+UPDATED Separated some options under "Settings/On screen GUI" preference screen out into the new
+ screen "Camera preview".
+UPDATED Made exposure UI panel more compact.
+UPDATED On-screen icon for RAW now displayed as "RAW ONLY" if only saving RAW.
+UPDATED Display number of remaining images to save when "processing".
+UPDATED Play notification sound (if shutter sound not disabled) when focus bracketing completes.
+UPDATED Also added option for 150 focus bracketed images.
+UPDATED Code to try to prevent video corruption when running out of space now enabled when using
+ Storage Access Framework.
+
+Version 1.45.2 (2019/01/20)
+
+FIXED Don't allow focus assist when recording video.
+
+Version 1.45.1 (2019/01/18)
+
+FIXED Crash when saving settings if SAF enabled, and save location does not correspond to a file
+ based directory.
+FIXED Some devices in Camera2 mode lost support for modes requiring burst or manual ISO/exposure.
+FIXED Improve popup view for small screens, to help some languages with longer strings.
+
+Version 1.45 (2019/01/14)
+
+FIXED Crash if going to background during focus bracketing.
+FIXED Crash for Camera2 API on devices with no color effects.
+FIXED Crashes related to saving with storage access framework.
+FIXED If Open Camera is running above screen lock, don't require screen to be unlocked just to
+ show an info dialog.
+FIXED Prevent exposure panel from overlapping with on-screen icons on small devices.
+FIXED Problems with popup view and left handed UI (e.g., wouldn't show when first opened).
+FIXED Unlimited repeat mode didn't work on some languages (if this affected you, you'll need to
+ re-select the option for unlimited, then it should start work).
+FIXED If Repeat Mode changed in Settings, the same option on popup menu would sometimes not be
+ updated.
+FIXED Don't pause on-screen preview when capturing in fast burst mode.
+FIXED Don't support expo bracketing, focus bracketing or fast burst photo modes if called from a
+ "Take photo" intent.
+FIXED Taking photos could hang on some devices with Camera2 API when using repeat mode and
+ continuous focus.
+FIXED ISO manual slider wasn't updating when ISO was changed by using the ISO buttons.
+FIXED Code meant to increase exposure time for dark scenes in Noise Reduction mode could end up
+ reducing exposure time in some cases.
+FIXED Wasn't requesting audio permission (for Android 6+) for "Audio control" options.
+ADDED New "Low light" mode for Noise Reduction photo mode.
+ADDED Continuous burst mode: holding down shutter button in standard or fast burst photo modes
+ will now enable a continuous burst. (Camera2 API only.)
+ADDED Focus assist option (Settings/On screen GUI/"Focus assist"), allows auto-zooming when
+ adjusting manual focus distance.
+ADDED Support for different photo image formats (Settings/Photo settings/"Image format"): WebP,
+ PNG.
+ADDED Support for different video file formats (Settings/Video settings/"Video format"): H264,
+ HEVC, 3GPP, WebM (WebM is without audio support).
+ADDED New options to save and restore all settings to a file (see Settings/"Settings manager").
+ADDED New option Settings/On screen GUI/UI placement/"Icons along top", which enables a new user
+ interface layout where control icons are laid out opposite where the "take photo" icon is
+ (top of the screen in portrait, or left of the screen in landscape).
+ADDED New options under Settings/On screen GUI/ to enable on-screen icons for enabling face
+ detection, auto-level, photo stamp, custom text stamp, white balance lock; also the
+ exposure lock icon is now optional.
+ADDED Timelapse capture rates of 120x and 240x.
+ADDED More auto-repeat options.
+ADDED Larger options for maximum filesize for video (up to 9GB).
+ADDED 2:1 crop guide.
+ADDED New "fine" setting for video flat (log) profile.
+ADDED New lower noise sensitivity for "loud noise" audio trigger.
+ADDED Vietnamese translation (thanks to Khánh Trần).
+UPDATED Improved HDR algorithm to avoid increasing noise in darker scenes. If you prefer the
+ original behaviour, see Settings/Photo settings/"HDR contrast enhancement" and set this to
+ "Always". Or, you can also set this to "Off" to disable this in all cases.
+UPDATED Improvements to contrast enhancement (as used by HDR, DRO, Noise Reduction): avoid
+ increasing contrast too much in dark regions.
+UPDATED Improvement to Noise Reduction mode colours for some scenes.
+UPDATED Improved quality of deghosting algorithm for Noise Reduction mode (manages to deghost with
+ less loss of quality).
+UPDATED Improved auto-alignment for Noise Reduction mode for dark scenes or when zoomed in.
+UPDATED Allow maximum of 2s instead of 0.5s manual exposure time in Noise Reduction mode.
+UPDATED Made user interface buttons larger.
+UPDATED Focus bracketing mode now supports up to 200 images.
+UPDATED Focus bracketing mode can now be cancelled by pressing the take photo button again.
+UPDATED Focus bracketing mode makes the shutter sound per shot.
+UPDATED Burst images are now labelled starting from "_0" in the filename suffix (to be consistent
+ with expo and focus bracketing).
+UPDATED Make it clearer that Storage Access Framework should be enabled to allow saving to SD cards.
+UPDATED Photo and video resolutions on popup menu now ordered left to right from smallest to
+ largest, rather than largest to smallest.
+UPDATED Ensure photo resolutions are sorted in order (by area).
+UPDATED Video frame rates now specify which are "high speed" (for Camera2 API); also make it clearer
+ if video resolution in settings is for high speed mode.
+UPDATED Improved performance when changing video speed on popup menu.
+UPDATED "Say cheese" voice control now remains on.
+UPDATED Minor improvements for accessibility.
+UPDATED Expo bracketing, focus bracketing, noise reduction and fast burst modes now display number
+ of photos being taken in each burst.
+UPDATED Allow Camera2 mode to be used on more devices (devices that have both some LEGACY cameras,
+ and some cameras with at least LIMITED support).
+UPDATED Content description for exposure lock now updates to say whether clicking will lock or
+ unlock exposure.
+
+Version 1.44.1 (2018/09/23)
+
+FIXED Crash in 1.44 for ghost image option if selected image could not be loaded due to
+ SecurityException.
+FIXED Crash with RAW (DNG) in some cases due to threading issue when "Pause after taking photo"
+ enabled.
+
+Version 1.44 (2018/09/18)
+
+FIXED Crash if activity is destroyed as photo is taken.
+FIXED Crash if camera reopened 128 times in a single instance of the application.
+FIXED Fixed some crashes for old camera API.
+FIXED Fixed crash with Camera2 API when using expo/HDR if fast burst disabled, and camera
+FIXED In Camera2 API mode, wasn't checking to see if video stabilization option is supported.
+FIXED Settings/Preview size/"Maximise preview size" option didn't always work correctly on some
+ devices (e.g., Nokia 8).
+FIXED Fixed a crash related to failing to save with storage access framework.
+FIXED Switching from slow motion back to regular speed wouldn't reset exposure level on some
+ devices.
+FIXED Prevent manual focus slider from overlapping with on-screen icons.
+FIXED Photos taken while recording video were being saved at 100% quality if photo mode was DRO or
+ HDR, even though the photo mode isn't relevant for photos when recording video.
+FIXED Don't show fast burst options on popup mode when in video mode.
+FIXED Problem of toasts for seekbars disappearing when continually moving seekbar.
+FIXED GUI not updating properly when using auto-repeat with timed interval in Camera2 API mode.
+FIXED Norwegian strings for video bitrate preferences were incorrect.
+ADDED New photo mode "NR" Noise Reduction, takes a burst of photos and aligns and merges to reduce
+ noise. (Camera2 API only, only supported on high end devices.)
+ADDED New photo mode "Focus {}" to enable Focus Bracketing. (Camera2 API only.)
+ADDED New option Settings/"Ghost image", to overlay either last photo taken, or a selected image
+ on your device.
+ADDED New option Settings/On screen GUI/"Show audio level meter", to display on-screen audio level
+ when recording video.
+ADDED New option Settings/Video settings/"Video flat (log) profile" to record video with a flat
+ profile. (Camera2 API only.)
+ADDED New options under Settings/"Processing settings..." for setting Edge Mode and Noise
+ Reduction Mode filtering options. (Camera2 API only.)
+ADDED New option Settings/Photo settings/"Distance unit" to allow using feet instead of metres for
+ GPS altitude on photo stamp and video subtitles.
+ADDED On-screen flash icon (to show whether flash will fire or not) now supported for front screen
+ flash.
+ADDED New Camera2 API debug option under Settings/"Photo settings" to disable taking photos whilst
+ recording video feature (if your device has problems recording videos with Camera2 API
+ enabled, try disabling this).
+ADDED Camera2 support for high resolution photo resolutions (required for supporting highest
+ resolutions on some devices, e.g., Nokia 6, Samsung Galaxy S6).
+ADDED Launching from intent now recognises request for front or back camera
+ (android.intent.extras.CAMERA_FACING, android.intent.extras.LENS_FACING_FRONT,
+ android.intent.extras.LENS_FACING_BACK, android.intent.extra.USE_FRONT_CAMERA).
+ADDED Video bitrate options for 150Mbps and 200Mbps (may not work on all devices).
+ADDED New video audio source options: UNPROCESSED (requires Android 7) and VOICE_RECOGNITION.
+ADDED Partial Greek translation (thanks to Wasilis Mandratzis-Walz).
+ADDED New option to disable ever showing the "What's New" dialog (under Settings/On screen GUI/
+ "Show What's New dialog").
+UPDATED No longer wait for image queue to be saved to go to settings, or background.
+UPDATED On Android 8+ when restarting video due to hitting maximum filesize, transition to new file
+ is now seamless.
+UPDATED Improved behaviour of seekbars for manual ISO, shutter speed, white balance (they now select
+ sensible "round" values).
+UPDATED Show pitch lines etc even when device is near vertically down or up; also highlight the 90
+ degree pitch line when almost vertically down or up.
+UPDATED Don't show auto-level on-screen icon when device is near vertically down or up (since auto
+ level won't occur in that situation).
+UPDATED Always show scrollbar on popup menu (so it's more obvious that it can be scrolled).
+UPDATED Show FPS on-screen when recording video (Camera2 API only).
+UPDATED Made disabled buttons on popup menu easier to see in bright light.
+UPDATED Made on-screen level lines clearer.
+UPDATED New material design gallery icon (used when no last thumbnail found, and also for shortcut
+ icons).
+UPDATED If an option that requires compass direction is set, warn user if compass is unreliable.
+UPDATED Current manual focus distance is now always saved.
+UPDATED Show video bitrate on info toast if not set to default.
+UPDATED More preferences in settings now show the currently selected value.
+UPDATED Battery optimisation: don't use magnetic compass sensor unless required for user
+ preferences.
+UPDATED Camera opening on background thread (for smoother operation) now enabled for Android 6
+ devices (was already supported for Android 7+).
+UPDATED Workaround for bug where popup menu appears too large (off-screen), reopening the popup at
+ least should fix this without having to restart Open Camera.
+
+Version 1.43.3 (2018/04/20)
+
+FIXED Fixed some crashes.
+ADDED Added video capture rate, capture rate factor and high speed info to About debug dialog.
+UPDATED Fix toast message if slow motion video fails.
+
+Version 1.43.2 (2018/04/13)
+
+FIXED Fixed some crashes.
+
+Version 1.43.1 (2018/04/12)
+
+FIXED Crash when selecting antibanding setting on devices with no support for antibanding; this
+ option is now removed on such devices.
+FIXED Crash when using Storage Access Framework to select "Downloads" on Android 8+.
+FIXED Fixed some other crashes.
+
+Version 1.43 (2018/04/10)
+
+FIXED Fixed crash when sharing images (if not using Storage Access Framework) on some Android 7+
+ devices.
+FIXED Fixed crash related to devices that don't support video.
+FIXED Fixed some crashes for when Camera2 API enabled.
+FIXED Problems when taking HDR or expo photos quickly without pause, the second photo would use
+ incorrect exposure.
+FIXED Problems with popup view layout on some non-English languages.
+FIXED On-screen icons (e.g., for HDR) didn't show after restarting.
+FIXED When using volume keys to switch auto-level on/off, user interface wasn't updating properly.
+FIXED Face detection mode on Camera2 API wasn't affecting resultant exposure etc.
+FIXED Bug with video subtitles (.SRT) option, overlapping subtitles could appear briefly when
+ pausing/resuming video.
+FIXED Bug on some devices (e.g., Nokia 8) where manual sliders (e.g., for manual ISO) could only
+ go up to 1 less than the true maximum allowed value.
+FIXED Some stored "values" for zh-rCN shouldn't have been translated.
+ADDED New Fast Burst photo mode (Camera2 API only).
+ADDED Slow motion video (Camera2 API only).
+ADDED Time lapse video (requires Android 5).
+ADDED Torch option for front cameras without flash, which lights up the screen.
+ADDED Option to save only in RAW/DNG format (requires Android 7).
+ADDED New options under "Photo settings" to support Artist and/or Copyright Exif tags in images
+ (requires Android 7).
+ADDED On-screen icons for DRO and Expo Bracketing modes.
+ADDED On-screen icon for when audio is disabled in video mode.
+UPDATED Now targetting Android 8.1.
+UPDATED Improved appearance of on-screen text and photo stamp text (draw fonts with outline, rather
+ than shaded background).
+UPDATED Improved layout of on-screen information.
+UPDATED Improved look of popup menu.
+UPDATED Improved support for setting frame rates (including high speed support) when using Camera2
+ API. Thanks to George Joseph.
+UPDATED Video frame rate preference is now stored per-camera (as with video resolution).
+UPDATED Burst mode now renamed to Repeat mode (note, some translations may still refer to "burst").
+UPDATED Some preferences in settings now show the currently selected value.
+UPDATED Improved performance when going to settings in portrait mode.
+UPDATED Don't request audio permission (or show toast if not available) if record audio option for
+ video is disabled.
+UPDATED Zoom now resets when pausing the application, switching camera, or switching between photo
+ and video modes (not resetting zoom tends to confuse users; this change also makes behaviour
+ consistent with other camera applications).
+UPDATED For "Pause after taking photo" option, pressing Back when preview is paused now unpauses the
+ preview and keeps the photo, rather than exiting the application.
+UPDATED Don't block UI when image saving queue is full, instead simply don't allow more photos to be
+ taken until queue is no longer full. Size of image saving queue is also increased, depending
+ on memory of device.
+UPDATED Datestamp format (for "Stamp photos" option) yyyy/mm/dd replaced with yyyy-mm-dd (to match
+ ISO 8601 standard).
+UPDATED Camera2 API now sets CONTROL_ENABLE_ZSL for standard and DRO photo modes on Android 8+.
+UPDATED "Use background thread" debug option removed (now defaults to using background thread for
+ saving photos).
+
+Version 1.42.2 (2017/12/30)
+
+FIXED Fixed some crashes.
+FIXED Problems with alignment of some dialogs in portrait mode on some devices (possibly an
+ Android 8 issue).
+ADDED On-screen face icon displayed when face detection is enabled.
+UPDATED Enable 60fps on Android 7+ for GUI rendering.
+
+Version 1.42.1 (2017/12/10)
+
+FIXED Crash related to front camera fake flash on devices with auto focus and Camera2 API.
+FIXED Possible fix for boot loop on LineageOS (this is a LineageOS bug exposed by using Android
+ Studio 3; workaround is to upgrade to build tools 27.0.1).
+FIXED Change in 1.42 for using suffix "_0" etc instead of "_EXP0" etc was only done for HDR expo
+ images; now done for Expo {} photo mode too.
+FIXED Text style "Plain text" for "Stamp photos" was still showing a shadow effect.
+FIXED Workaround for Google bug where crash can be reported if application is open when it's being
+ updated ( https://issuetracker.google.com/issues/36972466#comment14 ).
+UPDATED Now targetting Android 8.
+UPDATED New adaptive icon for Android 8 (thanks to Adam Lapinski).
+UPDATED Improve Camera2 quality for Samsung Galaxy S7 and S7 Edge (set EDGE_MODE_OFF and
+ NOISE_REDUCTION_MODE_OFF).
+UPDATED Updated Norwegian Bokmål translation (thanks to Imre Kristoffer Eilertsen).
+UPDATED Updated Polish translation (thanks to Jacek Buczyński).
+
+Version 1.42 (2017/11/19)
+
+FIXED Fixed some crashes.
+FIXED Bug since 1.41 where if camera failed to open, it wasn't possible to switch to other
+ cameras.
+ADDED Application shortcuts (requires Android 7.1).
+ADDED Face detection now supports accessibility services (e.g., if using Google Talkback,
+ information on the number and position of detected faces will be spoken).
+ADDED Link to GPL on About dialog.
+UPDATED Expo images (either in "Expo {}" photo mode, or HDR when "Save all images for HDR mode" is
+ selected) are now saved with suffix "_0" etc, instead of "_EXP0" etc. This means Google
+ Photos knows to group the images together.
+UPDATED Performance improvements.
+UPDATED "Show angle" no longer enabled by default.
+
+Version 1.41.1 (2017/10/21)
+
+FIXED Crash in 1.41 when opening popup menu in video mode on devices without flash.
+FIXED Crash in 1.41 related to taking photos on burst mode with no delay when camera closing.
+UPDATED Auto-repeat burst mode now closes when going to settings (not safe to be taking photos in
+ background when settings may change).
+
+Version 1.41 (2017/10/15)
+
+FIXED No longer shows exposure flashes on the screen when taking HDR photos with Camera2 API.
+FIXED "Pause after taking photo" option was pausing preview after stopping video recording.
+FIXED Minor bug in HDR auto-alignment.
+ADDED Take photos while recording video.
+ADDED What's New dialog now displays to user new features/changes.
+ADDED Norwegian Bokmål translation (thanks to Imre Kristoffer Eilertsen).
+UPDATED Switch camera icon now next to take photo button, for easier one-handed use.
+UPDATED Switching to video mode now done by selecting the smaller video icon next to the take photo
+ button; similarly switch back to photo mode by selecting the smaller photo icon next to the
+ record video button.
+UPDATED ISO controls now moved entirely to the exposure icon.
+UPDATED Changing ISO values is faster (except for switching between auto and manual mode on Camera2
+ API).
+UPDATED Improved performance by closing camera on background thread when application pauses.
+UPDATED HDR improvements (ghost removal, and brightening of too dark images).
+UPDATED Expo bracketing and HDR photo modes now have maximum exposure time of 0.5s (for Camera2
+ API).
+UPDATED Use material design icons for location/GPS.
+UPDATED Updated Italian translation (thanks to Stefano Gualmo).
+
+Version 1.40 (2017/08/18)
+
+FIXED Front screen flash wasn't showing if in "everything" immersive mode.
+FIXED Shouldn't show "RAW" on-screen icon when RAW is enabled, but it's not
+ supported by the current camera.
+FIXED Fixed title of calibrate level angle dialog (shouldn't be "About").
+FIXED Various crashes.
+FIXED Don't show two toasts when changing scene modes (bug in 1.39).
+FIXED For DRO and HDR modes with Camera2 API, don't set ISO value outside of
+ supported range.
+ADDED Polish translation (thanks to Jacek Buczyński).
+UPDATED Improvements to HDR algorithm for brightness levels (problems with HDR
+ photos coming out too dark).
+UPDATED Changing color effect or white balance is now faster, and no longer
+ closes the popup (unless switching to manual white balance); for
+ Camera2 mode, changing scene mode is also faster.
+UPDATED Improved performance when leaving Settings, if certain preferences
+ haven't been modified.
+UPDATED Close camera on background thread.
+UPDATED Pause icon now changes when pausing video (thanks to Johan Ejdemark).
+UPDATED Make pause icon easier to see against bright backgrounds.
+UPDATED Larger gap between bottom of zoom slider and edge of screen (reduce
+ risk of hitting device capacitive buttons when zooming out).
+UPDATED Don't show ISO info on debug window that's specific to old camera API,
+ when using Camera2 API.
+UPDATED Performance improvements: reduced memory allocations.
+UPDATED Make sure exposure lock icon is set whilst camera is opening.
+UPDATED White balances, scene modes, color effects are now shown as "user
+ readable" strings (except for old Camera API where there are device
+ specific values). This also means these strings can be translated in
+ future versions.
+UPDATED About debug info now lists information on supported exposure
+ compensation and manual ranges.
+
+Version 1.39 (2017/06/28)
+
+FIXED For Camera2, don't show flash of previous preview image when resuming
+ application.
+FIXED Crash when exiting (related to cleaning up renderscript object).
+UPDATED Camera now opens on background thread (for smoother operation) on
+ Android 7+.
+UPDATED Performance improvements for opening popup view.
+
+Version 1.38.2 (2017/06/01)
+
+FIXED Various crashes.
+UPDATED Performance improvements for opening popup view.
+
+Version 1.38.1 (2017/05/22)
+
+FIXED Various crashes.
+FIXED Don't show expo bracketing for original camera API if exposure
+ compensation is not supported.
+UPDATED Performance improvements.
+
+Version 1.38 (2017/05/13)
+
+FIXED Crash with video subtitles option when GPS is lost.
+FIXED Fixed various other crashes.
+FIXED Photo stamp now shows in correct orientation, on devices that store
+ orientation as Exif tag (e.g., Samsung devices).
+FIXED Still show flash symbol (for flash auto) even if "Show ISO" option is
+ disabled (for Camera2 API).
+FIXED Minor bug in DRO/HDR image generation.
+ADDED Manual white balance temperature (Camera2 API only).
+ADDED HDR and expo bracketing now available for original Camera API (HDR
+ still requires Android 5+) (Camera2 API will still be much faster).
+ADDED On-screen icons now displayed to indicate options being enabled, for
+ RAW, auto-stabilise, HDR, photo stamp.
+ADDED Hungarian translation (thanks to Báthory Péter).
+ADDED Ukranian translation (thanks to Olexandr).
+UPDATED Improved HDR algorithm for areas that are over-exposed even on the
+ darkest image.
+UPDATED More Exif tags preserved when saving in modes that require re-saving
+ the image (e.g., auto-stabilise, photo stamp, DRO, HDR) (requires
+ Android 7). Performance also improved for such modes on Android 7.
+UPDATED Improved ISO and exposure time scaling for the manual sliders.
+UPDATED Don't leave gaps between various on-screen info text (time, free
+ memory, etc) if some of them are disabled.
+UPDATED Number of exposure bracketing stops now refers to the full half-range.
+UPDATED "Auto-stabilise" option now renamed to "Auto-level", to avoid confusion
+ with image stabilisation.
+UPDATED Performance improvements.
+
+Version 1.37 (2017/02/12)
+
+FIXED Crash on devices not supporting scene mode.
+FIXED Crash if failed to start audio listener (for "loud noise").
+FIXED Problems with focusing on OnePlus 3T with Android 7 on Camera2.
+FIXED Problems with "alternative" flash method for Camera2 API on OnePlus 3T.
+FIXED Improved support for standard flash on Camera2 API on OnePlus 3T.
+FIXED Support for long manual exposure times (with Camera2 API) (e.g.,
+ OnePlus 3T).
+FIXED Remove duplicate entries from ISO buttons (e.g., for OnePlus 3T).
+FIXED Problems with "fast HDR/expo burst" on Nexus 6 now fixed (you should be
+ able to reenable this under "Photo Settings", for faster HDR and expo
+ bracketing shots.
+FIXED Shutter sound option wasn't working for Camera2 API unless a photo had
+ previously been taken in the same session.
+ADDED Display flash symbol for Camera2 API in flash auto mode, to indicate
+ whether flash will fire.
+UPDATED Improved battery life when going idle.
+UPDATED Improved support for flash auto decision on when to take a photo, when
+ using "alternative" flash method and/or HDR, expo bracketing for
+ Camera2 API (it now matches the decision when not using the alternative
+ flash method).
+UPDATED Finer control for manual seekbars.
+UPDATED Display "Capturing..." when manual exposure time is 0.5s or longer.
+UPDATED Support face detection on more devices (e.g., OnePlus 3T) with Camera2
+ API.
+UPDATED Improved popup view so selected button is centred rather than aligned
+ left.
+UPDATED Continuous focus is now the default for new installs on all devices.
+
+Version 1.36.2 (2017/01/01)
+
+FIXED Save location folder dialog wasn't working from Settings in 1.36.
+
+Version 1.36.1 (2016/12/31)
+
+FIXED Fixed a couple of crashes.
+
+Version 1.36 (2016/12/28)
+
+FIXED "Loud noise" audio control wasn't working.
+FIXED Remember focus mode for video mode.
+FIXED Focus circle for continuous focus mode would sometimes show green.
+FIXED Improved behaviour for photo/video mode when launched from a take photo
+ or record video intent (no longer keeps switching back to photo mode
+ when pause/resuming if called from a photo intent; now allows switching
+ to photo mode if called from a video intent).
+FIXED Make line-spacing for toasts more consistent.
+FIXED Improved "Max duration of video", was stopping up to a second
+ prematurely.
+ADDED New photo mode "DRO" (dynamic range optimisation) (requires Android 5).
+ADDED Quick settings tiles to open camera (requires Android 7): tiles for
+ photo, video and selfie mode.
+ADDED Pause/resume video recording (requires Android 7).
+ADDED New option to mirror photos for front camera (Settings/Photo settings/
+ "Front camera mirror").
+ADDED Option to calibrate the level angle (accelerometer) (see Settings/More
+ camera controls/"Calibrate level angle".
+ADDED New option to display on-screen pitch lines (Settings/On screen GUI/
+ "Show pitch lines") and compass direction lines (Settings/
+ On screen GUI/"Show compass direction lines").
+ADDED Camera2 API popup menu now has "ISO M" option to switch straight to
+ manual exposure mode, but defaulting to the current ISO value.
+ADDED New option "Video subtitles" (Settings/Video settings/). This creates a
+ subtitle file (in .SRT format) displaying date and time; and also GPS
+ location and direction if those options are enabled
+ADDED New option to disable showing the "take photo" button (under Settings/
+ On screen GUI/"Show take photo icon").
+ADDED 3 seconds entry for "Max duration of video" option.
+UPDATED HDR now has auto-alignment for better quality photos.
+UPDATED Moved white balance/scene mode/color effect options lower down the
+ popup menu.
+UPDATED No longer auto-stabilise or display level angle when device pointing
+ nearly up or down.
+UPDATED Apply low pass filter for compass direction.
+UPDATED Continuous focus mode is now the default for back camera for Nexus and
+ Pixel devices.
+UPDATED About/debug info now shows whether disabling shutter sound is supported.
+UPDATED Updated Russian translation (thanks to Grigorii Chirkov).
+UPDATED Updated Slovenian translation (thanks to Peter Klofutar).
+
+Version 1.35 (2016/10/30)
+
+FIXED Crashes for Galaxy Ace 4, Galaxy S Duos 3 (to do with Exif data).
+FIXED Workaround for Android 7 bug where rotated seekbars don't show
+ properly.
+FIXED Problem that after pausing and resuming in manual ISO mode, the
+ on-screen ISO was shown continually in red.
+FIXED Improved handling of camera errors - display error rather than just
+ allowing preview to freeze.
+ADDED New "Enable fast HDR/expo burst" option under "Photo settings" for
+ Camera2 mode. Disable this if your device has problems taking photos in
+ HDR or Exposure Bracketing photo modes.
+UPDATED Improvements to HDR algorithm: improved tonemapping; local contrast
+ enhancement.
+UPDATED Improved performance of on-screen toasts, gives smoother update for
+ seekbars (e.g., manual focus, exposure).
+UPDATED Launching Open Camera always opens in camera mode (rather than gallery,
+ if the user had previously gone to the gallery).
+UPDATED Improved look of seekbars, particularly for exposure.
+UPDATED Folder chooser now contains entry to jump to DCIM folder.
+UPDATED Timeout for camera2, give up waiting after 10s rather than freezing.
+UPDATED Updated Russian translation (thanks to Grigorii Chirkov).
+UPDATED Updated Slovenian translation (thanks to Peter Klofutar).
+UPDATED Now supporting Android 7.
+UPDATED Now compiling with Android Studio instead of Eclipse.
+
+Version 1.34 (2016/10/08)
+
+FIXED Crash on Camera2.
+FIXED Crash for Asus ZenFones with Android 5.0.
+FIXED Face detection and touch to focus not working properly with Camera2 and
+ "Rotate preview" option; this should also hopefully fix problems for
+ devices with unusual camera orientations (Nexus 5X).
+FIXED Face detection wouldn't switch off with Camera2 API without restart.
+FIXED For file dialog, still allow going up to parent folders even if a
+ folder contains no readable files.
+FIXED Pause preview option now shows exactly the saved photo (no lag, and
+ works with options such as auto-stabilise, photostamp, HDR).
+FIXED Nexus 6 got wrong date/time when using Camera2 API with geotagging
+ enabled.
+ADDED New photo mode Exposure Bracketing. Only supported for devices with
+ Camera2 API (and Camera2 must be enabled).
+ADDED New "Use alternative flash method" option under "Photo settings" for
+ Camera2 mode. This offers a possible workaround for devices that have
+ poor flash behaviour with Camera2 API.
+ADDED Screen flash to simulate flash auto/on modes for front cameras without
+ flash ("auto" requires Camera2).
+ADDED Material design icons for Settings.
+UPDATED Now supporting Android 6 permissions. You'll have to "allow"
+ permissions for camera and storage for Open Camera to work. Additional
+ permissions requested when required are microphone (for video) and
+ location (for geotagging).
+UPDATED HDR mode now supports flash modes.
+UPDATED Extra protection for video recording and running out of storage space -
+ if not using Storage Access Framework, and saving to internal memory,
+ videos should now stop rather than becoming corrupt when running out of
+ storage. Note this can't be supported for Storage Access Framework or
+ SD cards due to Android limitations.
+UPDATED Stop video when battery level critical, to give extra protection when
+ device runs out of video (see
+ Settings/Video settings/"Critical battery check" to disable this
+ behaviour).
+UPDATED Gallery icon now right-most.
+UPDATED On-screen icons now have transparent icons.
+UPDATED Improved look of popup-menu: headings are larger and in bold.
+UPDATED New material design icon for exposure.
+UPDATED On-screen ISO (for Camera2 API) displays red when auto-exposure routine
+ is scanning.
+UPDATED Manual exposure times longer than 1s now shown directly rather than as
+ a reciprocal.
+UPDATED Thicker lines for grid, auto-focus grid.
+UPDATED Battery icon shows red at 15% instead of 30%; also now flashes at 5% or
+ less.
+UPDATED On-screen icons now rotate with animation.
+UPDATED Reorganised some preference settings.
+UPDATED Set metadata tags for sound files beep.ogg and beep_hi.ogg.
+UPDATED Updated French translation (thanks to Eric Lassauge).
+UPDATED Updated Slovenian translation (thanks to Peter Klofutar).
+UPDATED Show compass direction option now defaults to off.
+
+Version 1.33 (2016/08/29)
+
+FIXED Hang when starting with Camera2 API on Android N.
+FIXED Problems with popup menu on some languages and screen sizes.
+FIXED Problems with Camera2 and flash: no longer does excessive flash; also
+ fixed problem where with flash auto, flash would sometimes misfire
+ resulting in too dark photos.
+FIXED Problem with Pause Preview option: if a photo with RAW was saved, then
+ another photo without RAW saved and deleted, the previous RAW was also
+ deleted.
+ADDED New photo mode HDR. Only supported for devices with Camera2 API (and
+ Camera2 must be enabled).
+ADDED Support for manual ISO for old camera API on Asus Zenphone 2 Z00A and
+ Z008 (thanks to Flávio Keglevich).
+ADDED Portuguese translation.
+ADDED Slovenian translation (thanks to Peter Klofutar).
+UPDATED Folder history (via Gallery long click) now supported when using Storage
+ Access Framework.
+UPDATED When switching from auto to manual ISO mode in Camera2 API, exposure
+ time now defaults to the current (auto) exposure time.
+UPDATED Updated Russian translation (thanks to Dmitry Vahnin aka JSBmanD).
+
+Version 1.32.1 (2016/07/24)
+
+FIXED Crash on some devices when taking photos with RAW enabled.
+
+Version 1.32 (2016/07/19)
+
+FIXED Don't repeatedly focus when volume key held down (for "focus" volume
+ key option). Hopefully this may help for devices with physical focus
+ buttons.
+FIXED Border to indicate photo is being taken no longer remains for "Pause
+ after taking photo" option.
+FIXED Now plays sound for start/stop video recording (if shutter sound
+ enabled) for Camera2 API.
+ADDED Support for RAW (DNG) files. Only available in Camera2 mode, and if
+ your device supports it. Note that most Gallery apps don't recognise
+ DNG files, instead use specialised applications like Google Snapseed
+ and Adobe Photoshop Lightroom, or transfer to a PC.
+UPDATED If volume keys are set to "focus", then holding down both volume keys
+ will take a photo (this makes your volume keys behave more like a
+ physical camera button - hold down one key to focus, then both to take
+ a photo).
+UPDATED Allow one more photo to be processed in background at a time.
+UPDATED Improved timing of shutter sound for Camera2 API.
+UPDATED Enabling auto-stabilise now shows an info dialog. Select "Don't show
+ again" to prevent the dialog from showing again.
+UPDATED Thumbnail for most recent photo/video now restricted to Open Camera's
+ save folder when using Storage Access Framework (this was aready the
+ case when not using Storage Access Framework).
+UPDATED 5s and 10s options for maximum video duration.
+
+Version 1.31 (2016/06/19)
+
+FIXED Crash on Camera2 API (checkArgumentNonnegative).
+FIXED Photo filename's date/time and date/time used for photostamp now match
+ better with time the photo was actually taken, rather than when the
+ image was saved.
+FIXED Improved Take Photo widget behaviour so it focuses in continuous focus
+ mode.
+ADDED New option for filenames to be based on UTC (Zulu) time (thanks to
+ David Pletcher).
+UPDATED Photos now processed in background thread, for smoother operation (if
+ this causes problems, you can disable it under Settings/Photo settings/
+ Use background thread).
+UPDATED Touching the screen with continuous focus mode in photo mode now causes
+ an autofocus.
+UPDATED Gallery button shows animation effect to indicate saving images.
+UPDATED Photo/video info toast now indicates if auto-stabilise option is on.
+UPDATED Don't display pointless toast for front/back camera any more.
+UPDATED Updated Russian translation (thanks to Grigorii Chirkov).
+
+Version 1.30.1 (2016/06/01)
+
+FIXED Crash in 1.29 on some devices on startup.
+
+Version 1.30 (2016/05/29)
+
+FIXED Crash in some cases when failing to save photo.
+FIXED Crash in some cases when opening settings.
+FIXED Focus/metering regions and face detection now works properly with
+ Camera2 when zoomed.
+FIXED Audio control icon still showed if audio control was disabled, when
+ previously set to "loud noise" option.
+UPDATED Popup menu now uses the more standard three dots icon.
+UPDATED Popup menu also shows indicator for flash being off.
+UPDATED Show zoom seekbar closer to the edge of screen if -/+ controls not
+ enabled; always show manual focus distance seekbar closer to the edge.
+
+Version 1.29 (2016/05/06)
+
+FIXED Still show Camera2 API option for Android N's LEVEL_3 hardware level.
+FIXED Problems with flash mode on Camera2 API - now seems to be working
+ better on my Nexus 6 at least...
+FIXED Sluggish zooming with Camera2 API.
+FIXED With Camera2 API, if starting up with flash auto or on, and in focus
+ modes other than auto, macro or locked, then flash wouldn't fire.
+FIXED Problems on some devices where we claimed failed to take photo, even
+ though it had succeed (some Samsung devices call the autofocus callback
+ more than once).
+FIXED Video recording icon would get stuck on red ("playing") if failed to
+ save video file.
+FIXED Too long string for 3x3 grid for French translation on popup menu.
+ADDED Proper support for continuous focus mode for photo mode.
+ADDED Graphical border effect when taking a photo (if you don't like this,
+ you can disable it under Settings/On screen GUI/"Show border when
+ taking photo").
+ADDED Animation effect for auto-focus square.
+UPDATED Improved on-screen graphics (line drawings now have proper
+ density-independent thickness rather than being hairline).
+UPDATED On-screen ISO display (for Camera2) simplified and moved to top of
+ screen.
+UPDATED Use Switches instead of Checkboxes in Settings where appropriate.
+UPDATED Content description for "switch photo/video mode" button now updates to
+ indicate whether it will switch to photo or video mode. Content
+ description for "switch camera" button now updates to indicate whether
+ it will switch to the front or back camera.
+UPDATED Improved content descriptions for buttons on popup menu (focus/flash
+ buttons now say which mode they are).
+UPDATED Disabled flash options for manual ISO mode in Camera2 API (doesn't work
+ properly; plus flash is less useful when setting ISO manually anyway).
+UPDATED Switching to ISO auto and back to manual now resets the exposure time
+ to default.
+UPDATED Debug info now includes device language setting.
+UPDATED Default to FullHD rather than higher video resolutions, if possible.
+
+Version 1.28.1 (2016/03/30)
+
+FIXED Crash when going to "Photo settings" on devices with Czech language
+ (bug introduced in 1.28).
+
+Version 1.28 (2016/03/25)
+
+FIXED Broadcast File-based Uri when using Storage Access Framework, instead
+ of SAF Uri (this fixes Owncloud crashing for auto-upload photos/videos
+ option; this might also fix problems with photos/videos not showing up
+ on SD card until after a reboot?)
+FIXED Unable to get location from network providers on some pre-Android 6
+ devices (e.g., Galaxy Nexus, Nexus 7) since v1.27 due to Android OS
+ bug, have worked around this.
+FIXED If using Storage Access Framework, and save location was on external SD
+ card, the on-screen free memory would show internal storage. (Note that
+ with this fix, it may be that the free memory is not shown for external
+ SD cards, but that's better than showing an incorrect value.)
+FIXED Toast for hitting max filesize on video recording wasn't shown for very
+ long.
+FIXED Was incorrectly showing "n repeats to go" toast even if it wasn't going
+ to repeat (e.g., due to user stopping video recording).
+FIXED Use unicode character for degrees symbol (fixes warning in Android
+ Studio).
+FIXED Corrected content description of take photo icon for when switched to
+ video mode (for accessibility).
+FIXED Don't show thumbnails of earlier photos/videos when device is locked.
+FIXED Fixed various FindBugs errors.
+ADDED New options to take photo/record video by making a noise, or responding
+ to the voice command "cheese".
+ADDED Option to control maximum filesize of videos.
+ADDED New option to disable performing auto-focus on startup (disable this if
+ you have the bug where flash turns on on startup).
+ADDED Belarusian translation (thanks to Zmicer Turok).
+ADDED Czech translation (thanks to Jaroslav Svoboda).
+ADDED Japanese translation (thanks to Mitsuse).
+ADDED Turkish translation (thanks to Serdar Erkoc).
+UPDATED Video now automatically restarts (rather than stopping) if the device's
+ maximum filesize (typically ~2GB), or the user specified limit, is hit.
+ (Option also added to not restart, if you prefer.)
+UPDATED Don't show info toasts so often - only if an unusual non-default
+ setting is set, or when switching between photo and video modes.
+UPDATED Improved quality of take photo/video icons.
+UPDATED Improved look of focus indicator.
+UPDATED Display countdown timer as hours:minutes:seconds when 60 seconds or
+ more.
+UPDATED No longer show pointless toast for stopping video recording.
+UPDATED Display if face detection is turned on in info toast.
+UPDATED Level line now has a crossbar to indicate the vertical angle better.
+UPDATED Reorganised the Settings ("Lock screen when recording video" moved to
+ "Video settings", "Rotate preview" moved to "On screen GUI").
+UPDATED Minor performance improvement to startup time.
+UPDATED Condense popup view by combining some titles into the buttons.
+UPDATED Updated Chinese Simplified translation (thanks to Michael Lu).
+
+Version 1.27 (2015/10/25)
+
+FIXED Crash on Android 6 when accessing popup menu.
+FIXED Crashes on Android 6 if users have denied permissions (currently Open
+ Camera won't request permissions at runtime, and please don't expect
+ well-defined behaviour if you've blocked a permission, this is a quick
+ fix to prevent crashes).
+FIXED Problems when holding down volume/focus/camera key to focus.
+FIXED Camera2 API photos sometimes taken before focused; autofocus square
+ sometimes turned green immediately before focus actually succeeded.
+FIXED Take photo icon would momentarily switch to video icon when going to
+ settings.
+FIXED Video bitrates now correctly documented as bps rather than bits.
+ADDED 3 second option for timer/repeat mode.
+ADDED German translation (thanks to Ronny Steiner and Sebastian Ahlborn).
+UPDATED Improved selfie stick button support (thanks to Lau Keat Hwa).
+UPDATED Improved icons for app, and take photo (thanks to Adam Lapinski).
+UPDATED Updated French translation (thanks to Olivier Seiler).
+UPDATED Updated Russian translation (thanks to Vitamin).
+
+Version 1.26 (2015/07/06)
+
+FIXED Crashes on some devices if camera couldn't be opened.
+FIXED Crash in Camera2 API to do with focusing.
+FIXED Crash in Camera2 API when camera not available.
+FIXED Crash if pressing volume keys to change exposure when camera couldn't
+ be opened.
+FIXED Taking photo would sometimes hang if flash on, and taking a photo
+ whilst already focusing.
+FIXED "Show thumbnail animation" preference was ignored since v1.24.
+FIXED Saving of Exif tags (including GPS compass direction) now handled
+ properly when Open Camera is called via another app to take a photo
+ (note that some 3rd party apps may still override the Exif tags).
+FIXED Was sometimes displaying "0" for timer countdown and playing a beep
+ when taking a photo on timer.
+FIXED If a photo was taken before startup autofocus completed, flash was
+ incorrectly turned off.
+FIXED If torch is on, don't turn it off and on when Open Camera is starting
+ up.
+FIXED Now switches to photo mode automatically when called from a photo
+ intent (ACTION_IMAGE_CAPTURE, ACTION_IMAGE_CAPTURE_SECURE,
+ INTENT_ACTION_STILL_IMAGE_CAMERA,
+ INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE).
+FIXED Reenable exposure compensation when using non-default ISO for original
+ Camera API.
+ADDED Support for Android 5's Storage Access Framework. This should allow
+ saving to SD cards, but has to be enabled under Settings/More camera
+ controls/"Use Storage Access Framework".
+ADDED New option "Touch to capture" - take photos by touching or
+ double-tapping anywhere on the preview.
+ADDED New option to allow disabling "toast" messages.
+ADDED New grids: 3x3 Phi, Crosshair, Golden (Fibbonaci) Spirals, Golden
+ Triangles, Diagonals.
+ADDED New crop guides: 1:1, 5:4, 7:5.
+ADDED Options for date, time and GPS formats (including 12/24 hour choice),
+ and font color, for date and time stamps.
+ADDED Option for highlight color to use for angle display and level line,
+ when camera is nearly level.
+ADDED Option for mono or stereo audio when recording video (stereo only
+ supported on some devices).
+ADDED Support for INTENT_ACTION_VIDEO_CAMERA intent (so Open Camera can now
+ be launched in video using OK Google's "record a video") and
+ ACTION_VIDEO_CAPTURE.
+ADDED Support for ACTION_IMAGE_CAPTURE_SECURE intent.
+ADDED Optional voice countdown for timer.
+ADDED Info toast now shows if timer and/or burst mode enabled.
+ADDED More repeat mode intervals, now up to 2 hours.
+ADDED New "Text style" option to render "stamp" text without a shadow
+ background effect.
+ADDED Azerbaijani translation (thanks to Eldost).
+ADDED Brazilian translation (thanks to Kaio Duarte).
+ADDED Chinese Traditional translation (thanks to You-Cheng Hsieh).
+ADDED French translation (thanks to Olivier Seiler).
+ADDED Korean translation (thanks to Halcyonest).
+UPDATED Choice of grid now available on the popup menu.
+UPDATED Focus mode remembered for photo mode even when switching to video mode
+ and back.
+UPDATED Improved timer beep sound; also plays a higher pitch sound for one
+ second to go.
+UPDATED Record video button now turns red when recording video.
+UPDATED Popup menu now shows symbol for red eye mode.
+UPDATED Make icons transparent again, when using Material Design theme.
+UPDATED Made popup menu slightly more transparent.
+UPDATED Slight tweak to colours of take photo/video icons.
+UPDATED "Timer beep" preference moved to "More camera controls..." page.
+UPDATED Updated Chinese Simplified translation (thanks to Michael Lu).
+UPDATED Updated Russian translation (thanks to Vitamin).
+UPDATED Updated Spanish translation (thanks to Mario Sanoguera).
+
+Version 1.25 (2015/04/23)
+
+FIXED Crash when clicking on settings if camera couldn't be opened.
+FIXED Various other crashes.
+
+Version 1.24 (2015/04/18)
+
+FIXED Problem on Nexus 6 where torch didn't turn off until going to flash
+ off mode (this came back again - possibly due to Android 5.1 update!).
+FIXED Problem that a toast wouldn't clear if new toasts were repeatedly
+ created.
+FIXED Toast for changing exposure compensation is no longer on top of the -/+
+ buttons.
+FIXED Some UI controls had missing contentDescription attribute (needed for
+ accessibility).
+FIXED Corrected Italian translation for "Save location" setting.
+FIXED Avoid slowdown when repeatedly pressing switch camera or switch video
+ buttons.
+ADDED New option to enable Android 5's Camera2 API! Currently experimental.
+ Restricted to only some devices (e.g., Nexus 6).
+ADDED True manual focus mode, which allows setting the focus distance with a
+ slider (only if Camera2 API is enabled).
+ADDED Option to display current ISO on screen (only if Camera2 API is
+ enabled).
+ADDED If using non-auto ISO, a specific ISO value and exposure time can now
+ be selected via sliders by clicking the exposure compensation button
+ (only if Camera2 API is enabled).
+ADDED Chinese Simplified translation (thanks to Michael Lu).
+UPDATED Focus Manual mode renamed to Focus Locked; also it no longer refocuses
+ when switching to this mode.
+UPDATED Improved look of on-screen level bar - now easier to see against white
+ background; it also becomes a double bar as well as turning green when
+ nearly level (for accessibility).
+UPDATED Angle display on-screen is now underlined as well as turning green when
+ nearly level (for accessibility).
+UPDATED Level angle no longer flips between "-0.0" and "0.0" when near zero
+ angle.
+UPDATED Option to reset settings now restarts app.
+
+Version 1.23 (2015/02/16)
+
+FIXED Some devices (Nexus 5, Nexus 6) had dark preview/photos in low light,
+ and frame rate not as smooth as it could be in good light.
+FIXED Problem on some devices (including Nexus 5 and Nexus 6) where torch
+ didn't turn off until going to flash off mode (have actually fixed it
+ this time!).
+FIXED Crash on some Sony Xperia devices when in video mode.
+FIXED Fix for devices where there is no write access for DCIM (thanks to
+ https://sourceforge.net/u/olevs ).
+FIXED Wasn't displaying free memory when first run, if OpenCamera folder
+ didn't already exist.
+FIXED If GPS lost, old possibly out of date position was still being used.
+FIXED Gallery now shows/goes to latest image/video in the chosen Open Camera
+ save folder, rather than the latest image/video on the device. Also
+ fixed problem if there existed a file with a datestamp in the future -
+ these are no longer shown, even if located in the save folder.
+FIXED Some devices displayed "ISO ISO100" etc on popup menu.
+FIXED Pressing "No" to "Clear Save History" left screen as all black.
+FIXED Don't created corrupted video file if video recording stopped too
+ quickly after starting (when no video data is received).
+FIXED Font size used for date/geo stamping photos now scales sensibly with
+ photo resolution.
+FIXED Thumbnail created after taking a photo didn't always reflect actual
+ image saved, if using auto-stabilise or date/geo stamp options.
+FIXED Popup button wasn't being hidden in immersive mode, on devices with
+ flash.
+FIXED Don't show "Force 4K UHD video" option if 4K mode is already supported
+ by the device in the standard video resolutions.
+ADDED Italian translation (thanks to Valerio Bozzolan).
+ADDED Options to customise photo and video filenames.
+ADDED New option "Keep display on", can now disable the behaviour to keep the
+ screen on.
+ADDED Burst mode control now available on popup menu.
+ADDED A custom text can now be stamped onto photos.
+ADDED New option to choose font size for date/geo/text stamping on photos.
+ADDED Can now change video resolution from popup menu.
+ADDED Crop guide 1.5 (3:2).
+ADDED Audio source can now be set to "default" or "Optimised for voice".
+UPDATED GPS listener now prefers GPS provider to Network provider if both are
+ available.
+UPDATED GPS listener now sets min time of 1s, to improve battery usage.
+UPDATED Now uses Material Design theme style on Android 5.
+UPDATED Now uses dark theme for Settings (more consistent with rest of app;
+ better for not ruining night vision; sorry if you preferred the old
+ look, but Android doesn't seem to cope well with mixing themes in the
+ same Activity).
+UPDATED "Photo and video settings" preference screen now split up into "Photo
+ settings", "Video settings" and "Location settings".
+UPDATED Exposure level now displayed in units of "EV".
+UPDATED Toast now displays if focus, ISO, color effect or white balance modes
+ are non-default.
+UPDATED Popup menu now supports horizontal scrolling if there are too many
+ flash, focus or ISO buttons.
+UPDATED Popup menu photo resolution switcher now displays megapixels of each
+ resolution.
+UPDATED Switching resolutions from popup menup is now smoother.
+UPDATED Removed android.hardware.camera.autofocus as being a required feature
+ (this meant Open Camera didn't show up in Google Play on devices
+ without auto-focus camera).
+
+Version 1.22 (2015/01/04)
+
+FIXED Crash if camera couldn't be opened, and GUI buttons were pressed.
+FIXED Crash that could occur if camera closes whilst taking a photo.
+FIXED Crash on Galaxy Nexus in rare circumstances when switching camera.
+FIXED Pressing take photo button didn't cancel burst mode when not using
+ timer.
+FIXED If failed to create video file when recording video, UI wasn't put back
+ into proper state.
+FIXED EXIF tags DateTimeOriginal and DateTimeDigitized weren't being saved in
+ some cases (if using auto-stabilise, geotagging or stamp photo
+ options).
+ADDED New immersive mode (requires Android 4.4 KitKat or higher), under
+ Settings/On screen GUI/Immersive mode. You can set the option to use
+ KitKat's immersive mode, either hiding the GUI, or even hiding
+ everything except the camera preview. Touch the preview or swipe in
+ from side to exit immersive mode. Also added an option to not even dim
+ the on-screen virtual navigation buttons.
+ADDED Option to reset all settings to default.
+ADDED Options to control whether time and/or battery status are displayed
+ on-screen.
+ADDED Can now set burst mode to unlimited.
+ADDED New option to only allow taking photo/video if GPS location data is
+ available.
+ADDED Tagging photos with GPS direction is now a separate option ("Store
+ compass direction" rather than "Store location data"). If you are
+ upgrading and want photos to remain tagged with GPS direction, please
+ enable the new option.
+UPDATED Can now choose new save location from recent save folders picker.
+UPDATED Improved look of recent save folders picker.
+UPDATED Popup menu button now also shows indicator for torch.
+UPDATED If camera only supports one scene mode etc, no point offering the
+ option to the user.
+UPDATED "Take Photo" shortcut is now done as a widget rather than a shortcut
+ (if you previously had this shortcut on your homescreen, you may need
+ to re-add it as a widget).
+
+Version 1.21 (2014/11/15)
+
+FIXED Broadcasts of Camera.ACTION_NEW_PICTURE, com.android.camera.NEW_PICTURE
+ and Camera.ACTION_NEW_VIDEO intents weren't supplying the Uri correctly
+ - auto-uploading for photos and videos with Owncloud should now work.
+FIXED Corrected look of popup animation when in left-handed UI mode.
+FIXED If called via an intent, and returning the bitmap via a parcel, the
+ bitmap wasn't being resized to a small size if auto-stabilise option
+ was enabled.
+ADDED Option to stamp photos with date and time; and also GPS if location
+ data is enabled.
+ADDED New application shortcut "Take Photo", which automatically takes a
+ photo after opening Open Camera.
+ADDED Lock screen widgets, to allow calling Open Camera from the lock screen,
+ and taking a photo from the lock screen (requires Android 4.2).
+UPDATED Popup menu button now shows flash indicator (for flash auto or on).
+UPDATED Cleaned up settings - removed some options that are more easily
+ selected from the popup menu (ISO, white balance, scene mode, color
+ effect, auto-stabilize).
+UPDATED New material design icon.
+
+Version 1.20 (2014/09/21)
+
+FIXED Crash (NumberFormatException in Parameters.getPreviewFpsRange()) on
+ startup for mb526.
+FIXED Problems if settings window was opened while timer was active (timer is
+ now cancelled when going to settings).
+ADDED New popup menu, allows quick access to changing: flash, focus, ISO,
+ white balance, scene mode, color effect, camera resolution, timer.
+UPDATED New icon (by Cosmin Saveanu).
+
+Version 1.19 (2014/09/08)
+
+FIXED Crashes on startup due to invalid parameters being set.
+FIXED Location info was lost when switching camera or changing scene mode.
+FIXED Focus rectangle was shown after switching from video to photo, and
+ wouldn't disappear until a focus occurred.
+FIXED Accents for Spanish translation.
+ADDED New option to display crop guides.
+ADDED Option to rotate preview 180 degrees (useful if using Open Camera with
+ adapters that invert the image).
+UPDATED Material Design icons and colours.
+UPDATED Improved look of toasts to match Android 4.4 look.
+UPDATED Made changing resolution on the popup menu smoother - now possible to
+ quickly go through the resolutions without a pause every time.
+UPDATED Removed some pointless toasts.
+
+Version 1.18 (2014/08/28)
+
+FIXED Problem on some devices (including Nexus 5) where torch didn't turn off
+ until going to flash off mode.
+FIXED Problem on some devices (e.g., Galaxy S5) if in video mode, and focus
+ mode is not continuous, and user went to settings and back, then tried
+ to record - video would hang.
+ADDED Spanish translation (thanks to Mario Sanoguera).
+UPDATED If camera can't be opened, touching the screen now tries to reopen the
+ camera.
+UPDATED Allow installation of app onto external storage.
+
+Version 1.17 (2014/08/24)
+
+FIXED Crash during auto-stabilise if unable to rotate bitmap (out of memory?)
+ now instead reports being unable to auto-stabilise.
+FIXED Crash if failed to start camera preview.
+FIXED Crash when changing flash mode, if camera was lost.
+FIXED Problem where photos were being taken out of focus on some devices (bug
+ introduced in v1.16).
+FIXED "Save location" option didn't work if folder didn't exist (this also
+ meant it didn't work when the app is first installed, unless a
+ photo/video was taken first) (bug introduced in v1.16 with the new file
+ chooser dialog).
+ADDED New options for volume keys: focus, and switch auto-stabilise on/off.
+UPDATED Allow changing flash mode while recording video (so torch can be
+ switched on and off).
+UPDATED Zoom -/+ controls are now disabled by default, to reduce clutter (for
+ those upgrading, you can change this in Settings/On screen GUI).
+UPDATED Use more subtle/natural colors for red/green/blue in UI; improve look
+ of take photo/video icon.
+
+Version 1.16 (2014/08/17)
+
+FIXED Device freeze when recording video on some Samsung devices (e.g.,
+ Galaxy S2, and some Galaxy S3 variants).
+FIXED Fail to take photo if in manual focus mode, and picture was taken
+ whilst focusing.
+FIXED Changing left/right handedness of UI didn't update until app
+ paused/resumed.
+FIXED Problems with left-handed UI when going to settings and back, icons
+ would shift to incorrect positions.
+FIXED When geotagging was enabled, this didn't take effect until the app was
+ paused and resumed (or restarted).
+FIXED If zoomed in, then switch camera or app paused, the camera preview
+ would reset to being unzoomed on some devices (e.g., Nexus 7).
+UPDATED Save folder location is now chosen via a GUI, rather than having to
+ type the path.
+UPDATED If exposure is non-zero, the exposure is displayed on the photo/video
+ toast.
+UPDATED Photo/video toast now displays scene mode if not auto.
+UPDATED Video toast now displays if audio recording is disabled.
+UPDATED Photo/video toast now displays for longer.
+UPDATED Improved behaviour of left-handed UI, to make it more consistent with
+ the behaviour of the right-handed UI.
+UPDATED Minor improvements to alignment of on-screen text.
+UPDATED New icon for switching between photo and video mode.
+UPDATED Virtual buttons now dimmed.
+ADDED Support for video stabilization.
+ADDED Option to disable showing the zoom slider control (Settings/
+ On screen GUI.../Show zoom slider control).
+ADDED Russian translation (thanks to maksnogin).
+
+Version 1.15 (2014/08/02)
+
+FIXED Crash when exiting settings, if camera wasn't opened (bug introduced in
+ v1.14).
+FIXED More crashes due to camera drivers that aren't following Android API
+ specs (Parameters.getFocusMode() should always be non-null, but isn't
+ on some devices!)
+FIXED If video failed due to error (e.g., hitting device max filesize,
+ running out of space, of other errors), Open Camera now stops properly
+ rather than thinking the video is still recording. Last video error is
+ also stored in the about/debug window.
+ADDED New option to lock orientation to portrait or landscape.
+ADDED New option Flash while recording video.
+UPDATED Touch to set focus/metering now works while recording video.
+
+Version 1.14 (2014/07/22)
+
+FIXED Crash when clicking to switch between photo/video modes if camera
+ couldn't be opened.
+FIXED Problem with face detection not resuming after focusing on mtk6589.
+FIXED Workaround for aspect ratio bug introduced in Android 4.4.3:
+ http://code.google.com/p/android/issues/detail?id=70830
+ Problem that this caused with aspect ratio with video recording.
+FIXED Open Camera now available as a choice when camera icon pressed from
+ Gallery app (Open Camera now responds to
+ android.media.action.STILL_IMAGE_CAMERA).
+FIXED Open Camera now available as a choice when camera icon pressed from
+ Cover Lock Screen (beta) app (Open Camera now responds to
+ android.media.action.STILL_IMAGE_CAMERA_SECURE).
+FIXED All available video resolutions are now supported.
+ADDED New "manual" focus mode - focusing happens when you touch the screen,
+ but it doesn't do automatic focusing when taking a photo.
+ADDED Support for more hardware buttons: camera button to take photo/video;
+ focus button; zoom in/out buttons.
+ADDED Long press on gallery icon now shows a popup of recent save folders (if
+ more than one is available), allowing you to quickly change between
+ them.
+ADDED Location data now stored in videos too, if geotagging option is enabled
+ (only for MPEG4 and 3GPP video formats).
+ADDED Option for volume keys to do nothing (not even changing the device
+ volume).
+ADDED Option to lock screen when recording video (swipe to unlock).
+ADDED Option to limit duration of recording video (automatically stops after
+ specified time); also option to restart video a specified number of
+ times.
+UPDATED Significantly improved speed for opening and closing settings (now
+ launched as a Fragment rather than a separate Activity).
+UPDATED Open Camera now remains active rather than being blocked by a "screen
+ lock" (face/PIN unlock still required to go to Gallery or Settings).
+ This behaviour can be switched off by going to Settings/More Camera
+ Controls/Show camera when locked.
+UPDATED Broadcast Camera.ACTION_NEW_PICTURE and com.android.camera.NEW_PICTURE
+ intents for new photos, and Camera.ACTION_NEW_VIDEO for new videos.
+UPDATED WYSIWYG mode is now the default setting for the preview size.
+UPDATED Expanded number of devices which show the "Force 4K UHD video
+ (experimental)" option (remember, 4K isn't officially supported by
+ Android API and this option is experimental - if this option shows, it
+ doesn't necessarily mean it will work on your device).
+UPDATED Video time is now shown with smaller font and off-centre, so as to not
+ obscure the view so much.
+
+Version 1.13 (2014/05/24)
+
+FIXED Crash when opening settings on devices that didn't support
+ auto-stabilise (bug introduced in v1.10).
+FIXED Crash introduced in v1.10 to do with cancelling autofocus on some
+ devices.
+ADDED Options for video bitrate and frame rate. Note that both of these are
+ approximate settings, and whether they can be achieved may depend on
+ your device, and other conditions such as lighting. Also note that
+ setting non-default values for bitrate of frame rate may cause video
+ recording to fail, if the values are not supported.
+UPDATED About window now shows device manufacturer and model, to help with
+ debugging.
+
+Version 1.12 (2014/05/19)
+
+UPDATED Improve support for ISO setting (didn't show up on some devices, e.g.,
+ some Galaxy S5 variants).
+
+Version 1.11 (2014/05/17)
+
+ADDED New GUI icon to set exposure lock.
+ADDED New option in settings to set ISO.
+ADDED Displays photo and video resolution etc, when starting up, switching
+ between photo/video, and switching camera.
+ADDED About window now displays all camera parameters.
+UPDATED Don't display switch camera icon if device only has 1 camera.
+UPDATED Always reset to continuous focus mode when starting camera in video
+ mode (fixes some problems on some devices).
+
+Version 1.10 (2014/05/07)
+
+FIXED Preview wouldn't restart after taking photo with continuous focus, on
+ Galaxy Nexus.
+FIXED Problems with aspect ratio when not using WYSIWYG preview mode and
+ recording video.
+FIXED Camcorder profile was always being initialised from the back camera,
+ even when recording video with the front camera.
+ADDED New (experimental!) option to support 4K UHD (3840x2160) video
+ (Settings->Photo and video settings->Force 4K UHD video). Note that 4K
+ video isn't properly supported by Android API, so this option may show
+ even if it isn't supported on your device, and may not work or even
+ crash. I've successfully tested this on a Samsung Galaxy S5 and Note 3.
+ADDED Option to not display the -/+ zoom control.
+UPDATED All available video resolutions offered by the camera are now supported.
+UPDATED Picture and video resolution preferences display aspect ratio and
+ megapixels of each resolution.
+UPDATED Reorganised preferences into new On screen GUI page.
+UPDATED Don't change camera settings whilst camera is autofocusing (may help
+ problems/crashes on some devices).
+
+Version 1.9 (2014/03/22)
+
+FIXED Crash on some devices when starting app or switching camera (bug
+ introduced in v1.8).
+
+Version 1.8 (2014/03/18)
+
+FIXED Crash on startup if Network or GPS location providers not available,
+ and geotagging was enabled.
+FIXED Crash if specified save folder was an empty string.
+FIXED Don't ever turn on flash during autofocus when app is launched.
+FIXED Various other crashes.
+ADDED Option to not force screen display to maximum brightness.
+ADDED Option to display horizontal "level" line.
+ADDED Support for hardware menu button (now opens settings).
+ADDED Option to display a 4x2 grid (if you previously had a 3x3 "rule of
+ thirds" grid, you'll have to reenable it under the Settings).
+ADDED Added privacy policy (for location permission/geotagging) to intro
+ window text (needed for Nokia Store).
+ADDED Uses setRecordingHint, may improve performance of starting video
+ recording.
+ADDED New About option in Settings, providing debug info.
+UPDATED Don't re-autofocus before taking a photo, if camera recently
+ successfully focused due to user touching the screen.
+UPDATED Display yellow or green dot next to earth icon to indicate location
+ accuracy.
+UPDATED Display earth icon with red dash through it, if geotagging is enabled,
+ but the app doesn't have a location.
+UPDATED Current zoom is now saved when app goes idle, or switching cameras.
+UPDATED Offset zoom slider slightly so as to not interfere with Google Now
+ swipe.
+UPDATED Allow greater range of characters (including unicode) for save location
+ (now allows any character other than those reserved by filesystem).
+
+Version 1.7 (2014/01/29)
+
+FIXED More fixes for aspect ratio - the preview display should now always
+ have a 1:1 aspect ratio (on some devices this may mean black bars are
+ shown, if there isn't a match between the camera's available preview
+ sizes, and the aspect ratio of the device's display).
+FIXED Possible crash relating to creating thumbnails.
+FIXED Autofocus on startup didn't always actually focus.
+FIXED If camera doesn't support focus areas, but does support metering areas,
+ still set the metering area.
+FIXED Was sometimes trying to set metering areas when metering areas not
+ supported by device.
+FIXED If image is deleted after taking the photo, the thumbnail is now
+ properly updated to what is now the most recent photo or video.
+ADDED New option to set preview aspect ratio to match the picture/video
+ aspect ratio (WYSIWIG).
+UPDATED Save folder can now be an absolute path, allowing possibility to save
+ on external SD cards (though you need to know what the path is, which
+ typically varies depending on device; I am unable to test this, so
+ please let me know if it does or doesn't work).
+UPDATED Zoom -/+ control now matches the zoom slider orientation.
+UPDATED Hide some icons when taking video, as they don't do anything.
+
+Version 1.6 (2014/01/20)
+
+FIXED Crash when trying to access image or video resolutions in settings, if
+ camera didn't offer these settings.
+FIXED Exposure compensation wasn't available on devices if min or max
+ exposure compensation level was equal to 0.
+FIXED Aspect ratio problems with the preview on some devices (if you are
+ still having problems, please let me know what Android device you are
+ using).
+FIXED Aspect ratio problems with the preview on all devices when switching
+ the camera.
+FIXED Problem on smaller devices where on-screen text overlapped with take
+ photo button; the text is now aligned to above the button on all
+ devices.
+ADDED Zoom can now be also controlled via on-screen slider (next to the
+ plus/minus zoom buttons).
+ADDED Option for volume keys to control the exposure compensation.
+ADDED Option to display compass direction of camera on-screen (defaults to
+ on, disable it in options if you don't like it).
+ADDED Option to choose microphone for recording audio (support for external
+ microphones).
+UPDATED Exposure compensation is now set via a new button in the on-screen GUI
+ (the black and white +/- symbol) instead of the settings. Clicking this
+ will bring up a slider and plus/minus buttons to adjust the exposure
+ compensation. To get rid of the slider and buttons, either click the
+ Exposure button again, or click elsewhere on the screen.
+UPDATED Geotagging now stores image compass direction (GPSImgDirection,
+ GPSImgDirectionRef).
+UPDATED Display degree symbol for displayed on-screen angles.
+UPDATED Zoom control is now transparent.
+UPDATED Filter applied to sensor for device angle.
+
+Version 1.5 (2014/01/09)
+
+FIXED Crash on Android 4.4 when launching for first time, or changing the
+ save folder (issue with creating the save folder, due to Android no
+ longer allowing ANDROID_MEDIA_MOUNTED to be broadcast).
+FIXED Crash if failed to open camera after switching cameras, then user tried
+ to zoom.
+FIXED Other potential crashes (NumberFormatException on "es209ra" on startup;
+ RuntimeException on Xperia Go when taking photo; RuntimeException on
+ "maxx_ax5" when taking photo with face detection).
+
+Version 1.4 (2013/12/16)
+
+FIXED Calculation for focus areas wasn't right for front facing cameras.
+FIXED Exif data wasn't getting saved if auto-stabilise option was enabled
+ (this also meant that on some cameras/focus modes, the orientation
+ would have been incorrect).
+FIXED "Toast" pop-up messages looked poor on Android 4.4.
+FIXED Fixed potential crash if taking picture fails.
+FIXED Touch to focus with continuous focus shouldn't show red box.
+FIXED Crash with auto-stabilise for some angles larger than 90 degrees.
+FIXED Crash when rotating device when viewing "Photo and video settings".
+FIXED If device was rotated when app was idle, the UI sometimes showed with
+ the incorrect orientation.
+ADDED Images now tagged with current location (optional, off by default). Note
+ that Open Camera now requires Location permission, for this feature.
+ADDED Option for face detection.
+ADDED Touch to select focus area also now sets the metering area (used to
+ determine exposure).
+ADDED Now displays current time.
+ADDED Option to display a 3x3 grid ("rule of thirds").
+ADDED Now displays flashy thumbnail animation when taking a photo (you can
+ disable this in the options under "More camera controls..." if you
+ don't like this sort of thing!)
+UPDATED Gallery button now displays thumbnail of last image/video taken.
+UPDATED Clicking the Gallery button now goes to most recent image/video.
+UPDATED Made it easier to see on-screen text when underlying photo preview is
+ bright, by drawing a background with the text.
+
+Version 1.3 (2013/11/18)
+
+FIXED Video files may not have shown up properly in other apps, or over USB,
+ until rebooting device.
+FIXED Make sure filenames for images/videos are unique.
+ADDED New burst mode option - take a repeated set of photos at once, or with
+ a delay.
+ADDED Option for video resolution.
+ADDED Display battery status on screen.
+UPDATED Reorganised settings screen to remove clutter - less commonly used
+ options have been moved to sub-screens; "Record audio?" is moved to
+ "Photo and video settings..."
+UPDATED Only show zoom level (if that option is set) when actually zoomed in.
+
+Version 1.2 (2013/11/09)
+
+FIXED Crash when launching gallery, if no Gallery app installed on device.
+FIXED Selecting a focus area shouldn't switch to focus mode auto.
+FIXED Focus area wasn't getting reset when it should (e.g., changing focus
+ mode or zooming).
+FIXED Fixed potential crash on auto focus.
+FIXED Hide GUI options while taking a photo (otherwise problems can be caused
+ by changing options, e.g., changing focus mode).
+FIXED Message for failing to open camera was too wide for portrait view.
+ADDED Option for exposure compensation.
+ADDED Show whether auto focus was successful or not (via red/green rectangle)
+ even if focus area not selected.
+UPDATED Pause after taking photo now defaults to false.
+UPDATED Made "toasts" look nicer.
+UPDATED Added simple instructions to the intro window shown on first time
+ start-up.
+
+Version 1.1 (2013/10/27)
+
+ADDED Touch to select focus area.
+ADDED Optional beep on timer countdown.
+ADDED Option on whether to display the current camera zoom level on screen.
+
+Version 1.0 (2013/10/17)
+
+First release.
+
+
+
+Open Camera Privacy Policy.
+This website uses icons from third party sources, see licences.
+Open Camera on Sourceforge.
+
+
+
+
diff --git a/_docs/ic_exposure_white_48dp.png b/_docs/ic_exposure_white_48dp.png
new file mode 100644
index 0000000..92742ef
Binary files /dev/null and b/_docs/ic_exposure_white_48dp.png differ
diff --git a/_docs/ic_gps_fixed_white_48dp.png b/_docs/ic_gps_fixed_white_48dp.png
new file mode 100644
index 0000000..b525cf8
Binary files /dev/null and b/_docs/ic_gps_fixed_white_48dp.png differ
diff --git a/_docs/ic_launcher.png b/_docs/ic_launcher.png
new file mode 100644
index 0000000..008873d
Binary files /dev/null and b/_docs/ic_launcher.png differ
diff --git a/_docs/ic_mic_white_48dp.png b/_docs/ic_mic_white_48dp.png
new file mode 100644
index 0000000..70e9ab0
Binary files /dev/null and b/_docs/ic_mic_white_48dp.png differ
diff --git a/_docs/ic_pause_circle_outline_white_48dp.png b/_docs/ic_pause_circle_outline_white_48dp.png
new file mode 100644
index 0000000..683d5c5
Binary files /dev/null and b/_docs/ic_pause_circle_outline_white_48dp.png differ
diff --git a/_docs/index.html b/_docs/index.html
new file mode 100644
index 0000000..d73e7db
--- /dev/null
+++ b/_docs/index.html
@@ -0,0 +1,326 @@
+
+
+
+Open Camera
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Open Camera
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Jump to Instructions.
+
+Open Camera is an Open Source Camera app for Android™ phones and tablets. Features:
+
+Option to auto-level so your pictures are perfectly level no matter what.
+Expose your camera's functionality: support for scene modes, color effects, white balance, ISO, exposure compensation/lock, selfie with "screen flash", HD video and more.
+Handy remote controls: timer (with optional voice countdown), auto-repeat mode (with configurable delay).
+Option to take photo remotely by making a noise.
+Configurable volume keys and user interface.
+Upside-down preview option for use with attachable lenses.
+Overlay a choice of grids and crop guides.
+Optional GPS location tagging (geotagging) of photos and videos; for photos this includes compass direction (GPSImgDirection, GPSImgDirectionRef).
+Apply date and timestamp, location coordinates, and custom text to photos; store date/time and location as video subtitles (.SRT).
+Option to remove device exif metadata from photos.
+Panorama, including for front camera.
+Support for HDR (with auto-alignment and ghost removal) and Exposure Bracketing.
+Support for Camera2 API: manual controls (with optional focus assist); burst mode; RAW (DNG) files; camera vendor extensions; slow motion video; log profile video.
+Noise reduction (including low light night mode) and Dynamic range optimisation modes.
+Options for on-screen histogram, zebra stripes, focus peaking.
+Focus bracketing mode.
+Completely free, and no third party ads in the app (I only run third party ads on the website). Open Source.
+
+
+(Some features may not be available on all devices, as they may depend on hardware or camera features, the Android version, etc.)
+
+
+Get it on Google Play.
+
+
+
+Open Camera Blog ~
+Discussion Forums ~
+Code Repository (Git)
+
+
+
+Contents:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Open Camera requires Android 5.0 or better (versions 1.53.1 or earlier also supported 4.0.3 or better).
+Some features may only be available
+on some devices (it may depend on Android version, or require specific support from the camera/device).
+
+Note that it's not possible for me to test Open Camera on every Android device out there, let alone in combination with different
+Android versions (or especially alternative ROMs). Please test before using Open Camera to photo/video
+your wedding etc :)
+
+See here for some details on issues with various devices.
+
+
+
+
+
+Open Camera is written by Mark Harman with additional contributors, see credits for details.
+
+
+
+
+
+See my privacy policy for details.
+
+
+
+
+
+
+Open Camera is released under the GPL v3 or later . The source code is
+available from
+
+https://sourceforge.net/projects/opencamera/files/ .
+
+Also see "Can I use the Open Camera source code in my app?" under the FAQ .
+
+
+Open Camera uses the AndroidX/Jetpack libraries, under Apache license version 2.0 .
+
+The following files are used in Open Camera:
+
+
+
+Open Camera uses icons from Google's Material Design icons - from
+ https://developer.android.com/design/downloads/index.html /
+ https://design.google.com/icons/ /
+ https://github.com/google/material-design-icons/ /
+ https://google.github.io/material-design-icons/ /
+ https://fonts.google.com/icons ,
+ by Google, under Apache license version 2.0
+ (licence text also available here .)
+ (some cases include modifications, no need to credit me).
+ In particular:
+ baseline_add_a_photo_white_48.png,
+ baseline_bedtime_white_48.png
+ baseline_bluetooth_white_48.png, baseline_check_white_48.png, baseline_close_white_48.png,
+ baseline_delete_white_48.png,
+ baseline_face_retouching_natural_white_48.png,
+ baseline_filter_vintage_white_48.png,
+ baseline_folder_open_white_48.png,
+ baseline_highlight_white_48.png,
+ baseline_panorama_horizontal_white_48.png,
+ baseline_photo_library_white_48.png,
+ baseline_portrait_white_48.png,
+ baseline_remove_red_eye_white_48.png,
+ baseline_rotate_left_white_48.png, baseline_rotate_right_white_48.png,
+ baseline_shutter_speed_white_48.png,
+ baseline_switch_camera_white_48.png,
+ baseline_text_fields_red_48.png (modified from baseline_text_fields_white_48), baseline_text_fields_white_48.png,
+ exposure_locked.png (modified from baseline_lock_white_48 and ic_exposure_white_48dp),
+ exposure_unlocked.png (modified from baseline_lock_open_white_48 and ic_exposure_white_48dp),
+ flash_auto.png (from baseline_flash_auto_white_48), flash_off.png (from baseline_flash_off_white_48),
+ flash_on.png (from ic_action_flash_on),
+ focus_mode_continuous_picture.png and focus_mode_continuous_video.png (from baseline_loop_white_48),
+ focus_mode_infinity (from baseline_loop_white_48),
+ focus_mode_locked.png (modified from baseline_lock_white_48),
+ ic_burst_mode_white_48dp.png, ic_colorize_white_48dp.png,
+ ic_exposure_red_48dp.png, ic_exposure_white_48dp.png, ic_face_red_48dp.png (modified from ic_face_white_48dp), ic_face_white_48dp.png,
+ ic_fast_forward_white_48dp.png,
+ ic_gps_fixed_red_48dp.png (modified from ic_gps_fixed_white_48dp), ic_gps_fixed_white_48dp.png,
+ ic_gps_off_white_48dp.png, ic_hdr_on_white_48dp.png, ic_help_outline_white_48dp.png, ic_info_outline_white_48dp.png,
+ ic_launcher_take_photo.png (modified from ic_photo_camera_white_48dp),
+ ic_mic_off_white_48dp.png, ic_mic_red_48dp.png (modified from ic_mic_white_48dp), ic_mic_white_48dp.png,
+ ic_more_horiz_white_48dp.png,
+ ic_pause_circle_outline_white_48dp.png, ic_photo_camera_white_48dp.png, ic_photo_size_select_large_white_48dp.png,
+ ic_play_circle_outline_white_48dp.png,
+ ic_power_settings_new_white_48dp.png, ic_save_white_48dp.png,
+ ic_slow_motion_video_white_48dp.png,
+ ic_text_format_red_48dp.png (modified from ic_text_format_white_48dp), ic_text_format_white_48dp.png,
+ ic_timelapse_white_48dp.png, ic_timer_white_48dp.png,
+ ic_touch_app_white_48dp.png, ic_videocam_white_48dp.png,
+ ic_stat_notify_take_photo.png (modified from ic_photo_camera_white_48dp),
+ key_visualizer_red.xml (modified from key_visualizer), key_visualizer.xml,
+ popup*.png (modified from ic_more_vert_white, baseline_highlight_white, baseline_remove_red_eye_white, baseline_flash_auto_white,
+ baseline_flash_off_white, ic_action_flash_on),
+ settings.png (from ic_action_settings), share.png (from ic_action_share),
+ switch_camera.png (modified from baseline_loop_white_48),
+ take_photo.png (modified from ic_photo_camera_white_48dp), take_photo_pref.png (modified from ic_photo_camera_white_48dp),
+ take_photo_pressed.png (modified from ic_photo_camera_white_48dp), take_photo_when_video_recording.png (modified from ic_photo_camera_white_48dp),
+ take_video.png (modified from baseline_videocam_white_48), take_video_pref.png (modified from baseline_videocam_white_48),
+ take_video_pressed.png (modified from baseline_videocam_white_48), take_video_recording.png (modified from baseline_videocam_white_48),
+ white_balance_locked.png (modified from baseline_lock_white_48),
+ white_balance_unlocked.png (modified from baseline_lock_open_white_48).
+ Modified versions of some of these icons are also used on this website.
+ Open Camera's app icon/logo also makes use of ic_photo_camera by Google (also Apache license version 2.0).
+
+
+Note that old versions of Open Camera also used the following:
+
+
+
+
+
+
+
+
+
+
+exposure_locked.png, focus_mode_locked.png, white_balance_locked.png modified from https://www.iconfinder.com/icons/128411/antivirus_close_forbid_hide_lock_locked_password_privacy_private_protection_restriction_safe_secure_security_icon#size=64 , by Aha-Soft, under CC BY 3.0 (no need to credit me).
+exposure_unlocked.png, white_balance_unlocked.png modified from https://www.iconfinder.com/icons/128416/free_freedom_hack_lock_open_padlock_password_secure_security_unlock_unlocked_icon#size=64 , by Aha-Soft, under CC BY 3.0 (no need to credit me).
+flash_off.png, flash_auto.png, flash_on.png from https://www.iconfinder.com/icons/62201/flash_icon#size=64, by The Working Group, under CC BY-SA 3.0 .
+flash_red_eye.png, popup_flash_red_eye.png from https://www.iconfinder.com/icons/103177/eye_see_view_watch_icon#size=128 , by Designmodo / Andrian Valeanu, under CC BY 3.0 (no need to credit me).
+flash_torch.png, popup_torch.png from https://www.iconfinder.com/icons/51924/bulb_light_icon#size=128 , by IconFinder - http://www.iconfinder.net , by CC BY 3.0 .
+focus_mode_macro.png from https://www.iconfinder.com/icons/81105/macro_mb_icon#size=128 , by Yankoa - http://yankoa.deviantart.com/ , under CC BY 3.0 .
+gallery.png from https://www.iconfinder.com/icons/6915/book_gallery_images_photos_pictures_icon#size=128, by Alessandro Rei, under GPL v3 .
+settings.png from https://www.iconfinder.com/icons/115801/settings_icon#size=128, by Designmodo / Andrian Valeanu, under CC BY 3.0 .
+share.png from https://www.iconfinder.com/icons/111030/share_icon#size=128, by WPZOOM, under CC BY-SA 3.0 .
+switch_camera.png from https://www.iconfinder.com/icons/103031/3d_rotate_icon#size=64, by Valera Zvonko, under CC BY 3.0 .
+switch_video.png from https://www.iconfinder.com/icons/92787/film_photo_icon#size=32, by FatCow Web Hosting, under CC BY 3.0 .
+switch_video.png - merged from images https://www.iconfinder.com/icons/81087/mb_photo_icon#size=128 and https://www.iconfinder.com/icons/81197/mb_rec_video_icon#size=128 by Yankoa, under CC BY 3.0 (no need to credit me).
+take_video.png, take_video_pref.png, take_video_pressed.png, take_video_recording.png from https://www.iconfinder.com/icons/81197/mb_rec_video_icon#size=128 , by Yankoa - http://yankoa.deviantart.com/ , under CC BY 3.0 .
+App icon/logo, take_photo.png, take_photo_pressed.png from https://www.iconfinder.com/icons/81087/mb_photo_icon#size=128, by Yankoa, under CC BY 3.0 .
+trash.png from https://www.iconfinder.com/icons/115789/trash_icon#size=128, by Designmodo / Andrian Valeanu, under CC BY 3.0 .
+
+
+Android, Google Play and the Google Play logo are trademarks of Google LLC.
+
+
+
+
+Open Camera Privacy Policy.
+This website uses icons from third party sources, see licences.
+Open Camera on Sourceforge.
+
+
+
+
diff --git a/_docs/info.html b/_docs/info.html
new file mode 100644
index 0000000..98df1c4
--- /dev/null
+++ b/_docs/info.html
@@ -0,0 +1,83 @@
+
+
+
+Open Camera
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Open Camera
+
+
+< Main Page.
+
+
+
+
+
+
+
+
+
+
+
+I am not currently accepting donations. Thanks to those who have supported me in the past!
+
+
+
+
+
+Open Camera can also be installed from F-Droid .
+
+The APK files are also available from
+
+https://sourceforge.net/projects/opencamera/files/ . To install the APK directly, you will likely have to enable "Unknown sources" to allow installation, on your device's Settings (usually under Security) - if so, you may wish to consider disabling the option again after installing, for security.
+
+
+
+
+Open Camera Privacy Policy.
+This website uses icons from third party sources, see licences.
+Open Camera on Sourceforge.
+
+
+
+
diff --git a/_docs/popup.png b/_docs/popup.png
new file mode 100644
index 0000000..5285d9b
Binary files /dev/null and b/_docs/popup.png differ
diff --git a/_docs/privacy_oc.html b/_docs/privacy_oc.html
new file mode 100644
index 0000000..c2251c3
--- /dev/null
+++ b/_docs/privacy_oc.html
@@ -0,0 +1,117 @@
+
+
+
+Open Camera Privacy Policy
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Open Camera Privacy Policy
+
+
+< Main Page.
+
+
+
+
+
+
+
+Open Camera is developed by Mark Harman.
+
+Open Camera accesses and records camera sensor and microphone data, which is used for the purpose
+of taking photos and recording videos, to fulfil its purpose as a camera. Microphone permission is also used for the optional "Audio control" options.
+
+Open Camera requires permission (at least for Android 9 and earlier, or using versions of Open Camera older than 1.48.3) to
+ "access photos, media and files on your devices" (storage permission), as this permission is required for Android to save resultant files such as photos and videos to your device.
+
+Location permission is requested in order to deliver the optional geotagging features (for photos and videos, including stamp and subtitles options).
+ When relevant option(s) are enabled, your device location will be stored in photo/video/subtitle files.
+
+Bluetooth permissions are used to allow the optional feature to discover and connect to Bluetooth LE remote control devices;
+ the Bluetooth remote control feature also requires location permission (on Android 11 or earlier) or
+ Nearby Devices permission (on Android 12 or later).
+
+Resultant data such as photos or videos can be shared with
+ other apps if you use the share option in Open Camera, or when Open Camera is called by
+ another app on your device, or when you use the Storage Access Framework option to save
+ to another app or service.
+
+Data handling procedures, data retention and deletion policies: Open Camera
+ does not transmit personal or sensitive information to me.
+
+Since Open Camera also uses operating system APIs, you should review relevant privacy policies
+ such as for your device, manufacturer, operating system and/or Google accounts. For example:
+
+ For versions 1.49.2 or earlier: the optional voice control option used the Android
+ speech recognition service .
+ When enabled, audio data is likely to be sent to remote servers by Android to perform speech recognition.
+ This is subject to the Data Processing Addendum for Products where Google is a Data Processor,
+ located at
+ https://privacy.google.com/businesses/gdprprocessorterms/ , as updated from time to time.
+ This option is no longer available in version 1.50 onwards.
+
+ For versions 1.49.2 or earlier: The "addresses" option for photo stamp or video subtitles used the Android
+ Geocoder API .
+ When this option is enabled, in order to deliver this functionality the API transmits your device location data across the Internet to a
+ third party (which may depend on what "backend services" are installed on your device).
+ This option is no longer available in version 1.50 onwards.
+
+ Apps/services such as cloud services on your device may auto-upload photos and videos that are saved on your device.
+
+
+
+If you have inquiries about my privacy policy, please contact me by email at
+ mark.harman.apps@gmail.com .
+
+
+Although the Open Camera application is ad-free, the Open Camera website has ads via Google Adsense: Third party vendors, including Google, use cookies to
+serve ads based on a user's previous visits to this website or other websites. Google's use of advertising cookies enables it and
+its partners to serve ads based on people's visit to this sites and/or other sites on the Internet. You may opt out of personalised
+advertising by visiting Google's Ads Settings. The cookies of other third-party
+vendors or ad networks may also be used to serve ads. You can opt out of some third-party vendors' uses of cookies for personalised advertising by visiting
+www.aboutads.info .
+
+
+
+Note that cookies are still used for serving even non-personalised ads.
+
+In countries where the GDPR is applicable, Google's Consent Management Platform (CMP) is used to obtain consent to use
+personal data for Google Adsense. In such countries, you may update your choice by either clicking on the privacy and
+cookie link at the bottom of other pages on this site that serve ads, or click "Revoke or change cookie consent" from
+the site menu .
+
+In US states with relevant privacy regulations, you should be able to opt out of personalised advertising by clicking the
+Do Not Sell or Share My Personal Information link at the bottom of other pages on this site that serve ads, and selecting to
+Opt Out.
+
+The Open Camera website also uses Google Analytics which uses cookies, please see their
+Privacy Policy for more details.
+
+Also see "How Google uses information from sites or apps
+that use our services" .
+
+Android is a trademark of Google LLC.
+
+
+Open Camera Privacy Policy.
+This website uses icons from third party sources, see licences.
+Open Camera on Sourceforge.
+
+
+
+
diff --git a/_docs/settings.png b/_docs/settings.png
new file mode 100644
index 0000000..8eb1017
Binary files /dev/null and b/_docs/settings.png differ
diff --git a/_docs/share.png b/_docs/share.png
new file mode 100644
index 0000000..082c915
Binary files /dev/null and b/_docs/share.png differ
diff --git a/_docs/stylesheet.css b/_docs/stylesheet.css
new file mode 100644
index 0000000..804a107
--- /dev/null
+++ b/_docs/stylesheet.css
@@ -0,0 +1,5 @@
+body {
+ color: #000000;
+ background-color: rgb(245,236,220);
+ font-family: Tahoma, Geneva, sans-serif;
+}
diff --git a/_docs/switch_camera.png b/_docs/switch_camera.png
new file mode 100644
index 0000000..fbd5731
Binary files /dev/null and b/_docs/switch_camera.png differ
diff --git a/_docs/take_photo.png b/_docs/take_photo.png
new file mode 100644
index 0000000..aa8464f
Binary files /dev/null and b/_docs/take_photo.png differ
diff --git a/_docs/take_video.png b/_docs/take_video.png
new file mode 100644
index 0000000..32c12eb
Binary files /dev/null and b/_docs/take_video.png differ
diff --git a/androidx_LICENSE-2.0.txt b/androidx_LICENSE-2.0.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/androidx_LICENSE-2.0.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed 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.
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..f9e5b88
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,67 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdk 35
+ compileOptions.encoding = 'UTF-8'
+
+ defaultConfig {
+ applicationId "net.sourceforge.opencamera"
+ minSdkVersion 21
+ targetSdkVersion 35
+ //compileSdkVersion 31 // needed to support appcompat:1.4.0 (which we need for emoji policy support, and not yet ready to target SDK 30)
+
+ testApplicationId "net.sourceforge.opencamera.test"
+ //testInstrumentationRunner "android.test.InstrumentationTestRunner"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
+ }
+ }
+
+
+ // needed to use android.test package (ActivityInstrumentationTestCase2 etc) when targetting sdk 28 (Android 9) -
+ // see https://developer.android.com/training/testing/set-up-project
+ useLibrary 'android.test.runner'
+ useLibrary 'android.test.base'
+ lint {
+ abortOnError false
+ checkReleaseBuilds false
+ }
+ namespace 'net.sourceforge.opencamera'
+ buildFeatures {
+ }
+ //useLibrary 'android.test.mock'
+}
+
+dependencies {
+ androidTestImplementation 'androidx.test.ext:junit:1.3.0'
+
+ //implementation 'androidx.activity:activity:1.9.3' // needed for EdgeToEdge.enable(this)
+
+ // appcompat version must be 1.4.0 or later to satisfy emoji policy!
+ implementation 'androidx.appcompat:appcompat:1.7.1'
+
+ // needed to fix errors since upgrading to appcompat:1.7.0, see https://stackoverflow.com/questions/75263047/duplicate-class-in-kotlin-android
+ implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.9.0"))
+
+ implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+
+ implementation 'androidx.exifinterface:exifinterface:1.4.1'
+
+ testImplementation 'junit:junit:4.13.2'
+
+ // newer AndroidJUnit4 InstrumentedTest
+ androidTestImplementation "androidx.test:runner:1.7.0"
+ androidTestImplementation "androidx.test:rules:1.7.0"
+ androidTestImplementation "androidx.test.espresso:espresso-core:3.7.0"
+}
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(17)
+ }
+}
diff --git a/app/src/androidTest/java/net/sourceforge/opencamera/AvgInstrumentedTests.java b/app/src/androidTest/java/net/sourceforge/opencamera/AvgInstrumentedTests.java
new file mode 100644
index 0000000..4bb4f4b
--- /dev/null
+++ b/app/src/androidTest/java/net/sourceforge/opencamera/AvgInstrumentedTests.java
@@ -0,0 +1,17 @@
+package net.sourceforge.opencamera;
+
+import org.junit.experimental.categories.Categories;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+/** Tests for Avg algorithm - only need to run on a single device
+ * Should manually look over the images dumped onto DCIM/
+ * To use these tests, the testdata/ subfolder should be manually copied to the test device in the DCIM/testOpenCamera/
+ * folder (so you have DCIM/testOpenCamera/testdata/). We don't use assets/ as we'd end up with huge APK sizes which takes
+ * time to transfer to the device every time we run the tests.
+ * On Android 10+, scoped storage permission needs to be given to Open Camera for the DCIM/testOpenCamera/ folder.
+ */
+@RunWith(Categories.class)
+@Categories.IncludeCategory(AvgTests.class)
+@Suite.SuiteClasses({InstrumentedTest.class})
+public class AvgInstrumentedTests {}
diff --git a/app/src/androidTest/java/net/sourceforge/opencamera/HDRInstrumentedTests.java b/app/src/androidTest/java/net/sourceforge/opencamera/HDRInstrumentedTests.java
new file mode 100644
index 0000000..402486e
--- /dev/null
+++ b/app/src/androidTest/java/net/sourceforge/opencamera/HDRInstrumentedTests.java
@@ -0,0 +1,17 @@
+package net.sourceforge.opencamera;
+
+import org.junit.experimental.categories.Categories;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+/** Tests for HDR algorithm - only need to run on a single device
+ * Should manually look over the images dumped onto DCIM/
+ * To use these tests, the testdata/ subfolder should be manually copied to the test device in the DCIM/testOpenCamera/
+ * folder (so you have DCIM/testOpenCamera/testdata/). We don't use assets/ as we'd end up with huge APK sizes which takes
+ * time to transfer to the device every time we run the tests.
+ * On Android 10+, scoped storage permission needs to be given to Open Camera for the DCIM/testOpenCamera/ folder.
+ */
+@RunWith(Categories.class)
+@Categories.IncludeCategory(HDRTests.class)
+@Suite.SuiteClasses({InstrumentedTest.class})
+public class HDRInstrumentedTests {}
diff --git a/app/src/androidTest/java/net/sourceforge/opencamera/HDRNInstrumentedTests.java b/app/src/androidTest/java/net/sourceforge/opencamera/HDRNInstrumentedTests.java
new file mode 100644
index 0000000..663ce78
--- /dev/null
+++ b/app/src/androidTest/java/net/sourceforge/opencamera/HDRNInstrumentedTests.java
@@ -0,0 +1,17 @@
+package net.sourceforge.opencamera;
+
+import org.junit.experimental.categories.Categories;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+/** Tests for HDR algorithm with more than 3 images - only need to run on a single device
+ * Should manually look over the images dumped onto DCIM/
+ * To use these tests, the testdata/ subfolder should be manually copied to the test device in the DCIM/testOpenCamera/
+ * folder (so you have DCIM/testOpenCamera/testdata/). We don't use assets/ as we'd end up with huge APK sizes which takes
+ * time to transfer to the device every time we run the tests.
+ * On Android 10+, scoped storage permission needs to be given to Open Camera for the DCIM/testOpenCamera/ folder.
+ */
+@RunWith(Categories.class)
+@Categories.IncludeCategory(HDRNTests.class)
+@Suite.SuiteClasses({InstrumentedTest.class})
+public class HDRNInstrumentedTests {}
diff --git a/app/src/androidTest/java/net/sourceforge/opencamera/InstrumentedTest.java b/app/src/androidTest/java/net/sourceforge/opencamera/InstrumentedTest.java
new file mode 100644
index 0000000..b66e826
--- /dev/null
+++ b/app/src/androidTest/java/net/sourceforge/opencamera/InstrumentedTest.java
@@ -0,0 +1,7311 @@
+package net.sourceforge.opencamera;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static org.hamcrest.Matchers.anyOf;
+import static org.hamcrest.Matchers.endsWith;
+import static org.junit.Assert.*;
+
+import android.annotation.TargetApi;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.Insets;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.media.CamcorderProfile;
+import android.os.Build;
+import android.os.Looper;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.view.View;
+import android.widget.RelativeLayout;
+import android.widget.SeekBar;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.espresso.matcher.ViewMatchers;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import net.sourceforge.opencamera.cameracontroller.CameraController;
+import net.sourceforge.opencamera.preview.Preview;
+import net.sourceforge.opencamera.ui.DrawPreview;
+import net.sourceforge.opencamera.ui.PopupView;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicReference;
+
+interface MainTests {}
+
+interface PhotoTests {}
+
+interface VideoTests {}
+
+interface HDRTests {}
+
+interface HDRNTests {}
+
+interface AvgTests {}
+
+interface PanoramaTests {}
+
+// ignore warning about "Call to Thread.sleep in a loop", this is only test code
+/** @noinspection BusyWait*/
+@RunWith(AndroidJUnit4.class)
+public class InstrumentedTest {
+
+ private static final String TAG = "InstrumentedTest";
+
+ static final Intent intent;
+ static {
+ // used for code to run before the activity is started
+ intent = new Intent(ApplicationProvider.getApplicationContext(), MainActivity.class);
+ TestUtils.setDefaultIntent(intent);
+ intent.putExtra("test_project_junit4", true);
+
+ // need to run this here, not in before(), so it's run before activity is created (otherwise Camera2 won't be enabled)
+ TestUtils.initTest(ApplicationProvider.getApplicationContext());
+ }
+
+ @Rule
+ //public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class);
+ //public ActivityScenarioRule mActivityRule = new ActivityScenarioRule<>(MainActivity.class);
+ public final ActivityScenarioRule mActivityRule = new ActivityScenarioRule<>(intent);
+
+ /** This is run before each test, but after the activity is started (unlike MainActivityTest.setUp() which
+ * is run before the activity is started).
+ */
+ @Before
+ public void before() throws InterruptedException {
+ Log.d(TAG, "before");
+
+ // don't run TestUtils.initTest() here - instead we do it in the static code block, and then
+ // after each test
+
+ // the following was true for MainActivityTest (using ActivityInstrumentationTestCase2), unclear if it's true for
+ // InstrumentedTest:
+ // don't waitUntilCameraOpened() here, as if an assertion fails in setUp(), it can cause later tests to hang in the suite
+ // instead we now wait for camera to open in setToDefault()
+ //waitUntilCameraOpened();
+ }
+
+ @After
+ public void after() throws InterruptedException {
+ Log.d(TAG, "after");
+
+ // As noted above, we need to call TestUtils.initTest() before the activity starts (so in
+ // the static code block, and not before()). But we should still call initTest() before every
+ // subsequent test (so that settings are reset, and test static variables are reset), so
+ // easiest to do this after each test. This also means the application is left in a default
+ // state after running tests.
+ mActivityRule.getScenario().onActivity(activity -> {
+ Log.d(TAG, "after: init");
+ TestUtils.initTest(activity);
+ });
+
+ Log.d(TAG, "after done");
+ }
+
+ private interface GetActivityValueCallback {
+ T get(MainActivity activity);
+ }
+
+ /** This helper method simplifies getting data from the MainActivity.
+ * We can't call MainActivity classes directly, but instead have to go via
+ * mActivityRule.getScenario().onActivity().
+ */
+ private T getActivityValue(GetActivityValueCallback cb) {
+ AtomicReference resultRef = new AtomicReference<>();
+ mActivityRule.getScenario().onActivity(activity -> resultRef.set( cb.get(activity) ));
+ return resultRef.get();
+ }
+
+ private void waitUntilCameraOpened() {
+ waitUntilCameraOpened(true);
+ }
+
+ private void waitUntilCameraOpened(boolean wait_for_preview) {
+ Log.d(TAG, "wait until camera opened");
+ long time_s = System.currentTimeMillis();
+
+ boolean done = false;
+ while( !done ) {
+ assertTrue( System.currentTimeMillis() - time_s < 20000 );
+ done = getActivityValue(activity -> activity.getPreview().openCameraAttempted());
+ }
+
+ Log.d(TAG, "camera is open!");
+
+ try {
+ Thread.sleep(100); // sleep a bit just to be safe
+ } catch (InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ }
+
+ if( wait_for_preview ) {
+ waitUntilPreviewStarted(); // needed for Camera2 API when starting preview on background thread and not waiting for it to start
+ }
+ }
+
+ private void waitUntilPreviewStarted() {
+ Log.d(TAG, "wait until preview started");
+ long time_s = System.currentTimeMillis();
+
+ boolean done = false;
+ while( !done ) {
+ assertTrue( System.currentTimeMillis() - time_s < 20000 );
+ done = getActivityValue(activity -> activity.getPreview().isPreviewStarted());
+ }
+
+ Log.d(TAG, "preview is started!");
+
+ try {
+ Thread.sleep(100); // sleep a bit just to be safe
+ } catch (InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ }
+ }
+
+ private void waitUntilTimer() {
+ Log.d(TAG, "wait until timer stopped");
+ boolean done = false;
+ while( !done ) {
+ done = getActivityValue(activity -> !activity.getPreview().isOnTimer());
+ }
+ }
+
+ private void restart() {
+ restart(true);
+ }
+
+ private void restart(boolean wait_for_preview) {
+ Log.d(TAG, "restart");
+ mActivityRule.getScenario().recreate();
+ waitUntilCameraOpened(wait_for_preview);
+ Log.d(TAG, "restart done");
+ }
+
+ private void pauseAndResume() {
+ Log.d(TAG, "pauseAndResume");
+ boolean camera_is_open = getActivityValue(activity -> activity.getPreview().getCameraController() != null);
+ pauseAndResume(camera_is_open);
+ }
+
+ private void pauseAndResume(boolean wait_until_camera_opened) {
+ Log.d(TAG, "pauseAndResume: " + wait_until_camera_opened);
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ Log.d(TAG, "pause...");
+ getInstrumentation().callActivityOnPause(activity);
+ Log.d(TAG, "resume...");
+ getInstrumentation().callActivityOnResume(activity);
+ });
+ if( wait_until_camera_opened ) {
+ waitUntilCameraOpened();
+ }
+ }
+
+ private void updateForSettings() {
+ Log.d(TAG, "updateForSettings");
+ mActivityRule.getScenario().onActivity(activity -> {
+ assertEquals(Looper.getMainLooper().getThread(), Thread.currentThread()); // check on UI thread
+ // updateForSettings has code that must run on UI thread
+ activity.initLocation(); // initLocation now called via MainActivity.setWindowFlagsForCamera() rather than updateForSettings()
+ activity.getApplicationInterface().getDrawPreview().updateSettings();
+ activity.updateForSettings(true);
+ });
+
+ waitUntilCameraOpened(); // may need to wait if camera is reopened, e.g., when changing scene mode - see testSceneMode()
+ // but we also need to wait for the delay if instead we've stopped and restarted the preview, the latter now only happens after dim_effect_time_c
+ try {
+ Thread.sleep(DrawPreview.dim_effect_time_c+50); // wait for updateForSettings
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ }
+ }
+
+ /** Used to click when we have View instead of an Id. It should only be called from onActivity()
+ * (so that we can be sure we're already on the UI thread).
+ */
+ private void clickView(final View view) {
+ Log.d(TAG, "clickView: "+ view);
+ assertEquals(Looper.getMainLooper().getThread(), Thread.currentThread()); // check on UI thread
+ assertEquals(view.getVisibility(), View.VISIBLE);
+ assertTrue(view.performClick());
+ }
+
+ private void openPopupMenu() {
+ Log.d(TAG, "openPopupMenu");
+ assertFalse( getActivityValue(MainActivity::popupIsOpen) );
+ onView(withId(R.id.popup)).check(matches(isDisplayed()));
+ onView(withId(R.id.popup)).perform(click());
+
+ Log.d(TAG, "wait for popup to open");
+
+ boolean done = false;
+ while( !done ) {
+ done = getActivityValue(MainActivity::popupIsOpen);
+ }
+ Log.d(TAG, "popup is now open");
+ }
+
+ private void switchToFlashValue(String required_flash_value) {
+ Log.d(TAG, "switchToFlashValue: "+ required_flash_value);
+ boolean supports_flash = getActivityValue(activity -> activity.getPreview().supportsFlash());
+ if( supports_flash ) {
+ String flash_value = getActivityValue(activity -> activity.getPreview().getCurrentFlashValue());
+ Log.d(TAG, "start flash_value: "+ flash_value);
+ if( !flash_value.equals(required_flash_value) ) {
+
+ openPopupMenu();
+
+ String flash_value_f = flash_value;
+ mActivityRule.getScenario().onActivity(activity -> {
+ View currentFlashButton = activity.getUIButton("TEST_FLASH_" + flash_value_f);
+ assertNotNull(currentFlashButton);
+ assertEquals(currentFlashButton.getAlpha(), PopupView.ALPHA_BUTTON_SELECTED, 1.0e-5);
+ View flashButton = activity.getUIButton("TEST_FLASH_" + required_flash_value);
+ assertNotNull(flashButton);
+ assertEquals(flashButton.getAlpha(), PopupView.ALPHA_BUTTON, 1.0e-5);
+ clickView(flashButton);
+ });
+
+ flash_value = getActivityValue(activity -> activity.getPreview().getCurrentFlashValue());
+ Log.d(TAG, "changed flash_value to: "+ flash_value);
+ }
+ assertEquals(flash_value, required_flash_value);
+ String controller_flash_value = getActivityValue(activity -> activity.getPreview().getCameraController().getFlashValue());
+ Log.d(TAG, "controller_flash_value: "+ controller_flash_value);
+ if( flash_value.equals("flash_frontscreen_auto") || flash_value.equals("flash_frontscreen_on") ) {
+ // for frontscreen flash, the controller flash value will be "" (due to real flash not supported) - although on Galaxy Nexus this is "flash_off" due to parameters.getFlashMode() returning Camera.Parameters.FLASH_MODE_OFF
+ assertTrue(controller_flash_value.isEmpty() || controller_flash_value.equals("flash_off"));
+ }
+ else {
+ Log.d(TAG, "expected_flash_value: "+ flash_value);
+ assertEquals(flash_value, controller_flash_value);
+ }
+ }
+ }
+
+ private void switchToFocusValue(String required_focus_value) {
+ Log.d(TAG, "switchToFocusValue: "+ required_focus_value);
+ boolean supports_focus = getActivityValue(activity -> activity.getPreview().supportsFocus());
+ if( supports_focus ) {
+ String focus_value = getActivityValue(activity -> activity.getPreview().getCurrentFocusValue());
+ Log.d(TAG, "start focus_value: "+ focus_value);
+ if( !focus_value.equals(required_focus_value) ) {
+
+ openPopupMenu();
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ View focusButton = activity.getUIButton("TEST_FOCUS_" + required_focus_value);
+ assertNotNull(focusButton);
+ clickView(focusButton);
+ });
+
+ focus_value = getActivityValue(activity -> activity.getPreview().getCurrentFocusValue());
+ Log.d(TAG, "changed focus_value to: "+ focus_value);
+ }
+ assertEquals(focus_value, required_focus_value);
+ String controller_focus_value = getActivityValue(activity -> activity.getPreview().getCameraController().getFocusValue());
+ Log.d(TAG, "controller_focus_value: "+ controller_focus_value);
+ boolean using_camera2 = getActivityValue(activity -> activity.getPreview().usingCamera2API());
+ String compare_focus_value = focus_value;
+ if( compare_focus_value.equals("focus_mode_locked") )
+ compare_focus_value = "focus_mode_auto";
+ else if( compare_focus_value.equals("focus_mode_infinity") && using_camera2 )
+ compare_focus_value = "focus_mode_manual2";
+ assertEquals(compare_focus_value, controller_focus_value);
+ }
+ }
+
+ private void switchToISO(int required_iso) {
+ Log.d(TAG, "switchToISO: "+ required_iso);
+ int iso = getActivityValue(activity -> activity.getPreview().getCameraController().getISO());
+ Log.d(TAG, "start iso: "+ iso);
+ if( iso != required_iso ) {
+ mActivityRule.getScenario().onActivity(activity -> {
+ View exposureButton = activity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureContainer = activity.findViewById(net.sourceforge.opencamera.R.id.exposure_container);
+ assertEquals(exposureContainer.getVisibility(), View.GONE);
+ clickView(exposureButton);
+ });
+ mActivityRule.getScenario().onActivity(activity -> {
+ View exposureContainer = activity.findViewById(net.sourceforge.opencamera.R.id.exposure_container);
+ assertEquals(exposureContainer.getVisibility(), View.VISIBLE);
+ View isoButton = activity.getUIButton("TEST_ISO_" + required_iso);
+ assertNotNull(isoButton);
+ clickView(isoButton);
+ });
+ try {
+ Thread.sleep(DrawPreview.dim_effect_time_c+50); // wait for updateForSettings
+ //this.getInstrumentation().waitForIdleSync();
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ }
+ iso = getActivityValue(activity -> activity.getPreview().getCameraController().getISO());
+ Log.d(TAG, "changed iso to: "+ iso);
+ mActivityRule.getScenario().onActivity(activity -> {
+ View exposureButton = activity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureContainer = activity.findViewById(net.sourceforge.opencamera.R.id.exposure_container);
+ clickView(exposureButton);
+ assertEquals(exposureContainer.getVisibility(), View.GONE);
+ });
+ }
+ assertEquals(iso, required_iso);
+ }
+
+ /* Sets the camera up to a predictable state:
+ * - Flash off (if flash supported)
+ * - Focus mode picture continuous (if focus modes supported)
+ * As a side-effect, the camera and/or camera parameters values may become invalid.
+ */
+ private void setToDefault() {
+ waitUntilCameraOpened();
+
+ assertFalse( getActivityValue(activity -> activity.getPreview().isVideo()) );
+
+ switchToFlashValue("flash_off");
+ switchToFocusValue("focus_mode_continuous_picture");
+
+ // pause for safety - needed for Nokia 8 at least otherwise some tests like testContinuousPictureFocusRepeat,
+ // testLocationOff result in hang whilst waiting for photo to be taken, and hit the timeout in waitForTakePhoto()
+ try {
+ Thread.sleep(200);
+ }
+ catch (InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ }
+ }
+
+ /*@Test
+ public void testDummy() {
+ Log.d(TAG, "testDummy");
+ }*/
+
+ private static void checkHistogramDetails(TestUtils.HistogramDetails hdrHistogramDetails, int exp_min_value, int exp_median_value, int exp_max_value) {
+ TestUtils.checkHistogramDetails(hdrHistogramDetails, exp_min_value, exp_median_value, exp_max_value);
+ }
+
+ /** Tests calling the DRO routine with 0.0 factor, and DROALGORITHM_NONE - and that the resultant image is identical.
+ */
+ @Category(HDRTests.class)
+ @Test
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public void testDROZero() throws IOException, InterruptedException {
+ Log.d(TAG, "testDROZero");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ Bitmap bitmap = TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR3/input1.jpg");
+ Bitmap bitmap_saved = bitmap.copy(bitmap.getConfig(), false);
+
+ try {
+ Thread.sleep(1000); // wait for camera to open
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ }
+
+ List inputs = new ArrayList<>();
+ inputs.add(bitmap);
+ try {
+ activity.getApplicationInterface().getHDRProcessor().processHDR(inputs, true, null, true, null, 0.0f, 4, true, HDRProcessor.TonemappingAlgorithm.TONEMAPALGORITHM_REINHARD, HDRProcessor.DROTonemappingAlgorithm.DROALGORITHM_NONE);
+ }
+ catch(HDRProcessorException e) {
+ Log.e(TAG, "processHDR failed", e);
+ throw new RuntimeException();
+ }
+
+ TestUtils.saveBitmap(activity, inputs.get(0), "droZerotestHDR3_output.jpg");
+ TestUtils.checkHistogram(activity, bitmap);
+
+ // check bitmaps are the same
+ Log.d(TAG, "compare bitmap " + bitmap);
+ Log.d(TAG, "with bitmap_saved " + bitmap_saved);
+ // sameAs doesn't seem to work
+ //assertTrue( bitmap.sameAs(bitmap_saved) );
+ assertEquals(bitmap.getWidth(), bitmap_saved.getWidth());
+ assertEquals(bitmap.getHeight(), bitmap_saved.getHeight());
+ int [] old_row = new int[bitmap.getWidth()];
+ int [] new_row = new int[bitmap.getWidth()];
+ for(int y=0;y { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.avg_images_path + "testAvg3/input0.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testDRODark0_output.jpg", true, -1, -1);
+ });
+ }
+
+ /** Tests DRO only on a dark image.
+ */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ @Category(HDRTests.class)
+ @Test
+ public void testDRODark1() throws IOException, InterruptedException {
+ Log.d(TAG, "testDRODark1");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.avg_images_path + "testAvg8/input0.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testDRODark1_output.jpg", true, -1, -1);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "saintpaul".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR1() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR1");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "saintpaul/input2.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "saintpaul/input3.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "saintpaul/input4.jpg") );
+
+ // actual ISO unknown, so guessing
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR1_output.jpg", false, 1600, 1000000000L);
+
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 39, 253);
+ //checkHistogramDetails(hdrHistogramDetails, 1, 44, 253);
+ //checkHistogramDetails(hdrHistogramDetails, 1, 42, 253);
+ //checkHistogramDetails(hdrHistogramDetails, 1, 24, 254);
+ checkHistogramDetails(hdrHistogramDetails, 2, 30, 254);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "saintpaul", but with 5 images.
+ */
+ @Category(HDRNTests.class)
+ @Test
+ public void testHDR1_exp5() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR1_exp5");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "saintpaul/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "saintpaul/input2.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "saintpaul/input3.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "saintpaul/input4.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "saintpaul/input5.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR1_exp5_output.jpg", false, -1, -1);
+
+ int [] exp_offsets_x = {0, 0, 0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+
+ //checkHistogramDetails(hdrHistogramDetails, 3, 43, 251);
+ //checkHistogramDetails(hdrHistogramDetails, 6, 42, 251);
+ checkHistogramDetails(hdrHistogramDetails, 6, 49, 252);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "stlouis".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR2() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR2");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "stlouis/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "stlouis/input2.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "stlouis/input3.jpg") );
+
+ // actual ISO unknown, so guessing
+ TestUtils.subTestHDR(activity, inputs, "testHDR2_output.jpg", false, 1600, (long)(1000000000L*2.5));
+
+ int [] exp_offsets_x = {0, 0, 2};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR3".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR3() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR3");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR3/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR3/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR3/input2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR3_output.jpg", false, 40, 1000000000L/680);
+
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {1, 0, -1};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+
+ //TestUtils.checkHistogramDetails(hdrHistogramDetails, 3, 104, 255);
+ //TestUtils.checkHistogramDetails(hdrHistogramDetails, 4, 113, 255);
+ TestUtils.checkHistogramDetails(hdrHistogramDetails, 8, 113, 255);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR4".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR4() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR4");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR4/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR4/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR4/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR4_output.jpg", true, 102, 1000000000L/60);
+
+ int [] exp_offsets_x = {-2, 0, 2};
+ int [] exp_offsets_y = {-1, 0, 1};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR5".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR5() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR5");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR5/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR5/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR5/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR5_output.jpg", false, 40, 1000000000L/398);
+
+ // Nexus 6:
+ //int [] exp_offsets_x = {0, 0, 0};
+ //int [] exp_offsets_y = {-1, 0, 0};
+ // OnePlus 3T:
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR6".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR6() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR6");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR6/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR6/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR6/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR6_output.jpg", false, 40, 1000000000L/2458);
+
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {1, 0, -1};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR7".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR7() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR7");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR7/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR7/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR7/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR7_output.jpg", false, 40, 1000000000L/538);
+
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 1};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR8".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR8() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR8");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR8/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR8/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR8/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR8_output.jpg", false, 40, 1000000000L/148);
+
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR9".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR9() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR9");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR9/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR9/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR9/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR9_output.jpg", false, 40, 1000000000L/1313);
+
+ int [] exp_offsets_x = {-1, 0, 1};
+ int [] exp_offsets_y = {0, 0, -1};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR10".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR10() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR10");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR10/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR10/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR10/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR10_output.jpg", false, 107, 1000000000L/120);
+
+ int [] exp_offsets_x = {2, 0, 0};
+ int [] exp_offsets_y = {5, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR11".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR11() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR11");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR11/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR11/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR11/input2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR11_output.jpg", true, 40, 1000000000L/2662);
+
+ int [] exp_offsets_x = {-2, 0, 1};
+ int [] exp_offsets_y = {1, 0, -1};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 48, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 65, 255);
+ checkHistogramDetails(hdrHistogramDetails, 0, 72, 255);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR12".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR12() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR12");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR12/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR12/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR12/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR12_output.jpg", true, 1196, 1000000000L/12);
+
+ int [] exp_offsets_x = {0, 0, 7};
+ int [] exp_offsets_y = {0, 0, 8};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR13".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR13() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR13");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR13/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR13/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR13/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR13_output.jpg", false, 323, 1000000000L/24);
+
+ int [] exp_offsets_x = {0, 0, 2};
+ int [] exp_offsets_y = {0, 0, -1};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR14".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR14() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR14");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR14/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR14/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR14/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR14_output.jpg", false, 40, 1000000000L/1229);
+
+ int [] exp_offsets_x = {0, 0, 1};
+ int [] exp_offsets_y = {0, 0, -1};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR15".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR15() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR15");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR15/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR15/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR15/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR15_output.jpg", false, 40, 1000000000L/767);
+
+ int [] exp_offsets_x = {1, 0, -1};
+ int [] exp_offsets_y = {2, 0, -3};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR16".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR16() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR16");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR16/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR16/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR16/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR16_output.jpg", false, 52, 1000000000L/120);
+
+ int [] exp_offsets_x = {-1, 0, 2};
+ int [] exp_offsets_y = {1, 0, -6};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR17".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR17() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR17");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR17/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR17/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR17/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR17_output.jpg", true, 557, 1000000000L/12);
+
+ // Nexus 6:
+ //int [] exp_offsets_x = {0, 0, -3};
+ //int [] exp_offsets_y = {0, 0, -4};
+ // OnePlus 3T:
+ int [] exp_offsets_x = {0, 0, -2};
+ int [] exp_offsets_y = {0, 0, -3};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR18".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR18() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR18");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR18/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR18/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR18/input2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR18_output.jpg", true, 100, 1000000000L/800);
+
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 113, 254);
+ //checkHistogramDetails(hdrHistogramDetails, 1, 119, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 5, 120, 255);
+ checkHistogramDetails(hdrHistogramDetails, 2, 120, 255);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR19".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR19() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR19");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR19/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR19/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR19/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR19_output.jpg", true, 100, 1000000000L/160);
+
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR20".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR20() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR20");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR20/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR20/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR20/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR20_output.jpg", true, 100, 1000000000L*2);
+
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {-1, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR21".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR21() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR21");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR21/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR21/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR21/input2.jpg") );
+
+ // ISO and exposure unknown, so guessing
+ TestUtils.subTestHDR(activity, inputs, "testHDR21_output.jpg", true, 800, 1000000000L/12);
+
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR22".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR22() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR22");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR22/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR22/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR22/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR22_output.jpg", true, 391, 1000000000L/12);
+
+ // Nexus 6:
+ //int [] exp_offsets_x = {1, 0, -5};
+ //int [] exp_offsets_y = {1, 0, -6};
+ // OnePlus 3T:
+ int [] exp_offsets_x = {0, 0, -5};
+ int [] exp_offsets_y = {1, 0, -6};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR23", but with 2 images.
+ */
+ @Category(HDRNTests.class)
+ @Test
+ public void testHDR23_exp2() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR23_exp2");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0068.png") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0064.png") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR23_exp2_output.jpg", false, -1, -1);
+
+ int [] exp_offsets_x = {0, 0};
+ int [] exp_offsets_y = {0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+
+ //checkHistogramDetails(hdrHistogramDetails, 33, 78, 250);
+ checkHistogramDetails(hdrHistogramDetails, 17, 75, 250);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR23", but with 2 images, and greater exposure gap.
+ */
+ @Category(HDRNTests.class)
+ @Test
+ public void testHDR23_exp2b() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR23_exp2b");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0070.png") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0062.png") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR23_exp2b_output.jpg", false, -1, -1);
+
+ int [] exp_offsets_x = {0, 0};
+ int [] exp_offsets_y = {0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR23".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR23() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR23");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0068.png") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0066.png") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0064.png") );
+
+ // ISO unknown, so guessing
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR23_output.jpg", false, 1600, 1000000000L);
+
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+
+ //checkHistogramDetails(hdrHistogramDetails, 17, 81, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 32, 74, 255);
+ checkHistogramDetails(hdrHistogramDetails, 23, 71, 255);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR23", but with 4 images.
+ */
+ @Category(HDRNTests.class)
+ @Test
+ public void testHDR23_exp4() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR23_exp4");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0070.png") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0068.png") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0064.png") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0062.png") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR23_exp4_output.jpg", false, -1, -1);
+
+ int [] exp_offsets_x = {0, 0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+
+ //checkHistogramDetails(hdrHistogramDetails, 31, 75, 254);
+ checkHistogramDetails(hdrHistogramDetails, 23, 74, 254);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR23", but with 5 images.
+ */
+ @Category(HDRNTests.class)
+ @Test
+ public void testHDR23_exp5() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR23_exp5");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0070.png") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0068.png") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0066.png") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0064.png") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0062.png") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR23_exp5_output.jpg", false, -1, -1);
+
+ int [] exp_offsets_x = {0, 0, 0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+
+ //checkHistogramDetails(hdrHistogramDetails, 17, 81, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 28, 82, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 21, 74, 255);
+ checkHistogramDetails(hdrHistogramDetails, 17, 74, 255);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR23", but with 6 images.
+ */
+ @Category(HDRNTests.class)
+ @Test
+ public void testHDR23_exp6() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR23_exp6");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0072.png") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0070.png") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0068.png") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0064.png") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0062.png") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0061.png") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR23_exp6_output.jpg", false, -1, -1);
+
+ int [] exp_offsets_x = {0, 0, 0, 0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0, 0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+
+ //checkHistogramDetails(hdrHistogramDetails, 32, 76, 254);
+ checkHistogramDetails(hdrHistogramDetails, 25, 75, 254);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR23", but with 7 images.
+ */
+ @Category(HDRNTests.class)
+ @Test
+ public void testHDR23_exp7() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR23_exp7");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0072.png") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0070.png") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0068.png") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0066.png") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0064.png") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0062.png") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR23/memorial0061.png") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR23_exp7_output.jpg", false, -1, -1);
+
+ int [] exp_offsets_x = {0, 0, 0, 0, 0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0, 0, 0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+
+ //checkHistogramDetails(hdrHistogramDetails, 17, 81, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 28, 82, 255);
+ checkHistogramDetails(hdrHistogramDetails, 20, 72, 255);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR24".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR24() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR24");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR24/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR24/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR24/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR24_output.jpg", true, 40, 1000000000L/422);
+
+ int [] exp_offsets_x = {0, 0, 1};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR25".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR25() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR25");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR25/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR25/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR25/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR25_output.jpg", true, 40, 1000000000L/1917);
+
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {1, 0, -1};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR26".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR26() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR26");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR26/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR26/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR26/input2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR26_output.jpg", true, 40, 1000000000L/5325);
+
+ int [] exp_offsets_x = {-1, 0, 1};
+ int [] exp_offsets_y = {1, 0, -1};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 104, 254);
+ checkHistogramDetails(hdrHistogramDetails, 0, 119, 254);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR27".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR27() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR27");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR27/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR27/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR27/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR27_output.jpg", true, 40, 1000000000L/949);
+
+ int [] exp_offsets_x = {0, 0, 2};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR28".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR28() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR28");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR28/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR28/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR28/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR28_output.jpg", true, 294, 1000000000L/20);
+
+ int [] exp_offsets_x = {0, 0, 2};
+ int [] exp_offsets_y = {0, 0, -1};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR29".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR29() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR29");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR29/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR29/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR29/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR29_output.jpg", false, 40, 1000000000L/978);
+
+ int [] exp_offsets_x = {-1, 0, 3};
+ int [] exp_offsets_y = {0, 0, -1};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR30".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR30() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR30");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR30/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR30/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR30/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR30_output.jpg", false, 40, 1000000000L/978);
+
+ // offsets for full image
+ //int [] exp_offsets_x = {-6, 0, -1};
+ //int [] exp_offsets_y = {23, 0, -13};
+ // offsets using centre quarter image
+ int [] exp_offsets_x = {-5, 0, 0};
+ int [] exp_offsets_y = {22, 0, -13};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR31".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR31() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR31");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR31/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR31/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR31/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR31_output.jpg", false, 40, 1000000000L/422);
+
+ // offsets for full image
+ //int [] exp_offsets_x = {0, 0, 4};
+ //int [] exp_offsets_y = {21, 0, -11};
+ // offsets using centre quarter image
+ int [] exp_offsets_x = {0, 0, 3};
+ int [] exp_offsets_y = {21, 0, -11};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR32".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR32() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR32");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR32/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR32/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR32/input2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR32_output.jpg", true, 40, 1000000000L/1331);
+
+ int [] exp_offsets_x = {1, 0, 0};
+ int [] exp_offsets_y = {13, 0, -10};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+
+ //checkHistogramDetails(hdrHistogramDetails, 3, 101, 251);
+ //checkHistogramDetails(hdrHistogramDetails, 3, 109, 251);
+ //checkHistogramDetails(hdrHistogramDetails, 6, 111, 252);
+ checkHistogramDetails(hdrHistogramDetails, 2, 111, 252);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR33".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR33() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR33");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR33/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR33/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR33/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR33_output.jpg", true, 40, 1000000000L/354);
+
+ int [] exp_offsets_x = {13, 0, -10};
+ int [] exp_offsets_y = {24, 0, -12};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR34".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR34() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR34");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR34/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR34/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR34/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR34_output.jpg", true, 40, 1000000000L/4792);
+
+ int [] exp_offsets_x = {5, 0, -8};
+ int [] exp_offsets_y = {0, 0, -2};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR35".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR35() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR35");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR35/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR35/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR35/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR35_output.jpg", true, 40, 1000000000L/792);
+
+ int [] exp_offsets_x = {-10, 0, 3};
+ int [] exp_offsets_y = {7, 0, -3};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR36".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR36() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR36");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR36/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR36/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR36/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR36_output.jpg", false, 100, 1000000000L/1148);
+
+ int [] exp_offsets_x = {2, 0, -2};
+ int [] exp_offsets_y = {-4, 0, 2};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR37".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR37() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR37");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR37/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR37/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR37/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR37_output.jpg", false, 46, 1000000000L/120);
+
+ int [] exp_offsets_x = {0, 0, 3};
+ int [] exp_offsets_y = {2, 0, -19};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR38".
+ * Tests with Filmic tonemapping.
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR38Filmic() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR38Filmic");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR38/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR38/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR38/input2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR38_filmic_output.jpg", false, 125, 1000000000L/2965, HDRProcessor.TonemappingAlgorithm.TONEMAPALGORITHM_FU2);
+
+ int [] exp_offsets_x = {-1, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 92, 254);
+ checkHistogramDetails(hdrHistogramDetails, 0, 93, 254);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR39".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR39() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR39");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR39/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR39/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR39/input2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR39_output.jpg", false, 125, 1000000000L/2135);
+
+ int [] exp_offsets_x = {-6, 0, -2};
+ int [] exp_offsets_y = {6, 0, -8};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+
+ checkHistogramDetails(hdrHistogramDetails, 0, 128, 222);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR40".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR40() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR40");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR40/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR40/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR40/input2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR40_output.jpg", false, 50, 1000000000L/262);
+
+ int [] exp_offsets_x = {5, 0, -2};
+ int [] exp_offsets_y = {13, 0, 24};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+
+ checkHistogramDetails(hdrHistogramDetails, 1, 138, 254);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR40" with Exponential tonemapping.
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR40Exponential() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR40Exponential");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR40/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR40/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR40/input2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR40_exponential_output.jpg", false, 50, 1000000000L/262, HDRProcessor.TonemappingAlgorithm.TONEMAPALGORITHM_EXPONENTIAL);
+
+ int [] exp_offsets_x = {5, 0, -2};
+ int [] exp_offsets_y = {13, 0, 24};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+
+ checkHistogramDetails(hdrHistogramDetails, 1, 138, 254);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR40" with Filmic tonemapping.
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR40Filmic() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR40Filmic");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR40/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR40/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR40/input2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR40_filmic_output.jpg", false, 50, 1000000000L/262, HDRProcessor.TonemappingAlgorithm.TONEMAPALGORITHM_FU2);
+
+ int [] exp_offsets_x = {5, 0, -2};
+ int [] exp_offsets_y = {13, 0, 24};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+
+ checkHistogramDetails(hdrHistogramDetails, 1, 130, 254);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR41".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR41() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR41");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR41/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR41/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR41/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR41_output.jpg", false, 925, 1000000000L/25);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR42".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR42() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR42");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR42/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR42/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR42/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR42_output.jpg", false, 112, 1000000000L/679);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR43".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR43() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR43");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR43/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR43/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR43/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR43_output.jpg", false, 1196, 1000000000L/12);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR44".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR44() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR44");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR44/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR44/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR44/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR44_output.jpg", false, 100, 1000000000L/1016);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR45".
+ */
+ @Category(HDRNTests.class)
+ @Test
+ public void testHDR45() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR45");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ //inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR45/IMG_6314.jpg") );
+ //inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR45/IMG_6312.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR45/IMG_6310.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR45/IMG_6309.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR45/IMG_6311.jpg") );
+ //inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR45/IMG_6313.jpg") );
+ //inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR45/IMG_6315.jpg") );
+
+ // ISO 100, exposure time 2s, but pass in -1 since these are HDRNTests
+ TestUtils.subTestHDR(activity, inputs, "testHDR45_output.jpg", false, -1, -1);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR45".
+ */
+ @Category(HDRNTests.class)
+ @Test
+ public void testHDR45_exp5() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR45_exp5");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ //inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR45/IMG_6314.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR45/IMG_6312.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR45/IMG_6310.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR45/IMG_6309.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR45/IMG_6311.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR45/IMG_6313.jpg") );
+ //inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR45/IMG_6315.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR45_exp5_output.jpg", false, -1, -1);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR45".
+ */
+ @Category(HDRNTests.class)
+ @Test
+ public void testHDR45_exp7() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR45_exp7");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR45/IMG_6314.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR45/IMG_6312.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR45/IMG_6310.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR45/IMG_6309.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR45/IMG_6311.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR45/IMG_6313.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR45/IMG_6315.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR45_exp7_output.jpg", false, -1, -1);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR46".
+ */
+ @Category(HDRNTests.class)
+ @Test
+ public void testHDR46() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR46");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ //inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR46/Izmir Harbor - ppw - 06.jpg") );
+ //inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR46/Izmir Harbor - ppw - 05.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR46/Izmir Harbor - ppw - 04.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR46/Izmir Harbor - ppw - 03.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR46/Izmir Harbor - ppw - 02.jpg") );
+ //inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR46/Izmir Harbor - ppw - 01.jpg") );
+
+ // ISO 100, exposure time 1/60s, but pass in -1 since these are HDRNTests
+ TestUtils.subTestHDR(activity, inputs, "testHDR46_output.jpg", false, -1, -1);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR46".
+ */
+ @Category(HDRNTests.class)
+ @Test
+ public void testHDR46_exp5() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR46_exp5");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ //inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR46/Izmir Harbor - ppw - 06.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR46/Izmir Harbor - ppw - 05.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR46/Izmir Harbor - ppw - 04.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR46/Izmir Harbor - ppw - 03.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR46/Izmir Harbor - ppw - 02.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR46/Izmir Harbor - ppw - 01.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR46_exp5_output.jpg", false, -1, -1);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR47".
+ */
+ @Category(HDRNTests.class)
+ @Test
+ public void testHDR47_exp2() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR47_exp2");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 05.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 03.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDR47_exp2_output.jpg", false, -1, -1);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR47".
+ */
+ @Category(HDRNTests.class)
+ @Test
+ public void testHDR47() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR47");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ //inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 08.jpg") );
+ //inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 07.jpg") );
+ //inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 06.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 05.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 04.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 03.jpg") );
+ //inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 02.jpg") );
+ //inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 01.jpg") );
+
+ // ISO 400, exposure time 1/60s, but pass in -1 since these are HDRNTests
+ TestUtils.subTestHDR(activity, inputs, "testHDR47_output.jpg", false, -1, -1);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR47".
+ */
+ @Category(HDRNTests.class)
+ @Test
+ public void testHDR47_exp5() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR47_exp5");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ //inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 08.jpg") );
+ //inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 07.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 06.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 05.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 04.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 03.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 02.jpg") );
+ //inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 01.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR47_exp5_output.jpg", false, -1, -1);
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 73, 255);
+ checkHistogramDetails(hdrHistogramDetails, 1, 80, 255);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR47".
+ */
+ @Category(HDRNTests.class)
+ @Test
+ public void testHDR47_exp7() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR47_exp7");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ //inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 08.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 07.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 06.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 05.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 04.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 03.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 02.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR47/High Five - ppw - 01.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR47_exp7_output.jpg", false, -1, -1);
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 73, 255);
+ checkHistogramDetails(hdrHistogramDetails, 1, 80, 255);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR48".
+ */
+ @Category(HDRNTests.class)
+ @Test
+ public void testHDR48() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR48");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ //inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR48/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR48/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR48/input2.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR48/input3.jpg") );
+ //inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR48/input4.jpg") );
+
+ // ISO 100, exposure time 1/716s, but pass in -1 since these are HDRNTests
+ TestUtils.subTestHDR(activity, inputs, "testHDR48_output.jpg", false, -1, -1);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR48".
+ */
+ @Category(HDRNTests.class)
+ @Test
+ public void testHDR48_exp5() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR48_exp5");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR48/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR48/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR48/input2.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR48/input3.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR48/input4.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR48_exp5_output.jpg", false, -1, -1);
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 59, 241);
+ checkHistogramDetails(hdrHistogramDetails, 0, 67, 241);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR49".
+ */
+ @Category(HDRNTests.class)
+ @Test
+ public void testHDR49_exp2() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR49_exp2");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR49/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR49/input3.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR49_exp2_output.jpg", false, -1, -1);
+
+ //checkHistogramDetails(hdrHistogramDetails, 12, 120, 251);
+ checkHistogramDetails(hdrHistogramDetails, 0, 122, 251);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR49".
+ */
+ @Category(HDRNTests.class)
+ @Test
+ public void testHDR49() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR49");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR49/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR49/input2.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR49/input3.jpg") );
+
+ // ISO 100, exposure time 1/417s, but pass in -1 since these are HDRNTests
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR49_output.jpg", false, -1, -1);
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 75, 255);
+ checkHistogramDetails(hdrHistogramDetails, 0, 89, 254);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR49".
+ */
+ @Category(HDRNTests.class)
+ @Test
+ public void testHDR49_exp4() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR49_exp4");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR49/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR49/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR49/input3.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR49/input4.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR49_exp4_output.jpg", false, -1, -1);
+
+ checkHistogramDetails(hdrHistogramDetails, 19, 109, 244);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR49".
+ */
+ @Category(HDRNTests.class)
+ @Test
+ public void testHDR49_exp5() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR49_exp5");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR49/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR49/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR49/input2.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR49/input3.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR49/input4.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR49_exp5_output.jpg", false, -1, -1);
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 72, 244);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 78, 243);
+ checkHistogramDetails(hdrHistogramDetails, 0, 87, 243);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR50".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR50() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR50");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR50/IMG_20180626_221357_0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR50/IMG_20180626_221357_1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR50/IMG_20180626_221357_2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR50_output.jpg", false, 867, 1000000000L/14);
+
+ checkHistogramDetails(hdrHistogramDetails, 0, 69, 255);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR51".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR51() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR51");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR51/IMG_20180323_104702_0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR51/IMG_20180323_104702_1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR51/IMG_20180323_104702_2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR51_output.jpg", true, 1600, 1000000000L/11);
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 75, 255);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR52".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR52() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR52");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR52/IMG_20181023_143633_EXP0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR52/IMG_20181023_143633_EXP1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR52/IMG_20181023_143633_EXP2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR52_output.jpg", false, 100, 1000000000L/2105);
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 75, 255);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR53".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR53() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR53");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR53/IMG_20181106_135411_EXP0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR53/IMG_20181106_135411_EXP1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR53/IMG_20181106_135411_EXP2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR53_output.jpg", false, 103, 1000000000L/5381);
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 55, 254);
+ checkHistogramDetails(hdrHistogramDetails, 0, 72, 255);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR54".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR54() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR54");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR54/IMG_20181107_115508_EXP0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR54/IMG_20181107_115508_EXP1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR54/IMG_20181107_115508_EXP2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR54_output.jpg", false, 752, 1000000000L/14);
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 75, 255);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR55".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR55() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR55");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR55/IMG_20181107_115608_EXP0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR55/IMG_20181107_115608_EXP1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR55/IMG_20181107_115608_EXP2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR55_output.jpg", false, 1505, 1000000000L/10);
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 75, 255);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR56".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR56() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR56");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR56/180502_141722_OC_0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR56/180502_141722_OC_1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR56/180502_141722_OC_2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR56_output.jpg", false, 50, 1000000000L/40);
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 75, 255);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR57".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR57() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR57");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR57/IMG_20181119_145313_EXP0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR57/IMG_20181119_145313_EXP1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR57/IMG_20181119_145313_EXP2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR57_output.jpg", true, 100, 1000000000L/204);
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 75, 255);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR58".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR58() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR58");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR58/IMG_20190911_210146_0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR58/IMG_20190911_210146_1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR58/IMG_20190911_210146_2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR58_output.jpg", false, 1250, 1000000000L/10);
+ //HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR58_output.jpg", false, 1250, 1000000000L/10, HDRProcessor.TonemappingAlgorithm.TONEMAPALGORITHM_CLAMP);
+
+ checkHistogramDetails(hdrHistogramDetails, 11, 119, 255);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR59".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR59() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR59");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR59/IMG_20190911_210154_0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR59/IMG_20190911_210154_1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR59/IMG_20190911_210154_2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR59_output.jpg", false, 1250, 1000000000L/10);
+ //HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR59_output.jpg", false, 1250, 1000000000L/10, HDRProcessor.TonemappingAlgorithm.TONEMAPALGORITHM_CLAMP);
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 75, 255);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR60".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR60() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR60");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR60/IMG_20200507_020319_0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR60/IMG_20200507_020319_1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR60/IMG_20200507_020319_2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR60_output.jpg", false, 491, 1000000000L/10);
+ //HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR60_output.jpg", false, 491, 1000000000L/10, HDRProcessor.TonemappingAlgorithm.TONEMAPALGORITHM_CLAMP);
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 75, 255);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR61".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR61() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR61");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR61/IMG_20191111_145230_0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR61/IMG_20191111_145230_1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR61/IMG_20191111_145230_2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR61_output.jpg", false, 50, 1000000000L/5025);
+
+ checkHistogramDetails(hdrHistogramDetails, 0, 93, 255);
+
+ int [] exp_offsets_x = {0, 0, 1};
+ int [] exp_offsets_y = {0, 0, -2};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR62".
+ */
+ @Category(HDRTests.class)
+ @Test
+ public void testHDR62() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR62");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR62/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR62/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDR62/input2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestHDR(activity, inputs, "testHDR62_output.jpg", false, 100, 1000000000L/485);
+
+ checkHistogramDetails(hdrHistogramDetails, 0, 113, 247);
+
+ int [] exp_offsets_x = {0, 0, -3};
+ int [] exp_offsets_y = {3, 0, -6};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y);
+ });
+ }
+
+ /** Tests HDR algorithm on test samples "testHDRtemp".
+ * Used for one-off testing, or to recreate HDR images from the base exposures to test an updated algorithm.
+ * The test images should be copied to the test device into DCIM/testOpenCamera/testdata/hdrsamples/testHDRtemp/ .
+ */
+ @Test
+ public void testHDRtemp() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDRtemp");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDRtemp/input0.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDRtemp/input1.jpg") );
+ inputs.add( TestUtils.getBitmapFromFile(activity, TestUtils.hdr_images_path + "testHDRtemp/input2.jpg") );
+
+ TestUtils.subTestHDR(activity, inputs, "testHDRtemp_output.jpg", true, 100, 1000000000L/100);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg1".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg1() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg1");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg1/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg1/input1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg1/input2.jpg");
+
+ // the input images record ISO=800, but they were taken with OnePlus 3T which has bug where ISO is reported as max
+ // of 800; in reality for a scene this dark, it was probably more like ISO 1600
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity, inputs, "testAvg1_output.jpg", 1600, 1000000000L/17, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ //int [] exp_offsets_x = {0, 3, 0};
+ //int [] exp_offsets_y = {0, 1, 0};
+ //int [] exp_offsets_x = {0, 4, 0};
+ //int [] exp_offsets_y = {0, 1, 0};
+ //int [] exp_offsets_x = {0, 2, 0};
+ //int [] exp_offsets_y = {0, 0, 0};
+ int [] exp_offsets_x = {0, 4, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ assertEquals(0, activity.getApplicationInterface().getHDRProcessor().sharp_index);
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 2 ) {
+ //int [] exp_offsets_x = {0, 6, 0};
+ //int [] exp_offsets_y = {0, 0, 0};
+ //int [] exp_offsets_x = {0, 8, 0};
+ //int [] exp_offsets_y = {0, 1, 0};
+ //int [] exp_offsets_x = {0, 7, 0};
+ //int [] exp_offsets_y = {0, -1, 0};
+ //int [] exp_offsets_x = {0, 8, 0};
+ //int [] exp_offsets_y = {0, -4, 0};
+ int [] exp_offsets_x = {0, 8, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else {
+ fail();
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 39, 253);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg2".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg2() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg2");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg2/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg2/input1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg2/input2.jpg");
+
+ // the input images record ISO=800, but they were taken with OnePlus 3T which has bug where ISO is reported as max
+ // of 800; in reality for a scene this dark, it was probably more like ISO 1600
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg2_output.jpg", 1600, 1000000000L/17, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ //int [] exp_offsets_x = {0, -15, 0};
+ //int [] exp_offsets_y = {0, -10, 0};
+ //int [] exp_offsets_x = {0, -15, 0};
+ //int [] exp_offsets_y = {0, -11, 0};
+ //int [] exp_offsets_x = {0, -12, 0};
+ //int [] exp_offsets_y = {0, -12, 0};
+ int [] exp_offsets_x = {0, -16, 0};
+ int [] exp_offsets_y = {0, -12, 0};
+ assertEquals(0, activity.getApplicationInterface().getHDRProcessor().sharp_index);
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 2 ) {
+ //int [] exp_offsets_x = {0, -15, 0};
+ //int [] exp_offsets_y = {0, -10, 0};
+ //int [] exp_offsets_x = {0, -13, 0};
+ //int [] exp_offsets_y = {0, -12, 0};
+ //int [] exp_offsets_x = {0, -12, 0};
+ //int [] exp_offsets_y = {0, -14, 0};
+ int [] exp_offsets_x = {0, -12, 0};
+ int [] exp_offsets_y = {0, -12, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else {
+ fail();
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 39, 253);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg3".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg3() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg3");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg3/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg3/input1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg3/input2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg3/input3.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg3/input4.jpg");
+
+ // the input images record ISO=800, but they were taken with OnePlus 3T which has bug where ISO is reported as max
+ // of 800; in reality for a scene this dark, it was probably more like ISO 1600
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg3_output.jpg", 1600, 1000000000L/16, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ /*if( index == 1 ) {
+ //int [] exp_offsets_x = {0, 2, 0};
+ //int [] exp_offsets_y = {0, -18, 0};
+ //int [] exp_offsets_x = {0, -1, 0};
+ //int [] exp_offsets_y = {0, 0, 0};
+ //int [] exp_offsets_x = {0, -9, 0};
+ //int [] exp_offsets_y = {0, -11, 0};
+ //int [] exp_offsets_x = {0, -8, 0};
+ //int [] exp_offsets_y = {0, -10, 0};
+ int [] exp_offsets_x = {0, -8, 0};
+ int [] exp_offsets_y = {0, -8, 0};
+ assertTrue(activity.getApplicationInterface().getHDRProcessor().sharp_index == 0);
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 2 ) {
+ //int [] exp_offsets_x = {0, -18, 0};
+ //int [] exp_offsets_y = {0, 17, 0};
+ //int [] exp_offsets_x = {0, -2, 0};
+ //int [] exp_offsets_y = {0, 0, 0};
+ //int [] exp_offsets_x = {0, -7, 0};
+ //int [] exp_offsets_y = {0, -2, 0};
+ //int [] exp_offsets_x = {0, -8, 0};
+ //int [] exp_offsets_y = {0, -8, 0};
+ int [] exp_offsets_x = {0, -12, 0};
+ int [] exp_offsets_y = {0, 8, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 3 ) {
+ //int [] exp_offsets_x = {0, -12, 0};
+ //int [] exp_offsets_y = {0, -25, 0};
+ //int [] exp_offsets_x = {0, -2, 0};
+ //int [] exp_offsets_y = {0, 0, 0};
+ //int [] exp_offsets_x = {0, -9, 0};
+ //int [] exp_offsets_y = {0, 14, 0};
+ //int [] exp_offsets_x = {0, -8, 0};
+ //int [] exp_offsets_y = {0, 2, 0};
+ //int [] exp_offsets_x = {0, -12, 0};
+ //int [] exp_offsets_y = {0, 12, 0};
+ int [] exp_offsets_x = {0, -12, 0};
+ int [] exp_offsets_y = {0, 4, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 4 ) {
+ //int [] exp_offsets_x = {0, -29, 0};
+ //int [] exp_offsets_y = {0, -22, 0};
+ //int [] exp_offsets_x = {0, -2, 0};
+ //int [] exp_offsets_y = {0, 0, 0};
+ //int [] exp_offsets_x = {0, -7, 0};
+ //int [] exp_offsets_y = {0, 11, 0};
+ //int [] exp_offsets_x = {0, -6, 0};
+ //int [] exp_offsets_y = {0, 14, 0};
+ //int [] exp_offsets_x = {0, -8, 0};
+ //int [] exp_offsets_y = {0, 2, 0};
+ //int [] exp_offsets_x = {0, -8, 0};
+ //int [] exp_offsets_y = {0, 12, 0};
+ int [] exp_offsets_x = {0, -8, 0};
+ int [] exp_offsets_y = {0, 4, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else {
+ assertTrue(false);
+ }*/
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 21, 177);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 21, 152);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 21, 166);
+ checkHistogramDetails(hdrHistogramDetails, 0, 23, 194);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg4".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg4() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg4");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg4/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg4/input1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg4/input2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg4/input3.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg4/input4.jpg");
+
+ // the input images record ISO=800, but they were taken with OnePlus 3T which has bug where ISO is reported as max
+ // of 800; in reality for a scene this dark, it was probably more like ISO 1600
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg4_output.jpg", 1600, 1000000000L/16, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ //int [] exp_offsets_x = {0, 5, 0};
+ //int [] exp_offsets_y = {0, 2, 0};
+ int [] exp_offsets_x = {0, 5, 0};
+ int [] exp_offsets_y = {0, 1, 0};
+ assertEquals(0, activity.getApplicationInterface().getHDRProcessor().sharp_index);
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 2 ) {
+ //int [] exp_offsets_x = {0, 3, 0};
+ //int [] exp_offsets_y = {0, 5, 0};
+ int [] exp_offsets_x = {0, 4, 0};
+ int [] exp_offsets_y = {0, 4, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 3 ) {
+ //int [] exp_offsets_x = {0, 0, 0};
+ //int [] exp_offsets_y = {0, 7, 0};
+ //int [] exp_offsets_x = {0, 1, 0};
+ //int [] exp_offsets_y = {0, 6, 0};
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, 8, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 4 ) {
+ //int [] exp_offsets_x = {0, 4, 0};
+ //int [] exp_offsets_y = {0, 8, 0};
+ //int [] exp_offsets_x = {0, 3, 0};
+ //int [] exp_offsets_y = {0, 7, 0};
+ //int [] exp_offsets_x = {0, 3, 0};
+ //int [] exp_offsets_y = {0, 8, 0};
+ int [] exp_offsets_x = {0, 3, 0};
+ int [] exp_offsets_y = {0, 9, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else {
+ fail();
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 39, 253);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg5".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg5() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg5");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg5/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg5/input1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg5/input2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg5/input3.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg5/input4.jpg");
+
+ // the input images record ISO=800, but they were taken with OnePlus 3T which has bug where ISO is reported as max
+ // of 800; in reality for a scene this dark, it was probably more like ISO 1600
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg5_output.jpg", 1600, 1000000000L/16, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ /*if( index == 1 ) {
+ //int [] exp_offsets_x = {0, 4, 0};
+ //int [] exp_offsets_y = {0, -1, 0};
+ //int [] exp_offsets_x = {0, 5, 0};
+ //int [] exp_offsets_y = {0, 0, 0};
+ //int [] exp_offsets_x = {0, 6, 0};
+ //int [] exp_offsets_y = {0, -2, 0};
+ int [] exp_offsets_x = {0, 4, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ assertTrue(activity.getApplicationInterface().getHDRProcessor().sharp_index == 0);
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 2 ) {
+ //int [] exp_offsets_x = {0, 7, 0};
+ //int [] exp_offsets_y = {0, -2, 0};
+ //int [] exp_offsets_x = {0, 8, 0};
+ //int [] exp_offsets_y = {0, -1, 0};
+ int [] exp_offsets_x = {0, 8, 0};
+ int [] exp_offsets_y = {0, -4, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 3 ) {
+ //int [] exp_offsets_x = {0, 9, 0};
+ //int [] exp_offsets_y = {0, -2, 0};
+ //int [] exp_offsets_x = {0, 8, 0};
+ //int [] exp_offsets_y = {0, -1, 0};
+ int [] exp_offsets_x = {0, 8, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 4 ) {
+ //int [] exp_offsets_x = {0, 10, 0};
+ //int [] exp_offsets_y = {0, -4, 0};
+ int [] exp_offsets_x = {0, 11, 0};
+ int [] exp_offsets_y = {0, -3, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else {
+ assertTrue(false);
+ }*/
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 39, 253);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg6".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg6() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg6");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg6/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg6/input1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg6/input2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg6/input3.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg6/input4.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg6/input5.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg6/input6.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg6/input7.jpg");
+
+ // the input images record ISO=800, but they were taken with OnePlus 3T which has bug where ISO is reported as max
+ // of 800; in reality for a scene this dark, it was probably more like ISO 1600
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg6_output.jpg", 1600, 1000000000L/17, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ /*if( true )
+ return;*/
+ if( index == 1 ) {
+ //int [] exp_offsets_x = {0, 0, 0};
+ //int [] exp_offsets_y = {0, 0, 0};
+ //int [] exp_offsets_x = {0, -2, 0};
+ //int [] exp_offsets_y = {0, 0, 0};
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ assertEquals(0, activity.getApplicationInterface().getHDRProcessor().sharp_index);
+ }
+ else if( index == 2 ) {
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 3 ) {
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 4 ) {
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 5 ) {
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 6 ) {
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 7 ) {
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else {
+ fail();
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 18, 51, 201);
+ //checkHistogramDetails(hdrHistogramDetails, 14, 38, 200);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 9, 193);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 9, 199);
+ //checkHistogramDetails(hdrHistogramDetails, 12, 46, 202);
+ //checkHistogramDetails(hdrHistogramDetails, 12, 46, 205);
+ //checkHistogramDetails(hdrHistogramDetails, 12, 44, 209);
+ //checkHistogramDetails(hdrHistogramDetails, 12, 44, 202);
+ //checkHistogramDetails(hdrHistogramDetails, 5, 16, 190);
+ checkHistogramDetails(hdrHistogramDetails, 5, 19, 199);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg7".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg7() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg7");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg7/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg7/input1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg7/input2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg7/input3.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg7/input4.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg7/input5.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg7/input6.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg7/input7.jpg");
+
+ // the input images record ISO=800, but they were taken with OnePlus 3T which has bug where ISO is reported as max
+ // of 800; in reality for a scene this dark, it was probably more like ISO 1600
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg7_output.jpg", 1600, 1000000000L/16, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ //int [] exp_offsets_x = {0, 0, 0};
+ //int [] exp_offsets_y = {0, 0, 0};
+ //int [] exp_offsets_x = {0, -10, 0};
+ //int [] exp_offsets_y = {0, 6, 0};
+ //int [] exp_offsets_x = {0, -6, 0};
+ //int [] exp_offsets_y = {0, 2, 0};
+ //int [] exp_offsets_x = {0, -4, 0};
+ //int [] exp_offsets_y = {0, 0, 0};
+ //int [] exp_offsets_x = {0, 0, 0};
+ //int [] exp_offsets_y = {0, 0, 0};
+ //int [] exp_offsets_x = {0, -4, 0};
+ //int [] exp_offsets_y = {0, 0, 0};
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ assertEquals(0, activity.getApplicationInterface().getHDRProcessor().sharp_index);
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 39, 253);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg8".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg8() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg8");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg8/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg8/input1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg8/input2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg8/input3.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg8/input4.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg8/input5.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg8/input6.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg8/input7.jpg");
+
+ // the input images record ISO=800, but they were taken with OnePlus 3T which has bug where ISO is reported as max
+ // of 800; in reality for a scene this dark, it was probably more like ISO 1600
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg8_output.jpg", 1600, 1000000000L/16, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ assertEquals(0, activity.getApplicationInterface().getHDRProcessor().sharp_index);
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 4, 26, 92);
+ //checkHistogramDetails(hdrHistogramDetails, 3, 19, 68);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 10, 60);
+ //checkHistogramDetails(hdrHistogramDetails, 1, 8, 72);
+ //checkHistogramDetails(hdrHistogramDetails, 1, 6, 64);
+ //checkHistogramDetails(hdrHistogramDetails, 1, 15, 75);
+ checkHistogramDetails(hdrHistogramDetails, 1, 16, 78);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg9".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg9() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg9");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ final boolean use_auto_photos = true;
+
+ if( use_auto_photos ) {
+ inputs.add(TestUtils.avg_images_path + "testAvg9/input_auto0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg9/input_auto1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg9/input_auto2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg9/input_auto3.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg9/input_auto4.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg9/input_auto5.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg9/input_auto6.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg9/input_auto7.jpg");
+ }
+ else {
+ inputs.add(TestUtils.avg_images_path + "testAvg9/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg9/input1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg9/input2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg9/input3.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg9/input4.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg9/input5.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg9/input6.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg9/input7.jpg");
+ }
+
+ String out_filename = use_auto_photos ? "testAvg9_auto_output.jpg" : "testAvg9_output.jpg";
+
+ // the input images record ISO=800, but they were taken with OnePlus 3T which has bug where ISO is reported as max
+ // of 800; in reality for a scene this dark, it was probably more like ISO 1600
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, out_filename, 1600, use_auto_photos ? 1000000000L/16 : 1000000000L/11, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ assertEquals(0, activity.getApplicationInterface().getHDRProcessor().sharp_index);
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 39, 253);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg10".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg10() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg10");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ final boolean use_auto_photos = false;
+
+ if( use_auto_photos ) {
+ inputs.add(TestUtils.avg_images_path + "testAvg10/input_auto0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg10/input_auto1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg10/input_auto2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg10/input_auto3.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg10/input_auto4.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg10/input_auto5.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg10/input_auto6.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg10/input_auto7.jpg");
+ }
+ else {
+ inputs.add(TestUtils.avg_images_path + "testAvg10/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg10/input1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg10/input2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg10/input3.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg10/input4.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg10/input5.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg10/input6.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg10/input7.jpg");
+ }
+
+ String out_filename = use_auto_photos ? "testAvg10_auto_output.jpg" : "testAvg10_output.jpg";
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, out_filename, 1196, use_auto_photos ? 1000000000L/12 : 1000000000L/10, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ assertEquals(0, activity.getApplicationInterface().getHDRProcessor().sharp_index);
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 39, 253);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg11".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg11() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg11");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ // note, we don't actually use 8 images for a bright scene like this, but it serves as a good test for
+ // misalignment/ghosting anyway
+ inputs.add(TestUtils.avg_images_path + "testAvg11/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg11/input1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg11/input2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg11/input3.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg11/input4.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg11/input5.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg11/input6.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg11/input7.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg11_output.jpg", 100, 1000000000L/338, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ //int [] exp_offsets_x = {0, 4, 0};
+ //int [] exp_offsets_y = {0, -8, 0};
+ //int [] exp_offsets_x = {0, 6, 0};
+ //int [] exp_offsets_y = {0, -8, 0};
+ //int [] exp_offsets_x = {0, -6, 0};
+ //int [] exp_offsets_y = {0, 8, 0};
+ int [] exp_offsets_x = {0, -4, 0};
+ int [] exp_offsets_y = {0, 8, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ //assertTrue(activity.getApplicationInterface().getHDRProcessor().sharp_index == 1);
+ }
+ else if( index == 2 ) {
+ //int [] exp_offsets_x = {0, -5, 0};
+ //int [] exp_offsets_y = {0, -1, 0};
+ //int [] exp_offsets_x = {0, -10, 0};
+ //int [] exp_offsets_y = {0, 6, 0};
+ int [] exp_offsets_x = {0, -8, 0};
+ int [] exp_offsets_y = {0, 8, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 3 ) {
+ //int [] exp_offsets_x = {0, -1, 0};
+ //int [] exp_offsets_y = {0, -18, 0};
+ //int [] exp_offsets_x = {0, 0, 0};
+ //int [] exp_offsets_y = {0, -16, 0};
+ //int [] exp_offsets_x = {0, -4, 0};
+ //int [] exp_offsets_y = {0, -10, 0};
+ //int [] exp_offsets_x = {0, -4, 0};
+ //int [] exp_offsets_y = {0, -8, 0};
+ int [] exp_offsets_x = {0, -4, 0};
+ int [] exp_offsets_y = {0, -12, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 4 ) {
+ //int [] exp_offsets_x = {0, -3, 0};
+ //int [] exp_offsets_y = {0, -20, 0};
+ //int [] exp_offsets_x = {0, -2, 0};
+ //int [] exp_offsets_y = {0, -18, 0};
+ //int [] exp_offsets_x = {0, -6, 0};
+ //int [] exp_offsets_y = {0, -12, 0};
+ int [] exp_offsets_x = {0, -8, 0};
+ int [] exp_offsets_y = {0, -12, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 5 ) {
+ //int [] exp_offsets_x = {0, -8, 0};
+ //int [] exp_offsets_y = {0, 2, 0};
+ //int [] exp_offsets_x = {0, -10, 0};
+ //int [] exp_offsets_y = {0, 4, 0};
+ //int [] exp_offsets_x = {0, -12, 0};
+ //int [] exp_offsets_y = {0, 10, 0};
+ int [] exp_offsets_x = {0, -12, 0};
+ int [] exp_offsets_y = {0, 8, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 6 ) {
+ //int [] exp_offsets_x = {0, 0, 0};
+ //int [] exp_offsets_y = {0, -6, 0};
+ //int [] exp_offsets_x = {0, 2, 0};
+ //int [] exp_offsets_y = {0, -6, 0};
+ //int [] exp_offsets_x = {0, -4, 0};
+ //int [] exp_offsets_y = {0, 2, 0};
+ int [] exp_offsets_x = {0, -4, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 7 ) {
+ //int [] exp_offsets_x = {0, 7, 0};
+ //int [] exp_offsets_y = {0, -2, 0};
+ //int [] exp_offsets_x = {0, 6, 0};
+ //int [] exp_offsets_y = {0, 6, 0};
+ //int [] exp_offsets_x = {0, 4, 0};
+ //int [] exp_offsets_y = {0, 4, 0};
+ //int [] exp_offsets_x = {0, 8, 0};
+ //int [] exp_offsets_y = {0, 8, 0};
+ int [] exp_offsets_x = {0, 4, 0};
+ int [] exp_offsets_y = {0, 4, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else {
+ fail();
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 39, 253);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg12".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg12() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg12");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg12/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg12/input1.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg12_output.jpg", 100, 1000000000L/1617, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ //assertTrue(activity.getApplicationInterface().getHDRProcessor().sharp_index == 1);
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 30, 254);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 27, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 20, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 17, 254);
+ checkHistogramDetails(hdrHistogramDetails, 0, 31, 255);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg13".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg13() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg13");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg13/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg13/input1.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg13_output.jpg", 100, 1000000000L/2482, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ //assertTrue(activity.getApplicationInterface().getHDRProcessor().sharp_index == 1);
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 39, 253);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg14".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg14() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg14");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg14/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg14/input1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg14/input2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg14/input3.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg14/input4.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg14/input5.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg14/input6.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg14/input7.jpg");
+
+ // the input images record ISO=800, but they were taken with OnePlus 3T which has bug where ISO is reported as max
+ // of 800; in reality for a scene this dark, it was probably more like ISO 1600
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg14_output.jpg", 1600, 1000000000L/10, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ int [] exp_offsets_x = {0, -8, 0};
+ int [] exp_offsets_y = {0, -8, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ assertEquals(0, activity.getApplicationInterface().getHDRProcessor().sharp_index);
+ }
+ else if( index == 7 ) {
+ //int [] exp_offsets_x = {0, 4, 0};
+ //int [] exp_offsets_y = {0, 28, 0};
+ int [] exp_offsets_x = {0, 4, 0};
+ int [] exp_offsets_y = {0, 40, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 25, 245);
+ checkHistogramDetails(hdrHistogramDetails, 0, 18, 246);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg15".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg15() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg15");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg15/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg15/input1.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg15_output.jpg", 100, 1000000000L/1525, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ assertEquals(0, activity.getApplicationInterface().getHDRProcessor().sharp_index);
+ }
+ }
+ });
+
+ checkHistogramDetails(hdrHistogramDetails, 0, 38, 254);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg16".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg16() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg16");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg16/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg16/input1.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg16_output.jpg", 100, 1000000000L/293, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ //assertTrue(activity.getApplicationInterface().getHDRProcessor().sharp_index == 1);
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 39, 253);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg17".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg17() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg17");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg17/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg17/input1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg17/input2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg17/input3.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg17/input4.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg17/input5.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg17/input6.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg17/input7.jpg");
+
+ // the input images record ISO=800, but they were taken with OnePlus 3T which has bug where ISO is reported as max
+ // of 800; in reality for a scene this dark, it was probably more like ISO 1600
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg17_output.jpg", 1600, 1000000000L/17, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ int [] exp_offsets_x = {0, -8, 0};
+ int [] exp_offsets_y = {0, 4, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ assertEquals(0, activity.getApplicationInterface().getHDRProcessor().sharp_index);
+ }
+ else if( index == 7 ) {
+ int [] exp_offsets_x = {0, 12, 0};
+ int [] exp_offsets_y = {0, 28, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 100, 233);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 100, 236);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 92, 234);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 102, 241);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 102, 238);
+ checkHistogramDetails(hdrHistogramDetails, 0, 103, 244);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg18".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg18() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg18");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg18/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg18/input1.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg18_output.jpg", 100, 1000000000L/591, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ //assertTrue(activity.getApplicationInterface().getHDRProcessor().sharp_index == 1);
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 39, 253);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg19".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg19() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg19");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ // repeat same image twice
+ inputs.add(TestUtils.avg_images_path + "testAvg19/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg19/input0.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg19_output.jpg", 100, 1000000000L/2483, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ assertEquals(0, activity.getApplicationInterface().getHDRProcessor().sharp_index);
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 88, 252);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 77, 252);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 87, 252);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 74, 255);
+ checkHistogramDetails(hdrHistogramDetails, 0, 58, 255);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg20".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg20() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg20");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ // repeat same image twice
+ inputs.add(TestUtils.avg_images_path + "testAvg20/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg20/input0.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg20_output.jpg", 100, 1000000000L/3124, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ assertEquals(0, activity.getApplicationInterface().getHDRProcessor().sharp_index);
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 39, 253);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg21".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg21() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg21");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ // repeat same image twice
+ inputs.add(TestUtils.avg_images_path + "testAvg21/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg21/input0.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg21_output.jpg", 102, 1000000000L/6918, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ assertEquals(0, activity.getApplicationInterface().getHDRProcessor().sharp_index);
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 39, 253);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg22".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg22() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg22");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ // repeat same image twice
+ inputs.add(TestUtils.avg_images_path + "testAvg22/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg22/input0.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg22_output.jpg", 100, 1000000000L/3459, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ assertEquals(0, activity.getApplicationInterface().getHDRProcessor().sharp_index);
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 39, 253);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg23".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg23() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg23");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg23/IMG_20180520_111250_0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg23/IMG_20180520_111250_1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg23/IMG_20180520_111250_2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg23/IMG_20180520_111250_3.jpg");
+ // only test 4 images, to reflect latest behaviour that we take 4 images for this ISO
+ /*inputs.add(TestUtils.avg_images_path + "testAvg23/IMG_20180520_111250_4.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg23/IMG_20180520_111250_5.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg23/IMG_20180520_111250_6.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg23/IMG_20180520_111250_7.jpg");*/
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg23_output.jpg", 1044, 1000000000L/10, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ int [] exp_offsets_x = {0, -4, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 2 ) {
+ int [] exp_offsets_x = {0, -4, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 3 ) {
+ int [] exp_offsets_x = {0, -8, 0};
+ int [] exp_offsets_y = {0, 4, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 4 ) {
+ int [] exp_offsets_x = {0, -8, 0};
+ int [] exp_offsets_y = {0, 4, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 5 ) {
+ int [] exp_offsets_x = {0, -12, 0};
+ int [] exp_offsets_y = {0, 4, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 6 ) {
+ int [] exp_offsets_x = {0, -12, 0};
+ int [] exp_offsets_y = {0, 4, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 7 ) {
+ int [] exp_offsets_x = {0, -12, 0};
+ int [] exp_offsets_y = {0, 4, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else {
+ fail();
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 81, 251);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 80, 255);
+ checkHistogramDetails(hdrHistogramDetails, 0, 83, 255);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg24".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg24() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg24");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg24/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg24/input1.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg24_output.jpg", 100, 1000000000L/2421, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 77, 250);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 74, 250);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 86, 250);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 86, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 80, 254);
+ checkHistogramDetails(hdrHistogramDetails, 0, 56, 254);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg25".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg25() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg25");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg25/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg25/input1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg25/input2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg25/input3.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg25_output.jpg", 512, 1000000000L/20, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 39, 253);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg26".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg26() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg26");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ // note we now take only 3 images for bright scenes, but still test with 4 images as this serves as a good test
+ // against ghosting
+ inputs.add(TestUtils.avg_images_path + "testAvg26/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg26/input1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg26/input2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg26/input3.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg26_output.jpg", 100, 1000000000L/365, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ /*if( true )
+ return;*/
+ if( index == 1 ) {
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 2 ) {
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 3 ) {
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, -4, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else {
+ fail();
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 39, 253);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg27".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg27() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg27");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg27/IMG_20180610_205929_0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg27/IMG_20180610_205929_1.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg27_output.jpg", 100, 1000000000L/482, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 39, 253);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg28".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg28() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg28");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ // example from Google HDR+ dataset
+ // note, the number of input images doesn't necessarily match what we'd take for this scene, but we want to compare
+ // to the Google HDR+ result
+ inputs.add(TestUtils.avg_images_path + "testAvg28/input001.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg28/input002.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg28/input003.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg28/input004.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg28/input005.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg28/input006.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg28/input007.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg28/input008.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg28_output.jpg", 811, 1000000000L/21, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 21, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 18, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 8, 255);
+ checkHistogramDetails(hdrHistogramDetails, 0, 13, 255);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg29".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg29() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg29");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ // example from Google HDR+ dataset
+ // note, the number of input images doesn't necessarily match what we'd take for this scene, but we want to compare
+ // to the Google HDR+ result
+ inputs.add(TestUtils.avg_images_path + "testAvg29/input001.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg29/input002.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg29/input003.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg29/input004.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg29/input005.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg29/input006.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg29/input007.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg29/input008.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg29/input009.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg29_output.jpg", 40, 1000000000L/2660, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 88, 127, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 92, 134, 255);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg30".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg30() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg30");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ // example from Google HDR+ dataset
+ // note, the number of input images doesn't necessarily match what we'd take for this scene, but we want to compare
+ // to the Google HDR+ result
+ inputs.add(TestUtils.avg_images_path + "testAvg30/input001.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg30/input002.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg30/input003.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg30_output.jpg", 60, 1000000000L/411, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 2 ) {
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, -4, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 3 ) {
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, -4, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else {
+ fail();
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 134, 254);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 144, 254);
+ checkHistogramDetails(hdrHistogramDetails, 0, 107, 254);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg31".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg31() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg31");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ // example from Google HDR+ dataset
+ // note, the number of input images doesn't necessarily match what we'd take for this scene, but we want to compare
+ // to the Google HDR+ result
+ inputs.add(TestUtils.avg_images_path + "testAvg31/input001.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg31/input002.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg31/input003.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg31/input004.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg31/input005.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg31/input006.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg31/input007.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg31/input008.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg31/input009.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg31/input010.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg31_output.jpg", 609, 1000000000L/25, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 24, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 9, 255);
+ checkHistogramDetails(hdrHistogramDetails, 0, 13, 255);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg32".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg32() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg32");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ // example from Google HDR+ dataset
+ // note, the number of input images doesn't necessarily match what we'd take for this scene, but we want to compare
+ // to the Google HDR+ result
+ inputs.add(TestUtils.avg_images_path + "testAvg32/input001.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg32/input002.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg32/input003.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg32/input004.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg32/input005.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg32/input006.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg32/input007.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg32_output.jpg", 335, 1000000000L/120, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 34, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 13, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 36, 255);
+ checkHistogramDetails(hdrHistogramDetails, 0, 61, 254);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg33".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg33() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg33");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ // example from Google HDR+ dataset
+ // note, the number of input images doesn't necessarily match what we'd take for this scene, but we want to compare
+ // to the Google HDR+ result
+ inputs.add(TestUtils.avg_images_path + "testAvg33/input001.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg33/input002.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg33/input003.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg33/input004.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg33/input005.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg33/input006.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg33/input007.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg33/input008.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg33/input009.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg33/input010.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg33_output.jpg", 948, 1000000000L/18, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 81, 255);
+ checkHistogramDetails(hdrHistogramDetails, 0, 63, 255);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg34".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg34() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg34");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg34/IMG_20180627_121959_0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg34/IMG_20180627_121959_1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg34/IMG_20180627_121959_2.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg34_output.jpg", 100, 1000000000L/289, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 86, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 108, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 114, 254);
+ checkHistogramDetails(hdrHistogramDetails, 0, 103, 255);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg35".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg35() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg35");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg35/IMG_20180711_144453_0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg35/IMG_20180711_144453_1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg35/IMG_20180711_144453_2.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg35_output.jpg", 100, 1000000000L/2549, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 165, 247);
+ checkHistogramDetails(hdrHistogramDetails, 0, 169, 248);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg36".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg36() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg36");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg36/IMG_20180709_114831_0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg36/IMG_20180709_114831_1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg36/IMG_20180709_114831_2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg36/IMG_20180709_114831_3.jpg");
+ // only test 4 images, to reflect latest behaviour that we take 4 images for this ISO/exposure time
+ /*inputs.add(TestUtils.avg_images_path + "testAvg36/IMG_20180709_114831_4.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg36/IMG_20180709_114831_5.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg36/IMG_20180709_114831_6.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg36/IMG_20180709_114831_7.jpg");*/
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg36_output.jpg", 752, 1000000000L/10, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ int [] exp_offsets_x = {0, -12, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ else if( index == 3 ) {
+ int [] exp_offsets_x = {0, -28, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 86, 255);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg37".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg37() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg37");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg37/IMG_20180715_173155_0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg37/IMG_20180715_173155_1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg37/IMG_20180715_173155_2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg37/IMG_20180715_173155_3.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg37_output.jpg", 131, 1000000000L/50, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 12, 109, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 3, 99, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 99, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 125, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 94, 255);
+ checkHistogramDetails(hdrHistogramDetails, 6, 94, 255);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg38".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg38() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg38");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg38/IMG_20180716_232102_0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg38/IMG_20180716_232102_1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg38/IMG_20180716_232102_2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg38/IMG_20180716_232102_3.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg38/IMG_20180716_232102_4.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg38/IMG_20180716_232102_5.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg38/IMG_20180716_232102_6.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg38/IMG_20180716_232102_7.jpg");
+
+ // n.b., this was a zoomed in photo, but can't quite remember the exact zoom level!
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg38_output.jpg", 1505, 1000000000L/10, 3.95f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg39".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg39() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg39");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ // example from Google HDR+ dataset
+ // note, the number of input images doesn't necessarily match what we'd take for this scene, but we want to compare
+ // to the Google HDR+ result
+ inputs.add(TestUtils.avg_images_path + "testAvg39/input001.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg39/input002.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg39/input003.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg39/input004.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg39/input005.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg39/input006.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg39/input007.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg39/input008.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg39/input009.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg39/input010.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg39_output.jpg", 521, 1000000000L/27, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 64, 255);
+ checkHistogramDetails(hdrHistogramDetails, 0, 25, 255);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg40".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg40() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg40");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ // example from Google HDR+ dataset
+ // note, the number of input images doesn't necessarily match what we'd take for this scene, but we want to compare
+ // to the Google HDR+ result
+ inputs.add(TestUtils.avg_images_path + "testAvg40/input001.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg40/input002.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg40/input003.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg40/input004.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg40/input005.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg40/input006.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg40/input007.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg40/input008.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg40/input009.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg40_output.jpg", 199, 1000000000L/120, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 50, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 19, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 50, 255);
+ checkHistogramDetails(hdrHistogramDetails, 0, 67, 255);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg41".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg41() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg41");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ // example from Google HDR+ dataset
+ // note, the number of input images doesn't necessarily match what we'd take for this scene, but we want to compare
+ // to the Google HDR+ result
+ inputs.add(TestUtils.avg_images_path + "testAvg41/input001.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg41/input002.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg41/input003.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg41/input004.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg41/input005.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg41/input006.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg41/input007.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg41/input008.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg41/input009.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg41/input010.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg41_output.jpg", 100, 1000000000L/869, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 49, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 0, 37, 255);
+ checkHistogramDetails(hdrHistogramDetails, 0, 59, 254);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg42".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg42() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg42");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg42/IMG_20180822_145152_0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg42/IMG_20180822_145152_1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg42/IMG_20180822_145152_2.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg42_output.jpg", 100, 1000000000L/2061, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 67, 254);
+ checkHistogramDetails(hdrHistogramDetails, 0, 61, 255);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg43".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg43() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg43");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg43/IMG_20180831_143226_0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg43/IMG_20180831_143226_1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg43/IMG_20180831_143226_2.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg43_output.jpg", 100, 1000000000L/2152, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ checkHistogramDetails(hdrHistogramDetails, 0, 69, 253);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg44".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg44() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg44");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg44/IMG_20180830_133917_0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg44/IMG_20180830_133917_1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg44/IMG_20180830_133917_2.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg44_output.jpg", 40, 1000000000L/2130, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ checkHistogramDetails(hdrHistogramDetails, 0, 75, 255);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg45".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg45() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg45");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg45/IMG_20180719_133947_0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg45/IMG_20180719_133947_1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg45/IMG_20180719_133947_2.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg45_output.jpg", 100, 1000000000L/865, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 75, 255);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg46".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg46() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg46");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg46/IMG_20180903_203141_0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg46/IMG_20180903_203141_1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg46/IMG_20180903_203141_2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg46/IMG_20180903_203141_3.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg46/IMG_20180903_203141_4.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg46/IMG_20180903_203141_5.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg46/IMG_20180903_203141_6.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg46/IMG_20180903_203141_7.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg46_output.jpg", 1505, 1000000000L/10, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ checkHistogramDetails(hdrHistogramDetails, 0, 30, 255);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg47".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg47() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg47");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg47/IMG_20180911_114752_0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg47/IMG_20180911_114752_1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg47/IMG_20180911_114752_2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg47/IMG_20180911_114752_3.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg47_output.jpg", 749, 1000000000L/12, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 30, 255);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg48".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg48() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg48");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg48/IMG_20180911_110520_0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg48/IMG_20180911_110520_1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg48/IMG_20180911_110520_2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg48/IMG_20180911_110520_3.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg48/IMG_20180911_110520_4.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg48/IMG_20180911_110520_5.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg48/IMG_20180911_110520_6.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg48/IMG_20180911_110520_7.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg48_output.jpg", 1196, 1000000000L/10, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 30, 255);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg49".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg49() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg49");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg49/IMG_20180911_120200_0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg49/IMG_20180911_120200_1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg49/IMG_20180911_120200_2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg49/IMG_20180911_120200_3.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg49/IMG_20180911_120200_4.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg49/IMG_20180911_120200_5.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg49/IMG_20180911_120200_6.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg49/IMG_20180911_120200_7.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg49_output.jpg", 1505, 1000000000L/10, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 30, 255);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg50".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg50() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg50");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg50/IMG_20181015_144335_0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg50/IMG_20181015_144335_1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg50/IMG_20181015_144335_2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg50/IMG_20181015_144335_3.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg50_output.jpg", 114, 1000000000L/33, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ checkHistogramDetails(hdrHistogramDetails, 0, 91, 255);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg51".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg51() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg51");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg51/IMG_20181025_182917_0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg51/IMG_20181025_182917_1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg51/IMG_20181025_182917_2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg51/IMG_20181025_182917_3.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg51/IMG_20181025_182917_4.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg51/IMG_20181025_182917_5.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg51/IMG_20181025_182917_6.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg51/IMG_20181025_182917_7.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg51_output.jpg", 1600, 1000000000L/3, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ if( index == 1 ) {
+ int [] exp_offsets_x = {0, 8, 0};
+ int [] exp_offsets_y = {0, 4, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ assertEquals(0, activity.getApplicationInterface().getHDRProcessor().sharp_index);
+ }
+ else if( index == 7 ) {
+ int [] exp_offsets_x = {0, 60, 0};
+ int [] exp_offsets_y = {0, 28, 0};
+ TestUtils.checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, activity.getApplicationInterface().getHDRProcessor().getAvgSampleSize());
+ }
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 91, 255);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvg52".
+ */
+ @Category(AvgTests.class)
+ @Test
+ public void testAvg52() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvg52");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvg52/IMG_20181119_144836_0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg52/IMG_20181119_144836_1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvg52/IMG_20181119_144836_2.jpg");
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvg52_output.jpg", 100, 1000000000L/297, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 0, 91, 255);
+ });
+ }
+
+ /** Tests Avg algorithm on test samples "testAvgtemp".
+ * Used for one-off testing, or to recreate NR images from the base exposures to test an updated alorithm.
+ * The test images should be copied to the test device into DCIM/testOpenCamera/testdata/hdrsamples/testAvgtemp/ .
+ */
+ @Test
+ public void testAvgtemp() throws IOException, InterruptedException {
+ Log.d(TAG, "testAvgtemp");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add(TestUtils.avg_images_path + "testAvgtemp/input0.png");
+ /*inputs.add(TestUtils.avg_images_path + "testAvgtemp/input0.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvgtemp/input1.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvgtemp/input2.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvgtemp/input3.jpg");*/
+ /*inputs.add(TestUtils.avg_images_path + "testAvgtemp/input4.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvgtemp/input5.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvgtemp/input6.jpg");
+ inputs.add(TestUtils.avg_images_path + "testAvgtemp/input7.jpg");*/
+
+ TestUtils.HistogramDetails hdrHistogramDetails = TestUtils.subTestAvg(activity,inputs, "testAvgtemp_output.jpg", 250, 1000000000L/33, 1.0f, new TestUtils.TestAvgCallback() {
+ @Override
+ public void doneProcessAvg(int index) {
+ Log.d(TAG, "doneProcessAvg: " + index);
+ }
+ });
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 39, 253);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanoramaWhite".
+ * This tests that auto-alignment fails gracefully if we can't find any matches.
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanoramaWhite() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanoramaWhite");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ inputs.add(TestUtils.panorama_images_path + "testPanoramaWhite/input0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanoramaWhite/input0.jpg");
+ float camera_angle_x = 66.3177f;
+ float camera_angle_y = 50.04736f;
+ float panorama_pics_per_screen = 2.0f;
+ String output_name = "testPanoramaWhite_output.jpg";
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, null, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 2.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama1".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama1() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama1");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ inputs.add(TestUtils.panorama_images_path + "testPanorama1/input0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama1/input1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama1/input2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama1/input3.jpg");
+ float camera_angle_x = 62.93796f;
+ float camera_angle_y = 47.44656f;
+ float panorama_pics_per_screen = 2.0f;
+ // these images were taken with incorrect camera view angles, so we compensate in the test:
+ panorama_pics_per_screen *= (float)(47.44656/49.56283);
+ String output_name = "testPanorama1_output.jpg";
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, null, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 2.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama2".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama2() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama2");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ /*final float panorama_pics_per_screen = 1.0f;
+ //inputs.add(TestUtils.panorama_images_path + "testPanorama2xxx/input0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama2xxx/input1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama2xxx/input2.jpg");*/
+ /*final float panorama_pics_per_screen = 2.0f;
+ //inputs.add(TestUtils.panorama_images_path + "testPanorama1/input0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama1/input1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama1/input2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama1/input3.jpg");
+ String output_name = "testPanorama1_output.jpg";*/
+ float panorama_pics_per_screen = 4.0f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama2/input0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama2/input1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama2/input2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama2/input3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama2/input4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama2/input5.jpg");
+ String output_name = "testPanorama2_output.jpg";
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+ // these images were taken with incorrect camera view angles, so we compensate in the test:
+ panorama_pics_per_screen *= (float)(50.282097/52.26029);
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, null, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 2.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama3".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama3() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama3");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 4.0f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama3/IMG_20190214_131249.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama3/IMG_20190214_131252.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama3/IMG_20190214_131255.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama3/IMG_20190214_131258.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama3/IMG_20190214_131301.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama3/IMG_20190214_131303.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama3/IMG_20190214_131305.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama3/IMG_20190214_131307.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama3/IMG_20190214_131315.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama3/IMG_20190214_131317.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama3/IMG_20190214_131320.jpg");
+ String output_name = "testPanorama3_output.jpg";
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+ // these images were taken with incorrect camera view angles, so we compensate in the test:
+ panorama_pics_per_screen *= (float)(50.282097/52.26029);
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, null, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama3", with panorama_pics_per_screen set
+ * to 4.0.
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama3_picsperscreen2() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama3_picsperscreen2");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 2.0f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama3/IMG_20190214_131249.jpg");
+ //inputs.add(TestUtils.panorama_images_path + "testPanorama3/IMG_20190214_131252.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama3/IMG_20190214_131255.jpg");
+ //inputs.add(TestUtils.panorama_images_path + "testPanorama3/IMG_20190214_131258.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama3/IMG_20190214_131301.jpg");
+ //inputs.add(TestUtils.panorama_images_path + "testPanorama3/IMG_20190214_131303.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama3/IMG_20190214_131305.jpg");
+ //inputs.add(TestUtils.panorama_images_path + "testPanorama3/IMG_20190214_131307.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama3/IMG_20190214_131315.jpg");
+ //inputs.add(TestUtils.panorama_images_path + "testPanorama3/IMG_20190214_131317.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama3/IMG_20190214_131320.jpg");
+ String output_name = "testPanorama3_picsperscreen2_output.jpg";
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+ // these images were taken with incorrect camera view angles, so we compensate in the test:
+ panorama_pics_per_screen *= (float)(50.282097/52.26029);
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, null, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama4".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama4() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama4");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 4.0f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama4/IMG_20190222_225317_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama4/IMG_20190222_225317_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama4/IMG_20190222_225317_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama4/IMG_20190222_225317_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama4/IMG_20190222_225317_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama4/IMG_20190222_225317_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama4/IMG_20190222_225317_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama4/IMG_20190222_225317_7.jpg");
+ String output_name = "testPanorama4_output.jpg";
+ String gyro_name = TestUtils.panorama_images_path + "testPanorama4/IMG_20190222_225317.xml";
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+ // these images were taken with incorrect camera view angles, so we compensate in the test:
+ panorama_pics_per_screen *= (float)(50.282097/52.26029);
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama5".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama5() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama5");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 4.0f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama5/IMG_20190223_220524_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama5/IMG_20190223_220524_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama5/IMG_20190223_220524_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama5/IMG_20190223_220524_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama5/IMG_20190223_220524_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama5/IMG_20190223_220524_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama5/IMG_20190223_220524_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama5/IMG_20190223_220524_7.jpg");
+ String output_name = "testPanorama5_output.jpg";
+ String gyro_name = TestUtils.panorama_images_path + "testPanorama5/IMG_20190223_220524.xml";
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+ // these images were taken with incorrect camera view angles, so we compensate in the test:
+ panorama_pics_per_screen *= (float)(50.282097/52.26029);
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 0.5f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama6".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama6() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama6");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 4.0f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama6/IMG_20190225_154232_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama6/IMG_20190225_154232_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama6/IMG_20190225_154232_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama6/IMG_20190225_154232_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama6/IMG_20190225_154232_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama6/IMG_20190225_154232_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama6/IMG_20190225_154232_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama6/IMG_20190225_154232_7.jpg");
+ String output_name = "testPanorama6_output.jpg";
+ String gyro_name = TestUtils.panorama_images_path + "testPanorama6/IMG_20190225_154232.xml";
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+ // these images were taken with incorrect camera view angles, so we compensate in the test:
+ panorama_pics_per_screen *= (float)(50.282097/52.26029);
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 0.5f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama7".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama7() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama7");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 4.0f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama7/IMG_20190225_155510_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama7/IMG_20190225_155510_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama7/IMG_20190225_155510_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama7/IMG_20190225_155510_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama7/IMG_20190225_155510_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama7/IMG_20190225_155510_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama7/IMG_20190225_155510_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama7/IMG_20190225_155510_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama7/IMG_20190225_155510_8.jpg");
+ String output_name = "testPanorama7_output.jpg";
+ String gyro_name = TestUtils.panorama_images_path + "testPanorama7/IMG_20190225_155510.xml";
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+ // these images were taken with incorrect camera view angles, so we compensate in the test:
+ panorama_pics_per_screen *= (float)(50.282097/52.26029);
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 0.5f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama8".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama8() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama8");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 2.0f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama8/IMG_20190227_001431_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama8/IMG_20190227_001431_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama8/IMG_20190227_001431_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama8/IMG_20190227_001431_3.jpg");
+ String output_name = "testPanorama8_output.jpg";
+ String gyro_name = TestUtils.panorama_images_path + "testPanorama8/IMG_20190227_001431.xml";
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+ // these images were taken with incorrect camera view angles, so we compensate in the test:
+ panorama_pics_per_screen *= (float)(50.282097/52.26029);
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 0.5f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama9".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama9() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama9");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.0f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama9/IMG_20190301_145213_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama9/IMG_20190301_145213_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama9/IMG_20190301_145213_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama9/IMG_20190301_145213_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama9/IMG_20190301_145213_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama9/IMG_20190301_145213_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama9/IMG_20190301_145213_6.jpg");
+ String output_name = "testPanorama9_output.jpg";
+ String gyro_name = TestUtils.panorama_images_path + "testPanorama9/IMG_20190301_145213.xml";
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+ // these images were taken with incorrect camera view angles, so we compensate in the test:
+ panorama_pics_per_screen *= (float)(50.282097/50.44399);
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 0.5f);
+
+ try {
+ Thread.sleep(1000); // need to wait for debug images to be saved/broadcast?
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ }
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama10".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama10() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama10");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.0f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama10/IMG_20190301_144948_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama10/IMG_20190301_144948_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama10/IMG_20190301_144948_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama10/IMG_20190301_144948_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama10/IMG_20190301_144948_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama10/IMG_20190301_144948_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama10/IMG_20190301_144948_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama10/IMG_20190301_144948_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama10/IMG_20190301_144948_8.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama10/IMG_20190301_144948_9.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama10/IMG_20190301_144948_10.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama10/IMG_20190301_144948_11.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama10/IMG_20190301_144948_12.jpg");
+ String output_name = "testPanorama10_output.jpg";
+ String gyro_name = TestUtils.panorama_images_path + "testPanorama10/IMG_20190301_144948.xml";
+ //gyro_name = null;
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+ // these images were taken with incorrect camera view angles, so we compensate in the test:
+ panorama_pics_per_screen *= (float)(50.282097/50.44399);
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 0.5f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama11".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama11() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama11");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.0f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama11/IMG_20190306_143652_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama11/IMG_20190306_143652_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama11/IMG_20190306_143652_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama11/IMG_20190306_143652_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama11/IMG_20190306_143652_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama11/IMG_20190306_143652_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama11/IMG_20190306_143652_6.jpg");
+ String output_name = "testPanorama11_output.jpg";
+ String gyro_name = TestUtils.panorama_images_path + "testPanorama11/IMG_20190306_143652.xml";
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+ // these images were taken with incorrect camera view angles, so we compensate in the test:
+ panorama_pics_per_screen *= (float)(50.282097/50.44399);
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 0.5f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama12".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama12() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama12");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.0f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama12/IMG_20190308_152008_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama12/IMG_20190308_152008_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama12/IMG_20190308_152008_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama12/IMG_20190308_152008_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama12/IMG_20190308_152008_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama12/IMG_20190308_152008_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama12/IMG_20190308_152008_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama12/IMG_20190308_152008_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama12/IMG_20190308_152008_8.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama12/IMG_20190308_152008_9.jpg");
+ String output_name = "testPanorama12_output.jpg";
+ String gyro_name = TestUtils.panorama_images_path + "testPanorama12/IMG_20190308_152008.xml";
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+ // these images were taken with incorrect camera view angles, so we compensate in the test:
+ panorama_pics_per_screen *= (float)(50.282097/50.44399);
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 0.5f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama13".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama13() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama13");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.0f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama13/IMG_20190512_014152_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama13/IMG_20190512_014152_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama13/IMG_20190512_014152_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama13/IMG_20190512_014152_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama13/IMG_20190512_014152_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama13/IMG_20190512_014152_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama13/IMG_20190512_014152_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama13/IMG_20190512_014152_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama13/IMG_20190512_014152_8.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama13/IMG_20190512_014152_9.jpg");
+ String output_name = "testPanorama13_output.jpg";
+ String gyro_name = TestUtils.panorama_images_path + "testPanorama13/IMG_20190512_014152.xml";
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 0.5f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama14".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama14() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama14");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama14/IMG_20190513_151249_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama14/IMG_20190513_151249_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama14/IMG_20190513_151249_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama14/IMG_20190513_151249_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama14/IMG_20190513_151249_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama14/IMG_20190513_151249_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama14/IMG_20190513_151249_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama14/IMG_20190513_151249_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama14/IMG_20190513_151249_8.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama14/IMG_20190513_151249_9.jpg");
+ String output_name = "testPanorama14_output.jpg";
+ String gyro_name = TestUtils.panorama_images_path + "testPanorama14/IMG_20190513_151249.xml";
+ //gyro_name = null;
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 0.5f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama15".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama15() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama15");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama15/IMG_20190513_151624_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama15/IMG_20190513_151624_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama15/IMG_20190513_151624_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama15/IMG_20190513_151624_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama15/IMG_20190513_151624_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama15/IMG_20190513_151624_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama15/IMG_20190513_151624_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama15/IMG_20190513_151624_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama15/IMG_20190513_151624_8.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama15/IMG_20190513_151624_9.jpg");
+ String output_name = "testPanorama15_output.jpg";
+ String gyro_name = TestUtils.panorama_images_path + "testPanorama15/IMG_20190513_151624.xml";
+ //gyro_name = null;
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 0.5f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama16".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama16() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama16");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama16/IMG_20190624_151731_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama16/IMG_20190624_151731_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama16/IMG_20190624_151731_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama16/IMG_20190624_151731_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama16/IMG_20190624_151731_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama16/IMG_20190624_151731_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama16/IMG_20190624_151731_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama16/IMG_20190624_151731_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama16/IMG_20190624_151731_8.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama16/IMG_20190624_151731_9.jpg");
+ String output_name = "testPanorama16_output.jpg";
+ String gyro_name = TestUtils.panorama_images_path + "testPanorama16/IMG_20190624_151731.xml";
+ //gyro_name = null;
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 0.5f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama17".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama17() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama17");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama17/IMG_20190625_135423_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama17/IMG_20190625_135423_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama17/IMG_20190625_135423_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama17/IMG_20190625_135423_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama17/IMG_20190625_135423_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama17/IMG_20190625_135423_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama17/IMG_20190625_135423_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama17/IMG_20190625_135423_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama17/IMG_20190625_135423_8.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama17/IMG_20190625_135423_9.jpg");
+ String output_name = "testPanorama17_output.jpg";
+ String gyro_name = TestUtils.panorama_images_path + "testPanorama17/IMG_20190625_135423.xml";
+ //gyro_name = null;
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 0.5f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama18".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama18() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama18");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama18/IMG_20190626_152559_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama18/IMG_20190626_152559_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama18/IMG_20190626_152559_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama18/IMG_20190626_152559_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama18/IMG_20190626_152559_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama18/IMG_20190626_152559_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama18/IMG_20190626_152559_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama18/IMG_20190626_152559_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama18/IMG_20190626_152559_8.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama18/IMG_20190626_152559_9.jpg");
+ String output_name = "testPanorama18_output.jpg";
+ String gyro_name = TestUtils.panorama_images_path + "testPanorama18/IMG_20190626_152559.xml";
+ //gyro_name = null;
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 0.5f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama19".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama19() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama19");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama19/IMG_20190627_134059_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama19/IMG_20190627_134059_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama19/IMG_20190627_134059_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama19/IMG_20190627_134059_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama19/IMG_20190627_134059_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama19/IMG_20190627_134059_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama19/IMG_20190627_134059_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama19/IMG_20190627_134059_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama19/IMG_20190627_134059_8.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama19/IMG_20190627_134059_9.jpg");
+ String output_name = "testPanorama19_output.jpg";
+ String gyro_name = TestUtils.panorama_images_path + "testPanorama19/IMG_20190627_134059.xml";
+ //gyro_name = null;
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama20".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama20() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama20");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama20/IMG_20190628_145027_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama20/IMG_20190628_145027_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama20/IMG_20190628_145027_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama20/IMG_20190628_145027_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama20/IMG_20190628_145027_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama20/IMG_20190628_145027_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama20/IMG_20190628_145027_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama20/IMG_20190628_145027_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama20/IMG_20190628_145027_8.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama20/IMG_20190628_145027_9.jpg");
+ String output_name = "testPanorama20_output.jpg";
+ String gyro_name = TestUtils.panorama_images_path + "testPanorama20/IMG_20190628_145027.xml";
+ //gyro_name = null;
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 0.5f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama21".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama21() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama21");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama21/IMG_20190628_145552_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama21/IMG_20190628_145552_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama21/IMG_20190628_145552_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama21/IMG_20190628_145552_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama21/IMG_20190628_145552_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama21/IMG_20190628_145552_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama21/IMG_20190628_145552_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama21/IMG_20190628_145552_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama21/IMG_20190628_145552_8.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama21/IMG_20190628_145552_9.jpg");
+ String output_name = "testPanorama21_output.jpg";
+ String gyro_name = TestUtils.panorama_images_path + "testPanorama21/IMG_20190628_145552.xml";
+ //gyro_name = null;
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 0.5f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama22".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama22() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama22");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama22/IMG_20190629_165627_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama22/IMG_20190629_165627_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama22/IMG_20190629_165627_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama22/IMG_20190629_165627_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama22/IMG_20190629_165627_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama22/IMG_20190629_165627_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama22/IMG_20190629_165627_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama22/IMG_20190629_165627_7.jpg");
+ String output_name = "testPanorama22_output.jpg";
+ String gyro_name = null;
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama23".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama23() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama23");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama23/IMG_20190702_145916_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama23/IMG_20190702_145916_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama23/IMG_20190702_145916_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama23/IMG_20190702_145916_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama23/IMG_20190702_145916_4.jpg");
+ String output_name = "testPanorama23_output.jpg";
+ String gyro_name = null;
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama24".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama24() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama24");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama24/IMG_20190703_154333_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama24/IMG_20190703_154333_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama24/IMG_20190703_154333_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama24/IMG_20190703_154333_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama24/IMG_20190703_154333_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama24/IMG_20190703_154333_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama24/IMG_20190703_154333_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama24/IMG_20190703_154333_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama24/IMG_20190703_154333_8.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama24/IMG_20190703_154333_9.jpg");
+ String output_name = "testPanorama24_output.jpg";
+ String gyro_name = null;
+ // taken with OnePlus 3T, Camera2 API:
+ float camera_angle_x = 62.93796f;
+ float camera_angle_y = 47.44656f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama25".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama25() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama25");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ //float panorama_pics_per_screen = 3.33333f / 2.0f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama25/IMG_20190706_215940_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama25/IMG_20190706_215940_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama25/IMG_20190706_215940_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama25/IMG_20190706_215940_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama25/IMG_20190706_215940_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama25/IMG_20190706_215940_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama25/IMG_20190706_215940_6.jpg");
+ String output_name = "testPanorama25_output.jpg";
+ String gyro_name = null;
+ // taken with Nokia 8, Camera2 API:
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama26".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama26() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama26");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama26/IMG_20190706_214842_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama26/IMG_20190706_214842_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama26/IMG_20190706_214842_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama26/IMG_20190706_214842_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama26/IMG_20190706_214842_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama26/IMG_20190706_214842_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama26/IMG_20190706_214842_6.jpg");
+ String output_name = "testPanorama26_output.jpg";
+ String gyro_name = null;
+ // taken with Nokia 8, Camera2 API:
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama27".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama27() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama27");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama27/IMG_20190706_192120_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama27/IMG_20190706_192120_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama27/IMG_20190706_192120_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama27/IMG_20190706_192120_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama27/IMG_20190706_192120_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama27/IMG_20190706_192120_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama27/IMG_20190706_192120_6.jpg");
+ String output_name = "testPanorama27_output.jpg";
+ String gyro_name = null;
+ // taken with Nokia 8, Camera2 API:
+ float camera_angle_x = 66.708595f;
+ float camera_angle_y = 50.282097f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama28".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama28() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama28");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ // right-to-left:
+ /*inputs.add(TestUtils.panorama_images_path + "testPanorama28/IMG_20190725_134756_9.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/IMG_20190725_134756_8.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/IMG_20190725_134756_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/IMG_20190725_134756_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/IMG_20190725_134756_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/IMG_20190725_134756_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/IMG_20190725_134756_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/IMG_20190725_134756_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/IMG_20190725_134756_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/IMG_20190725_134756_0.jpg");*/
+ // converted from original JPEGs to PNG using Nokia 8:
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/input_bitmap_0.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/input_bitmap_1.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/input_bitmap_2.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/input_bitmap_3.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/input_bitmap_4.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/input_bitmap_5.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/input_bitmap_6.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/input_bitmap_7.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/input_bitmap_8.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/input_bitmap_9.png");
+ String output_name = "testPanorama28_output.jpg";
+ String gyro_name = null;
+ // taken with Samsung Galaxy S10e, Camera2 API, standard rear camera:
+ float camera_angle_x = 66.3177f;
+ float camera_angle_y = 50.04736f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama28", but with a nbnq similar set of
+ * input images. Instead of converting the original JPEGs to PNG on Nokia 8, this was done on
+ * the Samsung Galaxy S10e, which gives small differences, but enough to show up potential
+ * stability issues.
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama28_galaxys10e() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama28_galaxys10e");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ // right-to-left:
+ /*inputs.add(TestUtils.panorama_images_path + "testPanorama28/IMG_20190725_134756_9.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/IMG_20190725_134756_8.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/IMG_20190725_134756_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/IMG_20190725_134756_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/IMG_20190725_134756_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/IMG_20190725_134756_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/IMG_20190725_134756_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/IMG_20190725_134756_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/IMG_20190725_134756_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/IMG_20190725_134756_0.jpg");*/
+ // converted from original JPEGs to PNG using Samsung Galaxy S10e:
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/galaxys10e_input_bitmap_0.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/galaxys10e_input_bitmap_1.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/galaxys10e_input_bitmap_2.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/galaxys10e_input_bitmap_3.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/galaxys10e_input_bitmap_4.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/galaxys10e_input_bitmap_5.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/galaxys10e_input_bitmap_6.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/galaxys10e_input_bitmap_7.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/galaxys10e_input_bitmap_8.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama28/galaxys10e_input_bitmap_9.png");
+ String output_name = "testPanorama28_galaxys10e_output.jpg";
+ String gyro_name = null;
+ // taken with Samsung Galaxy S10e, Camera2 API, standard rear camera:
+ float camera_angle_x = 66.3177f;
+ float camera_angle_y = 50.04736f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama29".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama29() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama29");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ // right-to-left:
+ inputs.add(TestUtils.panorama_images_path + "testPanorama29/IMG_20190719_145852_9.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama29/IMG_20190719_145852_8.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama29/IMG_20190719_145852_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama29/IMG_20190719_145852_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama29/IMG_20190719_145852_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama29/IMG_20190719_145852_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama29/IMG_20190719_145852_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama29/IMG_20190719_145852_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama29/IMG_20190719_145852_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama29/IMG_20190719_145852_0.jpg");
+ String output_name = "testPanorama29_output.jpg";
+ String gyro_name = null;
+ // taken with Nokia 8, old API:
+ float camera_angle_x = 66.1062f;
+ float camera_angle_y = 49.88347f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama30".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama30() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama30");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ /*inputs.add(TestUtils.panorama_images_path + "testPanorama30/IMG_20190723_142934_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/IMG_20190723_142934_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/IMG_20190723_142934_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/IMG_20190723_142934_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/IMG_20190723_142934_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/IMG_20190723_142934_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/IMG_20190723_142934_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/IMG_20190723_142934_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/IMG_20190723_142934_8.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/IMG_20190723_142934_9.jpg");*/
+ // converted from original JPEGs to PNG using Nokia 8:
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/nokia8_input_bitmap_0.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/nokia8_input_bitmap_1.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/nokia8_input_bitmap_2.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/nokia8_input_bitmap_3.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/nokia8_input_bitmap_4.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/nokia8_input_bitmap_5.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/nokia8_input_bitmap_6.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/nokia8_input_bitmap_7.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/nokia8_input_bitmap_8.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/nokia8_input_bitmap_9.png");
+ String output_name = "testPanorama30_output.jpg";
+ String gyro_name = null;
+ // taken with Samsung Galaxy S10e, old API, standard rear camera:
+ // n.b., camera angles are indeed the exact same as with Camera2
+ float camera_angle_x = 66.3177f;
+ float camera_angle_y = 50.04736f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama30", but with a nbnq similar set of
+ * input images. Instead of converting the original JPEGs to PNG on Nokia 8, this was done on
+ * the Samsung Galaxy S10e, which gives small differences, but enough to show up potential
+ * stability issues.
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama30_galaxys10e() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama30_galaxys10e");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ /*inputs.add(TestUtils.panorama_images_path + "testPanorama30/IMG_20190723_142934_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/IMG_20190723_142934_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/IMG_20190723_142934_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/IMG_20190723_142934_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/IMG_20190723_142934_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/IMG_20190723_142934_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/IMG_20190723_142934_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/IMG_20190723_142934_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/IMG_20190723_142934_8.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/IMG_20190723_142934_9.jpg");*/
+ // converted from original JPEGs to PNG using Samsung Galaxy S10e:
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/galaxys10e_input_bitmap_0.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/galaxys10e_input_bitmap_1.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/galaxys10e_input_bitmap_2.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/galaxys10e_input_bitmap_3.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/galaxys10e_input_bitmap_4.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/galaxys10e_input_bitmap_5.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/galaxys10e_input_bitmap_6.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/galaxys10e_input_bitmap_7.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/galaxys10e_input_bitmap_8.png");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama30/galaxys10e_input_bitmap_9.png");
+ String output_name = "testPanorama30_galaxys10e_output.jpg";
+ String gyro_name = null;
+ // taken with Samsung Galaxy S10e, old API, standard rear camera:
+ // n.b., camera angles are indeed the exact same as with Camera2
+ float camera_angle_x = 66.3177f;
+ float camera_angle_y = 50.04736f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama31".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama31() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama31");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama31/IMG_20190704_135633_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama31/IMG_20190704_135633_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama31/IMG_20190704_135633_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama31/IMG_20190704_135633_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama31/IMG_20190704_135633_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama31/IMG_20190704_135633_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama31/IMG_20190704_135633_6.jpg");
+ String output_name = "testPanorama31_output.jpg";
+ String gyro_name = null;
+ // taken with OnePlus 3T, Camera2 API:
+ float camera_angle_x = 62.93796f;
+ float camera_angle_y = 47.44656f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama3".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama32() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama32");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama32/IMG_20190705_145938_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama32/IMG_20190705_145938_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama32/IMG_20190705_145938_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama32/IMG_20190705_145938_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama32/IMG_20190705_145938_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama32/IMG_20190705_145938_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama32/IMG_20190705_145938_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama32/IMG_20190705_145938_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama32/IMG_20190705_145938_8.jpg");
+ String output_name = "testPanorama32_output.jpg";
+ String gyro_name = null;
+ // taken with OnePlus 3T, old API:
+ float camera_angle_x = 60.0f;
+ float camera_angle_y = 45.0f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama33".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama33() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama33");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama33/IMG_20190713_013437_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama33/IMG_20190713_013437_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama33/IMG_20190713_013437_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama33/IMG_20190713_013437_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama33/IMG_20190713_013437_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama33/IMG_20190713_013437_5.jpg");
+ String output_name = "testPanorama33_output.jpg";
+ String gyro_name = null;
+ // taken with Nokia 8, old API:
+ float camera_angle_x = 66.1062f;
+ float camera_angle_y = 49.88347f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama34".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama34() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama34");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ // right-to-left:
+ inputs.add(TestUtils.panorama_images_path + "testPanorama34/IMG_20190717_144042_9.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama34/IMG_20190717_144042_8.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama34/IMG_20190717_144042_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama34/IMG_20190717_144042_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama34/IMG_20190717_144042_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama34/IMG_20190717_144042_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama34/IMG_20190717_144042_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama34/IMG_20190717_144042_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama34/IMG_20190717_144042_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama34/IMG_20190717_144042_0.jpg");
+ String output_name = "testPanorama34_output.jpg";
+ String gyro_name = null;
+ // taken with Nexus 6, old API:
+ float camera_angle_x = 62.7533f;
+ float camera_angle_y = 47.298824f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama35".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama35() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama35");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama35/IMG_20190717_145114_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama35/IMG_20190717_145114_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama35/IMG_20190717_145114_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama35/IMG_20190717_145114_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama35/IMG_20190717_145114_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama35/IMG_20190717_145114_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama35/IMG_20190717_145114_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama35/IMG_20190717_145114_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama35/IMG_20190717_145114_8.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama35/IMG_20190717_145114_9.jpg");
+ String output_name = "testPanorama35_output.jpg";
+ String gyro_name = null;
+ // taken with Nexus 7, old API:
+ float camera_angle_x = 55.0f;
+ float camera_angle_y = 41.401073f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama36".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama36() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama36");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama36/IMG_20190722_201331_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama36/IMG_20190722_201331_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama36/IMG_20190722_201331_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama36/IMG_20190722_201331_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama36/IMG_20190722_201331_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama36/IMG_20190722_201331_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama36/IMG_20190722_201331_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama36/IMG_20190722_201331_7.jpg");
+ String output_name = "testPanorama36_output.jpg";
+ String gyro_name = null;
+ // taken with Samsung Galaxy S10e, Camera2 API, ultra wide rear camera:
+ float camera_angle_x = 104.00253f;
+ float camera_angle_y = 81.008804f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama37".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama37() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama37");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama37/IMG_20190723_203441_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama37/IMG_20190723_203441_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama37/IMG_20190723_203441_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama37/IMG_20190723_203441_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama37/IMG_20190723_203441_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama37/IMG_20190723_203441_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama37/IMG_20190723_203441_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama37/IMG_20190723_203441_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama37/IMG_20190723_203441_8.jpg");
+ String output_name = "testPanorama37_output.jpg";
+ String gyro_name = null;
+ // taken with Samsung Galaxy S10e, old API, standard rear camera:
+ // n.b., camera angles are indeed the exact same as with Camera2
+ float camera_angle_x = 66.3177f;
+ float camera_angle_y = 50.04736f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ /** Tests panorama algorithm on test samples "testPanorama38".
+ */
+ @Category(PanoramaTests.class)
+ @Test
+ public void testPanorama38() throws IOException, InterruptedException {
+ Log.d(TAG, "testPanorama38");
+
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> { // for simplicity, run the entire test on the UI thread
+ // list assets
+ List inputs = new ArrayList<>();
+
+ float panorama_pics_per_screen = 3.33333f;
+ inputs.add(TestUtils.panorama_images_path + "testPanorama38/IMG_20190722_141148_0.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama38/IMG_20190722_141148_1.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama38/IMG_20190722_141148_2.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama38/IMG_20190722_141148_3.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama38/IMG_20190722_141148_4.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama38/IMG_20190722_141148_5.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama38/IMG_20190722_141148_6.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama38/IMG_20190722_141148_7.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama38/IMG_20190722_141148_8.jpg");
+ inputs.add(TestUtils.panorama_images_path + "testPanorama38/IMG_20190722_141148_9.jpg");
+ String output_name = "testPanorama38_output.jpg";
+ String gyro_name = null;
+ // taken with Samsung Galaxy S10e, Camera2 API, standard rear camera:
+ float camera_angle_x = 66.3177f;
+ float camera_angle_y = 50.04736f;
+
+ TestUtils.subTestPanorama(activity, inputs, output_name, gyro_name, panorama_pics_per_screen, camera_angle_x, camera_angle_y, 1.0f);
+ });
+ }
+
+ private void waitForTakePhoto() {
+ Log.d(TAG, "wait until finished taking photo");
+ long time_s = System.currentTimeMillis();
+ while(true) {
+ boolean waiting = getActivityValue(activity -> (activity.getPreview().isTakingPhoto() || !activity.getApplicationInterface().canTakeNewPhoto()));
+ if( !waiting ) {
+ break;
+ }
+ mActivityRule.getScenario().onActivity(activity -> {
+ TestUtils.waitForTakePhotoChecks(activity, time_s);
+ });
+ }
+
+ Log.d(TAG, "done taking photo");
+ }
+
+ /** Tests behaviour of the MainActivity.OnApplyWindowInsetsListener() for edge-to-edge mode on
+ * Android 15.
+ */
+ @Category(MainTests.class)
+ @Test
+ public void testWindowInsets() throws InterruptedException {
+ Log.d(TAG, "testWindowInsets");
+ setToDefault();
+
+ if( !getActivityValue(MainActivity::getEdgeToEdgeMode) ) {
+ Log.d(TAG, "test requires edge-to-edge mode");
+ return;
+ }
+
+ MainActivity.test_force_system_orientation = true;
+ MainActivity.test_force_window_insets = true;
+
+ // portrait, typical with navigation at bottom, cutout at top
+ MainActivity.test_system_orientation = MainActivity.SystemOrientation.PORTRAIT;
+ MainActivity.test_insets = Insets.of(0, 200, 0, 300);
+ MainActivity.test_cutout_insets = Insets.of(0, 200, 0, 0);
+ restart(); // restart to force OnApplyWindowInsetsListener() to be called with new test values
+ mActivityRule.getScenario().onActivity(activity -> {
+ assertEquals(300, activity.getNavigationGap());
+ assertEquals(0, activity.getNavigationGapLandscape());
+ assertEquals(0, activity.getNavigationGapReverseLandscape());
+ });
+ // landscape
+ MainActivity.test_system_orientation = MainActivity.SystemOrientation.LANDSCAPE;
+ MainActivity.test_insets = Insets.of(200, 0, 300, 0);
+ MainActivity.test_cutout_insets = Insets.of(200, 0, 0, 0);
+ restart(); // restart to force OnApplyWindowInsetsListener() to be called with new test values
+ mActivityRule.getScenario().onActivity(activity -> {
+ assertEquals(300, activity.getNavigationGap());
+ assertEquals(0, activity.getNavigationGapLandscape());
+ assertEquals(0, activity.getNavigationGapReverseLandscape());
+ });
+ // reverse landscape
+ MainActivity.test_system_orientation = MainActivity.SystemOrientation.REVERSE_LANDSCAPE;
+ MainActivity.test_insets = Insets.of(300, 0, 200, 0);
+ MainActivity.test_cutout_insets = Insets.of(0, 0, 200, 0);
+ restart(); // restart to force OnApplyWindowInsetsListener() to be called with new test values
+ mActivityRule.getScenario().onActivity(activity -> {
+ assertEquals(300, activity.getNavigationGap());
+ assertEquals(0, activity.getNavigationGapLandscape());
+ assertEquals(0, activity.getNavigationGapReverseLandscape());
+ });
+
+ // portrait, navigation at bottom, double cutout at top and bottom
+ MainActivity.test_system_orientation = MainActivity.SystemOrientation.PORTRAIT;
+ MainActivity.test_insets = Insets.of(0, 100, 0, 500);
+ MainActivity.test_cutout_insets = Insets.of(0, 100, 0, 300);
+ restart(); // restart to force OnApplyWindowInsetsListener() to be called with new test values
+ mActivityRule.getScenario().onActivity(activity -> {
+ assertEquals(200, activity.getNavigationGap()); // should only include the gap for the navigation, not the bottom cutout
+ assertEquals(0, activity.getNavigationGapLandscape());
+ assertEquals(0, activity.getNavigationGapReverseLandscape());
+ });
+ // landscape
+ MainActivity.test_system_orientation = MainActivity.SystemOrientation.LANDSCAPE;
+ MainActivity.test_insets = Insets.of(100, 0, 500, 0);
+ MainActivity.test_cutout_insets = Insets.of(100, 0, 300, 0);
+ restart(); // restart to force OnApplyWindowInsetsListener() to be called with new test values
+ mActivityRule.getScenario().onActivity(activity -> {
+ assertEquals(200, activity.getNavigationGap()); // should only include the gap for the navigation, not the bottom cutout
+ assertEquals(0, activity.getNavigationGapLandscape());
+ assertEquals(0, activity.getNavigationGapReverseLandscape());
+ });
+ // reverse landscape
+ MainActivity.test_system_orientation = MainActivity.SystemOrientation.REVERSE_LANDSCAPE;
+ MainActivity.test_insets = Insets.of(500, 0, 100, 0);
+ MainActivity.test_cutout_insets = Insets.of(300, 0, 100, 0);
+ restart(); // restart to force OnApplyWindowInsetsListener() to be called with new test values
+ mActivityRule.getScenario().onActivity(activity -> {
+ assertEquals(200, activity.getNavigationGap()); // should only include the gap for the navigation, not the bottom cutout
+ assertEquals(0, activity.getNavigationGapLandscape());
+ assertEquals(0, activity.getNavigationGapReverseLandscape());
+ });
+
+ // portrait, no navigation bar, waterfall cutout
+ MainActivity.test_system_orientation = MainActivity.SystemOrientation.PORTRAIT;
+ MainActivity.test_insets = Insets.of(50, 100, 50, 100);
+ MainActivity.test_cutout_insets = Insets.of(50, 100, 50, 100);
+ restart(); // restart to force OnApplyWindowInsetsListener() to be called with new test values
+ mActivityRule.getScenario().onActivity(activity -> {
+ // navigation gaps should be 0, as shouldn't include cutout
+ assertEquals(0, activity.getNavigationGap());
+ assertEquals(0, activity.getNavigationGapLandscape());
+ assertEquals(0, activity.getNavigationGapReverseLandscape());
+ });
+
+ // landscape, navigation along landscape edge
+ MainActivity.test_system_orientation = MainActivity.SystemOrientation.LANDSCAPE;
+ MainActivity.test_insets = Insets.of(100, 0, 0, 250);
+ MainActivity.test_cutout_insets = Insets.of(100, 0, 0, 0);
+ restart(); // restart to force OnApplyWindowInsetsListener() to be called with new test values
+ mActivityRule.getScenario().onActivity(activity -> {
+ assertEquals(0, activity.getNavigationGap());
+ assertEquals(250, activity.getNavigationGapLandscape());
+ assertEquals(0, activity.getNavigationGapReverseLandscape());
+ });
+ if( getActivityValue(activity -> activity.getPreview().usingCamera2API()) ) {
+ // also test manual focus seekbar
+ mActivityRule.getScenario().onActivity(activity -> {
+ View focus_seekbar = activity.findViewById(R.id.focus_seekbar);
+ assertEquals(focus_seekbar.getVisibility(), View.GONE);
+ });
+ switchToFocusValue("focus_mode_manual2");
+ mActivityRule.getScenario().onActivity(activity -> {
+ View focus_seekbar = activity.findViewById(R.id.focus_seekbar);
+ assertEquals(focus_seekbar.getVisibility(), View.VISIBLE);
+ RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)focus_seekbar.getLayoutParams();
+ assertEquals(0, layoutParams.leftMargin);
+ assertEquals(0, layoutParams.topMargin);
+ assertEquals(0, layoutParams.rightMargin);
+ assertEquals(250, layoutParams.bottomMargin);
+ });
+ }
+
+ // reverse landscape, navigation along landscape edge
+ MainActivity.test_system_orientation = MainActivity.SystemOrientation.REVERSE_LANDSCAPE;
+ MainActivity.test_insets = Insets.of(0, 0, 100, 250);
+ MainActivity.test_cutout_insets = Insets.of(0, 0, 100, 0);
+ restart(); // restart to force OnApplyWindowInsetsListener() to be called with new test values
+ mActivityRule.getScenario().onActivity(activity -> {
+ assertEquals(0, activity.getNavigationGap());
+ assertEquals(0, activity.getNavigationGapLandscape());
+ assertEquals(250, activity.getNavigationGapReverseLandscape());
+ });
+ if( getActivityValue(activity -> activity.getPreview().usingCamera2API()) ) {
+ // also test manual focus seekbar
+ switchToFocusValue("focus_mode_manual2");
+ mActivityRule.getScenario().onActivity(activity -> {
+ View focus_seekbar = activity.findViewById(R.id.focus_seekbar);
+ assertEquals(focus_seekbar.getVisibility(), View.VISIBLE);
+ RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)focus_seekbar.getLayoutParams();
+ assertEquals(0, layoutParams.leftMargin);
+ assertEquals(0, layoutParams.topMargin);
+ assertEquals(0, layoutParams.rightMargin);
+ assertEquals(0, layoutParams.bottomMargin);
+ });
+ }
+ }
+
+ private void subTestTouchToFocus(final boolean wait_after_focus, final boolean single_tap_photo, final boolean double_tap_photo, final boolean manual_can_auto_focus, final boolean can_focus_area, final String focus_value, final String focus_value_ui) throws InterruptedException {
+ // touch to auto-focus with focus area (will also exit immersive mode)
+ // autofocus shouldn't be immediately, but after a delay
+ // and Galaxy S10e needs a longer delay for some reason, for the subsequent touch of the preview view to register
+ Thread.sleep(2000);
+ int saved_count = getActivityValue(activity -> activity.getPreview().count_cameraAutoFocus);
+ Log.d(TAG, "saved count_cameraAutoFocus: " + saved_count);
+ Log.d(TAG, "### about to click preview for autofocus");
+
+ onView(anyOf(ViewMatchers.withClassName(endsWith("MySurfaceView")), ViewMatchers.withClassName(endsWith("MyTextureView")))).perform(click());
+
+ Log.d(TAG, "### done click preview for autofocus");
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ TestUtils.touchToFocusChecks(activity, single_tap_photo, double_tap_photo, manual_can_auto_focus, can_focus_area, focus_value, focus_value_ui, saved_count);
+ });
+
+ if( double_tap_photo ) {
+ Thread.sleep(100);
+ Log.d(TAG, "about to click preview again for double tap");
+ //onView(withId(preview_view_id)).perform(ViewActions.doubleClick());
+ mActivityRule.getScenario().onActivity(activity -> {
+ //onView(anyOf(ViewMatchers.withClassName(endsWith("MySurfaceView")), ViewMatchers.withClassName(endsWith("MyTextureView")))).perform(click());
+ activity.getPreview().onDoubleTap(); // calling tapView twice doesn't seem to work consistently, so we call this directly!
+ });
+ }
+ if( wait_after_focus && !single_tap_photo && !double_tap_photo) {
+ // don't wait after single or double tap photo taking, as the photo taking operation is already started
+ Log.d(TAG, "wait after focus...");
+ Thread.sleep(3000);
+ }
+ }
+
+ private void subTestTakePhoto(boolean locked_focus, boolean immersive_mode, boolean touch_to_focus, boolean wait_after_focus, boolean single_tap_photo, boolean double_tap_photo, boolean is_raw, boolean test_wait_capture_result) throws InterruptedException {
+ Thread.sleep(500);
+
+ TestUtils.SubTestTakePhotoInfo info = getActivityValue(activity -> TestUtils.getSubTestTakePhotoInfo(activity, immersive_mode, single_tap_photo, double_tap_photo));
+
+ int saved_count_cameraTakePicture = getActivityValue(activity -> activity.getPreview().count_cameraTakePicture);
+
+ // count initial files in folder
+ String [] files = getActivityValue(activity -> TestUtils.filesInSaveFolder(activity));
+ int n_files = files == null ? 0 : files.length;
+ Log.d(TAG, "n_files at start: " + n_files);
+
+ int saved_count = getActivityValue(activity -> activity.getPreview().count_cameraAutoFocus);
+
+ int saved_thumbnail_count = getActivityValue(activity -> activity.getApplicationInterface().getDrawPreview().test_thumbnail_anim_count);
+ Log.d(TAG, "saved_thumbnail_count: " + saved_thumbnail_count);
+
+ if( touch_to_focus ) {
+ subTestTouchToFocus(wait_after_focus, single_tap_photo, double_tap_photo, info.manual_can_auto_focus, info.can_focus_area, info.focus_value, info.focus_value_ui);
+ }
+ Log.d(TAG, "saved count_cameraAutoFocus: " + saved_count);
+
+ if( !single_tap_photo && !double_tap_photo ) {
+ mActivityRule.getScenario().onActivity(activity -> {
+ View takePhotoButton = activity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ assertFalse( activity.hasThumbnailAnimation() );
+ Log.d(TAG, "about to click take photo");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take photo");
+ });
+ }
+
+ waitForTakePhoto();
+
+ int new_count_cameraTakePicture = getActivityValue(activity -> activity.getPreview().count_cameraTakePicture);
+ Log.d(TAG, "take picture count: " + new_count_cameraTakePicture);
+ assertEquals(new_count_cameraTakePicture, saved_count_cameraTakePicture + 1);
+
+ /*if( test_wait_capture_result ) {
+ // if test_wait_capture_result, then we'll have waited too long for thumbnail animation
+ }
+ else if( info.is_focus_bracketing ) {
+ // thumbnail animation may have already occurred (e.g., see testTakePhotoFocusBracketingHeavy()
+ }
+ else*/ if( info.has_thumbnail_anim ) {
+ long time_s = System.currentTimeMillis();
+ for(;;) {
+ //boolean waiting = getActivityValue(activity -> !activity.hasThumbnailAnimation());
+ boolean waiting = getActivityValue(activity -> (activity.getApplicationInterface().getDrawPreview().test_thumbnail_anim_count <= saved_thumbnail_count));
+ if( !waiting ) {
+ break;
+ }
+ Log.d(TAG, "waiting for thumbnail animation");
+ Thread.sleep(10);
+ int allowed_time_ms = 10000;
+ if( info.is_hdr || info.is_nr || info.is_expo ) {
+ // some devices need longer time (especially Nexus 6)
+ allowed_time_ms = 16000;
+ }
+ assertTrue( System.currentTimeMillis() - time_s < allowed_time_ms );
+ }
+ }
+ else {
+ boolean has_thumbnail_animation = getActivityValue(activity -> activity.hasThumbnailAnimation());
+ assertFalse( has_thumbnail_animation );
+ int new_thumbnail_count = getActivityValue(activity -> activity.getApplicationInterface().getDrawPreview().test_thumbnail_anim_count);
+ assertEquals(saved_thumbnail_count, new_thumbnail_count);
+ }
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ activity.waitUntilImageQueueEmpty();
+
+ TestUtils.checkFocusAfterTakePhoto(activity, info.focus_value, info.focus_value_ui);
+
+ try {
+ TestUtils.checkFilesAfterTakePhoto(activity, is_raw, test_wait_capture_result, files);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from checkFilesAfterTakePhoto", e);
+ }
+
+ TestUtils.checkFocusAfterTakePhoto2(activity, touch_to_focus, single_tap_photo, double_tap_photo, test_wait_capture_result, locked_focus, info.can_auto_focus, info.can_focus_area, saved_count);
+
+ TestUtils.postTakePhotoChecks(activity, immersive_mode, info.exposureVisibility, info.exposureLockVisibility);
+
+ assertFalse(activity.getApplicationInterface().getImageSaver().test_queue_blocked);
+ assertTrue( activity.getPreview().getCameraController() == null || activity.getPreview().getCameraController().count_camera_parameters_exception == 0 );
+ });
+
+ }
+
+ /*@Category(PhotoTests.class)
+ @Test
+ public void testTakePhoto() throws InterruptedException {
+ Log.d(TAG, "testTakePhoto");
+ setToDefault();
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ }*/
+
+ /** Tests option to remove device exif info.
+ */
+ @Category(PhotoTests.class)
+ @Test
+ public void testTakePhotoRemoveExifOn() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoRemoveExifOn");
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(activity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.RemoveDeviceExifPreferenceKey, "preference_remove_device_exif_on");
+ editor.apply();
+ });
+ updateForSettings();
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ try {
+ TestUtils.testExif(activity, activity.test_last_saved_image, activity.test_last_saved_imageuri, false, false, false);
+ }
+ catch(IOException e) {
+ Log.e(TAG, "testExif failed", e);
+ fail();
+ }
+ });
+ }
+
+ /** Tests option to remove device exif info, but with auto-level to test codepath where we
+ * resave the bitmap.
+ */
+ @Category(PhotoTests.class)
+ @Test
+ public void testTakePhotoRemoveExifOn2() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoRemoveExifOn2");
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(activity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.RemoveDeviceExifPreferenceKey, "preference_remove_device_exif_on");
+ editor.putBoolean(PreferenceKeys.AutoStabilisePreferenceKey, true);
+ editor.apply();
+ });
+ updateForSettings();
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ try {
+ TestUtils.testExif(activity, activity.test_last_saved_image, activity.test_last_saved_imageuri, false, false, false);
+ }
+ catch(IOException e) {
+ Log.e(TAG, "testExif failed", e);
+ fail();
+ }
+ });
+ }
+
+ /** Tests option to remove device exif info, but keeping datetime tags.
+ */
+ @Category(PhotoTests.class)
+ @Test
+ public void testTakePhotoRemoveExifKeepDatetime() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoRemoveExifKeepDatetime");
+ setToDefault();
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(activity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.RemoveDeviceExifPreferenceKey, "preference_remove_device_exif_keep_datetime");
+ editor.apply();
+ });
+ updateForSettings();
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ try {
+ TestUtils.testExif(activity, activity.test_last_saved_image, activity.test_last_saved_imageuri, false, true, false);
+ }
+ catch(IOException e) {
+ Log.e(TAG, "testExif failed", e);
+ fail();
+ }
+ });
+ }
+
+ @Category(PhotoTests.class)
+ @Test
+ public void testTakePhotoVendorExtensions() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoVendorExtensions");
+ setToDefault();
+
+ List supported_extension_modes = new ArrayList<>();
+ mActivityRule.getScenario().onActivity(activity -> {
+ if( activity.supportsCameraExtension(CameraExtensionCharacteristics.EXTENSION_AUTOMATIC) )
+ supported_extension_modes.add("preference_photo_mode_x_auto");
+ if( activity.supportsCameraExtension(CameraExtensionCharacteristics.EXTENSION_HDR) )
+ supported_extension_modes.add("preference_photo_mode_x_hdr");
+ if( activity.supportsCameraExtension(CameraExtensionCharacteristics.EXTENSION_NIGHT) )
+ supported_extension_modes.add("preference_photo_mode_x_night");
+ if( activity.supportsCameraExtension(CameraExtensionCharacteristics.EXTENSION_BOKEH) )
+ supported_extension_modes.add("preference_photo_mode_x_bokeh");
+ if( activity.supportsCameraExtension(CameraExtensionCharacteristics.EXTENSION_BEAUTY) )
+ supported_extension_modes.add("preference_photo_mode_x_beauty");
+ });
+
+ if( supported_extension_modes.isEmpty() ) {
+ Log.d(TAG, "test requires camera extensions");
+ return;
+ }
+
+ boolean check_exif = true;
+ boolean is_samsung = Build.MANUFACTURER.toLowerCase(Locale.US).contains("samsung");
+ if( is_samsung && Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ) {
+ // Samsung Galaxy S10e Android 12 doesn't store various exif tags with vendor extensions
+ // unclear if this is Samsung specific or Android version specific
+ check_exif = false;
+ }
+
+ for(String photo_mode : supported_extension_modes) {
+ mActivityRule.getScenario().onActivity(activity -> {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(activity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, photo_mode);
+ editor.apply();
+ });
+ updateForSettings();
+
+ subTestTakePhoto(false, false, false, false, false, false, false, false);
+
+ if( check_exif ) {
+ mActivityRule.getScenario().onActivity(activity -> {
+ try {
+ TestUtils.testExif(activity, activity.test_last_saved_image, activity.test_last_saved_imageuri, true, true, false);
+ }
+ catch(IOException e) {
+ Log.e(TAG, "testExif failed", e);
+ fail();
+ }
+ });
+ }
+ }
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(activity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_std");
+ editor.apply();
+ });
+ updateForSettings();
+
+ if( getActivityValue(activity -> activity.getPreview().getCameraControllerManager().getNumberOfCameras()) > 1 ) {
+ Log.d(TAG, "test front camera");
+ mActivityRule.getScenario().onActivity(activity -> {
+ Log.d(TAG, "switch camera");
+ View switchCameraButton = activity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ });
+ waitUntilCameraOpened();
+
+ for(String photo_mode : supported_extension_modes) {
+ mActivityRule.getScenario().onActivity(activity -> {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(activity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, photo_mode);
+ editor.apply();
+ });
+ updateForSettings();
+
+ subTestTakePhoto(false, false, false, false, false, false, false, false);
+
+ if( check_exif ) {
+ mActivityRule.getScenario().onActivity(activity -> {
+ try {
+ TestUtils.testExif(activity, activity.test_last_saved_image, activity.test_last_saved_imageuri, true, true, false);
+ }
+ catch(IOException e) {
+ Log.e(TAG, "testExif failed", e);
+ fail();
+ }
+ });
+ }
+ }
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(activity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_std");
+ editor.apply();
+ });
+ updateForSettings();
+ }
+ }
+
+ /** Tests preshots.
+ */
+ @Category(PhotoTests.class)
+ @Test
+ public void testTakePhotoPreshots() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoPreshots");
+ setToDefault();
+
+ if( !getActivityValue(activity -> activity.getPreview().usingCamera2API()) ) {
+ Log.d(TAG, "test requires camera2 api");
+ return;
+ }
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ assertNotNull(activity.getPreview().getPreShotsRingBuffer());
+ assertEquals(0, activity.getPreview().getPreShotsRingBuffer().getNBitmaps());
+ });
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(activity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PreShotsPreferenceKey, "preference_save_preshots_on");
+ editor.apply();
+ });
+ updateForSettings();
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ assertNotNull(activity.getPreview().getPreShotsRingBuffer());
+ assertTrue(activity.getPreview().getPreShotsRingBuffer().getNBitmaps() > 0);
+ });
+
+ // test ring buffer flushed on pause
+ mActivityRule.getScenario().onActivity(activity -> {
+ Log.d(TAG, "pause...");
+ getInstrumentation().callActivityOnPause(activity);
+ });
+ mActivityRule.getScenario().onActivity(activity -> {
+ assertNotNull(activity.getPreview().getPreShotsRingBuffer());
+ assertEquals(0, activity.getPreview().getPreShotsRingBuffer().getNBitmaps());
+ });
+ }
+
+ /** Tests manual exposure longer than preview exposure rate, with the RequestTagType.RUN_POST_CAPTURE flag for Camera2 API.
+ */
+ @Category(PhotoTests.class)
+ @Test
+ public void testTakePhotoManualISOExposurePostCapture() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoPreshots");
+ setToDefault();
+
+ if( !getActivityValue(activity -> activity.getPreview().usingCamera2API()) ) {
+ Log.d(TAG, "test requires camera2 api");
+ return;
+ }
+ else if( !getActivityValue(activity -> activity.getPreview().supportsISORange()) ) {
+ Log.d(TAG, "test requires manual iso range");
+ return;
+ }
+
+ switchToISO(100);
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ // ensure we test RequestTagType.RUN_POST_CAPTURE even when not testing on a Samsung
+ activity.getPreview().getCameraController().test_force_run_post_capture = true;
+ });
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ // open exposure UI
+ View exposureButton = activity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureContainer = activity.findViewById(net.sourceforge.opencamera.R.id.manual_exposure_container);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.GONE);
+
+ clickView(exposureButton);
+ });
+ AtomicReference chosen_exposureRef = new AtomicReference<>();
+ mActivityRule.getScenario().onActivity(activity -> {
+ View exposureButton = activity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureContainer = activity.findViewById(net.sourceforge.opencamera.R.id.manual_exposure_container);
+ SeekBar isoSeekBar = activity.findViewById(net.sourceforge.opencamera.R.id.iso_seekbar);
+ SeekBar exposureTimeSeekBar = activity.findViewById(net.sourceforge.opencamera.R.id.exposure_time_seekbar);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.VISIBLE);
+ assertEquals(isoSeekBar.getVisibility(), View.VISIBLE);
+ assertEquals(exposureTimeSeekBar.getVisibility(), (activity.getPreview().supportsExposureTime() ? View.VISIBLE : View.GONE));
+ //subTestISOButtonAvailability();
+
+ // change exposure time to min of (max, 0.5s)
+ int progress = exposureTimeSeekBar.getMax();
+ while( progress > exposureTimeSeekBar.getMin() && activity.getManualSeekbars().getExposureTime(progress) > 1000000000L/2 ) {
+ progress--;
+ }
+ chosen_exposureRef.set(activity.getManualSeekbars().getExposureTime(progress));
+
+ Log.d(TAG, "change exposure time to progress " + progress + " time " + chosen_exposureRef.get());
+ exposureTimeSeekBar.setProgress(progress);
+ });
+ long chosen_exposure = chosen_exposureRef.get();
+ mActivityRule.getScenario().onActivity(activity -> {
+ assertEquals(activity.getPreview().getCameraController().getISO(), 100);
+ assertEquals(activity.getPreview().getCameraController().getExposureTime(), chosen_exposure);
+
+ // close the exposure UI
+ View exposureButton = activity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ clickView(exposureButton);
+ });
+ mActivityRule.getScenario().onActivity(activity -> {
+ View exposureButton = activity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureContainer = activity.findViewById(net.sourceforge.opencamera.R.id.manual_exposure_container);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.GONE);
+ });
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+
+ }
+
+ @Category(PhotoTests.class)
+ @Test
+ public void testTakePhotoJpegR() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoJpegR");
+
+ setToDefault();
+
+ if( !getActivityValue(activity -> activity.getPreview().supportsJpegR()) ) {
+ Log.d(TAG, "jpeg_r not supported");
+ return;
+ }
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(activity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.ImageFormatPreferenceKey, "preference_image_format_jpeg_r");
+ editor.apply();
+ });
+ updateForSettings();
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ }
+
+ /** Tests with flag set to force preview to take 6s to start (tests Camera2 behaviour for this happening on background thread, and
+ * not).
+ */
+ @Category(PhotoTests.class)
+ @Test
+ public void testTakePhotoSlowPreviewStart() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoSlowPreviewStart");
+
+ setToDefault();
+
+ if( !getActivityValue(activity -> activity.getPreview().usingCamera2API()) ) {
+ Log.d(TAG, "test requires camera2 api");
+ return;
+ }
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE ) {
+ // matches the use of wait_until_started in Preview.cameraOpened()
+ Log.d(TAG, "test requires Android 14+");
+ return;
+ }
+
+ CameraController.test_force_slow_preview_start = true;
+ long time_s = System.currentTimeMillis();
+ restart(false); // restart to force preview to start with test flag
+ Log.d(TAG, "time to restart: " + (System.currentTimeMillis() - time_s));
+ assertTrue( System.currentTimeMillis() - time_s < 3000 ); // test didn't get stuck on UI thread
+
+ // make sure the application isn't stuck on the UI thread
+ time_s = System.currentTimeMillis();
+ boolean done = false;
+ while( !done ) {
+ assertTrue( System.currentTimeMillis() - time_s < 500 );
+ done = getActivityValue(activity -> activity.getPreview().isPreviewStarting());
+ }
+
+ waitUntilPreviewStarted();
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ }
+
+ /** Tests with flag set to force preview to take 6s to start (tests Camera2 behaviour for this happening on background thread, and
+ * not).
+ */
+ @Category(PhotoTests.class)
+ @Test
+ public void testSettingsSlowPreviewStart() throws InterruptedException {
+ Log.d(TAG, "testSettingsSlowPreviewStart");
+
+ setToDefault();
+
+ if( !getActivityValue(activity -> activity.getPreview().usingCamera2API()) ) {
+ Log.d(TAG, "test requires camera2 api");
+ return;
+ }
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE ) {
+ // matches the use of wait_until_started in Preview.cameraOpened()
+ Log.d(TAG, "test requires Android 14+");
+ return;
+ }
+
+ CameraController.test_force_slow_preview_start = true;
+ long time_s = System.currentTimeMillis();
+ restart(false); // restart to force preview to start with test flag
+ Log.d(TAG, "time to restart: " + (System.currentTimeMillis() - time_s));
+ assertTrue( System.currentTimeMillis() - time_s < 3000 ); // test didn't get stuck on UI thread
+
+ assertFalse(getActivityValue(activity -> activity.getPreview().isPreviewStarted())); // shouldn't have started preview yet
+
+ // go to settings
+ assertFalse(getActivityValue(activity -> activity.isCameraInBackground()));
+ mActivityRule.getScenario().onActivity(activity -> {
+ View settingsButton = activity.findViewById(net.sourceforge.opencamera.R.id.settings);
+ clickView(settingsButton);
+ });
+ assertTrue(getActivityValue(activity -> activity.isCameraInBackground()));
+
+ // leave settings
+ Thread.sleep(500);
+ mActivityRule.getScenario().onActivity(activity -> {
+ Log.d(TAG, "on back pressed...");
+ activity.onBackPressed();
+ });
+ Thread.sleep(500);
+ assertFalse(getActivityValue(activity -> activity.isCameraInBackground()));
+
+ // make sure preview starts up
+ waitUntilPreviewStarted();
+ }
+
+ private int getNFiles() {
+ // count initial files in folder
+ String [] files = getActivityValue(activity -> TestUtils.filesInSaveFolder(activity));
+ Log.d(TAG, "getNFiles: " + Arrays.toString(files));
+ return files == null ? 0 : files.length;
+ }
+
+ /**
+ * @return The number of resultant video files
+ */
+ private int subTestTakeVideo(boolean test_exposure_lock, boolean test_focus_area, boolean allow_failure, boolean immersive_mode, TestUtils.VideoTestCallback test_cb, long time_ms, boolean max_filesize, int n_non_video_files) throws InterruptedException {
+ boolean supports_exposure_lock = getActivityValue(activity -> activity.getPreview().supportsExposureLock());
+ if( test_exposure_lock && !supports_exposure_lock ) {
+ return 0;
+ }
+
+ Thread.sleep(500); // needed for Pixel 6 Pro with Camera 2 API
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ TestUtils.preTakeVideoChecks(activity, immersive_mode);
+
+ if( !activity.getPreview().isVideo() ) {
+ View switchVideoButton = activity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ clickView(switchVideoButton);
+ }
+ });
+
+ waitUntilCameraOpened();
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ assertTrue(activity.getPreview().isVideo());
+ TestUtils.preTakeVideoChecks(activity, immersive_mode);
+ // reset:
+ activity.getApplicationInterface().test_n_videos_scanned = 0;
+ });
+
+ // count initial files in folder
+ int n_files = getNFiles();
+ Log.d(TAG, "n_files at start: " + n_files);
+
+ // store status to compare with later
+ int exposureVisibility = getActivityValue(activity -> {
+ View exposureButton = activity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ return exposureButton.getVisibility();
+ });
+ int exposureLockVisibility = getActivityValue(activity -> {
+ View exposureLockButton = activity.findViewById(net.sourceforge.opencamera.R.id.exposure_lock);
+ return exposureLockButton.getVisibility();
+ });
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ Log.d(TAG, "about to click take video");
+ View takePhotoButton = activity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take video");
+ });
+ getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ Preview preview = activity.getPreview();
+ if( preview.usingCamera2API() ) {
+ assertEquals(preview.getCurrentPreviewSize().width, preview.getCameraController().test_texture_view_buffer_w);
+ assertEquals(preview.getCurrentPreviewSize().height, preview.getCameraController().test_texture_view_buffer_h);
+ }
+ });
+
+ waitUntilTimer();
+
+ int exp_n_new_files = 0;
+ boolean failed_to_start = false;
+ boolean is_video_recording = getActivityValue(activity -> activity.getPreview().isVideoRecording());
+ if( is_video_recording ) {
+ mActivityRule.getScenario().onActivity(activity -> {
+ TestUtils.takeVideoRecordingChecks(activity, immersive_mode, exposureVisibility, exposureLockVisibility);
+ });
+
+ if( test_cb == null ) {
+ if( !immersive_mode && time_ms > 500 ) {
+ // test turning torch on/off (if in immersive mode, popup button will be hidden)
+ switchToFlashValue("flash_torch");
+ Thread.sleep(500);
+ switchToFlashValue("flash_off");
+ }
+
+ Thread.sleep(time_ms);
+ mActivityRule.getScenario().onActivity(activity -> {
+ TestUtils.takeVideoRecordingChecks(activity, immersive_mode, exposureVisibility, exposureLockVisibility);
+
+ Preview preview = activity.getPreview();
+ assertFalse(preview.hasFocusArea());
+ if( !allow_failure ) {
+ assertNull(preview.getCameraController().getFocusAreas());
+ assertNull(preview.getCameraController().getMeteringAreas());
+ }
+ });
+
+ if( test_focus_area ) {
+ // touch to auto-focus with focus area
+ Log.d(TAG, "touch to focus");
+ onView(anyOf(ViewMatchers.withClassName(endsWith("MySurfaceView")), ViewMatchers.withClassName(endsWith("MyTextureView")))).perform(click());
+ Log.d(TAG, "done touch to focus");
+ Thread.sleep(1000); // wait for autofocus
+ mActivityRule.getScenario().onActivity(activity -> {
+ Preview preview = activity.getPreview();
+ if( preview.supportsFocus() ) {
+ assertTrue(preview.hasFocusArea());
+ assertNotNull(preview.getCameraController().getFocusAreas());
+ assertEquals(1, preview.getCameraController().getFocusAreas().size());
+ assertNotNull(preview.getCameraController().getMeteringAreas());
+ assertEquals(1, preview.getCameraController().getMeteringAreas().size());
+ }
+ });
+
+ // this time, don't wait
+ Log.d(TAG, "touch again to focus");
+ onView(anyOf(ViewMatchers.withClassName(endsWith("MySurfaceView")), ViewMatchers.withClassName(endsWith("MyTextureView")))).perform(click());
+ Log.d(TAG, "done touch to focus");
+ }
+
+ if( test_exposure_lock ) {
+ mActivityRule.getScenario().onActivity(activity -> {
+ Log.d(TAG, "test exposure lock");
+ assertFalse(activity.getPreview().getCameraController().getAutoExposureLock());
+ View exposureLockButton = activity.findViewById(net.sourceforge.opencamera.R.id.exposure_lock);
+ clickView(exposureLockButton);
+ });
+ getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ mActivityRule.getScenario().onActivity(activity -> {
+ assertTrue( activity.getPreview().getCameraController().getAutoExposureLock() );
+ });
+ Thread.sleep(2000);
+ }
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ TestUtils.takeVideoRecordingChecks(activity, immersive_mode, exposureVisibility, exposureLockVisibility);
+
+ Log.d(TAG, "about to click stop video");
+ View takePhotoButton = activity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking stop video");
+ });
+ getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ }
+ else {
+ exp_n_new_files = test_cb.doTest();
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ if( activity.getPreview().isVideoRecording() ) {
+ Log.d(TAG, "about to click stop video");
+ View takePhotoButton = activity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking stop video");
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ }
+ }
+ else {
+ Log.d(TAG, "didn't start video");
+ assertTrue(allow_failure);
+ failed_to_start = true;
+ }
+
+ int n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ int exp_n_new_files_f = exp_n_new_files;
+ boolean failed_to_start_f = failed_to_start;
+ mActivityRule.getScenario().onActivity(activity -> {
+ TestUtils.checkFilesAfterTakeVideo(activity, allow_failure, test_cb != null, time_ms, n_non_video_files, failed_to_start_f, exp_n_new_files_f, n_new_files);
+
+ TestUtils.postTakeVideoChecks(activity, immersive_mode, max_filesize, exposureVisibility, exposureLockVisibility);
+ });
+
+ return n_new_files;
+ }
+
+ /*@Category(VideoTests.class)
+ @Test
+ public void testTakeVideo() throws InterruptedException {
+ Log.d(TAG, "testTakeVideo");
+
+ setToDefault();
+
+ int n_new_files = subTestTakeVideo(false, false, false, false, null, 5000, false, 0);
+
+ assertEquals(1, n_new_files);
+ }*/
+
+ /** Test for bug fix made on 20221112, to do with Pixel 6 Pro and video resolution larger than
+ * FullHD but smaller than 4K. Problem that we selected 60fps because that's supported at
+ * FullHD (and is in the CamcorderProfile for FullHD), but the larger non-4K resolution does
+ * not support 60fps.
+ */
+ @Category(VideoTests.class)
+ @Test
+ public void testTakeVideoAltResolution() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoAltResolution");
+
+ setToDefault();
+
+ if( !getActivityValue(activity -> activity.getPreview().usingCamera2API()) ) {
+ Log.d(TAG, "test requires camera2 api");
+ return;
+ }
+
+ String chosen_video_quality = getActivityValue(activity -> {
+ String return_quality = null;
+ CamcorderProfile best_profile = null;
+ List supported_video_quality = activity.getPreview().getVideoQualityHander().getSupportedVideoQuality();
+ if( supported_video_quality != null ) {
+ for(String quality : supported_video_quality) {
+ CamcorderProfile profile = activity.getPreview().getCamcorderProfile(quality);
+ if( profile.videoFrameWidth > 1920 && profile.videoFrameHeight > 1080 && profile.videoFrameWidth < 3840 && profile.videoFrameHeight < 2160 ) {
+ if( best_profile == null || profile.videoFrameWidth*profile.videoFrameHeight < best_profile.videoFrameWidth*best_profile.videoFrameHeight ) {
+ return_quality = quality;
+ best_profile = profile;
+ }
+ }
+ }
+ }
+ if( return_quality != null ) {
+ Log.d(TAG, "video_quality: " + return_quality);
+ Log.d(TAG, "best_profile: " + best_profile.videoFrameWidth + " x " + best_profile.videoFrameHeight);
+ }
+ return return_quality;
+ });
+ if( chosen_video_quality == null ) {
+ Log.d(TAG, "can't find desired video resolution");
+ return;
+ }
+ mActivityRule.getScenario().onActivity(activity -> {
+ activity.getApplicationInterface().setVideoQualityPref(chosen_video_quality);
+ });
+
+ int n_new_files = subTestTakeVideo(false, false, false, false, null, 5000, false, 0);
+
+ assertEquals(1, n_new_files);
+
+ pauseAndResume();
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ String video_quality = activity.getApplicationInterface().getVideoQualityPref();
+ Log.d(TAG, "video_quality: " + video_quality);
+ assertEquals(chosen_video_quality, video_quality);
+ });
+ }
+
+ private void subTestTakeVideoSnapshot() throws InterruptedException {
+ Log.d(TAG, "subTestTakeVideoSnapshot");
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ View takePhotoVideoButton = activity.findViewById(net.sourceforge.opencamera.R.id.take_photo_when_video_recording);
+ assertEquals(takePhotoVideoButton.getVisibility(), View.GONE);
+ });
+
+ subTestTakeVideo(false, false, false, false, new TestUtils.VideoTestCallback() {
+ @Override
+ public int doTest() {
+ Log.d(TAG, "wait before taking photo");
+ try {
+ Thread.sleep(3000);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+ mActivityRule.getScenario().onActivity(activity -> {
+ View takePhotoButton = activity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ View takePhotoVideoButton = activity.findViewById(net.sourceforge.opencamera.R.id.take_photo_when_video_recording);
+ assertEquals(takePhotoButton.getContentDescription(), activity.getResources().getString(net.sourceforge.opencamera.R.string.stop_video));
+ assertEquals(takePhotoVideoButton.getVisibility(), View.VISIBLE);
+ assertTrue( activity.getPreview().isVideoRecording() );
+ assertFalse(activity.getPreview().isVideoRecordingPaused());
+
+ Log.d(TAG, "about to click take photo snapshot");
+ clickView(takePhotoVideoButton);
+ Log.d(TAG, "done clicking take photo snapshot");
+ });
+
+ getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+
+ waitForTakePhoto();
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ View takePhotoVideoButton = activity.findViewById(net.sourceforge.opencamera.R.id.take_photo_when_video_recording);
+ assertEquals(takePhotoVideoButton.getVisibility(), View.VISIBLE);
+ assertTrue( activity.getPreview().isVideoRecording() );
+ assertFalse(activity.getPreview().isVideoRecordingPaused());
+ });
+
+ Log.d(TAG, "wait before stopping");
+ try {
+ Thread.sleep(3000);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+ mActivityRule.getScenario().onActivity(activity -> {
+ View takePhotoButton = activity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ View takePhotoVideoButton = activity.findViewById(net.sourceforge.opencamera.R.id.take_photo_when_video_recording);
+ assertEquals(takePhotoButton.getContentDescription(), activity.getResources().getString(net.sourceforge.opencamera.R.string.stop_video));
+ assertEquals(takePhotoVideoButton.getVisibility(), View.VISIBLE);
+ assertTrue( activity.getPreview().isVideoRecording() );
+
+ Log.d(TAG, "about to click stop video");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking stop video");
+ });
+
+ getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+
+ return 2;
+ }
+ }, 5000, false, 1);
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ activity.waitUntilImageQueueEmpty();
+ });
+ }
+
+ /** Test taking photo while recording video.
+ */
+ /*@Category(VideoTests.class)
+ @Test
+ public void testTakeVideoSnapshot() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoSnapshot");
+
+ setToDefault();
+
+ if( !getActivityValue(activity -> activity.getPreview().supportsPhotoVideoRecording()) ) {
+ Log.d(TAG, "video snapshot not supported");
+ return;
+ }
+
+ subTestTakeVideoSnapshot();
+ }*/
+
+ /** Test taking photo while recording video, when JPEG_R is set.
+ */
+ @Category(VideoTests.class)
+ @Test
+ public void testTakeVideoSnapshotJpegR() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoSnapshotJpegR");
+
+ setToDefault();
+
+ if( !getActivityValue(activity -> activity.getPreview().supportsPhotoVideoRecording()) ) {
+ Log.d(TAG, "video snapshot not supported");
+ return;
+ }
+ else if( !getActivityValue(activity -> activity.getPreview().supportsJpegR()) ) {
+ Log.d(TAG, "jpeg_r not supported");
+ return;
+ }
+
+ mActivityRule.getScenario().onActivity(activity -> {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(activity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.ImageFormatPreferenceKey, "preference_image_format_jpeg_r");
+ editor.apply();
+ });
+ updateForSettings();
+
+ subTestTakeVideoSnapshot();
+ }
+}
diff --git a/app/src/androidTest/java/net/sourceforge/opencamera/MainInstrumentedTests.java b/app/src/androidTest/java/net/sourceforge/opencamera/MainInstrumentedTests.java
new file mode 100644
index 0000000..5bd52a2
--- /dev/null
+++ b/app/src/androidTest/java/net/sourceforge/opencamera/MainInstrumentedTests.java
@@ -0,0 +1,13 @@
+package net.sourceforge.opencamera;
+
+import org.junit.experimental.categories.Categories;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+/** Tests that don't fit into another of the Test suites.
+ */
+
+@RunWith(Categories.class)
+@Categories.IncludeCategory(MainTests.class)
+@Suite.SuiteClasses({InstrumentedTest.class})
+public class MainInstrumentedTests {}
diff --git a/app/src/androidTest/java/net/sourceforge/opencamera/PanoramaInstrumentedTests.java b/app/src/androidTest/java/net/sourceforge/opencamera/PanoramaInstrumentedTests.java
new file mode 100644
index 0000000..f0155aa
--- /dev/null
+++ b/app/src/androidTest/java/net/sourceforge/opencamera/PanoramaInstrumentedTests.java
@@ -0,0 +1,17 @@
+package net.sourceforge.opencamera;
+
+import org.junit.experimental.categories.Categories;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+/** Tests for Panorama algorithm - only need to run on a single device
+ * Should manually look over the images dumped onto DCIM/
+ * To use these tests, the testdata/ subfolder should be manually copied to the test device in the DCIM/testOpenCamera/
+ * folder (so you have DCIM/testOpenCamera/testdata/). We don't use assets/ as we'd end up with huge APK sizes which takes
+ * time to transfer to the device every time we run the tests.
+ * On Android 10+, scoped storage permission needs to be given to Open Camera for the DCIM/testOpenCamera/ folder.
+ */
+@RunWith(Categories.class)
+@Categories.IncludeCategory(PanoramaTests.class)
+@Suite.SuiteClasses({InstrumentedTest.class})
+public class PanoramaInstrumentedTests {}
diff --git a/app/src/androidTest/java/net/sourceforge/opencamera/PhotoInstrumentedTests.java b/app/src/androidTest/java/net/sourceforge/opencamera/PhotoInstrumentedTests.java
new file mode 100644
index 0000000..d9441d0
--- /dev/null
+++ b/app/src/androidTest/java/net/sourceforge/opencamera/PhotoInstrumentedTests.java
@@ -0,0 +1,13 @@
+package net.sourceforge.opencamera;
+
+import org.junit.experimental.categories.Categories;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+/** Tests related to taking photos; note that tests to do with photo mode that don't take photos are still part of MainInstrumentedTests.
+ */
+
+@RunWith(Categories.class)
+@Categories.IncludeCategory(PhotoTests.class)
+@Suite.SuiteClasses({InstrumentedTest.class})
+public class PhotoInstrumentedTests {}
diff --git a/app/src/androidTest/java/net/sourceforge/opencamera/TestUtils.java b/app/src/androidTest/java/net/sourceforge/opencamera/TestUtils.java
new file mode 100644
index 0000000..58b1ac7
--- /dev/null
+++ b/app/src/androidTest/java/net/sourceforge/opencamera/TestUtils.java
@@ -0,0 +1,1681 @@
+package net.sourceforge.opencamera;
+
+import static org.junit.Assert.*;
+
+import android.annotation.TargetApi;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.preference.PreferenceManager;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.View;
+
+import androidx.exifinterface.media.ExifInterface;
+
+import net.sourceforge.opencamera.preview.Preview;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+/** Helper class for testing. This method should not include any code specific to any test framework
+ * (e.g., shouldn't be specific to ActivityInstrumentationTestCase2).
+ */
+public class TestUtils {
+ private static final String TAG = "TestUtils";
+
+ public static final boolean test_camera2 = false;
+ //public static final boolean test_camera2 = true;
+
+ final private static String images_base_path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath();
+ final public static String hdr_images_path = images_base_path + "/testOpenCamera/testdata/hdrsamples/";
+ final public static String avg_images_path = images_base_path + "/testOpenCamera/testdata/avgsamples/";
+ final public static String logprofile_images_path = images_base_path + "/testOpenCamera/testdata/logprofilesamples/";
+ final public static String panorama_images_path = images_base_path + "/testOpenCamera/testdata/panoramasamples/";
+
+ public static void setDefaultIntent(Intent intent) {
+ intent.putExtra("test_project", true);
+ }
+
+ /** Code to call before running each test.
+ */
+ public static void initTest(Context context) {
+ Log.d(TAG, "initTest: " + test_camera2);
+ // initialise test statics (to avoid the persisting between tests in a test suite run!)
+ MainActivity.test_preview_want_no_limits = false;
+ MainActivity.test_preview_want_no_limits_value = false;
+ ImageSaver.test_small_queue_size = false;
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.clear();
+ if( test_camera2 ) {
+ MainActivity.test_force_supports_camera2 = true;
+ //editor.putBoolean(PreferenceKeys.UseCamera2PreferenceKey, true);
+ editor.putString(PreferenceKeys.CameraAPIPreferenceKey, "preference_camera_api_camera2");
+ }
+ editor.apply();
+
+ Log.d(TAG, "initTest: done");
+ }
+
+ public static boolean isEmulator() {
+ return Build.MODEL.contains("Android SDK built for x86");
+ }
+
+ /** Converts a path to a Uri for com.android.providers.media.documents.
+ */
+ private static Uri getDocumentUri(String filename) throws FileNotFoundException {
+ Log.d(TAG, "getDocumentUri: " + filename);
+
+ // convert from File path format to Storage Access Framework form
+ Uri treeUri = Uri.parse("content://com.android.externalstorage.documents/tree/primary%3ADCIM%2FtestOpenCamera");
+ Log.d(TAG, "treeUri: " + treeUri);
+ if( !filename.startsWith(images_base_path) ) {
+ Log.e(TAG, "unknown base for: " + filename);
+ throw new FileNotFoundException();
+ }
+ String stem = filename.substring(images_base_path.length());
+ Uri stemUri = Uri.parse("content://com.android.externalstorage.documents/tree/primary%3ADCIM" + stem.replace("/", "%2F"));
+ Log.d(TAG, "stem: " + stem);
+ Log.d(TAG, "stemUri: " + stemUri);
+ //String docID = "primary:DCIM" + stem;
+ String docID = DocumentsContract.getTreeDocumentId(stemUri);
+ Log.d(TAG, "docID: " + docID);
+ Uri uri = DocumentsContract.buildDocumentUriUsingTree(treeUri, docID);
+
+ if( uri == null ) {
+ throw new FileNotFoundException();
+ }
+ return uri;
+ }
+
+ public static Bitmap getBitmapFromFile(MainActivity activity, String filename) {
+ return getBitmapFromFile(activity, filename, 1);
+ }
+
+ public static Bitmap getBitmapFromFile(MainActivity activity, String filename, int inSampleSize) {
+ try {
+ return getBitmapFromFileCore(activity, filename, inSampleSize);
+ }
+ catch(FileNotFoundException e) {
+ Log.e(TAG, "FileNotFoundException loading: " + filename, e);
+ fail("FileNotFoundException loading: " + filename);
+ return null;
+ }
+ }
+
+ /** Loads bitmap from supplied filename.
+ * Note that on Android 10+ (with scoped storage), this uses Storage Access Framework, which
+ * means Open Camera must have SAF permission to the folder DCIM/testOpenCamera.
+ */
+ private static Bitmap getBitmapFromFileCore(MainActivity activity, String filename, int inSampleSize) throws FileNotFoundException {
+ Log.d(TAG, "getBitmapFromFileCore: " + filename);
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inMutable = true;
+ //options.inSampleSize = inSampleSize;
+ if( inSampleSize > 1 ) {
+ // use inDensity for better quality, as inSampleSize uses nearest neighbour
+ // see same code in ImageSaver.setBitmapOptionsSampleSize()
+ options.inDensity = inSampleSize;
+ options.inTargetDensity = 1;
+ }
+
+ Uri uri = null;
+ Bitmap bitmap;
+
+ if( MainActivity.useScopedStorage() ) {
+ uri = getDocumentUri(filename);
+ Log.d(TAG, "uri: " + uri);
+ InputStream is = activity.getContentResolver().openInputStream(uri);
+ bitmap = BitmapFactory.decodeStream(is, null, options);
+ try {
+ is.close();
+ }
+ catch(IOException e) {
+ Log.e(TAG, "failed to close input stream", e);
+ }
+ }
+ else {
+ bitmap = BitmapFactory.decodeFile(filename, options);
+ }
+ if( bitmap == null )
+ throw new FileNotFoundException();
+ Log.d(TAG, " done: " + bitmap);
+
+ // now need to take exif orientation into account, as some devices or camera apps store the orientation in the exif tag,
+ // which getBitmap() doesn't account for
+ ParcelFileDescriptor parcelFileDescriptor = null;
+ FileDescriptor fileDescriptor;
+ try {
+ ExifInterface exif = null;
+ if( uri != null ) {
+ parcelFileDescriptor = activity.getContentResolver().openFileDescriptor(uri, "r");
+ if( parcelFileDescriptor != null ) {
+ fileDescriptor = parcelFileDescriptor.getFileDescriptor();
+ exif = new ExifInterface(fileDescriptor);
+ }
+ }
+ else {
+ exif = new ExifInterface(filename);
+ }
+ if( exif != null ) {
+ int exif_orientation_s = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
+ boolean needs_tf = false;
+ int exif_orientation = 0;
+ // from http://jpegclub.org/exif_orientation.html
+ // and http://stackoverflow.com/questions/20478765/how-to-get-the-correct-orientation-of-the-image-selected-from-the-default-image
+ if( exif_orientation_s == ExifInterface.ORIENTATION_UNDEFINED || exif_orientation_s == ExifInterface.ORIENTATION_NORMAL ) {
+ // leave unchanged
+ }
+ else if( exif_orientation_s == ExifInterface.ORIENTATION_ROTATE_180 ) {
+ needs_tf = true;
+ exif_orientation = 180;
+ }
+ else if( exif_orientation_s == ExifInterface.ORIENTATION_ROTATE_90 ) {
+ needs_tf = true;
+ exif_orientation = 90;
+ }
+ else if( exif_orientation_s == ExifInterface.ORIENTATION_ROTATE_270 ) {
+ needs_tf = true;
+ exif_orientation = 270;
+ }
+ else {
+ // just leave unchanged for now
+ Log.e(TAG, " unsupported exif orientation: " + exif_orientation_s);
+ }
+ Log.d(TAG, " exif orientation: " + exif_orientation);
+
+ if( needs_tf ) {
+ Log.d(TAG, " need to rotate bitmap due to exif orientation tag");
+ Matrix m = new Matrix();
+ m.setRotate(exif_orientation, bitmap.getWidth() * 0.5f, bitmap.getHeight() * 0.5f);
+ Bitmap rotated_bitmap = Bitmap.createBitmap(bitmap, 0, 0,bitmap.getWidth(), bitmap.getHeight(), m, true);
+ if( rotated_bitmap != bitmap ) {
+ bitmap.recycle();
+ bitmap = rotated_bitmap;
+ }
+ }
+ }
+ }
+ catch(IOException e) {
+ Log.e(TAG, "failed to load bitmap", e);
+ }
+ finally {
+ if( parcelFileDescriptor != null ) {
+ try {
+ parcelFileDescriptor.close();
+ }
+ catch(IOException e) {
+ Log.e(TAG, "failed to close parcelFileDescriptor", e);
+ }
+ }
+ }
+ /*{
+ for(int y=0;y= Build.VERSION_CODES.Q ?
+ MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) :
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+
+ // first try to delete pre-existing image
+ Uri old_uri = getUriFromName(activity, folder, name);
+ if( old_uri != null ) {
+ Log.d(TAG, "delete: " + old_uri);
+ activity.getContentResolver().delete(old_uri, null, null);
+ }
+
+ contentValues = new ContentValues();
+ contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, name);
+ String extension = name.substring(name.lastIndexOf("."));
+ String mime_type = activity.getStorageUtils().getImageMimeType(extension);
+ Log.d(TAG, "mime_type: " + mime_type);
+ contentValues.put(MediaStore.Images.Media.MIME_TYPE, mime_type);
+ if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ) {
+ String relative_path = Environment.DIRECTORY_DCIM + File.separator;
+ Log.d(TAG, "relative_path: " + relative_path);
+ contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, relative_path);
+ contentValues.put(MediaStore.Images.Media.IS_PENDING, 1);
+ }
+
+ uri = activity.getContentResolver().insert(folder, contentValues);
+ Log.d(TAG, "saveUri: " + uri);
+ if( uri == null ) {
+ throw new IOException();
+ }
+ outputStream = activity.getContentResolver().openOutputStream(uri);
+ }
+ else {
+ file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) + File.separator + name);
+ outputStream = new FileOutputStream(file);
+ }
+
+ bitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream);
+ outputStream.close();
+
+ if( MainActivity.useScopedStorage() ) {
+ if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ) {
+ contentValues.clear();
+ contentValues.put(MediaStore.Images.Media.IS_PENDING, 0);
+ activity.getContentResolver().update(uri, contentValues, null, null);
+ }
+ }
+ else {
+ activity.getStorageUtils().broadcastFile(file, true, false, true, false, null);
+ }
+ }
+
+ public static class HistogramDetails {
+ public final int min_value;
+ public final int median_value;
+ public final int max_value;
+
+ HistogramDetails(int min_value, int median_value, int max_value) {
+ this.min_value = min_value;
+ this.median_value = median_value;
+ this.max_value = max_value;
+ }
+ }
+
+ /** Checks for the resultant histogram.
+ * We check that we have a single range of non-zero values.
+ * @param bitmap The bitmap to compute and check a histogram for.
+ */
+ public static HistogramDetails checkHistogram(MainActivity activity, Bitmap bitmap) {
+ int [] histogram = activity.getApplicationInterface().getHDRProcessor().computeHistogram(bitmap, HDRProcessor.HistogramType.HISTOGRAM_TYPE_INTENSITY);
+ assertEquals(256, histogram.length);
+ int total = 0;
+ for(int i=0;i= middle && median_value == -1 )
+ median_value = i;
+ }
+ }
+ Log.d(TAG, "min_value: " + min_value);
+ Log.d(TAG, "median_value: " + median_value);
+ Log.d(TAG, "max_value: " + max_value);
+ return new HistogramDetails(min_value, median_value, max_value);
+ }
+
+ public static HistogramDetails subTestHDR(MainActivity activity, List inputs, String output_name, boolean test_dro, int iso, long exposure_time) {
+ return subTestHDR(activity, inputs, output_name, test_dro, iso, exposure_time, HDRProcessor.TonemappingAlgorithm.TONEMAPALGORITHM_REINHARD);
+ }
+
+ /** The testHDRX tests test the HDR algorithm on a given set of input images.
+ * By testing on a fixed sample, this makes it easier to finetune the HDR algorithm for quality and performance.
+ * To use these tests, the testdata/ subfolder should be manually copied to the test device in the DCIM/testOpenCamera/
+ * folder (so you have DCIM/testOpenCamera/testdata/). We don't use assets/ as we'd end up with huge APK sizes which takes
+ * time to transfer to the device everytime we run the tests.
+ * @param iso The ISO of the middle image (for testing Open Camera's "smart" contrast enhancement). If set to -1, then use "always" contrast enhancement.
+ * @param exposure_time The exposure time of the middle image (for testing Open Camera's "smart" contrast enhancement)
+ */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static HistogramDetails subTestHDR(MainActivity activity, List inputs, String output_name, boolean test_dro, int iso, long exposure_time, HDRProcessor.TonemappingAlgorithm tonemapping_algorithm/*, HDRTestCallback test_callback*/) {
+ Log.d(TAG, "subTestHDR");
+
+ try {
+ Thread.sleep(1000); // wait for camera to open
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ }
+
+ Bitmap dro_bitmap_in = null;
+ if( test_dro ) {
+ // save copy of input bitmap to also test DRO (since the HDR routine will free the inputs)
+ int mid = (inputs.size()-1)/2;
+ dro_bitmap_in = inputs.get(mid);
+ dro_bitmap_in = dro_bitmap_in.copy(dro_bitmap_in.getConfig(), true);
+ }
+
+ HistogramDetails hdrHistogramDetails = null;
+ if( inputs.size() > 1 ) {
+ String preference_hdr_contrast_enhancement = (iso==-1) ? "preference_hdr_contrast_enhancement_always" : "preference_hdr_contrast_enhancement_smart";
+ float hdr_alpha = ImageSaver.getHDRAlpha(preference_hdr_contrast_enhancement, exposure_time, inputs.size());
+ long time_s = System.currentTimeMillis();
+ try {
+ activity.getApplicationInterface().getHDRProcessor().processHDR(inputs, true, null, true, null, hdr_alpha, 4, true, tonemapping_algorithm, HDRProcessor.DROTonemappingAlgorithm.DROALGORITHM_GAINGAMMA);
+ //test_callback.doHDR(inputs, tonemapping_algorithm, hdr_alpha);
+ }
+ catch(HDRProcessorException e) {
+ Log.e(TAG, "processHDR failed", e);
+ throw new RuntimeException();
+ }
+ Log.d(TAG, "HDR time: " + (System.currentTimeMillis() - time_s));
+
+ saveBitmap(activity, inputs.get(0), output_name);
+ hdrHistogramDetails = checkHistogram(activity, inputs.get(0));
+ }
+ inputs.get(0).recycle();
+ inputs.clear();
+
+ if( test_dro ) {
+ inputs.add(dro_bitmap_in);
+ long time_s = System.currentTimeMillis();
+ try {
+ activity.getApplicationInterface().getHDRProcessor().processHDR(inputs, true, null, true, null, 0.5f, 4, true, HDRProcessor.TonemappingAlgorithm.TONEMAPALGORITHM_REINHARD, HDRProcessor.DROTonemappingAlgorithm.DROALGORITHM_GAINGAMMA);
+ //test_callback.doHDR(inputs, HDRProcessor.TonemappingAlgorithm.TONEMAPALGORITHM_REINHARD, 0.5f);
+ }
+ catch(HDRProcessorException e) {
+ Log.e(TAG, "processHDR failed", e);
+ throw new RuntimeException();
+ }
+ Log.d(TAG, "DRO time: " + (System.currentTimeMillis() - time_s));
+
+ saveBitmap(activity, inputs.get(0), "dro" + output_name);
+ checkHistogram(activity, inputs.get(0));
+ inputs.get(0).recycle();
+ inputs.clear();
+ }
+ try {
+ Thread.sleep(500);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ }
+
+ return hdrHistogramDetails;
+ }
+
+ public static void checkHDROffsets(MainActivity activity, int [] exp_offsets_x, int [] exp_offsets_y) {
+ checkHDROffsets(activity, exp_offsets_x, exp_offsets_y, 1);
+ }
+
+ /** Checks that the HDR offsets used for auto-alignment are as expected.
+ */
+ public static void checkHDROffsets(MainActivity activity, int [] exp_offsets_x, int [] exp_offsets_y, int scale) {
+ int [] offsets_x = activity.getApplicationInterface().getHDRProcessor().offsets_x;
+ int [] offsets_y = activity.getApplicationInterface().getHDRProcessor().offsets_y;
+ for(int i=0;i inputs, String output_name, int iso, long exposure_time, float zoom_factor, TestAvgCallback cb) {
+ Log.d(TAG, "subTestAvg");
+
+ try {
+ Thread.sleep(1000); // wait for camera to open
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ }
+
+ /*Bitmap nr_bitmap = getBitmapFromFile(activity, inputs.get(0));
+ long time_s = System.currentTimeMillis();
+ try {
+ for(int i=1;i times = new ArrayList<>();
+ long time_s = System.currentTimeMillis();
+ HDRProcessor.AvgData avg_data = hdrProcessor.processAvg(bitmap0, bitmap1, avg_factor, iso, exposure_time, zoom_factor);
+ times.add(System.currentTimeMillis() - time_s);
+ // processAvg recycles both bitmaps
+ if( cb != null ) {
+ cb.doneProcessAvg(1);
+ }
+
+ for(int i=2;i inputs, String output_name, String gyro_debug_info_filename, float panorama_pics_per_screen, float camera_angle_x, float camera_angle_y, float gyro_tol_degrees) {
+ Log.d(TAG, "subTestPanorama");
+
+ // we set panorama_pics_per_screen in the test rather than using MyApplicationInterface.panorama_pics_per_screen,
+ // in case the latter value is changed
+
+ boolean first = true;
+ Matrix scale_matrix = null;
+ int bitmap_width = 0;
+ int bitmap_height = 0;
+ List bitmaps = new ArrayList<>();
+ for(String input : inputs) {
+ Bitmap bitmap = getBitmapFromFile(activity, input);
+
+ if( first ) {
+ bitmap_width = bitmap.getWidth();
+ bitmap_height = bitmap.getHeight();
+ Log.d(TAG, "bitmap_width: " + bitmap_width);
+ Log.d(TAG, "bitmap_height: " + bitmap_height);
+
+ final int max_height = 2080;
+ //final int max_height = 2079; // test non power of 2
+ if( bitmap_height > max_height ) {
+ float scale = ((float)max_height) / ((float)bitmap_height);
+ Log.d(TAG, "scale: " + scale);
+ scale_matrix = new Matrix();
+ scale_matrix.postScale(scale, scale);
+ }
+
+ first = false;
+ }
+
+ // downscale
+ if( scale_matrix != null ) {
+ Bitmap new_bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap_width, bitmap_height, scale_matrix, true);
+ bitmap.recycle();
+ bitmap = new_bitmap;
+ }
+
+ bitmaps.add(bitmap);
+ }
+
+ bitmap_width = bitmaps.get(0).getWidth();
+ bitmap_height = bitmaps.get(0).getHeight();
+ Log.d(TAG, "bitmap_width is now: " + bitmap_width);
+ Log.d(TAG, "bitmap_height is now: " + bitmap_height);
+
+
+ /*ImageSaver.GyroDebugInfo gyro_debug_info = null;
+ if( gyro_debug_info_filename != null ) {
+ InputStream inputStream;
+ try {
+ inputStream = new FileInputStream(gyro_debug_info_filename);
+ }
+ catch(FileNotFoundException e) {
+ Log.e(TAG, "failed to load gyro debug info file: " + gyro_debug_info_filename, e);
+ throw new RuntimeException();
+ }
+
+ gyro_debug_info = new ImageSaver.GyroDebugInfo();
+ if( !ImageSaver.readGyroDebugXml(inputStream, gyro_debug_info) ) {
+ Log.e(TAG, "failed to read gyro debug xml");
+ throw new RuntimeException();
+ }
+ else if( gyro_debug_info.image_info.size() != bitmaps.size() ) {
+ Log.e(TAG, "gyro debug xml has unexpected number of images: " + gyro_debug_info.image_info.size());
+ throw new RuntimeException();
+ }
+ }*/
+ //bitmaps.subList(2,bitmaps.size()).clear(); // test
+
+ Bitmap panorama = null;
+ try {
+ final boolean crop = true;
+ //final boolean crop = false; // test
+ panorama = activity.getApplicationInterface().getPanoramaProcessor().panorama(bitmaps, panorama_pics_per_screen, camera_angle_y, crop);
+ }
+ catch(PanoramaProcessorException e) {
+ Log.e(TAG, "panorama failed", e);
+ fail();
+ }
+
+ saveBitmap(activity, panorama, output_name);
+ try {
+ Thread.sleep(500);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ }
+
+ // check we've cropped correctly:
+ final float black_factor = 0.9f;
+ // top:
+ int n_black = 0;
+ for(int i=0;i> 16) & 0xff) == 0 && ((color >> 8) & 0xff) == 0 && ((color) & 0xff) == 0 ) {
+ n_black++;
+ }
+ }
+ if( n_black >= panorama.getWidth()*black_factor ) {
+ Log.e(TAG, "too many black pixels on top border: " + n_black);
+ fail();
+ }
+ // bottom:
+ n_black = 0;
+ for(int i=0;i> 16) & 0xff) == 0 && ((color >> 8) & 0xff) == 0 && ((color) & 0xff) == 0 ) {
+ n_black++;
+ }
+ }
+ if( n_black >= panorama.getWidth()*black_factor ) {
+ Log.e(TAG, "too many black pixels on bottom border: " + n_black);
+ fail();
+ }
+ // left:
+ n_black = 0;
+ for(int i=0;i> 16) & 0xff) == 0 && ((color >> 8) & 0xff) == 0 && ((color) & 0xff) == 0 ) {
+ n_black++;
+ }
+ }
+ if( n_black >= panorama.getHeight()*black_factor ) {
+ Log.e(TAG, "too many black pixels on left border: " + n_black);
+ fail();
+ }
+ // right:
+ n_black = 0;
+ for(int i=0;i> 16) & 0xff) == 0 && ((color >> 8) & 0xff) == 0 && ((color) & 0xff) == 0 ) {
+ n_black++;
+ }
+ }
+ if( n_black >= panorama.getHeight()*black_factor ) {
+ Log.e(TAG, "too many black pixels on right border: " + n_black);
+ fail();
+ }
+ }
+
+ public static void waitForTakePhotoChecks(MainActivity activity, long time_s) {
+ Preview preview = activity.getPreview();
+ View switchCameraButton = activity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ View switchMultiCameraButton = activity.findViewById(net.sourceforge.opencamera.R.id.switch_multi_camera);
+ View switchVideoButton = activity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ //View flashButton = activity.findViewById(net.sourceforge.opencamera.R.id.flash);
+ //View focusButton = activity.findViewById(net.sourceforge.opencamera.R.id.focus_mode);
+ View exposureButton = activity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureLockButton = activity.findViewById(net.sourceforge.opencamera.R.id.exposure_lock);
+ View audioControlButton = activity.findViewById(net.sourceforge.opencamera.R.id.audio_control);
+ View popupButton = activity.findViewById(net.sourceforge.opencamera.R.id.popup);
+ View trashButton = activity.findViewById(net.sourceforge.opencamera.R.id.trash);
+ View shareButton = activity.findViewById(net.sourceforge.opencamera.R.id.share);
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity);
+ boolean is_focus_bracketing = activity.supportsFocusBracketing() && sharedPreferences.getString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_std").equals("preference_photo_mode_focus_bracketing");
+ boolean is_panorama = activity.supportsPanorama() && sharedPreferences.getString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_std").equals("preference_photo_mode_panorama");
+
+ // make sure the test fails rather than hanging, if for some reason we get stuck (note that testTakePhotoManualISOExposure takes over 10s on Nexus 6)
+ // also see note at end of setToDefault for Nokia 8, need to sleep briefly to avoid hanging here
+ if( !is_focus_bracketing ) {
+ assertTrue(System.currentTimeMillis() - time_s < (is_panorama ? 50000 : 20000)); // need longer for panorama on Nexus 7 for testTakePhotoPanoramaMax
+ }
+ assertTrue(!preview.isTakingPhoto() || switchCameraButton.getVisibility() == View.GONE);
+ assertTrue(!preview.isTakingPhoto() || switchMultiCameraButton.getVisibility() == View.GONE);
+ assertTrue(!preview.isTakingPhoto() || switchVideoButton.getVisibility() == View.GONE);
+ //assertTrue(!preview.isTakingPhoto() || flashButton.getVisibility() == View.GONE);
+ //assertTrue(!preview.isTakingPhoto() || focusButton.getVisibility() == View.GONE);
+ assertTrue(!preview.isTakingPhoto() || exposureButton.getVisibility() == View.GONE);
+ assertTrue(!preview.isTakingPhoto() || exposureLockButton.getVisibility() == View.GONE);
+ assertTrue(!preview.isTakingPhoto() || audioControlButton.getVisibility() == View.GONE);
+ assertTrue(!preview.isTakingPhoto() || popupButton.getVisibility() == View.GONE);
+ assertTrue(!preview.isTakingPhoto() || trashButton.getVisibility() == View.GONE);
+ assertTrue(!preview.isTakingPhoto() || shareButton.getVisibility() == View.GONE);
+ }
+
+ private static void checkFocusInitial(MainActivity activity, final String focus_value, final String focus_value_ui) {
+ String new_focus_value_ui = activity.getPreview().getCurrentFocusValue();
+ //noinspection StringEquality
+ assertTrue(new_focus_value_ui == focus_value_ui || new_focus_value_ui.equals(focus_value_ui)); // also need to do == check, as strings may be null if focus not supported
+ assertEquals(activity.getPreview().getCameraController().getFocusValue(), focus_value);
+ }
+
+ public static void checkFocusAfterTakePhoto(MainActivity activity, final String focus_value, final String focus_value_ui) {
+ // focus should be back to normal now:
+ String new_focus_value_ui = activity.getPreview().getCurrentFocusValue();
+ Log.d(TAG, "focus_value_ui: " + focus_value_ui);
+ Log.d(TAG, "new new_focus_value_ui: " + new_focus_value_ui);
+ //noinspection StringEquality
+ assertTrue(new_focus_value_ui == focus_value_ui || new_focus_value_ui.equals(focus_value_ui)); // also need to do == check, as strings may be null if focus not supported
+ String new_focus_value = activity.getPreview().getCameraController().getFocusValue();
+ Log.d(TAG, "focus_value: " + focus_value);
+ Log.d(TAG, "new focus_value: " + new_focus_value);
+ if( new_focus_value_ui != null && new_focus_value_ui.equals("focus_mode_continuous_picture") && focus_value.equals("focus_mode_auto") && new_focus_value.equals("focus_mode_continuous_picture") ) {
+ // this is fine, it just means we were temporarily in touch-to-focus mode
+ }
+ else {
+ assertEquals(new_focus_value, focus_value);
+ }
+ }
+
+ public static void checkFocusAfterTakePhoto2(MainActivity activity, final boolean touch_to_focus, final boolean single_tap_photo, final boolean double_tap_photo, final boolean test_wait_capture_result, final boolean locked_focus, final boolean can_auto_focus, final boolean can_focus_area, final int saved_count) {
+ Preview preview = activity.getPreview();
+ // in locked focus mode, taking photo should never redo an auto-focus
+ // if photo mode, we may do a refocus if the previous auto-focus failed, but not if it succeeded
+ Log.d(TAG, "2 count_cameraAutoFocus: " + preview.count_cameraAutoFocus);
+ if( locked_focus ) {
+ assertEquals(preview.count_cameraAutoFocus, (can_auto_focus ? saved_count + 1 : saved_count));
+ }
+ if( test_wait_capture_result ) {
+ // if test_wait_capture_result, then we'll have waited too long, so focus settings may have changed
+ }
+ else if( touch_to_focus ) {
+ Log.d(TAG, "can_focus_area?: " + can_focus_area);
+ Log.d(TAG, "hasFocusArea?: " + preview.hasFocusArea());
+ if( single_tap_photo || double_tap_photo ) {
+ assertFalse(preview.hasFocusArea());
+ assertNull(preview.getCameraController().getFocusAreas());
+ assertNull(preview.getCameraController().getMeteringAreas());
+ }
+ else if( can_focus_area ) {
+ assertTrue(preview.hasFocusArea());
+ assertNotNull(preview.getCameraController().getFocusAreas());
+ assertEquals(1, preview.getCameraController().getFocusAreas().size());
+ assertNotNull(preview.getCameraController().getMeteringAreas());
+ assertEquals(1, preview.getCameraController().getMeteringAreas().size());
+ }
+ else {
+ assertFalse(preview.hasFocusArea());
+ assertNull(preview.getCameraController().getFocusAreas());
+
+ if( preview.getCameraController().supportsMetering() ) {
+ // we still set metering areas
+ assertNotNull(preview.getCameraController().getMeteringAreas());
+ assertEquals(1, preview.getCameraController().getMeteringAreas().size());
+ }
+ else {
+ assertNull(preview.getCameraController().getMeteringAreas());
+ }
+ }
+ }
+ else {
+ assertFalse(preview.hasFocusArea());
+ assertNull(preview.getCameraController().getFocusAreas());
+ assertNull(preview.getCameraController().getMeteringAreas());
+ }
+ }
+
+ private static int getExpNNewFiles(MainActivity activity, final boolean is_raw) {
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity);
+ boolean hdr_save_expo = sharedPreferences.getBoolean(PreferenceKeys.HDRSaveExpoPreferenceKey, false);
+ boolean is_hdr = activity.supportsHDR() && sharedPreferences.getString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_std").equals("preference_photo_mode_hdr");
+ boolean is_expo = activity.supportsExpoBracketing() && sharedPreferences.getString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_std").equals("preference_photo_mode_expo_bracketing");
+ boolean is_focus_bracketing = activity.supportsFocusBracketing() && sharedPreferences.getString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_std").equals("preference_photo_mode_focus_bracketing");
+ boolean is_fast_burst = activity.supportsFastBurst() && sharedPreferences.getString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_std").equals("preference_photo_mode_fast_burst");
+ String n_expo_images_s = sharedPreferences.getString(PreferenceKeys.ExpoBracketingNImagesPreferenceKey, "3");
+ int n_expo_images = Integer.parseInt(n_expo_images_s);
+ String n_focus_bracketing_images_s = sharedPreferences.getString(PreferenceKeys.FocusBracketingNImagesPreferenceKey, "3");
+ int n_focus_bracketing_images = Integer.parseInt(n_focus_bracketing_images_s);
+ String n_fast_burst_images_s = sharedPreferences.getString(PreferenceKeys.FastBurstNImagesPreferenceKey, "5");
+ int n_fast_burst_images = Integer.parseInt(n_fast_burst_images_s);
+ boolean is_preshot = activity.getApplicationInterface().getPreShotsPref(activity.getApplicationInterface().getPhotoMode());
+
+ int exp_n_new_files;
+ if( is_hdr && hdr_save_expo ) {
+ exp_n_new_files = 4;
+ if( is_raw && !activity.getApplicationInterface().isRawOnly() ) {
+ exp_n_new_files += 3;
+ }
+ }
+ else if( is_expo ) {
+ exp_n_new_files = n_expo_images;
+ if( is_raw && !activity.getApplicationInterface().isRawOnly() ) {
+ exp_n_new_files *= 2;
+ }
+ }
+ else if( is_focus_bracketing ) {
+ exp_n_new_files = n_focus_bracketing_images;
+ if( is_raw && !activity.getApplicationInterface().isRawOnly() ) {
+ exp_n_new_files *= 2;
+ }
+ }
+ else if( is_fast_burst )
+ exp_n_new_files = n_fast_burst_images;
+ else {
+ exp_n_new_files = 1;
+ if( is_raw && !activity.getApplicationInterface().isRawOnly() ) {
+ exp_n_new_files *= 2;
+ }
+ }
+
+ if( is_preshot )
+ exp_n_new_files++;
+
+ Log.d(TAG, "exp_n_new_files: " + exp_n_new_files);
+ return exp_n_new_files;
+ }
+
+ private static void checkFilenames(MainActivity activity, final boolean is_raw, final String [] files, final String [] files2) {
+ Log.d(TAG, "checkFilenames");
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity);
+ boolean hdr_save_expo = sharedPreferences.getBoolean(PreferenceKeys.HDRSaveExpoPreferenceKey, false);
+ boolean is_hdr = activity.supportsHDR() && sharedPreferences.getString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_std").equals("preference_photo_mode_hdr");
+ boolean is_fast_burst = activity.supportsFastBurst() && sharedPreferences.getString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_std").equals("preference_photo_mode_fast_burst");
+ boolean is_expo = activity.supportsExpoBracketing() && sharedPreferences.getString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_std").equals("preference_photo_mode_expo_bracketing");
+ boolean is_focus_bracketing = activity.supportsFocusBracketing() && sharedPreferences.getString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_std").equals("preference_photo_mode_focus_bracketing");
+ boolean is_preshot = activity.getApplicationInterface().getPreShotsPref(activity.getApplicationInterface().getPhotoMode());
+
+ // check files have names as expected
+ String filename_jpeg = null;
+ String filename_dng = null;
+ String filename_preshot_video = null;
+ int n_files = files == null ? 0 : files.length;
+ for(String file : files2) {
+ Log.d(TAG, "check file: " + file);
+ boolean is_new = true;
+ for(int j=0;j mediaFilesinSaveFolder(MainActivity activity, Uri baseUri, String bucket_id, UriType uri_type) {
+ List files = new ArrayList<>();
+ final int column_name_c = 0; // filename (without path), including extension
+
+ String [] projection;
+ switch( uri_type ) {
+ case MEDIASTORE_IMAGES:
+ projection = new String[] {MediaStore.Images.ImageColumns.DISPLAY_NAME};
+ break;
+ case MEDIASTORE_VIDEOS:
+ //noinspection DuplicateBranchesInSwitch
+ projection = new String[] {MediaStore.Video.VideoColumns.DISPLAY_NAME};
+ break;
+ case STORAGE_ACCESS_FRAMEWORK:
+ projection = new String[] {DocumentsContract.Document.COLUMN_DISPLAY_NAME};
+ break;
+ default:
+ throw new RuntimeException("unknown uri_type: " + uri_type);
+ }
+
+ String selection = "";
+ switch( uri_type ) {
+ case MEDIASTORE_IMAGES:
+ selection = MediaStore.Images.ImageColumns.BUCKET_ID + " = " + bucket_id;
+ break;
+ case MEDIASTORE_VIDEOS:
+ //noinspection DuplicateBranchesInSwitch
+ selection = MediaStore.Video.VideoColumns.BUCKET_ID + " = " + bucket_id;
+ break;
+ case STORAGE_ACCESS_FRAMEWORK:
+ break;
+ default:
+ throw new RuntimeException("unknown uri_type: " + uri_type);
+ }
+ Log.d(TAG, "selection: " + selection);
+
+ Cursor cursor = activity.getContentResolver().query(baseUri, projection, selection, null, null);
+ if( cursor != null && cursor.moveToFirst() ) {
+ Log.d(TAG, "found: " + cursor.getCount());
+
+ do {
+ String name = cursor.getString(column_name_c);
+ files.add(name);
+ }
+ while( cursor.moveToNext() );
+ }
+
+ if( cursor != null ) {
+ cursor.close();
+ }
+
+ return files;
+ }
+
+ /** Returns an array of filenames (not including full path) in the current save folder.
+ */
+ public static String [] filesInSaveFolder(MainActivity activity) {
+ Log.d(TAG, "filesInSaveFolder");
+ if( MainActivity.useScopedStorage() ) {
+ List files = new ArrayList<>();
+ if( activity.getStorageUtils().isUsingSAF() ) {
+ // See documentation for StorageUtils.getLatestMediaSAF() - for some reason with scoped storage when not having READ_EXTERNAL_STORAGE,
+ // we can't query the mediastore for files saved via SAF!
+ Uri treeUri = activity.getStorageUtils().getTreeUriSAF();
+ Uri baseUri = DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, DocumentsContract.getTreeDocumentId(treeUri));
+ files.addAll( mediaFilesinSaveFolder(activity, baseUri, null, UriType.STORAGE_ACCESS_FRAMEWORK) );
+ }
+ else {
+ String save_folder = activity.getStorageUtils().getImageFolderPath();
+ String bucket_id = String.valueOf(save_folder.toLowerCase().hashCode());
+ files.addAll( mediaFilesinSaveFolder(activity, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, bucket_id, UriType.MEDIASTORE_IMAGES) );
+ files.addAll( mediaFilesinSaveFolder(activity, MediaStore.Video.Media.EXTERNAL_CONTENT_URI, bucket_id, UriType.MEDIASTORE_VIDEOS) );
+ }
+
+ if( files.isEmpty() ) {
+ return null;
+ }
+ else {
+ return files.toArray(new String[0]);
+ }
+ }
+ else {
+ File folder = activity.getImageFolder();
+ File [] files = folder.listFiles();
+ if( files == null )
+ return null;
+ String [] filenames = new String[files.length];
+ for(int i=0;i 1 ? View.VISIBLE : View.GONE));
+ assertEquals(switchMultiCameraButton.getVisibility(), (activity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE));
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+ if( !immersive_mode ) {
+ assertEquals(exposureButton.getVisibility(), exposureVisibility);
+ assertEquals(exposureLockButton.getVisibility(), exposureLockVisibility);
+ }
+ assertEquals(audioControlButton.getVisibility(), (has_audio_control_button ? View.VISIBLE : View.GONE));
+ assertEquals(popupButton.getVisibility(), View.VISIBLE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ }
+ }
+
+ public static class SubTestTakePhotoInfo {
+ public boolean has_thumbnail_anim;
+ public boolean is_hdr;
+ public boolean is_nr;
+ public boolean is_expo;
+ public int exposureVisibility;
+ public int exposureLockVisibility;
+ public String focus_value;
+ public String focus_value_ui;
+ public boolean can_auto_focus;
+ public boolean manual_can_auto_focus;
+ public boolean can_focus_area;
+ }
+
+ public static SubTestTakePhotoInfo getSubTestTakePhotoInfo(MainActivity activity, boolean immersive_mode, boolean single_tap_photo, boolean double_tap_photo) {
+ assertTrue(activity.getPreview().isPreviewStarted());
+ assertFalse(activity.getApplicationInterface().getImageSaver().test_queue_blocked);
+
+ SubTestTakePhotoInfo info = new SubTestTakePhotoInfo();
+
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity);
+
+ info.has_thumbnail_anim = sharedPreferences.getBoolean(PreferenceKeys.ThumbnailAnimationPreferenceKey, true);
+ info.is_hdr = activity.supportsHDR() && sharedPreferences.getString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_std").equals("preference_photo_mode_hdr");
+ info.is_nr = activity.supportsNoiseReduction() && sharedPreferences.getString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_std").equals("preference_photo_mode_noise_reduction");
+ info.is_expo = activity.supportsExpoBracketing() && sharedPreferences.getString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_std").equals("preference_photo_mode_expo_bracketing");
+
+ boolean has_audio_control_button = !sharedPreferences.getString(PreferenceKeys.AudioControlPreferenceKey, "none").equals("none");
+
+ View switchCameraButton = activity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ View switchMultiCameraButton = activity.findViewById(net.sourceforge.opencamera.R.id.switch_multi_camera);
+ View switchVideoButton = activity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ //View flashButton = activity.findViewById(net.sourceforge.opencamera.R.id.flash);
+ //View focusButton = activity.findViewById(net.sourceforge.opencamera.R.id.focus_mode);
+ View exposureButton = activity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureLockButton = activity.findViewById(net.sourceforge.opencamera.R.id.exposure_lock);
+ View audioControlButton = activity.findViewById(net.sourceforge.opencamera.R.id.audio_control);
+ View popupButton = activity.findViewById(net.sourceforge.opencamera.R.id.popup);
+ View trashButton = activity.findViewById(net.sourceforge.opencamera.R.id.trash);
+ View shareButton = activity.findViewById(net.sourceforge.opencamera.R.id.share);
+ assertEquals(switchCameraButton.getVisibility(), (immersive_mode ? View.GONE : (activity.getPreview().getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE)));
+ assertEquals(switchMultiCameraButton.getVisibility(), (immersive_mode ? View.GONE : (activity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE)));
+ assertEquals(switchVideoButton.getVisibility(), (immersive_mode ? View.GONE : View.VISIBLE));
+ info.exposureVisibility = exposureButton.getVisibility();
+ info.exposureLockVisibility = exposureLockButton.getVisibility();
+ assertEquals(audioControlButton.getVisibility(), ((has_audio_control_button && !immersive_mode) ? View.VISIBLE : View.GONE));
+ assertEquals(popupButton.getVisibility(), (immersive_mode ? View.GONE : View.VISIBLE));
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+
+ info.focus_value = activity.getPreview().getCameraController().getFocusValue();
+ info.focus_value_ui = activity.getPreview().getCurrentFocusValue();
+ info.can_auto_focus = false;
+ info.manual_can_auto_focus = false;
+ info.can_focus_area = false;
+ if( info.focus_value.equals("focus_mode_auto") || info.focus_value.equals("focus_mode_macro") ) {
+ info.can_auto_focus = true;
+ }
+
+ if( info.focus_value.equals("focus_mode_auto") || info.focus_value.equals("focus_mode_macro") ) {
+ info.manual_can_auto_focus = true;
+ }
+ else if( info.focus_value.equals("focus_mode_continuous_picture") && !single_tap_photo && !double_tap_photo ) {
+ // if single_tap_photo or double_tap_photo, and continuous mode, we go straight to taking a photo rather than doing a touch to focus
+ info.manual_can_auto_focus = true;
+ }
+
+ if( activity.getPreview().getMaxNumFocusAreas() != 0 && ( info.focus_value.equals("focus_mode_auto") || info.focus_value.equals("focus_mode_macro") || info.focus_value.equals("focus_mode_continuous_picture") || info.focus_value.equals("focus_mode_continuous_video") || info.focus_value.equals("focus_mode_manual2") ) ) {
+ info.can_focus_area = true;
+ }
+ Log.d(TAG, "focus_value? " + info.focus_value);
+ Log.d(TAG, "can_auto_focus? " + info.can_auto_focus);
+ Log.d(TAG, "manual_can_auto_focus? " + info.manual_can_auto_focus);
+ Log.d(TAG, "can_focus_area? " + info.can_focus_area);
+
+ checkFocusInitial(activity, info.focus_value, info.focus_value_ui);
+
+ return info;
+ }
+
+ public static void touchToFocusChecks(MainActivity activity, final boolean single_tap_photo, final boolean double_tap_photo, final boolean manual_can_auto_focus, final boolean can_focus_area, final String focus_value, final String focus_value_ui, int saved_count) {
+ Preview preview = activity.getPreview();
+ Log.d(TAG, "1 count_cameraAutoFocus: " + preview.count_cameraAutoFocus);
+ assertEquals((manual_can_auto_focus ? saved_count + 1 : saved_count), preview.count_cameraAutoFocus);
+ Log.d(TAG, "has focus area?: " + preview.hasFocusArea());
+ if( single_tap_photo || double_tap_photo ) {
+ assertFalse(preview.hasFocusArea());
+ assertNull(preview.getCameraController().getFocusAreas());
+ assertNull(preview.getCameraController().getMeteringAreas());
+ }
+ else if( can_focus_area ) {
+ assertTrue(preview.hasFocusArea());
+ assertNotNull(preview.getCameraController().getFocusAreas());
+ assertEquals(1, preview.getCameraController().getFocusAreas().size());
+ assertNotNull(preview.getCameraController().getMeteringAreas());
+ assertEquals(1, preview.getCameraController().getMeteringAreas().size());
+ }
+ else {
+ assertFalse(preview.hasFocusArea());
+ assertNull(preview.getCameraController().getFocusAreas());
+ if( preview.getCameraController().supportsMetering() ) {
+ // we still set metering areas
+ assertNotNull(preview.getCameraController().getMeteringAreas());
+ assertEquals(1, preview.getCameraController().getMeteringAreas().size());
+ }
+ else {
+ assertNull(preview.getCameraController().getMeteringAreas());
+ }
+ }
+ String new_focus_value_ui = preview.getCurrentFocusValue();
+ //noinspection StringEquality
+ assertTrue(new_focus_value_ui == focus_value_ui || new_focus_value_ui.equals(focus_value_ui)); // also need to do == check, as strings may be null if focus not supported
+ if( focus_value.equals("focus_mode_continuous_picture") && !single_tap_photo && !double_tap_photo && preview.supportsFocus() && preview.getSupportedFocusValues().contains("focus_mode_auto") )
+ assertEquals("focus_mode_auto", preview.getCameraController().getFocusValue()); // continuous focus mode switches to auto focus on touch (unless single_tap_photo, or auto focus not supported)
+ else
+ assertEquals(preview.getCameraController().getFocusValue(), focus_value);
+ }
+
+ private static boolean gpsIsNull(String gps_string) {
+ return gps_string == null || gps_string.equals("0/1,0/1,0/100000");
+ }
+
+ /** Tests the Exif tags in the resultant file. If the file is null, the uri will be
+ * used instead to read the Exif tags.
+ */
+ public static void testExif(MainActivity activity, String file, Uri uri, boolean expect_device_tags, boolean expect_datetime, boolean expect_gps) throws IOException {
+ //final String TAG_GPS_IMG_DIRECTION = "GPSImgDirection";
+ //final String TAG_GPS_IMG_DIRECTION_REF = "GPSImgDirectionRef";
+ InputStream inputStream = null;
+ ExifInterface exif;
+ if( file != null ) {
+ assertNull(uri); // should only supply one of file or uri
+ exif = new ExifInterface(file);
+ }
+ else {
+ assertNotNull(uri);
+ inputStream = activity.getContentResolver().openInputStream(uri);
+ exif = new ExifInterface(inputStream);
+ }
+
+ assertNotNull(exif.getAttribute(ExifInterface.TAG_ORIENTATION));
+ if( !( isEmulator() && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1 ) ) {
+ // older Android emulator versions don't store exif info in photos
+ if( expect_device_tags ) {
+ assertNotNull(exif.getAttribute(ExifInterface.TAG_MAKE));
+ assertNotNull(exif.getAttribute(ExifInterface.TAG_MODEL));
+ }
+ else {
+ assertNull(exif.getAttribute(ExifInterface.TAG_MAKE));
+ assertNull(exif.getAttribute(ExifInterface.TAG_MODEL));
+
+ assertNull(exif.getAttribute(ExifInterface.TAG_F_NUMBER));
+ assertNull(exif.getAttribute(ExifInterface.TAG_EXPOSURE_TIME));
+ assertNull(exif.getAttribute(ExifInterface.TAG_FLASH));
+ assertNull(exif.getAttribute(ExifInterface.TAG_FOCAL_LENGTH));
+ assertNull(exif.getAttribute(ExifInterface.TAG_IMAGE_DESCRIPTION));
+ assertNull(exif.getAttribute(ExifInterface.TAG_IMAGE_UNIQUE_ID));
+ assertNull(exif.getAttribute(ExifInterface.TAG_USER_COMMENT));
+ assertNull(exif.getAttribute(ExifInterface.TAG_ARTIST));
+ assertNull(exif.getAttribute(ExifInterface.TAG_COPYRIGHT));
+ }
+
+ if( expect_datetime ) {
+ assertNotNull(exif.getAttribute(ExifInterface.TAG_DATETIME));
+ assertNotNull(exif.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL));
+ assertNotNull(exif.getAttribute(ExifInterface.TAG_DATETIME_DIGITIZED));
+ if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ) {
+ // not available on Galaxy Nexus Android 4.3 at least
+ assertNotNull(exif.getAttribute(ExifInterface.TAG_SUBSEC_TIME));
+ assertNotNull(exif.getAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL));
+ assertNotNull(exif.getAttribute(ExifInterface.TAG_SUBSEC_TIME_DIGITIZED));
+ // TAG_OFFSET_TIME at least no longer saved on Pixel 6 Pro
+ //assertNotNull(exif.getAttribute(ExifInterface.TAG_OFFSET_TIME));
+ //assertNotNull(exif.getAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL));
+ //assertNotNull(exif.getAttribute(ExifInterface.TAG_OFFSET_TIME_DIGITIZED));
+ }
+ }
+ else {
+ assertNull(exif.getAttribute(ExifInterface.TAG_DATETIME));
+ assertNull(exif.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL));
+ assertNull(exif.getAttribute(ExifInterface.TAG_DATETIME_DIGITIZED));
+ assertNull(exif.getAttribute(ExifInterface.TAG_SUBSEC_TIME));
+ assertNull(exif.getAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL));
+ assertNull(exif.getAttribute(ExifInterface.TAG_SUBSEC_TIME_DIGITIZED));
+ assertNull(exif.getAttribute(ExifInterface.TAG_OFFSET_TIME));
+ assertNull(exif.getAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL));
+ assertNull(exif.getAttribute(ExifInterface.TAG_OFFSET_TIME_DIGITIZED));
+ }
+
+ if( expect_gps ) {
+ assertFalse(gpsIsNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE)));
+ assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF));
+ assertFalse(gpsIsNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE)));
+ assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF));
+ // can't read custom tags, even though we can write them?!
+ //assertTrue(exif.getAttribute(TAG_GPS_IMG_DIRECTION) != null);
+ //assertTrue(exif.getAttribute(TAG_GPS_IMG_DIRECTION_REF) != null);
+ }
+ else {
+ assertTrue(gpsIsNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE)));
+ assertTrue(gpsIsNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE)));
+ // TAG_GPS_LATITUDE_REF, TAG_GPS_LONGITUDE_REF are still non-null on Samsung Galaxy S24+ with Camera2 API
+ //assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF));
+ //assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF));
+ // can't read custom tags, even though we can write them?!
+ //assertTrue(exif.getAttribute(TAG_GPS_IMG_DIRECTION) == null);
+ //assertTrue(exif.getAttribute(TAG_GPS_IMG_DIRECTION_REF) == null);
+ }
+ }
+
+ if( inputStream != null ) {
+ inputStream.close();
+ }
+ }
+
+ public static void preTakeVideoChecks(MainActivity activity, boolean immersive_mode) {
+ Preview preview = activity.getPreview();
+
+ assertTrue(preview.isPreviewStarted());
+ if( preview.usingCamera2API() ) {
+ assertEquals(preview.getCurrentPreviewSize().width, preview.getCameraController().test_texture_view_buffer_w);
+ assertEquals(preview.getCurrentPreviewSize().height, preview.getCameraController().test_texture_view_buffer_h);
+ }
+
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity);
+ boolean has_audio_control_button = !sharedPreferences.getString(PreferenceKeys.AudioControlPreferenceKey, "none").equals("none");
+
+ View takePhotoButton = activity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ View pauseVideoButton = activity.findViewById(net.sourceforge.opencamera.R.id.pause_video);
+ View takePhotoVideoButton = activity.findViewById(net.sourceforge.opencamera.R.id.take_photo_when_video_recording);
+ View switchVideoButton = activity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ View switchCameraButton = activity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ View switchMultiCameraButton = activity.findViewById(net.sourceforge.opencamera.R.id.switch_multi_camera);
+ View audioControlButton = activity.findViewById(net.sourceforge.opencamera.R.id.audio_control);
+ View popupButton = activity.findViewById(net.sourceforge.opencamera.R.id.popup);
+ View trashButton = activity.findViewById(net.sourceforge.opencamera.R.id.trash);
+ View shareButton = activity.findViewById(net.sourceforge.opencamera.R.id.share);
+
+ if( preview.isVideo() ) {
+ assertEquals((int) (Integer) takePhotoButton.getTag(), net.sourceforge.opencamera.R.drawable.take_video_selector);
+ assertEquals((int) (Integer) switchVideoButton.getTag(), net.sourceforge.opencamera.R.drawable.take_photo);
+ assertEquals(takePhotoButton.getContentDescription(), activity.getResources().getString(net.sourceforge.opencamera.R.string.start_video));
+ assertEquals(pauseVideoButton.getContentDescription(), activity.getResources().getString(net.sourceforge.opencamera.R.string.pause_video));
+ assertEquals(switchVideoButton.getContentDescription(), activity.getResources().getString(net.sourceforge.opencamera.R.string.switch_to_photo));
+ }
+ else {
+ assertEquals((int) (Integer) takePhotoButton.getTag(), net.sourceforge.opencamera.R.drawable.take_photo_selector);
+ assertEquals((int) (Integer) switchVideoButton.getTag(), net.sourceforge.opencamera.R.drawable.take_video);
+ assertEquals(takePhotoButton.getContentDescription(), activity.getResources().getString(net.sourceforge.opencamera.R.string.take_photo));
+ assertEquals(pauseVideoButton.getContentDescription(), activity.getResources().getString(net.sourceforge.opencamera.R.string.pause_video));
+ assertEquals(switchVideoButton.getContentDescription(), activity.getResources().getString(net.sourceforge.opencamera.R.string.switch_to_video));
+ }
+ assertEquals(pauseVideoButton.getVisibility(), View.GONE);
+ assertEquals(takePhotoVideoButton.getVisibility(), View.GONE);
+
+ assertEquals(switchCameraButton.getVisibility(), (immersive_mode ? View.GONE : (preview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE)));
+ assertEquals(switchMultiCameraButton.getVisibility(), (immersive_mode ? View.GONE : (activity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE)));
+ assertEquals(switchVideoButton.getVisibility(), (immersive_mode ? View.GONE : View.VISIBLE));
+ assertEquals(audioControlButton.getVisibility(), ((has_audio_control_button && !immersive_mode) ? View.VISIBLE : View.GONE));
+ assertEquals(popupButton.getVisibility(), (immersive_mode ? View.GONE : View.VISIBLE));
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ }
+
+ public static void takeVideoRecordingChecks(MainActivity activity, boolean immersive_mode, int exposureVisibility, int exposureLockVisibility) {
+ Preview preview = activity.getPreview();
+
+ View takePhotoButton = activity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ View pauseVideoButton = activity.findViewById(net.sourceforge.opencamera.R.id.pause_video);
+ View takePhotoVideoButton = activity.findViewById(net.sourceforge.opencamera.R.id.take_photo_when_video_recording);
+ View switchVideoButton = activity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ View switchCameraButton = activity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ View switchMultiCameraButton = activity.findViewById(net.sourceforge.opencamera.R.id.switch_multi_camera);
+ View exposureButton = activity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureLockButton = activity.findViewById(net.sourceforge.opencamera.R.id.exposure_lock);
+ View audioControlButton = activity.findViewById(net.sourceforge.opencamera.R.id.audio_control);
+ View popupButton = activity.findViewById(net.sourceforge.opencamera.R.id.popup);
+ View trashButton = activity.findViewById(net.sourceforge.opencamera.R.id.trash);
+ View shareButton = activity.findViewById(net.sourceforge.opencamera.R.id.share);
+
+ assertEquals((int) (Integer) takePhotoButton.getTag(), net.sourceforge.opencamera.R.drawable.take_video_recording);
+ assertEquals((int) (Integer) switchVideoButton.getTag(), net.sourceforge.opencamera.R.drawable.take_photo);
+ assertEquals(takePhotoButton.getContentDescription(), activity.getResources().getString(net.sourceforge.opencamera.R.string.stop_video));
+ assertEquals(pauseVideoButton.getContentDescription(), activity.getResources().getString(net.sourceforge.opencamera.R.string.pause_video));
+ if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.N )
+ assertEquals(pauseVideoButton.getVisibility(), View.VISIBLE);
+ else
+ assertEquals(pauseVideoButton.getVisibility(), View.GONE);
+ if( preview.supportsPhotoVideoRecording() )
+ assertEquals(takePhotoVideoButton.getVisibility(), View.VISIBLE);
+ else
+ assertEquals(takePhotoVideoButton.getVisibility(), View.GONE);
+ assertEquals(switchCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchMultiCameraButton.getVisibility(), View.GONE);
+ //assertTrue(switchVideoButton.getVisibility() == (immersive_mode ? View.GONE : View.VISIBLE));
+ assertEquals(switchVideoButton.getVisibility(), View.GONE);
+ assertEquals(audioControlButton.getVisibility(), View.GONE);
+ assertEquals(popupButton.getVisibility(), (!immersive_mode && preview.supportsFlash() ? View.VISIBLE : View.GONE)); // popup button only visible when recording video if flash supported
+ assertEquals(exposureButton.getVisibility(), exposureVisibility);
+ assertEquals(exposureLockButton.getVisibility(), exposureLockVisibility);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ }
+
+ public static void checkFilesAfterTakeVideo(MainActivity activity, boolean allow_failure, boolean has_cb, long time_ms, int n_non_video_files, boolean failed_to_start, int exp_n_new_files, int n_new_files) {
+ if( !has_cb ) {
+ if( time_ms <= 500 ) {
+ // if quick, should have deleted corrupt video - but may be device dependent, sometimes we manage to record a video anyway!
+ assertTrue(n_new_files == 0 || n_new_files == 1);
+ }
+ else if( failed_to_start ) {
+ // if video recording failed to start, we should have deleted any file created!
+ assertEquals(0, n_new_files);
+ }
+ else {
+ assertEquals(n_non_video_files+1, n_new_files);
+ }
+ }
+ else {
+ Log.d(TAG, "exp_n_new_files: " + exp_n_new_files);
+ if( exp_n_new_files >= 0 ) {
+ assertEquals(exp_n_new_files, n_new_files);
+ }
+ }
+
+ Log.d(TAG, "test_n_videos_scanned: " + activity.getApplicationInterface().test_n_videos_scanned);
+ if( !allow_failure ) {
+ assertEquals(n_new_files-n_non_video_files, activity.getApplicationInterface().test_n_videos_scanned);
+ }
+ }
+
+ public static void postTakeVideoChecks(MainActivity activity, boolean immersive_mode, boolean max_filesize, int exposureVisibility, int exposureLockVisibility) {
+ Preview preview = activity.getPreview();
+
+ assertTrue(preview.isPreviewStarted()); // check preview restarted
+
+ if( preview.usingCamera2API() ) {
+ assertNotNull(preview.getCameraController());
+ assertEquals(preview.getCurrentPreviewSize().width, preview.getCameraController().test_texture_view_buffer_w);
+ assertEquals(preview.getCurrentPreviewSize().height, preview.getCameraController().test_texture_view_buffer_h);
+ }
+
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity);
+ boolean has_audio_control_button = !sharedPreferences.getString(PreferenceKeys.AudioControlPreferenceKey, "none").equals("none");
+
+ View takePhotoButton = activity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ View pauseVideoButton = activity.findViewById(net.sourceforge.opencamera.R.id.pause_video);
+ View takePhotoVideoButton = activity.findViewById(net.sourceforge.opencamera.R.id.take_photo_when_video_recording);
+ View switchVideoButton = activity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ View switchCameraButton = activity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ View switchMultiCameraButton = activity.findViewById(net.sourceforge.opencamera.R.id.switch_multi_camera);
+ View exposureButton = activity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureLockButton = activity.findViewById(net.sourceforge.opencamera.R.id.exposure_lock);
+ View audioControlButton = activity.findViewById(net.sourceforge.opencamera.R.id.audio_control);
+ View popupButton = activity.findViewById(net.sourceforge.opencamera.R.id.popup);
+ View trashButton = activity.findViewById(net.sourceforge.opencamera.R.id.trash);
+ View shareButton = activity.findViewById(net.sourceforge.opencamera.R.id.share);
+
+ if( !max_filesize ) {
+ // if doing restart on max filesize, we may have already restarted by now (on Camera2 API at least)
+ Log.d(TAG, "switchCameraButton.getVisibility(): " + switchCameraButton.getVisibility());
+ assertEquals(switchCameraButton.getVisibility(), (immersive_mode ? View.GONE : (preview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE)));
+ assertEquals(switchMultiCameraButton.getVisibility(), (immersive_mode ? View.GONE : (activity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE)));
+ assertEquals(audioControlButton.getVisibility(), ((has_audio_control_button && !immersive_mode) ? View.VISIBLE : View.GONE));
+ }
+ assertEquals(switchVideoButton.getVisibility(), (immersive_mode ? View.GONE : View.VISIBLE));
+ assertEquals(exposureButton.getVisibility(), exposureVisibility);
+ assertEquals(exposureLockButton.getVisibility(), exposureLockVisibility);
+ assertEquals(popupButton.getVisibility(), (immersive_mode ? View.GONE : View.VISIBLE));
+ // trash/share only shown when preview is paused after taking a photo
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+
+ assertFalse( preview.isVideoRecording() );
+ assertEquals((int) (Integer) takePhotoButton.getTag(), net.sourceforge.opencamera.R.drawable.take_video_selector);
+ assertEquals((int) (Integer) switchVideoButton.getTag(), net.sourceforge.opencamera.R.drawable.take_photo);
+ assertEquals( takePhotoButton.getContentDescription(), activity.getResources().getString(net.sourceforge.opencamera.R.string.start_video) );
+ assertEquals(pauseVideoButton.getContentDescription(), activity.getResources().getString(net.sourceforge.opencamera.R.string.pause_video));
+ Log.d(TAG, "pauseVideoButton.getVisibility(): " + pauseVideoButton.getVisibility());
+ assertEquals(pauseVideoButton.getVisibility(), View.GONE);
+ assertEquals(takePhotoVideoButton.getVisibility(), View.GONE);
+
+ assertTrue( preview.getCameraController() == null || preview.getCameraController().count_camera_parameters_exception == 0 );
+ }
+
+ public interface VideoTestCallback {
+ int doTest(); // return expected number of new files (or -1 to indicate not to check this)
+ }
+}
diff --git a/app/src/androidTest/java/net/sourceforge/opencamera/VideoInstrumentedTests.java b/app/src/androidTest/java/net/sourceforge/opencamera/VideoInstrumentedTests.java
new file mode 100644
index 0000000..292b51d
--- /dev/null
+++ b/app/src/androidTest/java/net/sourceforge/opencamera/VideoInstrumentedTests.java
@@ -0,0 +1,13 @@
+package net.sourceforge.opencamera;
+
+import org.junit.experimental.categories.Categories;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+/** Tests related to video recording; note that tests to do with video mode that don't record are still part of MainTests.
+ */
+
+@RunWith(Categories.class)
+@Categories.IncludeCategory(VideoTests.class)
+@Suite.SuiteClasses({InstrumentedTest.class})
+public class VideoInstrumentedTests {}
diff --git a/app/src/androidTest/java/net/sourceforge/opencamera/test/AvgTests.java b/app/src/androidTest/java/net/sourceforge/opencamera/test/AvgTests.java
new file mode 100644
index 0000000..b66b9ad
--- /dev/null
+++ b/app/src/androidTest/java/net/sourceforge/opencamera/test/AvgTests.java
@@ -0,0 +1,71 @@
+package net.sourceforge.opencamera.test;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class AvgTests {
+ /** Tests for Avg algorithm - only need to run on a single device
+ * Should manually look over the images dumped onto DCIM/
+ * To use these tests, the testdata/ subfolder should be manually copied to the test device in the DCIM/testOpenCamera/
+ * folder (so you have DCIM/testOpenCamera/testdata/). We don't use assets/ as we'd end up with huge APK sizes which takes
+ * time to transfer to the device every time we run the tests.
+ * On Android 10+, scoped storage permission needs to be given to Open Camera for the DCIM/testOpenCamera/ folder.
+ * UPDATE: now deprecated, replaced with AvgInstrumentedTests.
+ */
+ public static Test suite() {
+ TestSuite suite = new TestSuite(MainTests.class.getName());
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg1"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg2"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg3"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg4"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg5"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg6"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg7"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg8"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg9"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg10"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg11"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg12"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg13"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg14"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg15"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg16"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg17"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg18"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg19"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg20"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg21"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg22"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg23"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg24"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg25"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg26"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg27"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg28"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg29"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg30"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg31"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg32"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg33"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg34"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg35"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg36"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg37"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg38"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg39"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg40"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg41"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg42"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg43"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg44"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg45"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg46"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg47"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg48"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg49"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg50"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg51"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testAvg52"));
+ return suite;
+ }
+}
diff --git a/app/src/androidTest/java/net/sourceforge/opencamera/test/HDRNTests.java b/app/src/androidTest/java/net/sourceforge/opencamera/test/HDRNTests.java
new file mode 100644
index 0000000..72d2367
--- /dev/null
+++ b/app/src/androidTest/java/net/sourceforge/opencamera/test/HDRNTests.java
@@ -0,0 +1,47 @@
+package net.sourceforge.opencamera.test;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class HDRNTests {
+ /** Tests for HDR algorithm with more than 3 images - only need to run on a single device
+ * Should manually look over the images dumped onto DCIM/
+ * To use these tests, the testdata/ subfolder should be manually copied to the test device in the DCIM/testOpenCamera/
+ * folder (so you have DCIM/testOpenCamera/testdata/). We don't use assets/ as we'd end up with huge APK sizes which takes
+ * time to transfer to the device every time we run the tests.
+ * On Android 10+, scoped storage permission needs to be given to Open Camera for the DCIM/testOpenCamera/ folder.
+ * UPDATE: now deprecated, replaced with HDRNInstrumentedTests.
+ */
+ public static Test suite() {
+ TestSuite suite = new TestSuite(MainTests.class.getName());
+
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR23_exp2"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR23_exp2b"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR47_exp2"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR49_exp2"));
+
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR45"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR46"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR47"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR48"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR49"));
+
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR23_exp4"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR49_exp4"));
+
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR1_exp5"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR23_exp5"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR45_exp5"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR46_exp5"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR47_exp5"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR48_exp5"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR49_exp5"));
+
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR23_exp6"));
+
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR23_exp7"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR45_exp7"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR47_exp7"));
+ return suite;
+ }
+}
diff --git a/app/src/androidTest/java/net/sourceforge/opencamera/test/HDRTests.java b/app/src/androidTest/java/net/sourceforge/opencamera/test/HDRTests.java
new file mode 100644
index 0000000..105d894
--- /dev/null
+++ b/app/src/androidTest/java/net/sourceforge/opencamera/test/HDRTests.java
@@ -0,0 +1,85 @@
+package net.sourceforge.opencamera.test;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class HDRTests {
+ /** Tests for HDR algorithm - only need to run on a single device
+ * Should manually look over the images dumped onto DCIM/
+ * To use these tests, the testdata/ subfolder should be manually copied to the test device in the DCIM/testOpenCamera/
+ * folder (so you have DCIM/testOpenCamera/testdata/). We don't use assets/ as we'd end up with huge APK sizes which takes
+ * time to transfer to the device every time we run the tests.
+ * On Android 10+, scoped storage permission needs to be given to Open Camera for the DCIM/testOpenCamera/ folder.
+ * UPDATE: now deprecated, replaced with HDRInstrumentedTests.
+ */
+ public static Test suite() {
+ TestSuite suite = new TestSuite(MainTests.class.getName());
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testDROZero"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testDRODark0"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testDRODark1"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR1"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR2"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR3"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR4"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR5"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR6"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR7"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR8"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR9"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR10"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR11"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR12"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR13"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR14"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR15"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR16"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR17"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR18"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR19"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR20"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR21"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR22"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR23"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR24"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR25"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR26"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR27"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR28"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR29"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR30"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR31"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR32"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR33"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR34"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR35"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR36"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR37"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR38Filmic"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR39"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR40"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR40Exponential"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR40Filmic"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR41"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR42"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR43"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR44"));
+ // don't include testHDR45, this is tested as part of HDRNTests
+ // don't include testHDR46, this is tested as part of HDRNTests
+ // don't include testHDR47, this is tested as part of HDRNTests
+ // don't include testHDR48, this is tested as part of HDRNTests
+ // don't include testHDR49, this is tested as part of HDRNTests
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR50"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR51"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR52"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR53"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR54"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR55"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR56"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR57"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR58"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR59"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR60"));
+ suite.addTest(TestSuite.createTest(MainActivityTest.class, "testHDR61"));
+ return suite;
+ }
+}
diff --git a/app/src/androidTest/java/net/sourceforge/opencamera/test/MainActivityTest.java b/app/src/androidTest/java/net/sourceforge/opencamera/test/MainActivityTest.java
new file mode 100644
index 0000000..86d6911
--- /dev/null
+++ b/app/src/androidTest/java/net/sourceforge/opencamera/test/MainActivityTest.java
@@ -0,0 +1,17623 @@
+package net.sourceforge.opencamera.test;
+
+import static org.junit.Assert.assertNotEquals;
+
+import java.io.File;
+//import java.io.FileInputStream;
+//import java.io.FileNotFoundException;
+//import java.io.InputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+import net.sourceforge.opencamera.LocationSupplier;
+import net.sourceforge.opencamera.MyPreferenceFragment;
+import net.sourceforge.opencamera.TestUtils;
+import net.sourceforge.opencamera.cameracontroller.CameraController2;
+import net.sourceforge.opencamera.HDRProcessor;
+import net.sourceforge.opencamera.HDRProcessorException;
+import net.sourceforge.opencamera.ImageSaver;
+import net.sourceforge.opencamera.MainActivity;
+import net.sourceforge.opencamera.MyApplicationInterface;
+import net.sourceforge.opencamera.PreferenceKeys;
+import net.sourceforge.opencamera.preview.ApplicationInterface;
+import net.sourceforge.opencamera.preview.VideoProfile;
+import net.sourceforge.opencamera.SaveLocationHistory;
+import net.sourceforge.opencamera.cameracontroller.CameraController;
+import net.sourceforge.opencamera.preview.Preview;
+import net.sourceforge.opencamera.ui.DrawPreview;
+import net.sourceforge.opencamera.ui.FolderChooserDialog;
+import net.sourceforge.opencamera.ui.MainUI;
+import net.sourceforge.opencamera.ui.PopupView;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.content.Intent;
+import android.content.SharedPreferences;
+//import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.TonemapCurve;
+import android.location.Location;
+import android.media.CamcorderProfile;
+import android.media.MediaScannerConnection;
+import android.net.Uri;
+import android.os.Build;
+import android.preference.PreferenceManager;
+import android.provider.MediaStore;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.TouchUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+// ignore warning about "Call to Thread.sleep in a loop", this is only test code
+@SuppressWarnings("BusyWait")
+public class MainActivityTest extends ActivityInstrumentationTestCase2 {
+ private static final String TAG = "MainActivityTest";
+ private MainActivity mActivity = null;
+ private Preview mPreview = null;
+
+ public MainActivityTest() {
+ //noinspection deprecation
+ super("net.sourceforge.opencamera", MainActivity.class);
+ }
+
+ private static Intent createDefaultIntent() {
+ Intent intent = new Intent();
+ TestUtils.setDefaultIntent(intent);
+ return intent;
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ Log.d(TAG, "setUp");
+ super.setUp();
+
+ setActivityInitialTouchMode(false);
+
+ // use getTargetContext() as we haven't started the activity yet (and don't want to, as we want to set prefs before starting)
+ TestUtils.initTest(this.getInstrumentation().getTargetContext());
+
+ Intent intent = createDefaultIntent();
+ setActivityIntent(intent);
+ Log.d(TAG, "setUp: about to get activity");
+ mActivity = getActivity();
+ Log.d(TAG, "setUp: activity: " + mActivity);
+ mPreview = mActivity.getPreview();
+ Log.d(TAG, "setUp: preview: " + mPreview);
+
+ // don't waitUntilCameraOpened() here, as if an assertion fails in setUp(), it can cause later tests to hang in the suite
+ // instead we now wait for camera to open in setToDefault()
+ //waitUntilCameraOpened();
+
+ //restart(); // no longer need to restart, as we reset prefs before starting up; not restarting makes tests run faster!
+
+ //Camera camera = mPreview.getCamera();
+ /*mSpinner = (Spinner) mActivity.findViewById(
+ com.android.example.spinner.R.id.Spinner01
+ );*/
+
+ //mPlanetData = mSpinner.getAdapter();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ Log.d(TAG, "tearDown");
+
+ // shouldn't have assertions in tearDown, otherwise we'll never cleanup properly - when run as suite, the next test will either fail or hang!
+ //assertTrue( mPreview.getCameraController() == null || mPreview.getCameraController().count_camera_parameters_exception == 0 );
+ //assertTrue( mPreview.getCameraController() == null || mPreview.getCameraController().count_precapture_timeout == 0 );
+
+ // reset back to defaults (whilst each test will reset the settings anyway via before()->TestUtils.initTest(), it's useful
+ // to leave the application in a default state after running tests)
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.clear();
+ editor.apply();
+
+ Log.d(TAG, "tearDown done");
+ super.tearDown();
+ }
+
+ public void testPreConditions() {
+ assertNotNull(mPreview);
+ //assertTrue(mPreview.getCamera() != null);
+ //assertTrue(mCamera != null);
+ //assertTrue(mSpinner.getOnItemSelectedListener() != null);
+ //assertTrue(mPlanetData != null);
+ //assertEquals(mPlanetData.getCount(),ADAPTER_COUNT);
+ }
+
+ private void waitUntilCameraOpened() {
+ waitUntilCameraOpened(true);
+ }
+
+ private void waitUntilCameraOpened(boolean wait_for_preview) {
+ Log.d(TAG, "wait until camera opened");
+ long time_s = System.currentTimeMillis();
+ while( !mPreview.openCameraAttempted() ) {
+ assertTrue( System.currentTimeMillis() - time_s < 20000 );
+ }
+ Log.d(TAG, "camera is open!");
+ this.getInstrumentation().waitForIdleSync(); // allow the onPostExecute of open camera task run
+ Log.d(TAG, "done idle sync");
+ try {
+ Thread.sleep(100); // sleep a bit just to be safe
+ } catch (InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ }
+
+ if( wait_for_preview ) {
+ waitUntilPreviewStarted(); // needed for Camera2 API when starting preview on background thread and not waiting for it to start
+ }
+ }
+
+ private void waitUntilPreviewStarted() {
+ Log.d(TAG, "wait until preview started");
+ long time_s = System.currentTimeMillis();
+ while( !mPreview.isPreviewStarted() ) {
+ assertTrue( System.currentTimeMillis() - time_s < 20000 );
+ }
+ Log.d(TAG, "preview is started!");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "done idle sync");
+ try {
+ Thread.sleep(100); // sleep a bit just to be safe
+ } catch (InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ }
+ }
+
+ private void restart() {
+ restart(true);
+ }
+
+ /** Restarts Open Camera.
+ * WARNING: Make sure that any assigned variables related to the activity, e.g., anything
+ * returned by findViewById(), is updated to the new mActivity after calling this method!
+ */
+ private void restart(boolean wait_for_preview) {
+ Log.d(TAG, "restart");
+ mActivity.finish();
+ setActivity(null);
+ Log.d(TAG, "now starting");
+ mActivity = getActivity();
+ Log.d(TAG, "mActivity is now: " + mActivity);
+ mPreview = mActivity.getPreview();
+ Log.d(TAG, "mPreview is now: " + mPreview);
+ waitUntilCameraOpened(wait_for_preview);
+ Log.d(TAG, "restart done");
+ }
+
+ private void pauseAndResume() {
+ Log.d(TAG, "pauseAndResume");
+ boolean camera_is_open = mPreview.getCameraController() != null;
+ pauseAndResume(camera_is_open);
+ }
+
+ private void pauseAndResume(boolean wait_until_camera_opened) {
+ Log.d(TAG, "pauseAndResume: " + wait_until_camera_opened);
+ // onResume has code that must run on UI thread
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ Log.d(TAG, "pause...");
+ getInstrumentation().callActivityOnPause(mActivity);
+ Log.d(TAG, "resume...");
+ getInstrumentation().callActivityOnResume(mActivity);
+ /*Handler handler = new Handler();
+ handler.postDelayed(new Runnable() {
+ public void run() {
+ Log.d(TAG, "resume...");
+ getInstrumentation().callActivityOnResume(mActivity);
+ }
+ }, 500);*/
+ }
+ });
+ // need to wait for UI code to finish before leaving
+ this.getInstrumentation().waitForIdleSync();
+ if( wait_until_camera_opened ) {
+ waitUntilCameraOpened();
+ }
+ }
+
+ private void updateForSettings() {
+ Log.d(TAG, "updateForSettings");
+ // updateForSettings has code that must run on UI thread
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mActivity.initLocation(); // initLocation now called via MainActivity.setWindowFlagsForCamera() rather than updateForSettings()
+ mActivity.getApplicationInterface().getDrawPreview().updateSettings();
+ mActivity.updateForSettings(true);
+ }
+ });
+ // need to wait for UI code to finish before leaving
+ this.getInstrumentation().waitForIdleSync();
+ waitUntilCameraOpened(); // may need to wait if camera is reopened, e.g., when changing scene mode - see testSceneMode()
+ // but we also need to wait for the delay if instead we've stopped and restarted the preview, the latter now only happens after dim_effect_time_c
+ try {
+ Thread.sleep(DrawPreview.dim_effect_time_c+50); // wait for updateForSettings
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ }
+ this.getInstrumentation().waitForIdleSync();
+
+ }
+
+ private void clickView(final View view) {
+ // TouchUtils.clickView doesn't work properly if phone held in portrait mode!
+ //TouchUtils.clickView(MainActivityTest.this, view);
+ Log.d(TAG, "clickView: "+ view);
+ assertEquals(view.getVisibility(), View.VISIBLE);
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ assertTrue(view.performClick());
+ }
+ });
+ // need to wait for UI code to finish before leaving
+ this.getInstrumentation().waitForIdleSync();
+ }
+
+ private void switchToCamera(int cameraId) {
+ int origCameraId = mPreview.getCameraId();
+ Log.d(TAG, "switchToCamera: "+ cameraId);
+ Log.d(TAG, "origCameraId: "+ origCameraId);
+ int newCameraId = origCameraId;
+ while( newCameraId != cameraId ) {
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ this.getInstrumentation().waitForIdleSync();
+ waitUntilCameraOpened();
+ newCameraId = mPreview.getCameraId();
+ Log.d(TAG, "changed cameraId to: "+ newCameraId);
+ assertTrue(newCameraId != origCameraId);
+ }
+ }
+
+ private void openPopupMenu() {
+ Log.d(TAG, "openPopupMenu");
+ assertFalse( mActivity.popupIsOpen() );
+ View popupButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.popup);
+ clickView(popupButton);
+ Log.d(TAG, "wait for popup to open");
+ while( !mActivity.popupIsOpen() ) {
+ }
+ Log.d(TAG, "popup is now open");
+ }
+
+ private void closePopupMenu() {
+ Log.d(TAG, "closePopupMenu");
+ assertTrue( mActivity.popupIsOpen() );
+ View popupButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.popup);
+ clickView(popupButton);
+ Log.d(TAG, "wait for popup to close");
+ while( mActivity.popupIsOpen() ) {
+ }
+ Log.d(TAG, "popup is now closed");
+ }
+
+ private void switchToFlashValue(String required_flash_value) {
+ Log.d(TAG, "switchToFlashValue: "+ required_flash_value);
+ if( mPreview.supportsFlash() ) {
+ String flash_value = mPreview.getCurrentFlashValue();
+ Log.d(TAG, "start flash_value: "+ flash_value);
+ if( !flash_value.equals(required_flash_value) ) {
+ openPopupMenu();
+ View currentFlashButton = mActivity.getUIButton("TEST_FLASH_" + flash_value);
+ assertNotNull(currentFlashButton);
+ assertEquals(currentFlashButton.getAlpha(), PopupView.ALPHA_BUTTON_SELECTED);
+ View flashButton = mActivity.getUIButton("TEST_FLASH_" + required_flash_value);
+ assertNotNull(flashButton);
+ assertEquals(flashButton.getAlpha(), PopupView.ALPHA_BUTTON, 1.0e-5);
+ clickView(flashButton);
+ flash_value = mPreview.getCurrentFlashValue();
+ Log.d(TAG, "changed flash_value to: "+ flash_value);
+ }
+ assertEquals(flash_value, required_flash_value);
+ String controller_flash_value = mPreview.getCameraController().getFlashValue();
+ Log.d(TAG, "controller_flash_value: "+ controller_flash_value);
+ if( flash_value.equals("flash_frontscreen_auto") || flash_value.equals("flash_frontscreen_on") ) {
+ // for frontscreen flash, the controller flash value will be "" (due to real flash not supported) - although on Galaxy Nexus this is "flash_off" due to parameters.getFlashMode() returning Camera.Parameters.FLASH_MODE_OFF
+ assertTrue(controller_flash_value.isEmpty() || controller_flash_value.equals("flash_off"));
+ }
+ else {
+ Log.d(TAG, "expected_flash_value: "+ flash_value);
+ assertEquals(flash_value, controller_flash_value);
+ }
+ }
+ }
+
+ private void switchToFocusValue(String required_focus_value) {
+ Log.d(TAG, "switchToFocusValue: "+ required_focus_value);
+ if( mPreview.supportsFocus() ) {
+ String focus_value = mPreview.getCurrentFocusValue();
+ Log.d(TAG, "start focus_value: "+ focus_value);
+ if( !focus_value.equals(required_focus_value) ) {
+ openPopupMenu();
+ View focusButton = mActivity.getUIButton("TEST_FOCUS_" + required_focus_value);
+ assertNotNull(focusButton);
+ clickView(focusButton);
+ focus_value = mPreview.getCurrentFocusValue();
+ Log.d(TAG, "changed focus_value to: "+ focus_value);
+ }
+ assertEquals(focus_value, required_focus_value);
+ String controller_focus_value = mPreview.getCameraController().getFocusValue();
+ Log.d(TAG, "controller_focus_value: "+ controller_focus_value);
+ String compare_focus_value = focus_value;
+ if( compare_focus_value.equals("focus_mode_locked") )
+ compare_focus_value = "focus_mode_auto";
+ else if( compare_focus_value.equals("focus_mode_infinity") && mPreview.usingCamera2API() )
+ compare_focus_value = "focus_mode_manual2";
+ assertEquals(compare_focus_value, controller_focus_value);
+ }
+ }
+
+ private void switchToISO(int required_iso) {
+ Log.d(TAG, "switchToISO: "+ required_iso);
+ int iso = mPreview.getCameraController().getISO();
+ Log.d(TAG, "start iso: "+ iso);
+ if( iso != required_iso ) {
+ /*openPopupMenu();
+ View isoButton = mActivity.getUIButton("TEST_ISO_" + required_iso);
+ assertTrue(isoButton != null);
+ clickView(isoButton);
+ iso = mPreview.getCameraController().getISO();
+ Log.d(TAG, "changed iso to: "+ iso);*/
+ View exposureButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureContainer = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure_container);
+ assertEquals(exposureContainer.getVisibility(), View.GONE);
+ clickView(exposureButton);
+ assertEquals(exposureContainer.getVisibility(), View.VISIBLE);
+ View isoButton = mActivity.getUIButton("TEST_ISO_" + required_iso);
+ assertNotNull(isoButton);
+ clickView(isoButton);
+ try {
+ Thread.sleep(DrawPreview.dim_effect_time_c+50); // wait for updateForSettings
+ this.getInstrumentation().waitForIdleSync();
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ }
+ iso = mPreview.getCameraController().getISO();
+ Log.d(TAG, "changed iso to: "+ iso);
+ clickView(exposureButton);
+ assertEquals(exposureContainer.getVisibility(), View.GONE);
+ }
+ assertEquals(iso, required_iso);
+ }
+
+ /* Sets the camera up to a predictable state:
+ * - Back camera
+ * - Photo mode
+ * - Flash off (if flash supported)
+ * - Focus mode picture continuous (if focus modes supported)
+ * As a side-effect, the camera and/or camera parameters values may become invalid.
+ */
+ private void setToDefault() {
+ waitUntilCameraOpened();
+
+ if( mPreview.isVideo() ) {
+ Log.d(TAG, "turn off video mode");
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ }
+ assertFalse(mPreview.isVideo());
+
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ) {
+ switchToCamera(0);
+ }
+
+ switchToFlashValue("flash_off");
+ switchToFocusValue("focus_mode_continuous_picture");
+
+ // pause for safety - needed for Nokia 8 at least otherwise some tests like testContinuousPictureFocusRepeat,
+ // testLocationOff result in hang whilst waiting for photo to be taken, and hit the timeout in waitForTakePhoto()
+ try {
+ Thread.sleep(200);
+ }
+ catch (InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ }
+ }
+
+ /* Ensures that shut down properly when pausing.
+ */
+ public void testPause() throws InterruptedException {
+ Log.d(TAG, "testPause");
+
+ setToDefault();
+ Thread.sleep(1000);
+
+ // checker ticker is running okay
+ assertTrue(mPreview.test_ticker_called);
+ mPreview.test_ticker_called = false;
+ Thread.sleep(300);
+ assertTrue(mPreview.test_ticker_called);
+
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ Log.d(TAG, "pause...");
+ getInstrumentation().callActivityOnPause(mActivity);
+ }
+ });
+ this.getInstrumentation().waitForIdleSync();
+
+ // ensure ticker is turned off after certain time
+ Thread.sleep(3000);
+ mPreview.test_ticker_called = false;
+ Thread.sleep(1000);
+ assertFalse(mPreview.test_ticker_called);
+
+ // resume, and assume we've started the ticker again
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ Log.d(TAG, "resume...");
+ getInstrumentation().callActivityOnResume(mActivity);
+ }
+ });
+ Thread.sleep(3000);
+ waitUntilCameraOpened();
+ assertTrue(mPreview.test_ticker_called);
+ mPreview.test_ticker_called = false;
+ Thread.sleep(300);
+ assertTrue(mPreview.test_ticker_called);
+ }
+
+ /** Tests that we clean up the background task for opening camera properly.
+ */
+ public void testImmediatelyQuit() throws InterruptedException {
+ Log.d(TAG, "testImmediatelyQuit");
+ setToDefault();
+
+ for(int i=0;i<5;i++) {
+ // like restart, but don't wait for camera to be opened
+ Log.d(TAG, "call finish");
+ mActivity.finish();
+ setActivity(null);
+ Log.d(TAG, "now starting");
+ mActivity = getActivity();
+ mPreview = mActivity.getPreview();
+
+ // now restart straight away
+ restart();
+
+ Thread.sleep(1000);
+ }
+ }
+
+ /* Ensures that we only start the camera preview once when starting up.
+ */
+ public void testStartCameraPreviewCount() {
+ Log.d(TAG, "testStartCameraPreviewCount");
+ /*Log.d(TAG, "1 count_cameraStartPreview: " + mPreview.count_cameraStartPreview);
+ int init_count_cameraStartPreview = mPreview.count_cameraStartPreview;
+ mActivity.finish();
+ setActivity(null);
+ mActivity = this.getActivity();
+ mPreview = mActivity.getPreview();
+ Log.d(TAG, "2 count_cameraStartPreview: " + mPreview.count_cameraStartPreview);
+ assertTrue(mPreview.count_cameraStartPreview == init_count_cameraStartPreview);
+ this.getInstrumentation().callActivityOnPause(mActivity);
+ Log.d(TAG, "3 count_cameraStartPreview: " + mPreview.count_cameraStartPreview);
+ assertTrue(mPreview.count_cameraStartPreview == init_count_cameraStartPreview);
+ this.getInstrumentation().callActivityOnResume(mActivity);
+ Log.d(TAG, "4 count_cameraStartPreview: " + mPreview.count_cameraStartPreview);
+ assertTrue(mPreview.count_cameraStartPreview == init_count_cameraStartPreview+1);*/
+ setToDefault();
+
+ restart();
+ // onResume has code that must run on UI thread
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ Log.d(TAG, "1 count_cameraStartPreview: " + mPreview.count_cameraStartPreview);
+ assertEquals(1, mPreview.count_cameraStartPreview);
+ getInstrumentation().callActivityOnPause(mActivity);
+ Log.d(TAG, "2 count_cameraStartPreview: " + mPreview.count_cameraStartPreview);
+ assertEquals(1, mPreview.count_cameraStartPreview);
+ getInstrumentation().callActivityOnResume(mActivity);
+ }
+ });
+ // need to wait for UI code to finish before leaving
+ Log.d(TAG, "wait for idle sync");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "done waiting for idle sync");
+ // waiting for camera to open can't be on the ui thread, as it's on the ui thread that Open Camera sets that we've opened the camera
+ waitUntilCameraOpened();
+ Log.d(TAG, "3 count_cameraStartPreview: " + mPreview.count_cameraStartPreview);
+ assertEquals(2, mPreview.count_cameraStartPreview);
+ }
+
+ /* Ensures that we save the video mode.
+ * Also tests the icons and content descriptions of the take photo and switch photo/video buttons are as expected.
+ */
+ private void subTestSaveVideoMode() {
+ Log.d(TAG, "subTestSaveVideoMode");
+ setToDefault();
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+
+ assertFalse(mPreview.isVideo());
+ assertEquals(takePhotoButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.take_photo));
+ assertEquals(switchVideoButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.switch_to_video));
+
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertTrue(mPreview.isVideo());
+ assertEquals(takePhotoButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.start_video));
+ assertEquals(switchVideoButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.switch_to_photo));
+
+ restart();
+ assertTrue(mPreview.isVideo());
+ assertEquals(takePhotoButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.start_video));
+ assertEquals(switchVideoButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.switch_to_photo));
+
+ pauseAndResume();
+ assertTrue(mPreview.isVideo());
+ assertEquals(takePhotoButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.start_video));
+ assertEquals(switchVideoButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.switch_to_photo));
+ }
+
+ /* Returns a focus mode that is supported by the device, but not the default focus mode.
+ */
+ private String getNonDefaultFocus() {
+ String non_default_focus;
+ if( mPreview.getSupportedFocusValues().contains("focus_mode_macro") ) {
+ non_default_focus = "focus_mode_macro";
+ }
+ else if( mPreview.getSupportedFocusValues().contains("focus_mode_infinity") ) {
+ non_default_focus = "focus_mode_infinity";
+ }
+ else {
+ non_default_focus = null;
+ fail("can't choose a non-default focus for this device");
+ }
+ return non_default_focus;
+ }
+
+ /* Ensures that we save the focus mode for photos when restarting.
+ * Note that saving the focus mode for video mode is tested in testFocusSwitchVideoResetContinuous.
+ */
+ private void subTestSaveFocusMode() {
+ Log.d(TAG, "subTestSaveFocusMode");
+ setToDefault();
+
+ if( !mPreview.supportsFocus() ) {
+ Log.d(TAG, "test requires focus");
+ return;
+ }
+
+ String non_default_focus = getNonDefaultFocus();
+
+ String focus_value = mPreview.getCameraController().getFocusValue();
+ assertNotEquals(focus_value, non_default_focus);
+
+ switchToFocusValue(non_default_focus);
+
+ restart();
+ focus_value = mPreview.getCameraController().getFocusValue();
+ assertEquals(focus_value, non_default_focus);
+
+ pauseAndResume();
+ focus_value = mPreview.getCameraController().getFocusValue();
+ assertEquals(focus_value, non_default_focus);
+ }
+
+ /* Ensures that we save the flash mode torch when quitting and restarting.
+ */
+ private void subTestSaveFlashTorchQuit() throws InterruptedException {
+ Log.d(TAG, "subTestSaveFlashTorchQuit");
+
+ setToDefault();
+
+ if( !mPreview.supportsFlash() ) {
+ Log.d(TAG, "doesn't support flash");
+ return;
+ }
+
+ switchToFlashValue("flash_torch");
+
+ restart();
+ Thread.sleep(4000); // needs to be long enough for the autofocus to complete
+ String controller_flash_value = mPreview.getCameraController().getFlashValue();
+ Log.d(TAG, "controller_flash_value: " + controller_flash_value);
+ assertEquals("flash_torch", controller_flash_value);
+ String flash_value = mPreview.getCurrentFlashValue();
+ Log.d(TAG, "flash_value: " + flash_value);
+ assertEquals("flash_torch", flash_value);
+ }
+
+ private void subTestExposureLockNotSaved() {
+ Log.d(TAG, "subTestExposureLockNotSaved");
+
+ if( !mPreview.supportsExposureLock() ) {
+ return;
+ }
+
+ setToDefault();
+
+ View exposureLockButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure_lock);
+ assertEquals(exposureLockButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.exposure_lock));
+ clickView(exposureLockButton);
+ assertTrue(mPreview.getCameraController().getAutoExposureLock());
+ assertEquals(exposureLockButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.exposure_unlock));
+
+ this.pauseAndResume();
+ assertFalse(mPreview.getCameraController().getAutoExposureLock());
+ assertEquals(exposureLockButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.exposure_lock));
+
+ // now with restart
+
+ clickView(exposureLockButton);
+ assertTrue(mPreview.getCameraController().getAutoExposureLock());
+ assertEquals(exposureLockButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.exposure_unlock));
+
+ restart();
+ assertFalse(mPreview.getCameraController().getAutoExposureLock());
+ exposureLockButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure_lock);
+ assertEquals(exposureLockButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.exposure_lock));
+ }
+
+ private void subTestWhiteBalanceLockNotSaved() {
+ Log.d(TAG, "subTestWhiteBalanceLockNotSaved");
+
+ if( !mPreview.supportsWhiteBalanceLock() ) {
+ return;
+ }
+
+ setToDefault();
+
+ View whiteBalanceLockButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.white_balance_lock);
+ assertEquals(whiteBalanceLockButton.getVisibility(), View.GONE);
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.ShowWhiteBalanceLockPreferenceKey, true);
+ editor.apply();
+ updateForSettings();
+ assertEquals(whiteBalanceLockButton.getVisibility(), View.VISIBLE);
+
+ assertEquals(whiteBalanceLockButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.white_balance_lock));
+ clickView(whiteBalanceLockButton);
+ assertTrue(mPreview.getCameraController().getAutoWhiteBalanceLock());
+ assertEquals(whiteBalanceLockButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.white_balance_unlock));
+
+ this.pauseAndResume();
+ assertFalse(mPreview.getCameraController().getAutoWhiteBalanceLock());
+ assertEquals(whiteBalanceLockButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.white_balance_lock));
+
+ // now with restart
+
+ clickView(whiteBalanceLockButton);
+ assertTrue(mPreview.getCameraController().getAutoWhiteBalanceLock());
+ assertEquals(whiteBalanceLockButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.white_balance_unlock));
+
+ restart();
+ assertFalse(mPreview.getCameraController().getAutoWhiteBalanceLock());
+ whiteBalanceLockButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.white_balance_lock);
+ assertEquals(whiteBalanceLockButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.white_balance_lock));
+ }
+
+ /** Tests for things which should (or shouldn't) be saved.
+ */
+ public void testSaveModes() throws InterruptedException {
+ Log.d(TAG, "testSaveModes");
+ subTestSaveVideoMode();
+ subTestSaveFocusMode();
+ subTestSaveFlashTorchQuit();
+ subTestExposureLockNotSaved();
+ subTestWhiteBalanceLockNotSaved();
+ }
+
+ /* Ensures that the flash mode changes as expected when switching between photo and video modes.
+ */
+ public void testFlashVideoMode() {
+ Log.d(TAG, "testFlashVideoMode");
+ setToDefault();
+
+ if( !mPreview.supportsFlash() ) {
+ return;
+ }
+
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ assertFalse(mPreview.isVideo());
+
+ switchToFlashValue("flash_auto");
+ assertEquals("flash_auto", mPreview.getCurrentFlashValue());
+
+ Log.d(TAG, "switch to video");
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertTrue(mPreview.isVideo());
+
+ // flash should turn off when in video mode, so that flash doesn't fire for photo snapshot while recording video
+ assertEquals("flash_off", mPreview.getCurrentFlashValue());
+
+ restart();
+ switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ assertTrue(mPreview.isVideo());
+ assertEquals("flash_off", mPreview.getCurrentFlashValue());
+
+ // switch back to photo mode, should return to flash auto
+ Log.d(TAG, "switch to photo");
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertFalse(mPreview.isVideo());
+ assertEquals("flash_auto", mPreview.getCurrentFlashValue());
+
+ // turn on torch, check it remains on for video
+ switchToFlashValue("flash_torch");
+ assertEquals("flash_torch", mPreview.getCurrentFlashValue());
+
+ Log.d(TAG, "switch to video");
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertTrue(mPreview.isVideo());
+ assertEquals("flash_torch", mPreview.getCurrentFlashValue());
+
+ restart();
+ switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ assertTrue(mPreview.isVideo());
+ assertEquals("flash_torch", mPreview.getCurrentFlashValue());
+
+ // switch back to photo mode, should remain in flash torch
+ Log.d(TAG, "switch to photo");
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertFalse(mPreview.isVideo());
+ assertEquals("flash_torch", mPreview.getCurrentFlashValue());
+ }
+
+ /* Ensures that we save the flash mode torch when switching to front camera and then to back
+ * Note that this sometimes fail on Galaxy Nexus, because flash turns off after autofocus (and other camera apps do this too), but this only seems to happen some of the time!
+ * And Nexus 7 has no flash anyway.
+ * So commented out test for now.
+ */
+ /*public void testSaveFlashTorchSwitchCamera() {
+ Log.d(TAG, "testSaveFlashTorchSwitchCamera");
+
+ setToDefault();
+
+ if( !mPreview.supportsFlash() ) {
+ return;
+ }
+ else if( Camera.getNumberOfCameras() <= 1 ) {
+ return;
+ }
+
+ switchToFlashValue("flash_torch");
+
+ int cameraId = mPreview.getCameraId();
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+ int new_cameraId = mPreview.getCameraId();
+ assertTrue(cameraId != new_cameraId);
+
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+ new_cameraId = mPreview.getCameraId();
+ assertTrue(cameraId == new_cameraId);
+
+ Camera camera = mPreview.getCamera();
+ Camera.Parameters parameters = camera.getParameters();
+ Log.d(TAG, "parameters flash mode: " + parameters.getFlashMode());
+ assertTrue(parameters.getFlashMode().equals(Camera.Parameters.FLASH_MODE_TORCH));
+ String flash_value = mPreview.getCurrentFlashValue();
+ Log.d(TAG, "flash_value: " + flash_value);
+ assertTrue(flash_value.equals("flash_torch"));
+ }*/
+
+ public void testFlashStartup() throws InterruptedException {
+ Log.d(TAG, "testFlashStartup");
+ setToDefault();
+
+ if( !mPreview.supportsFlash() ) {
+ return;
+ }
+
+ Log.d(TAG, "# switch to flash on");
+ switchToFlashValue("flash_on");
+ Log.d(TAG, "# restart");
+ restart();
+
+ Log.d(TAG, "# switch flash mode");
+ // now switch to torch - the idea is that this is done while the camera is starting up
+ // though note that sometimes we might not be quick enough here!
+ // don't use switchToFlashValue here, it'll get confused due to the autofocus changing the parameters flash mode
+ // update: now okay to use it, now we have the popup UI
+ //View flashButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.flash);
+ //clickView(flashButton);
+ switchToFlashValue("flash_torch");
+
+ //Camera camera = mPreview.getCamera();
+ //Camera.Parameters parameters = camera.getParameters();
+ //String flash_mode = mPreview.getCurrentFlashMode();
+ String flash_value = mPreview.getCurrentFlashValue();
+ Log.d(TAG, "# flash value is now: " + flash_value);
+ Log.d(TAG, "# sleep");
+ Thread.sleep(4000); // needs to be long enough for the autofocus to complete
+ /*parameters = camera.getParameters();
+ Log.d(TAG, "# parameters flash mode: " + parameters.getFlashMode());
+ assertTrue(parameters.getFlashMode().equals(flash_mode));*/
+ String camera_flash_value = mPreview.getCameraController().getFlashValue();
+ Log.d(TAG, "# camera flash value: " + camera_flash_value);
+ assertEquals(camera_flash_value, flash_value);
+ }
+
+ /** Tests that flash remains on, with the startup focus flash hack.
+ */
+ public void testFlashStartup2() throws InterruptedException {
+ Log.d(TAG, "testFlashStartup2");
+ setToDefault();
+
+ if( !mPreview.supportsFlash() ) {
+ return;
+ }
+
+ Log.d(TAG, "# switch to flash on");
+ switchToFlashValue("flash_on");
+ Log.d(TAG, "# restart");
+ restart();
+ Thread.sleep(3000);
+ String flash_value = mPreview.getCameraController().getFlashValue();
+ Log.d(TAG, "1 flash value is now: " + flash_value);
+ assertEquals("flash_on", flash_value);
+
+ switchToFocusValue("focus_mode_continuous_picture");
+ restart();
+ Thread.sleep(3000);
+ flash_value = mPreview.getCameraController().getFlashValue();
+ Log.d(TAG, "2 flash value is now: " + flash_value);
+ assertEquals("flash_on", flash_value);
+ }
+
+ private void checkOptimalPreviewSize() {
+ Log.d(TAG, "preview size: " + mPreview.getCameraController().getPreviewSize().width + ", " + mPreview.getCameraController().getPreviewSize().height);
+ List sizes = mPreview.getSupportedPreviewSizes();
+ CameraController.Size best_size = mPreview.getOptimalPreviewSize(sizes);
+ Log.d(TAG, "best size: " + best_size.width + ", " + best_size.height);
+ assertEquals(best_size.width, mPreview.getCameraController().getPreviewSize().width);
+ assertEquals(best_size.height, mPreview.getCameraController().getPreviewSize().height);
+ }
+
+ private void checkOptimalVideoPictureSize(double targetRatio) {
+ // even the picture resolution should have same aspect ratio for video - otherwise have problems on Nexus 7 with Android 4.4.3
+ Log.d(TAG, "video picture size: " + mPreview.getCameraController().getPictureSize().width + ", " + mPreview.getCameraController().getPictureSize().height);
+ List sizes = mPreview.getSupportedPictureSizes(false);
+ CameraController.Size best_size = mPreview.getOptimalVideoPictureSize(sizes, targetRatio);
+ Log.d(TAG, "best size: " + best_size.width + ", " + best_size.height);
+ assertEquals(best_size.width, mPreview.getCameraController().getPictureSize().width);
+ assertEquals(best_size.height, mPreview.getCameraController().getPictureSize().height);
+ }
+
+ private void checkSquareAspectRatio() {
+ Log.d(TAG, "preview size: " + mPreview.getCameraController().getPreviewSize().width + ", " + mPreview.getCameraController().getPreviewSize().height);
+ Log.d(TAG, "frame size: " + mPreview.getView().getWidth() + ", " + mPreview.getView().getHeight());
+ double frame_aspect_ratio = ((double)mPreview.getView().getWidth()) / (double)mPreview.getView().getHeight();
+ double preview_aspect_ratio = ((double)mPreview.getCameraController().getPreviewSize().width) / (double)mPreview.getCameraController().getPreviewSize().height;
+ if( mActivity.getSystemOrientation() == MainActivity.SystemOrientation.PORTRAIT ) {
+ frame_aspect_ratio = 1.0f / frame_aspect_ratio;
+ }
+ Log.d(TAG, "frame_aspect_ratio: " + frame_aspect_ratio);
+ Log.d(TAG, "preview_aspect_ratio: " + preview_aspect_ratio);
+ // we calculate etol like this, due to errors from rounding
+ //double etol = 1.0f / Math.min((double)mPreview.getWidth(), (double)mPreview.getHeight()) + 1.0e-5;
+ double min_dim = Math.min(mPreview.getView().getWidth(), (double)mPreview.getView().getHeight());
+ min_dim = Math.min(min_dim, mPreview.getCameraController().getPreviewSize().width);
+ min_dim = Math.min(min_dim, mPreview.getCameraController().getPreviewSize().height);
+ double etol = 1.0f / min_dim + 1.0e-5;
+ //double etol = (double)mPreview.getView().getWidth() / (double)(mPreview.getView().getHeight() * (mPreview.getView().getHeight()-1) ) + 1.0e-5;
+ Log.d(TAG, "min_dim: " + min_dim);
+ Log.d(TAG, "etol: " + etol);
+ assertTrue( Math.abs(frame_aspect_ratio - preview_aspect_ratio) <= etol );
+ }
+
+ /* Ensures that preview resolution is set as expected in non-WYSIWYG mode
+ */
+ public void testPreviewSize() throws InterruptedException {
+ Log.d(TAG, "testPreviewSize");
+
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PreviewSizePreferenceKey, "preference_preview_size_display");
+ editor.apply();
+ updateForSettings();
+
+ Point display_size = new Point();
+ {
+ mActivity.getApplicationInterface().getDisplaySize(display_size, false);
+ Log.d(TAG, "display_size: " + display_size.x + " x " + display_size.y);
+ }
+ //double targetRatio = mPreview.getTargetRatioForPreview(display_size);
+ double targetRatio = mPreview.getTargetRatio();
+ double expTargetRatio = ((double)display_size.x) / (double)display_size.y;
+ if( mActivity.getSystemOrientation() == MainActivity.SystemOrientation.PORTRAIT ) {
+ expTargetRatio = 1.0f / expTargetRatio;
+ }
+ Log.d(TAG, "targetRatio: " + targetRatio);
+ Log.d(TAG, "expTargetRatio: " + expTargetRatio);
+ assertTrue( Math.abs(targetRatio - expTargetRatio) <= 1.0e-5 );
+ checkOptimalPreviewSize();
+ checkSquareAspectRatio();
+
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ) {
+ Log.d(TAG, "switch camera");
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+
+ //targetRatio = mPreview.getTargetRatioForPreview(display_size);
+ targetRatio = mPreview.getTargetRatio();
+ Log.d(TAG, "targetRatio: " + targetRatio);
+ Log.d(TAG, "expTargetRatio: " + expTargetRatio);
+ assertTrue( Math.abs(targetRatio - expTargetRatio) <= 1.0e-5 );
+ checkOptimalPreviewSize();
+ checkSquareAspectRatio();
+ }
+ }
+
+ /* Ensures that preview resolution is set as expected in WYSIWYG mode
+ */
+ public void testPreviewSizeWYSIWYG() {
+ Log.d(TAG, "testPreviewSizeWYSIWYG");
+
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PreviewSizePreferenceKey, "preference_preview_size_wysiwyg");
+ editor.apply();
+ updateForSettings();
+
+ Point display_size = new Point();
+ {
+ mActivity.getApplicationInterface().getDisplaySize(display_size, false);
+ Log.d(TAG, "display_size: " + display_size.x + " x " + display_size.y);
+ }
+ CameraController.Size picture_size = mPreview.getCameraController().getPictureSize();
+ CameraController.Size preview_size = mPreview.getCameraController().getPreviewSize();
+ //double targetRatio = mPreview.getTargetRatioForPreview(display_size);
+ double targetRatio = mPreview.getTargetRatio();
+ double expTargetRatio = ((double)picture_size.width) / (double)picture_size.height;
+ double previewRatio = ((double)preview_size.width) / (double)preview_size.height;
+ Log.d(TAG, "picture_size: " + picture_size.width + " x " + picture_size.height);
+ Log.d(TAG, "preview_size: " + preview_size.width + " x " + preview_size.height);
+ Log.d(TAG, "expTargetRatio: " + expTargetRatio);
+ Log.d(TAG, "targetRatio: " + targetRatio);
+ Log.d(TAG, "previewRatio: " + previewRatio);
+ // need larger tolerance for Pixel 6 Pro, as default resolution 4080x3072 has aspect ratio 1.328125,
+ // but closet preview aspect ratio is 4:3
+ assertTrue( Math.abs(targetRatio - expTargetRatio) <= 1.0e-5 );
+ assertTrue( Math.abs(previewRatio - expTargetRatio) <= 0.01 );
+ checkOptimalPreviewSize();
+ checkSquareAspectRatio();
+
+ Log.d(TAG, "switch to video");
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertTrue(mPreview.isVideo());
+ VideoProfile profile = mPreview.getVideoProfile();
+ CameraController.Size video_preview_size = mPreview.getCameraController().getPreviewSize();
+ //targetRatio = mPreview.getTargetRatioForPreview(display_size);
+ targetRatio = mPreview.getTargetRatio();
+ expTargetRatio = ((double)profile.videoFrameWidth) / (double)profile.videoFrameHeight;
+ previewRatio = ((double)video_preview_size.width) / (double)video_preview_size.height;
+ assertTrue( Math.abs(targetRatio - expTargetRatio) <= 1.0e-5 );
+ assertTrue( Math.abs(previewRatio - expTargetRatio) <= 0.01 );
+ checkOptimalPreviewSize();
+ checkSquareAspectRatio();
+ checkOptimalVideoPictureSize(expTargetRatio);
+
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertFalse(mPreview.isVideo());
+ CameraController.Size new_picture_size = mPreview.getCameraController().getPictureSize();
+ CameraController.Size new_preview_size = mPreview.getCameraController().getPreviewSize();
+ Log.d(TAG, "picture_size: " + picture_size.width + " x " + picture_size.height);
+ Log.d(TAG, "new_picture_size: " + new_picture_size.width + " x " + new_picture_size.height);
+ Log.d(TAG, "preview_size: " + preview_size.width + " x " + preview_size.height);
+ Log.d(TAG, "new_preview_size: " + new_preview_size.width + " x " + new_preview_size.height);
+ assertEquals(new_picture_size, picture_size);
+ assertEquals(new_preview_size, preview_size);
+
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ) {
+ Log.d(TAG, "switch camera");
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+
+ picture_size = mPreview.getCameraController().getPictureSize();
+ preview_size = mPreview.getCameraController().getPreviewSize();
+ //targetRatio = mPreview.getTargetRatioForPreview(display_size);
+ targetRatio = mPreview.getTargetRatio();
+ expTargetRatio = ((double)picture_size.width) / (double)picture_size.height;
+ previewRatio = ((double)preview_size.width) / (double)preview_size.height;
+ assertTrue( Math.abs(targetRatio - expTargetRatio) <= 1.0e-5 );
+ assertTrue( Math.abs(previewRatio - expTargetRatio) <= 0.01 );
+ checkOptimalPreviewSize();
+ checkSquareAspectRatio();
+
+ Log.d(TAG, "switch to video again");
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertTrue(mPreview.isVideo());
+ profile = mPreview.getVideoProfile();
+ video_preview_size = mPreview.getCameraController().getPreviewSize();
+ //targetRatio = mPreview.getTargetRatioForPreview(display_size);
+ targetRatio = mPreview.getTargetRatio();
+ expTargetRatio = ((double)profile.videoFrameWidth) / (double)profile.videoFrameHeight;
+ previewRatio = ((double)video_preview_size.width) / (double)video_preview_size.height;
+ assertTrue( Math.abs(targetRatio - expTargetRatio) <= 1.0e-5 );
+ assertTrue( Math.abs(previewRatio - expTargetRatio) <= 0.01 );
+ checkOptimalPreviewSize();
+ checkSquareAspectRatio();
+ checkOptimalVideoPictureSize(expTargetRatio);
+
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertFalse(mPreview.isVideo());
+ new_picture_size = mPreview.getCameraController().getPictureSize();
+ new_preview_size = mPreview.getCameraController().getPreviewSize();
+ assertEquals(new_picture_size, picture_size);
+ assertEquals(new_preview_size, preview_size);
+ }
+ }
+
+ private void subTestResolutionMaxMP(String photo_mode_preference, MyApplicationInterface.PhotoMode photo_mode, int max_mp, boolean test_change_resolution, boolean expect_reduce_resolution, boolean expect_supports_burst) {
+ Log.d(TAG, "subTestResolutionMaxMP");
+ Log.d(TAG, " photo_mode_preference: " + photo_mode_preference);
+ Log.d(TAG, " photo_mode: " + photo_mode);
+ Log.d(TAG, " max_mp: " + max_mp);
+ Log.d(TAG, " test_change_resolution: " + test_change_resolution);
+
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.Standard);
+
+ CameraController.Size std_size = mPreview.getCurrentPictureSize();
+ assertNotNull(std_size);
+ Log.d(TAG, "std_size: " + std_size.width + " x " + std_size.height);
+ final List all_picture_sizes = new ArrayList<>(mPreview.getSupportedPictureSizes(false));
+ final List std_picture_sizes = new ArrayList<>(mPreview.getSupportedPictureSizes(true));
+ assertEquals(all_picture_sizes, std_picture_sizes);
+ assertTrue(all_picture_sizes.contains(std_size));
+
+ // switch to the photo mode and check we reduce the resolution
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, photo_mode_preference);
+ editor.apply();
+ updateForSettings();
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), photo_mode);
+
+ CameraController.Size new_size = mPreview.getCurrentPictureSize();
+ Log.d(TAG, "new_size: " + new_size.width + " x " + new_size.height);
+ if( expect_reduce_resolution ) {
+ assertNotEquals(new_size, std_size);
+ assertTrue(new_size.width*new_size.height <= max_mp);
+ }
+ else {
+ assertEquals(new_size, std_size);
+ }
+ if( expect_supports_burst ) {
+ assertTrue(new_size.supports_burst);
+ }
+ final List all_picture_sizes_new = new ArrayList<>(mPreview.getSupportedPictureSizes(false));
+ final List picture_sizes_new = new ArrayList<>(mPreview.getSupportedPictureSizes(true));
+ assertEquals(all_picture_sizes, all_picture_sizes_new);
+ if( expect_reduce_resolution ) {
+ assertTrue(picture_sizes_new.size() < all_picture_sizes.size());
+ // check the filtered modes are a subset of all of them
+ assertTrue(all_picture_sizes.containsAll(picture_sizes_new));
+ // check all of the filtered modes satisfy the max_mp
+ for(CameraController.Size size : picture_sizes_new) {
+ assertTrue(size.width*size.height <= max_mp);
+ }
+ }
+ else {
+ assertEquals(all_picture_sizes, picture_sizes_new);
+ }
+ if( expect_supports_burst ) {
+ // check all of the filtered modes support burst
+ for(CameraController.Size size : picture_sizes_new) {
+ assertTrue(size.supports_burst);
+ }
+ }
+ // check the filtered modes include the chosen mode
+ assertTrue(picture_sizes_new.contains(new_size));
+
+ // pause and resume, check resolutions unchanged
+ pauseAndResume();
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), photo_mode);
+ CameraController.Size new_size2 = mPreview.getCurrentPictureSize();
+ assertEquals(new_size, new_size2);
+ final List all_picture_sizes_new2 = new ArrayList<>(mPreview.getSupportedPictureSizes(false));
+ final List picture_sizes_new2 = new ArrayList<>(mPreview.getSupportedPictureSizes(true));
+ assertEquals(all_picture_sizes_new, all_picture_sizes_new2);
+ assertEquals(picture_sizes_new, picture_sizes_new2);
+
+ CameraController.Size change_to_size = null;
+ String settings_size = "";
+ if( test_change_resolution ) {
+ // test changing the resolution in the new mode
+
+ // save old resolution
+ settings_size = settings.getString(PreferenceKeys.getResolutionPreferenceKey(mPreview.getCameraId(), mActivity.getApplicationInterface().getCameraIdSPhysicalPref()), "");
+
+ // find a different resolution
+ for(CameraController.Size size : picture_sizes_new) {
+ if( !size.equals(new_size) ) {
+ change_to_size = size;
+ break;
+ }
+ }
+ assertNotNull(change_to_size);
+ Log.d(TAG, "set size to " + change_to_size.width + " x " + change_to_size.height);
+ settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ editor = settings.edit();
+ editor.putString(PreferenceKeys.getResolutionPreferenceKey(mPreview.getCameraId(), mActivity.getApplicationInterface().getCameraIdSPhysicalPref()), change_to_size.width + " " + change_to_size.height);
+ editor.apply();
+ updateForSettings();
+
+ CameraController.Size new_size3 = mPreview.getCurrentPictureSize();
+ assertEquals(change_to_size, new_size3);
+ assertNotEquals(new_size, new_size3);
+ }
+
+ // switch back to STD, and check we return to the original resolution (or not, if test_change_resolution==true)
+ settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_std");
+ editor.apply();
+ updateForSettings();
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.Standard);
+
+ new_size = mPreview.getCurrentPictureSize();
+ if( test_change_resolution ) {
+ assertEquals(change_to_size, new_size);
+ }
+ else {
+ assertEquals(std_size, new_size);
+ }
+ final List all_picture_sizes2 = new ArrayList<>(mPreview.getSupportedPictureSizes(false));
+ final List std_picture_sizes2 = new ArrayList<>(mPreview.getSupportedPictureSizes(true));
+ assertEquals(all_picture_sizes, all_picture_sizes2);
+ assertEquals(all_picture_sizes, std_picture_sizes2);
+ assertTrue(std_picture_sizes2.contains(new_size));
+
+ if( test_change_resolution ) {
+ // set back, so we don't confuse later parts of the test
+ settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ editor = settings.edit();
+ editor.putString(PreferenceKeys.getResolutionPreferenceKey(mPreview.getCameraId(), mActivity.getApplicationInterface().getCameraIdSPhysicalPref()), settings_size);
+ editor.apply();
+ updateForSettings();
+ }
+ }
+
+ /* Ensures that we enforce a maximum resolution correctly in some photo modes.
+ */
+ public void testResolutionMaxMP() {
+ Log.d(TAG, "testResolutionMaxMP");
+
+ setToDefault();
+
+ CameraController.Size std_size = mPreview.getCurrentPictureSize();
+ assertNotNull(std_size);
+ Log.d(TAG, "std_size: " + std_size.width + " x " + std_size.height);
+
+ int max_mp = (std_size.width*std_size.height-100);
+ mActivity.getApplicationInterface().test_max_mp = max_mp;
+ Log.d(TAG, "test_max_mp: " + mActivity.getApplicationInterface().test_max_mp);
+
+ if( mActivity.supportsHDR() ) {
+ subTestResolutionMaxMP("preference_photo_mode_hdr", MyApplicationInterface.PhotoMode.HDR, max_mp, false, true, true);
+ subTestResolutionMaxMP("preference_photo_mode_hdr", MyApplicationInterface.PhotoMode.HDR, max_mp, true, true, true);
+ }
+ if( mActivity.supportsNoiseReduction() ) {
+ subTestResolutionMaxMP("preference_photo_mode_noise_reduction", MyApplicationInterface.PhotoMode.NoiseReduction, max_mp, false, true, true);
+ subTestResolutionMaxMP("preference_photo_mode_noise_reduction", MyApplicationInterface.PhotoMode.NoiseReduction, max_mp, true, true, true);
+ }
+ if( mActivity.supportsDRO() ) {
+ subTestResolutionMaxMP("preference_photo_mode_dro", MyApplicationInterface.PhotoMode.DRO, max_mp, false, false, false);
+ subTestResolutionMaxMP("preference_photo_mode_dro", MyApplicationInterface.PhotoMode.DRO, max_mp, true, false, false);
+ }
+ }
+
+ /* Ensures that we handle correctly when the largest resolution doesn't support burst.
+ */
+ public void testResolutionBurst() {
+ Log.d(TAG, "testResolutionBurst");
+
+ if( !mPreview.usingCamera2API() ) {
+ Log.d(TAG, "test requires camera2 api");
+ return;
+ }
+
+ setToDefault();
+
+ mPreview.test_burst_resolution = true;
+ pauseAndResume(); // needed for test_burst_resolution to take effect
+
+ CameraController.Size std_size = mPreview.getCurrentPictureSize();
+ // check the test_burst_resolution flag took effect:
+ assertFalse(std_size.supports_burst);
+
+ // now find the maximum mp that supports burst
+ final List all_picture_sizes = new ArrayList<>(mPreview.getSupportedPictureSizes(false));
+ int max_mp = 0;
+ for(CameraController.Size size : all_picture_sizes) {
+ if( size.supports_burst ) {
+ int mp = size.width*size.height;
+ max_mp = Math.max(max_mp, mp);
+ }
+ }
+ Log.d(TAG, "max_mp: " + max_mp);
+ assertTrue(max_mp < std_size.width*std_size.height);
+
+ if( mActivity.supportsHDR() ) {
+ subTestResolutionMaxMP("preference_photo_mode_hdr", MyApplicationInterface.PhotoMode.HDR, max_mp, false, true, true);
+ subTestResolutionMaxMP("preference_photo_mode_hdr", MyApplicationInterface.PhotoMode.HDR, max_mp, true, true, true);
+ }
+ if( mActivity.supportsNoiseReduction() ) {
+ subTestResolutionMaxMP("preference_photo_mode_noise_reduction", MyApplicationInterface.PhotoMode.NoiseReduction, max_mp, false, true, true);
+ subTestResolutionMaxMP("preference_photo_mode_noise_reduction", MyApplicationInterface.PhotoMode.NoiseReduction, max_mp, true, true, true);
+ }
+ if( mActivity.supportsDRO() ) {
+ subTestResolutionMaxMP("preference_photo_mode_dro", MyApplicationInterface.PhotoMode.DRO, max_mp, false, false, false);
+ subTestResolutionMaxMP("preference_photo_mode_dro", MyApplicationInterface.PhotoMode.DRO, max_mp, true, false, false);
+ }
+ if( mActivity.supportsFastBurst() ) {
+ subTestResolutionMaxMP("preference_photo_mode_fast_burst", MyApplicationInterface.PhotoMode.FastBurst, max_mp, false, true, true);
+ subTestResolutionMaxMP("preference_photo_mode_fast_burst", MyApplicationInterface.PhotoMode.FastBurst, max_mp, true, true, true);
+ }
+ }
+
+ /* Tests camera error handling.
+ */
+ public void testOnError() {
+ Log.d(TAG, "testOnError");
+ setToDefault();
+
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ Log.d(TAG, "onError...");
+ mPreview.getCameraController().onError();
+ }
+ });
+ this.getInstrumentation().waitForIdleSync();
+ assertNull(mPreview.getCameraController());
+ }
+
+ /* Various tests for auto-focus.
+ */
+ public void testAutoFocus() throws InterruptedException {
+ Log.d(TAG, "testAutoFocus");
+ setToDefault();
+
+ if( !mPreview.supportsFocus() ) {
+ return;
+ }
+ //int saved_count = mPreview.count_cameraAutoFocus;
+ int saved_count = 0; // set to 0 rather than count_cameraAutoFocus, as on Galaxy Nexus, it can happen that startup autofocus has already occurred by the time we reach here
+ Log.d(TAG, "saved_count: " + saved_count);
+ switchToFocusValue("focus_mode_auto");
+
+ assertFalse(mPreview.hasFocusArea());
+ assertNull(mPreview.getCameraController().getFocusAreas());
+ assertNull(mPreview.getCameraController().getMeteringAreas());
+
+ Thread.sleep(2000); // wait until autofocus startup (and for toasts to clear, for Android 10+ toast behaviour)
+ Log.d(TAG, "1 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus + " compare to saved_count: " + saved_count);
+ assertEquals(saved_count + 1, mPreview.count_cameraAutoFocus);
+ assertFalse(mPreview.hasFocusArea());
+ assertNull(mPreview.getCameraController().getFocusAreas());
+ assertNull(mPreview.getCameraController().getMeteringAreas());
+
+ // touch to auto-focus with focus area
+ saved_count = mPreview.count_cameraAutoFocus;
+ Log.d(TAG, "about to touch preview to auto-focus");
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ Log.d(TAG, "done touch preview to auto-focus");
+ Log.d(TAG, "2 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus + " compare to saved_count: " + saved_count);
+ assertEquals(saved_count + 1, mPreview.count_cameraAutoFocus);
+ assertTrue(mPreview.hasFocusArea());
+ assertNotNull(mPreview.getCameraController().getFocusAreas());
+ assertEquals(1, mPreview.getCameraController().getFocusAreas().size());
+ assertNotNull(mPreview.getCameraController().getMeteringAreas());
+ assertEquals(1, mPreview.getCameraController().getMeteringAreas().size());
+
+ saved_count = mPreview.count_cameraAutoFocus;
+ // test selecting same mode doesn't set off an autofocus or reset the focus area
+ switchToFocusValue("focus_mode_auto");
+ Log.d(TAG, "3 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count);
+ assertTrue(mPreview.hasFocusArea());
+ assertNotNull(mPreview.getCameraController().getFocusAreas());
+ assertEquals(1, mPreview.getCameraController().getFocusAreas().size());
+ assertNotNull(mPreview.getCameraController().getMeteringAreas());
+ assertEquals(1, mPreview.getCameraController().getMeteringAreas().size());
+
+ if( mPreview.getSupportedFocusValues().contains("focus_mode_macro") ) {
+ saved_count = mPreview.count_cameraAutoFocus;
+ // test switching mode sets off an autofocus, and resets the focus area
+ switchToFocusValue("focus_mode_macro");
+ Log.d(TAG, "4 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count + 1);
+ assertFalse(mPreview.hasFocusArea());
+ assertNull(mPreview.getCameraController().getFocusAreas());
+ assertNull(mPreview.getCameraController().getMeteringAreas());
+ }
+
+ saved_count = mPreview.count_cameraAutoFocus;
+ // switching to focus locked shouldn't set off an autofocus
+ switchToFocusValue("focus_mode_locked");
+ Log.d(TAG, "5 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count);
+
+ saved_count = mPreview.count_cameraAutoFocus;
+ // touch to focus should autofocus
+ Thread.sleep(2000);
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ Log.d(TAG, "6 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count + 1);
+
+ saved_count = mPreview.count_cameraAutoFocus;
+ // switching to focus continuous shouldn't set off an autofocus
+ switchToFocusValue("focus_mode_continuous_picture");
+ Log.d(TAG, "7 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ assertFalse(mPreview.isFocusWaiting());
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count);
+
+ // but touch to focus should
+ Thread.sleep(2000);
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ Log.d(TAG, "8 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count + 1);
+ assertTrue(mPreview.hasFocusArea());
+ assertNotNull(mPreview.getCameraController().getFocusAreas());
+ assertEquals(1, mPreview.getCameraController().getFocusAreas().size());
+ assertNotNull(mPreview.getCameraController().getMeteringAreas());
+ assertEquals(1, mPreview.getCameraController().getMeteringAreas().size());
+
+ switchToFocusValue("focus_mode_locked"); // change to a mode that isn't auto (so that the first iteration of the next loop will set of an autofocus, due to changing the focus mode)
+ List supported_focus_values = mPreview.getSupportedFocusValues();
+ assertNotNull(supported_focus_values);
+ assertTrue( supported_focus_values.size() > 1 );
+ for(String supported_focus_value : supported_focus_values) {
+ Log.d(TAG, "supported_focus_value: " + supported_focus_value);
+ saved_count = mPreview.count_cameraAutoFocus;
+ Log.d(TAG, "saved autofocus count: " + saved_count);
+ //View focusModeButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.focus_mode);
+ //clickView(focusModeButton);
+ switchToFocusValue(supported_focus_value);
+ // test that switching focus mode resets the focus area
+ assertFalse(mPreview.hasFocusArea());
+ assertNull(mPreview.getCameraController().getFocusAreas());
+ assertNull(mPreview.getCameraController().getMeteringAreas());
+ // test that switching focus mode sets off an autofocus in focus auto or macro mode
+ String focus_value = mPreview.getCameraController().getFocusValue();
+ Log.d(TAG, "changed focus_value to: "+ focus_value);
+ Log.d(TAG, "count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ if( focus_value.equals("focus_mode_auto") || focus_value.equals("focus_mode_macro") ) {
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count + 1);
+ }
+ else {
+ assertFalse(mPreview.isFocusWaiting());
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count);
+ }
+
+ // test that touch to auto-focus region only works in focus auto, macro or continuous mode, and that we set off an autofocus for focus auto and macro
+ // test that touch to set metering area works in any focus mode
+ saved_count = mPreview.count_cameraAutoFocus;
+ Thread.sleep(2000);
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ Log.d(TAG, "count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ if( focus_value.equals("focus_mode_auto") || focus_value.equals("focus_mode_macro") || focus_value.equals("focus_mode_continuous_picture") || focus_value.equals("focus_mode_continuous_video") ) {
+ if( focus_value.equals("focus_mode_continuous_picture") || focus_value.equals("focus_mode_continuous_video") ) {
+ assertFalse(mPreview.isFocusWaiting());
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count);
+ }
+ else {
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count + 1);
+ }
+ assertTrue(mPreview.hasFocusArea());
+ assertNotNull(mPreview.getCameraController().getFocusAreas());
+ assertEquals(1, mPreview.getCameraController().getFocusAreas().size());
+ assertNotNull(mPreview.getCameraController().getMeteringAreas());
+ assertEquals(1, mPreview.getCameraController().getMeteringAreas().size());
+ }
+ else {
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count);
+ assertFalse(mPreview.hasFocusArea());
+ assertNull(mPreview.getCameraController().getFocusAreas());
+ assertNotNull(mPreview.getCameraController().getMeteringAreas());
+ assertEquals(1, mPreview.getCameraController().getMeteringAreas().size());
+ }
+ // also check that focus mode is unchanged
+ assertEquals(mPreview.getCameraController().getFocusValue(), focus_value);
+ if( focus_value.equals("focus_mode_auto") ) {
+ break;
+ }
+ }
+ }
+
+ /* Test we do startup autofocus as expected depending on focus mode.
+ */
+ public void testStartupAutoFocus() throws InterruptedException {
+ Log.d(TAG, "testStartupAutoFocus");
+ setToDefault();
+
+ if( !mPreview.supportsFocus() ) {
+ return;
+ }
+ //int saved_count = mPreview.count_cameraAutoFocus;
+ int saved_count = 0; // set to 0 rather than count_cameraAutoFocus, as on Galaxy Nexus, it can happen that startup autofocus has already occurred by the time we reach here
+ Log.d(TAG, "saved_count: " + saved_count);
+ switchToFocusValue("focus_mode_auto");
+
+ Thread.sleep(1000);
+ Log.d(TAG, "1 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus + " compare to saved_count: " + saved_count);
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count + 1);
+
+ restart();
+ //saved_count = mPreview.count_cameraAutoFocus;
+ saved_count = 0;
+ Log.d(TAG, "saved_count: " + saved_count);
+ Thread.sleep(1000);
+ Log.d(TAG, "2 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus + " compare to saved_count: " + saved_count);
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count + 1);
+
+ if( mPreview.getSupportedFocusValues().contains("focus_mode_infinity") ) {
+ switchToFocusValue("focus_mode_infinity");
+ restart();
+ //saved_count = mPreview.count_cameraAutoFocus;
+ saved_count = 0;
+ Log.d(TAG, "saved_count: " + saved_count);
+ Thread.sleep(1000);
+ Log.d(TAG, "3 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus + " compare to saved_count: " + saved_count);
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count);
+ }
+
+ if( mPreview.getSupportedFocusValues().contains("focus_mode_macro") ) {
+ switchToFocusValue("focus_mode_macro");
+ restart();
+ //saved_count = mPreview.count_cameraAutoFocus;
+ saved_count = 0;
+ Log.d(TAG, "saved_count: " + saved_count);
+ Thread.sleep(1000);
+ Log.d(TAG, "4 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus + " compare to saved_count: " + saved_count);
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count + 1);
+ }
+
+ if( mPreview.getSupportedFocusValues().contains("focus_mode_locked") ) {
+ switchToFocusValue("focus_mode_locked");
+ restart();
+ //saved_count = mPreview.count_cameraAutoFocus;
+ saved_count = 0;
+ Log.d(TAG, "saved_count: " + saved_count);
+ Thread.sleep(1000);
+ Log.d(TAG, "5 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus + " compare to saved_count: " + saved_count);
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count + 1);
+ }
+
+ if( mPreview.getSupportedFocusValues().contains("focus_mode_continuous_picture") ) {
+ switchToFocusValue("focus_mode_continuous_picture");
+ restart();
+ //saved_count = mPreview.count_cameraAutoFocus;
+ saved_count = 0;
+ Log.d(TAG, "saved_count: " + saved_count);
+ Thread.sleep(1000);
+ Log.d(TAG, "6 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus + " compare to saved_count: " + saved_count);
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count);
+ }
+ }
+
+ /* Test doing touch to auto-focus region by swiping to all four corners works okay.
+ * Update: now only do one corner, due to complications with different orientations and devices.
+ */
+ public void testAutoFocusCorners() {
+ Log.d(TAG, "testAutoFocusCorners");
+ setToDefault();
+ {
+ // icons along top mode interferes with doing the touch at corners (e.g., on Galaxy Nexus)
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.UIPlacementPreferenceKey, "ui_right");
+ editor.apply();
+ updateForSettings();
+ }
+
+ if( !mPreview.supportsFocus() ) {
+ return;
+ }
+
+ int [] gui_location = new int[2];
+ mPreview.getView().getLocationOnScreen(gui_location);
+ Log.d(TAG, "gui_location: " + Arrays.toString(gui_location));
+ final int step_dist_c = 2;
+ final float scale = mActivity.getResources().getDisplayMetrics().density;
+ final int offset_dist_c = (int) (80 * scale + 0.5f); // convert dps to pixels
+ final int large_step_dist_c = (int) (80 * scale + 0.5f); // convert dps to pixels
+ final int step_count_c = 10;
+ int width = mPreview.getView().getWidth();
+ int height = mPreview.getView().getHeight();
+ Log.d(TAG, "preview size: " + width + " x " + height);
+
+ assertFalse(mPreview.hasFocusArea());
+ assertNull(mPreview.getCameraController().getFocusAreas());
+ assertNull(mPreview.getCameraController().getMeteringAreas());
+
+ Log.d(TAG, "top-left");
+ TouchUtils.drag(MainActivityTest.this, gui_location[0] + offset_dist_c + step_dist_c, gui_location[0] + offset_dist_c, gui_location[1] + offset_dist_c + step_dist_c, gui_location[1] + offset_dist_c, step_count_c);
+ assertTrue(mPreview.hasFocusArea());
+ assertNotNull(mPreview.getCameraController().getFocusAreas());
+ assertEquals(1, mPreview.getCameraController().getFocusAreas().size());
+ assertNotNull(mPreview.getCameraController().getMeteringAreas());
+ assertEquals(1, mPreview.getCameraController().getMeteringAreas().size());
+
+ mPreview.clearFocusAreas();
+ assertFalse(mPreview.hasFocusArea());
+ assertNull(mPreview.getCameraController().getFocusAreas());
+ assertNull(mPreview.getCameraController().getMeteringAreas());
+
+ // skip top-right, as in portrait orientation we'd conflict with settings button
+ /*Log.d(TAG, "top-right");
+ TouchUtils.drag(MainActivityTest.this, gui_location[0]+width-1-large_step_dist_c, gui_location[0]+width-1, gui_location[1]+large_step_dist_c, gui_location[1], step_count_c);
+ assertFalse(mPreview.hasFocusArea());
+ assertNull(mPreview.getCameraController().getFocusAreas());
+ assertNull(mPreview.getCameraController().getMeteringAreas());*/
+
+ // skip bottom right, conflicts with zoom on various devices
+ // but note in portrait mode, this is bottom-left that we need to skip
+ // update: and in portrait mode, that now conflicts with settings, so we just skip this too
+
+ /*if( mActivity.getSystemOrientation() == MainActivity.SystemOrientation.PORTRAIT ) {
+ Log.d(TAG, "bottom-right");
+ TouchUtils.drag(MainActivityTest.this, gui_location[0]+width-1-step_dist_c, gui_location[0]+width-1, gui_location[1]+height-1-step_dist_c, gui_location[1]+height-1, step_count_c);
+ assertTrue(mPreview.hasFocusArea());
+ assertNotNull(mPreview.getCameraController().getFocusAreas());
+ assertEquals(1, mPreview.getCameraController().getFocusAreas().size());
+ assertNotNull(mPreview.getCameraController().getMeteringAreas());
+ assertEquals(1, mPreview.getCameraController().getMeteringAreas().size());
+ }
+ else {
+ Log.d(TAG, "bottom-left");
+ TouchUtils.drag(MainActivityTest.this, gui_location[0]+step_dist_c, gui_location[0], gui_location[1]+height-1-step_dist_c, gui_location[1]+height-1, step_count_c);
+ assertTrue(mPreview.hasFocusArea());
+ assertNotNull(mPreview.getCameraController().getFocusAreas());
+ assertEquals(1, mPreview.getCameraController().getFocusAreas().size());
+ assertNotNull(mPreview.getCameraController().getMeteringAreas());
+ assertEquals(1, mPreview.getCameraController().getMeteringAreas().size());
+ }*/
+
+ mPreview.clearFocusAreas();
+ assertFalse(mPreview.hasFocusArea());
+ assertNull(mPreview.getCameraController().getFocusAreas());
+ assertNull(mPreview.getCameraController().getMeteringAreas());
+
+ }
+
+ /* Test face detection, and that we don't get the focus/metering areas set.
+ */
+ public void testFaceDetection() throws InterruptedException {
+ Log.d(TAG, "testFaceDetection");
+ setToDefault();
+
+ View faceDetectionButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.face_detection);
+ assertEquals(faceDetectionButton.getVisibility(), View.GONE);
+
+ if( !mPreview.supportsFaceDetection() ) {
+ Log.d(TAG, "face detection not supported");
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.FaceDetectionPreferenceKey, true);
+ editor.apply();
+ updateForSettings();
+
+ assertEquals(faceDetectionButton.getVisibility(), View.GONE);
+
+ int saved_count;
+ Log.d(TAG, "0 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ /*
+ // autofocus shouldn't be immediately, but after a delay
+ saved_count = mPreview.count_cameraAutoFocus;
+ Thread.sleep(1000);
+ Log.d(TAG, "1 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ assertTrue(mPreview.count_cameraAutoFocus == saved_count+1);
+ */
+ Thread.sleep(2000);
+ assertFalse(mPreview.hasFocusArea());
+ assertNull(mPreview.getCameraController().getFocusAreas());
+ assertNull(mPreview.getCameraController().getMeteringAreas());
+ // check face detection already started
+ assertFalse( mPreview.getCameraController().startFaceDetection() );
+
+ // touch to auto-focus with focus area
+ saved_count = mPreview.count_cameraAutoFocus;
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ Log.d(TAG, "2 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count + 1); // for autofocus
+ assertFalse(mPreview.hasFocusArea());
+ assertNull(mPreview.getCameraController().getFocusAreas());
+ assertNull(mPreview.getCameraController().getMeteringAreas());
+
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ) {
+ int cameraId = mPreview.getCameraId();
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+ // check face detection already started
+ assertFalse( mPreview.getCameraController().startFaceDetection() );
+
+ // return to back camera
+ switchToCamera(cameraId);
+ }
+
+ // test show face detection icon
+
+ editor.putBoolean(PreferenceKeys.ShowFaceDetectionPreferenceKey, true);
+ editor.apply();
+ updateForSettings();
+
+ assertEquals(faceDetectionButton.getVisibility(), View.VISIBLE);
+ assertEquals(faceDetectionButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.face_detection_disable));
+
+ // check face detection already started
+ assertFalse( mPreview.getCameraController().startFaceDetection() );
+
+ // restart and check still enabled
+ restart();
+ faceDetectionButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.face_detection);
+
+ assertEquals(faceDetectionButton.getVisibility(), View.VISIBLE);
+ assertEquals(faceDetectionButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.face_detection_disable));
+
+ clickView(faceDetectionButton);
+ waitUntilCameraOpened();
+ assertFalse( settings.getBoolean(PreferenceKeys.FaceDetectionPreferenceKey, false) );
+ assertEquals(faceDetectionButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.face_detection_enable));
+
+ // check face detection not already started
+ assertTrue( mPreview.getCameraController().startFaceDetection() );
+
+ assertTrue( mPreview.getCameraController() == null || mPreview.getCameraController().count_camera_parameters_exception == 0 );
+ }
+
+ private void subTestPopupButtonAvailability(String test_key, String option, boolean expected) {
+ Log.d(TAG, "test_key: "+ test_key);
+ Log.d(TAG, "option: "+ option);
+ Log.d(TAG, "expected?: "+ expected);
+ View button = mActivity.getUIButton(test_key + "_" + option);
+ if( expected ) {
+ boolean is_video = mPreview.isVideo();
+ if( option.equals("focus_mode_continuous_picture") && is_video ) {
+ // not allowed in video mode
+ assertNull(button);
+ }
+ else if( option.equals("focus_mode_continuous_video") && !is_video ) {
+ // not allowed in picture mode
+ assertNull(button);
+ }
+ else if( option.equals("flash_auto") && is_video ) {
+ // not allowed in video mode
+ assertNull(button);
+ }
+ else if( option.equals("flash_on") && is_video ) {
+ // not allowed in video mode
+ assertNull(button);
+ }
+ else if( option.equals("flash_red_eye") && is_video ) {
+ // not allowed in video mode
+ assertNull(button);
+ }
+ else if( option.equals("flash_frontscreen_auto") && is_video ) {
+ // not allowed in video mode
+ assertNull(button);
+ }
+ else if( option.equals("flash_frontscreen_on") && is_video ) {
+ // not allowed in video mode
+ assertNull(button);
+ }
+ else {
+ assertNotNull(button);
+ }
+ }
+ else {
+ Log.d(TAG, "option? "+ option);
+ Log.d(TAG, "button? "+ button);
+ assertNull(button);
+ }
+ }
+
+ private void subTestPopupButtonAvailability(String test_key, String option, List options) {
+ subTestPopupButtonAvailability(test_key, option, options != null && options.contains(option));
+ }
+
+ private void subTestPopupButtonAvailability(String option, boolean expected) {
+ View button = mActivity.getUIButton(option);
+ if( expected ) {
+ assertNotNull(button);
+ }
+ else {
+ assertNull(button);
+ }
+ }
+
+ private void subTestPopupButtonAvailability() {
+ List supported_flash_values = mPreview.getSupportedFlashValues();
+ Log.d(TAG, "supported_flash_values: "+ supported_flash_values);
+ subTestPopupButtonAvailability("TEST_FLASH", "flash_off", supported_flash_values);
+ subTestPopupButtonAvailability("TEST_FLASH", "flash_auto", supported_flash_values);
+ subTestPopupButtonAvailability("TEST_FLASH", "flash_on", supported_flash_values);
+ subTestPopupButtonAvailability("TEST_FLASH", "flash_torch", supported_flash_values);
+ subTestPopupButtonAvailability("TEST_FLASH", "flash_red_eye", supported_flash_values);
+ List supported_focus_values = mPreview.getSupportedFocusValues();
+ subTestPopupButtonAvailability("TEST_FOCUS", "focus_mode_auto", supported_focus_values);
+ subTestPopupButtonAvailability("TEST_FOCUS", "focus_mode_locked", supported_focus_values);
+ subTestPopupButtonAvailability("TEST_FOCUS", "focus_mode_infinity", supported_focus_values);
+ subTestPopupButtonAvailability("TEST_FOCUS", "focus_mode_macro", supported_focus_values);
+ subTestPopupButtonAvailability("TEST_FOCUS", "focus_mode_fixed", supported_focus_values);
+ subTestPopupButtonAvailability("TEST_FOCUS", "focus_mode_edof", supported_focus_values);
+ subTestPopupButtonAvailability("TEST_FOCUS", "focus_mode_continuous_picture", supported_focus_values);
+ subTestPopupButtonAvailability("TEST_FOCUS", "focus_mode_continuous_video", supported_focus_values);
+ subTestPopupButtonAvailability("TEST_WHITE_BALANCE", mPreview.getSupportedWhiteBalances() != null);
+ subTestPopupButtonAvailability("TEST_SCENE_MODE", mPreview.getSupportedSceneModes() != null);
+ subTestPopupButtonAvailability("TEST_COLOR_EFFECT", mPreview.getSupportedColorEffects() != null);
+ }
+
+ private void subTestFocusFlashAvailability() {
+ //View focusModeButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.focus_mode);
+ //View flashButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.flash);
+ View exposureButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureLockButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure_lock);
+ View popupButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.popup);
+ /*boolean focus_visible = focusModeButton.getVisibility() == View.VISIBLE;
+ Log.d(TAG, "focus_visible? "+ focus_visible);
+ boolean flash_visible = flashButton.getVisibility() == View.VISIBLE;
+ Log.d(TAG, "flash_visible? "+ flash_visible);*/
+ boolean exposure_visible = exposureButton.getVisibility() == View.VISIBLE;
+ Log.d(TAG, "exposure_visible? "+ exposure_visible);
+ boolean exposure_lock_visible = exposureLockButton.getVisibility() == View.VISIBLE;
+ Log.d(TAG, "exposure_lock_visible? "+ exposure_lock_visible);
+ boolean popup_visible = popupButton.getVisibility() == View.VISIBLE;
+ Log.d(TAG, "popup_visible? "+ popup_visible);
+ boolean has_focus = mPreview.supportsFocus();
+ Log.d(TAG, "has_focus? "+ has_focus);
+ boolean has_flash = mPreview.supportsFlash();
+ Log.d(TAG, "has_flash? "+ has_flash);
+ boolean has_exposure = mPreview.supportsExposures();
+ Log.d(TAG, "has_exposure? "+ has_exposure);
+ boolean has_exposure_lock = mPreview.supportsExposureLock();
+ Log.d(TAG, "has_exposure_lock? "+ has_exposure_lock);
+ //assertTrue(has_focus == focus_visible);
+ //assertTrue(has_flash == flash_visible);
+ assertEquals(has_exposure, exposure_visible);
+ assertEquals(has_exposure_lock, exposure_lock_visible);
+ assertTrue(popup_visible);
+
+ openPopupMenu();
+ subTestPopupButtonAvailability();
+ }
+
+ /*
+ * For each camera, test that visibility of flash and focus etc buttons matches the availability of those camera parameters.
+ * Added to guard against a bug where on Nexus 7, the flash and focus buttons were made visible by showGUI, even though they aren't supported by Nexus 7 front camera.
+ */
+ public void testFocusFlashAvailability() {
+ Log.d(TAG, "testFocusFlashAvailability");
+ setToDefault();
+
+ subTestFocusFlashAvailability();
+
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ) {
+ int cameraId = mPreview.getCameraId();
+ Log.d(TAG, "cameraId? "+ cameraId);
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ //mActivity.clickedSwitchCamera(switchCameraButton);
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+ int new_cameraId = mPreview.getCameraId();
+ Log.d(TAG, "new_cameraId? "+ new_cameraId);
+ assertTrue(cameraId != new_cameraId);
+
+ subTestFocusFlashAvailability();
+ }
+ }
+
+ private void subTestPopupButtonContentDescription(int title_id, String test_key, boolean next, boolean expected) {
+ Log.d(TAG, "subTestPopupButtonContentDescription");
+ String title = mActivity.getResources().getString(title_id);
+ Log.d(TAG, "title: " + title);
+ Log.d(TAG, "test_key: " + test_key);
+ Log.d(TAG, "next: " + next);
+ Log.d(TAG, "expected: " + expected);
+ View main_button = mActivity.getUIButton(test_key);
+ assertNotNull(main_button);
+ View button = mActivity.getUIButton(test_key + (next ? "_NEXT" : "_PREV"));
+ if( expected ) {
+ assertNotNull(button);
+ }
+ if( button != null ) {
+ assertNotNull(button.getContentDescription());
+ String content_description = button.getContentDescription().toString();
+ assertFalse(content_description.isEmpty());
+ String next_string = mActivity.getResources().getString(next ? net.sourceforge.opencamera.R.string.next : net.sourceforge.opencamera.R.string.previous);
+ assertFalse(next_string.isEmpty());
+ assertTrue(content_description.startsWith(next_string + " " + title));
+ }
+ else {
+ Log.d(TAG, "no button found");
+ }
+ }
+
+ /* Tests switching to/from video mode, for front and back cameras, and tests the focus mode changes as expected.
+ * If this test fails with nullpointerexception on preview.getCameraController() after switching to video mode, check
+ * that record audio permission is granted!
+ * Also tests content descriptions of <> buttons on the popup menu.
+ */
+ public void testSwitchVideo() {
+ Log.d(TAG, "testSwitchVideo");
+
+ setToDefault();
+ assertFalse(mPreview.isVideo());
+ String photo_focus_value = mPreview.getCameraController().getFocusValue();
+ Log.d(TAG, "picture photo_focus_value: "+ photo_focus_value);
+
+ openPopupMenu();
+
+ // test popup buttons for photo mode:
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_resolution, "PHOTO_RESOLUTIONS", false, true);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_resolution, "PHOTO_RESOLUTIONS", true, false);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_timer, "TIMER", false, false);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_timer, "TIMER", true, true);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_burst_mode, "REPEAT_MODE", false, false);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_burst_mode, "REPEAT_MODE", true, true);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.grid, "GRID", false, true);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.grid, "GRID", true, true);
+
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertTrue(mPreview.isVideo());
+ String focus_value = mPreview.getCameraController().getFocusValue();
+ Log.d(TAG, "video focus_value: "+ focus_value);
+ if( mPreview.supportsFocus() ) {
+ assertEquals("focus_mode_continuous_video", focus_value);
+ }
+
+ // test popup buttons for video mode:
+ openPopupMenu();
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.video_quality, "VIDEO_RESOLUTIONS", false, true);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.video_quality, "VIDEO_RESOLUTIONS", true, false);
+ if( mActivity.getApplicationInterface().getSupportedVideoCaptureRates().size() > 1 ) {
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_video_capture_rate, "VIDEOCAPTURERATE", false, false);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_video_capture_rate, "VIDEOCAPTURERATE", true, true);
+ }
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_timer, "TIMER", false, false);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_timer, "TIMER", true, true);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_burst_mode, "REPEAT_MODE", false, false);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_burst_mode, "REPEAT_MODE", true, true);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.grid, "GRID", false, true);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.grid, "GRID", true, true);
+
+ int saved_count = mPreview.count_cameraAutoFocus;
+ Log.d(TAG, "0 count_cameraAutoFocus: " + saved_count);
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertFalse(mPreview.isVideo());
+ focus_value = mPreview.getCameraController().getFocusValue();
+ Log.d(TAG, "picture focus_value: "+ focus_value);
+ if( mPreview.supportsFocus() ) {
+ assertEquals(focus_value, photo_focus_value);
+ // check that this doesn't cause an autofocus
+ assertFalse(mPreview.isFocusWaiting());
+ Log.d(TAG, "1 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count);
+ }
+
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ) {
+ int cameraId = mPreview.getCameraId();
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+ int new_cameraId = mPreview.getCameraId();
+ assertTrue(cameraId != new_cameraId);
+ // n.b., front camera default photo focus value not necessarily same as back camera, if they have different focus modes
+ photo_focus_value = mPreview.getCameraController().getFocusValue();
+ Log.d(TAG, "front picture photo_focus_value: "+ photo_focus_value);
+
+ // test popup buttons for photo mode:
+ openPopupMenu();
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_resolution, "PHOTO_RESOLUTIONS", false, true);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_resolution, "PHOTO_RESOLUTIONS", true, false);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_timer, "TIMER", false, false);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_timer, "TIMER", true, true);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_burst_mode, "REPEAT_MODE", false, false);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_burst_mode, "REPEAT_MODE", true, true);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.grid, "GRID", false, true);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.grid, "GRID", true, true);
+
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertTrue(mPreview.isVideo());
+ focus_value = mPreview.getCameraController().getFocusValue();
+ Log.d(TAG, "front video focus_value: "+ focus_value);
+ if( mPreview.supportsFocus() ) {
+ assertEquals("focus_mode_continuous_video", focus_value);
+ }
+
+ // test popup buttons for video mode:
+ openPopupMenu();
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.video_quality, "VIDEO_RESOLUTIONS", false, true);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.video_quality, "VIDEO_RESOLUTIONS", true, false);
+ if( mActivity.getApplicationInterface().getSupportedVideoCaptureRates().size() > 1 ) {
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_video_capture_rate, "VIDEOCAPTURERATE", false, false);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_video_capture_rate, "VIDEOCAPTURERATE", true, true);
+ }
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_timer, "TIMER", false, false);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_timer, "TIMER", true, true);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_burst_mode, "REPEAT_MODE", false, false);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.preference_burst_mode, "REPEAT_MODE", true, true);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.grid, "GRID", false, true);
+ subTestPopupButtonContentDescription(net.sourceforge.opencamera.R.string.grid, "GRID", true, true);
+
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertFalse(mPreview.isVideo());
+ focus_value = mPreview.getCameraController().getFocusValue();
+ Log.d(TAG, "front picture focus_value: "+ focus_value);
+ if( mPreview.supportsFocus() ) {
+ assertEquals(focus_value, photo_focus_value);
+ }
+
+ // now switch back
+ switchToCamera(cameraId);
+ }
+
+ if( mPreview.supportsFocus() ) {
+ // now test we remember the focus mode for photo and video
+
+ switchToFocusValue("focus_mode_continuous_picture");
+
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertTrue(mPreview.isVideo());
+ focus_value = mPreview.getCameraController().getFocusValue();
+ Log.d(TAG, "video focus_value: "+ focus_value);
+ assertEquals("focus_mode_continuous_video", focus_value);
+
+ String non_default_focus = getNonDefaultFocus();
+ switchToFocusValue(non_default_focus);
+
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertFalse(mPreview.isVideo());
+ focus_value = mPreview.getCameraController().getFocusValue();
+ Log.d(TAG, "picture focus_value: "+ focus_value);
+ assertEquals("focus_mode_continuous_picture", focus_value);
+
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertTrue(mPreview.isVideo());
+ focus_value = mPreview.getCameraController().getFocusValue();
+ if( non_default_focus.equals("focus_mode_infinity") && focus_value.equals("focus_mode_manual2") ) {
+ // for Camera2, focus_mode_infinity is represented as focus_mode_manual2
+ focus_value = "focus_mode_infinity";
+ }
+ Log.d(TAG, "video focus_value: "+ focus_value);
+ assertEquals(non_default_focus, focus_value);
+ }
+ }
+
+ /* Tests continuous picture focus, including switching to video and back.
+ * Tends to fail on Galaxy Nexus, where the continuous picture focusing doesn't happen too often.
+ */
+ public void testContinuousPictureFocus() throws InterruptedException {
+ Log.d(TAG, "testContinuousPictureFocus");
+
+ setToDefault();
+
+ if( !mPreview.supportsFocus() ) {
+ return;
+ }
+
+ // first switch to auto-focus (if we're already in continuous picture mode, we might have already done the continuous focus moving - although also see note below)
+ switchToFocusValue("focus_mode_auto");
+ pauseAndResume();
+ switchToFocusValue("focus_mode_continuous_picture");
+
+ // check continuous focus is working
+ int saved_count_cameraContinuousFocusMoving = mPreview.count_cameraContinuousFocusMoving;
+ Thread.sleep(2000); // n.b., Galaxy S10e seems to need longer delay than other devices for continuous focus to occur
+ int new_count_cameraContinuousFocusMoving = mPreview.count_cameraContinuousFocusMoving;
+ Log.d(TAG, "count_cameraContinuousFocusMoving compare saved: "+ saved_count_cameraContinuousFocusMoving + " to new: " + new_count_cameraContinuousFocusMoving);
+ assertEquals(0, mPreview.getCameraController().test_af_state_null_focus);
+ // allow for new_count_cameraContinuousFocusMoving > 0 as some devices like OnePlus Pad won't repeat the continuous focus (even when changing focus modes), unless necessary due to scene changing
+ assertTrue( new_count_cameraContinuousFocusMoving > saved_count_cameraContinuousFocusMoving || new_count_cameraContinuousFocusMoving > 0 );
+
+ // switch to video
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ String focus_value = mPreview.getCameraController().getFocusValue();
+ Log.d(TAG, "video focus_value: "+ focus_value);
+ assertEquals("focus_mode_continuous_video", focus_value);
+
+ saved_count_cameraContinuousFocusMoving = mPreview.count_cameraContinuousFocusMoving;
+
+ // switch to photo
+ clickView(switchVideoButton);
+ Log.d(TAG, "count_cameraContinuousFocusMoving after clicking to switch to video: "+ mPreview.count_cameraContinuousFocusMoving);
+ waitUntilCameraOpened();
+ Log.d(TAG, "count_cameraContinuousFocusMoving after waiting for camera to open: "+ mPreview.count_cameraContinuousFocusMoving);
+ focus_value = mPreview.getCameraController().getFocusValue();
+ Log.d(TAG, "video focus_value: "+ focus_value);
+ assertEquals("focus_mode_continuous_picture", focus_value);
+
+ // check continuous focus is working
+ Thread.sleep(3000);
+ new_count_cameraContinuousFocusMoving = mPreview.count_cameraContinuousFocusMoving;
+ Log.d(TAG, "count_cameraContinuousFocusMoving compare saved: "+ saved_count_cameraContinuousFocusMoving + " to new: " + new_count_cameraContinuousFocusMoving);
+ assertEquals(0, mPreview.getCameraController().test_af_state_null_focus);
+ assertTrue( new_count_cameraContinuousFocusMoving > saved_count_cameraContinuousFocusMoving );
+ }
+
+ /* Tests everything works okay if starting in continuous video focus mode when in photo mode, including opening popup, and switching to video and back.
+ * This shouldn't be possible normal, but could happen if a user is upgrading from version 1.28 or earlier, to version 1.29 or later.
+ */
+ public void testContinuousVideoFocusForPhoto() throws InterruptedException {
+ Log.d(TAG, "testContinuousVideoFocusForPhoto");
+
+ setToDefault();
+
+ if( !mPreview.supportsFocus() ) {
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.getFocusPreferenceKey(mPreview.getCameraId(), false), "focus_mode_continuous_video");
+ editor.apply();
+ restart();
+
+ Thread.sleep(1000);
+
+ openPopupMenu();
+
+ Thread.sleep(1000);
+
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ }
+
+ /** Return the number of files in the save folder. 0 will be returned if the folder doesn't
+ * exist.
+ */
+ private int getNFiles() {
+ //File folder = mActivity.getImageFolder();
+ //File [] files = folder.listFiles();
+ String [] files = filesInSaveFolder();
+ Log.d(TAG, "getNFiles: " + Arrays.toString(files));
+ return files == null ? 0 : files.length;
+ }
+
+ private void subTestContinuousPictureFocusRepeat() throws InterruptedException {
+ Log.d(TAG, "subTestContinuousPictureFocusRepeat");
+
+ if( !mPreview.supportsFocus() ) {
+ return;
+ }
+
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.RepeatModePreferenceKey, "3");
+ editor.apply();
+ }
+ switchToFocusValue("focus_mode_continuous_picture");
+
+ // count initial files in folder
+ int n_files = getNFiles();
+ Log.d(TAG, "n_files at start: " + n_files);
+
+ assertEquals(0, mPreview.count_cameraTakePicture);
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ Log.d(TAG, "about to click take photo");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take photo");
+ assertFalse(mPreview.isOnTimer());
+
+ // wait until photos taken
+ // wait, and test that we've taken the photos by then
+ long time_s = System.currentTimeMillis();
+ while( mPreview.count_cameraTakePicture < 3 ) {
+ assertTrue( System.currentTimeMillis() - time_s < 20000 );
+ }
+ Thread.sleep(2000); // allow pictures to save
+ assertTrue(mPreview.isPreviewStarted()); // check preview restarted
+ Log.d(TAG, "count_cameraTakePicture: " + mPreview.count_cameraTakePicture);
+ assertEquals(3, mPreview.count_cameraTakePicture);
+ int n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(3, n_new_files);
+ }
+
+ /* Tests continuous picture focus with repeat mode.
+ */
+ public void testContinuousPictureFocusRepeat() throws InterruptedException {
+ Log.d(TAG, "testContinuousPictureFocusRepeat");
+
+ setToDefault();
+
+ subTestContinuousPictureFocusRepeat();
+ }
+
+ /* As testContinuousPictureFocusRepeat, but with test_wait_capture_result flag set.
+ */
+ public void testContinuousPictureFocusRepeatWaitCaptureResult() throws InterruptedException {
+ Log.d(TAG, "testContinuousPictureFocusRepeatWaitCaptureResult");
+
+ setToDefault();
+
+ mPreview.getCameraController().test_wait_capture_result = true;
+ subTestContinuousPictureFocusRepeat();
+ }
+
+ /* Test for continuous picture photo mode.
+ * Touch, wait 8s, check that continuous focus mode has resumed, then take photo.
+ */
+ public void testContinuousPicture1() throws InterruptedException {
+ Log.d(TAG, "testContinuousPicture1");
+
+ setToDefault();
+
+ if( !mPreview.supportsFocus() ) {
+ return;
+ }
+
+ switchToFocusValue("focus_mode_continuous_picture");
+
+ String focus_value = "focus_mode_continuous_picture";
+ String focus_value_ui = "focus_mode_continuous_picture";
+
+ // count initial files in folder
+ int n_files = getNFiles();
+ Log.d(TAG, "n_files at start: " + n_files);
+
+ Thread.sleep(1000);
+ assertEquals(0, mPreview.count_cameraTakePicture);
+ assertEquals(mPreview.getCurrentFocusValue(), focus_value_ui);
+ assertEquals(mPreview.getCameraController().getFocusValue(), focus_value);
+
+ Log.d(TAG, "about to click preview for autofocus");
+ int saved_count = mPreview.count_cameraAutoFocus;
+ Thread.sleep(1000); // needed for Galaxy S10e for the touch to register
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ Log.d(TAG, "1 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count + 1);
+ assertEquals("focus_mode_continuous_picture", mPreview.getCurrentFocusValue());
+ assertEquals("focus_mode_auto", mPreview.getCameraController().getFocusValue());
+ assertEquals(mPreview.getCurrentFocusValue(), focus_value_ui);
+ if( focus_value.equals("focus_mode_continuous_picture") )
+ assertEquals("focus_mode_auto", mPreview.getCameraController().getFocusValue()); // continuous focus mode switches to auto focus on touch
+ else
+ assertEquals(mPreview.getCameraController().getFocusValue(), focus_value);
+
+ Thread.sleep(8000);
+ assertEquals(mPreview.getCurrentFocusValue(), focus_value_ui);
+ assertEquals(mPreview.getCameraController().getFocusValue(), focus_value);
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ Log.d(TAG, "about to click take photo");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take photo");
+
+ Log.d(TAG, "wait until finished taking photo");
+ waitForTakePhoto();
+ Log.d(TAG, "done taking photo");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+
+ assertEquals(mPreview.getCurrentFocusValue(), focus_value_ui);
+ assertEquals(mPreview.getCameraController().getFocusValue(), focus_value);
+ assertEquals(1, mPreview.count_cameraTakePicture);
+ mActivity.waitUntilImageQueueEmpty();
+
+ int n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(1, n_new_files);
+ }
+
+ /* Test for continuous picture photo mode.
+ * Touch, wait 1s, check that continuous focus mode hasn't resumed, then take photo, then check continuous focus mode has resumed.
+ */
+ public void testContinuousPicture2() throws InterruptedException {
+ Log.d(TAG, "testContinuousPicture1");
+
+ setToDefault();
+
+ if( !mPreview.supportsFocus() ) {
+ return;
+ }
+
+ switchToFocusValue("focus_mode_continuous_picture");
+
+ String focus_value = "focus_mode_continuous_picture";
+ String focus_value_ui = "focus_mode_continuous_picture";
+
+ // count initial files in folder
+ int n_files = getNFiles();
+ Log.d(TAG, "n_files at start: " + n_files);
+
+ Thread.sleep(1000);
+ assertEquals(0, mPreview.count_cameraTakePicture);
+ assertEquals(mPreview.getCurrentFocusValue(), focus_value_ui);
+ assertEquals(mPreview.getCameraController().getFocusValue(), focus_value);
+
+ Log.d(TAG, "about to click preview for autofocus");
+ int saved_count = mPreview.count_cameraAutoFocus;
+ Thread.sleep(1000); // needed for Galaxy S10e for the touch to register
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "1 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count + 1);
+ assertEquals("focus_mode_continuous_picture", mPreview.getCurrentFocusValue());
+ assertEquals("focus_mode_auto", mPreview.getCameraController().getFocusValue());
+ assertEquals(mPreview.getCurrentFocusValue(), focus_value_ui);
+ if( focus_value.equals("focus_mode_continuous_picture") )
+ assertEquals("focus_mode_auto", mPreview.getCameraController().getFocusValue()); // continuous focus mode switches to auto focus on touch
+ else
+ assertEquals(mPreview.getCameraController().getFocusValue(), focus_value);
+
+ int saved_count_cameraContinuousFocusMoving = mPreview.count_cameraContinuousFocusMoving;
+
+ Thread.sleep(1000);
+ assertEquals(mPreview.getCurrentFocusValue(), focus_value_ui);
+ if( focus_value.equals("focus_mode_continuous_picture") )
+ assertEquals("focus_mode_auto", mPreview.getCameraController().getFocusValue()); // continuous focus mode switches to auto focus on touch
+ else
+ assertEquals(mPreview.getCameraController().getFocusValue(), focus_value);
+ int new_count_cameraContinuousFocusMoving = mPreview.count_cameraContinuousFocusMoving;
+ assertEquals(new_count_cameraContinuousFocusMoving, saved_count_cameraContinuousFocusMoving);
+ Log.d(TAG, "2 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count + 1);
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ Log.d(TAG, "about to click take photo");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take photo");
+
+ Log.d(TAG, "wait until finished taking photo");
+ waitForTakePhoto();
+ Log.d(TAG, "done taking photo");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+
+ assertEquals(mPreview.getCurrentFocusValue(), focus_value_ui);
+ assertEquals(mPreview.getCameraController().getFocusValue(), focus_value);
+ assertEquals(1, mPreview.count_cameraTakePicture);
+ Log.d(TAG, "3 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count + 1);
+ mActivity.waitUntilImageQueueEmpty();
+
+ int n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(1, n_new_files);
+ }
+
+ /* Test for continuous picture photo mode.
+ * Touch repeatedly with 1s delays for 8 times, make sure continuous focus mode hasn't resumed.
+ * Then wait 5s, and check continuous focus mode has resumed.
+ */
+ public void testContinuousPictureRepeatTouch() throws InterruptedException {
+ Log.d(TAG, "testContinuousPictureRepeatTouch");
+
+ setToDefault();
+
+ if( !mPreview.supportsFocus() ) {
+ return;
+ }
+
+ switchToFocusValue("focus_mode_continuous_picture");
+
+ String focus_value = "focus_mode_continuous_picture";
+ String focus_value_ui = "focus_mode_continuous_picture";
+
+ Thread.sleep(1000);
+ assertEquals(mPreview.getCurrentFocusValue(), focus_value_ui);
+ assertEquals(mPreview.getCameraController().getFocusValue(), focus_value);
+
+ for(int i=0;i<8;i++) {
+ Log.d(TAG, "about to click preview for autofocus: " + i);
+ int saved_count = mPreview.count_cameraAutoFocus;
+ Thread.sleep(1000); // needed for Galaxy S10e for the touch to register
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "1 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count + 1);
+ int saved_count_cameraContinuousFocusMoving = mPreview.count_cameraContinuousFocusMoving;
+ Thread.sleep(1000);
+
+ assertEquals("focus_mode_continuous_picture", mPreview.getCurrentFocusValue());
+ assertEquals("focus_mode_auto", mPreview.getCameraController().getFocusValue());
+ assertEquals(mPreview.getCurrentFocusValue(), focus_value_ui);
+ if( focus_value.equals("focus_mode_continuous_picture") )
+ assertEquals("focus_mode_auto", mPreview.getCameraController().getFocusValue()); // continuous focus mode switches to auto focus on touch
+ else
+ assertEquals(mPreview.getCameraController().getFocusValue(), focus_value);
+ int new_count_cameraContinuousFocusMoving = mPreview.count_cameraContinuousFocusMoving;
+ assertEquals(new_count_cameraContinuousFocusMoving, saved_count_cameraContinuousFocusMoving);
+ }
+
+ int saved_count = mPreview.count_cameraAutoFocus;
+ Thread.sleep(6000);
+ assertEquals(focus_value_ui, mPreview.getCurrentFocusValue());
+ assertEquals(focus_value, mPreview.getCameraController().getFocusValue());
+ Log.d(TAG, "2 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count);
+ }
+
+ /* Test for continuous picture photo mode.
+ * Touch, then after 1s switch to focus auto in UI, wait 8s, ensure still in autofocus mode.
+ */
+ public void testContinuousPictureSwitchAuto() throws InterruptedException {
+ Log.d(TAG, "testContinuousPictureSwitchAuto");
+
+ setToDefault();
+
+ if( !mPreview.supportsFocus() ) {
+ return;
+ }
+
+ switchToFocusValue("focus_mode_continuous_picture");
+
+ String focus_value = "focus_mode_continuous_picture";
+ String focus_value_ui = "focus_mode_continuous_picture";
+
+ Thread.sleep(1000);
+ assertEquals(mPreview.getCurrentFocusValue(), focus_value_ui);
+ assertEquals(mPreview.getCameraController().getFocusValue(), focus_value);
+
+ Log.d(TAG, "about to click preview for autofocus");
+ int saved_count = mPreview.count_cameraAutoFocus;
+ Thread.sleep(2000); // needed for Galaxy S10e for the touch to register
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "1 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count + 1);
+ int saved_count_cameraContinuousFocusMoving = mPreview.count_cameraContinuousFocusMoving;
+ Thread.sleep(1000);
+
+ assertEquals(mPreview.getCurrentFocusValue(), focus_value_ui);
+ if( focus_value.equals("focus_mode_continuous_picture") )
+ assertEquals("focus_mode_auto", mPreview.getCameraController().getFocusValue()); // continuous focus mode switches to auto focus on touch
+ else
+ assertEquals(mPreview.getCameraController().getFocusValue(), focus_value);
+ int new_count_cameraContinuousFocusMoving = mPreview.count_cameraContinuousFocusMoving;
+ assertEquals(new_count_cameraContinuousFocusMoving, saved_count_cameraContinuousFocusMoving);
+
+ Thread.sleep(1000);
+ assertEquals(mPreview.getCurrentFocusValue(), focus_value_ui);
+ if( focus_value.equals("focus_mode_continuous_picture") )
+ assertEquals("focus_mode_auto", mPreview.getCameraController().getFocusValue()); // continuous focus mode switches to auto focus on touch
+ else
+ assertEquals(mPreview.getCameraController().getFocusValue(), focus_value);
+ new_count_cameraContinuousFocusMoving = mPreview.count_cameraContinuousFocusMoving;
+ assertEquals(new_count_cameraContinuousFocusMoving, saved_count_cameraContinuousFocusMoving);
+
+ switchToFocusValue("focus_mode_auto");
+ assertEquals("focus_mode_auto", mPreview.getCurrentFocusValue());
+ assertEquals("focus_mode_auto", mPreview.getCameraController().getFocusValue());
+ new_count_cameraContinuousFocusMoving = mPreview.count_cameraContinuousFocusMoving;
+ assertEquals(new_count_cameraContinuousFocusMoving, saved_count_cameraContinuousFocusMoving);
+
+ Thread.sleep(8000);
+ assertEquals("focus_mode_auto", mPreview.getCurrentFocusValue());
+ assertEquals("focus_mode_auto", mPreview.getCameraController().getFocusValue());
+ new_count_cameraContinuousFocusMoving = mPreview.count_cameraContinuousFocusMoving;
+ assertEquals(new_count_cameraContinuousFocusMoving, saved_count_cameraContinuousFocusMoving);
+ }
+
+ /* Test for taking HDR photo then going to background[, also tests notifications].
+ * [Note test is unstable on Android emulator when testing for the notification, unclear why.]
+ */
+ public void testPhotoBackgroundHDR() throws InterruptedException {
+ Log.d(TAG, "testPhotoBackgroundHDR");
+
+ setToDefault();
+
+ if( !mActivity.supportsHDR() ) {
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_hdr");
+ editor.putBoolean(PreferenceKeys.AutoStabilisePreferenceKey, true); // also set auto-stabilise so we have a photo that takes longer to process
+ editor.apply();
+ updateForSettings();
+
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.HDR);
+
+ // count initial files in folder
+ int n_files = getNFiles();
+ Log.d(TAG, "n_files at start: " + n_files);
+
+ Thread.sleep(1000);
+ assertEquals(0, mPreview.count_cameraTakePicture);
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ Log.d(TAG, "about to click take photo");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take photo");
+
+ Log.d(TAG, "wait until finished taking photo");
+ waitForTakePhoto();
+ Log.d(TAG, "done taking photo");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+
+ // go to background after a short pause
+ Thread.sleep(500);
+ //assertFalse(mActivity.testHasNotification());
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ Log.d(TAG, "pause...");
+ getInstrumentation().callActivityOnPause(mActivity);
+ Log.d(TAG, "done pause");
+ }
+ });
+ this.getInstrumentation().waitForIdleSync();
+
+ assertEquals(1, mPreview.count_cameraTakePicture);
+ /*if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ) {
+ assertTrue(mActivity.testHasNotification());
+ }*/
+ mActivity.waitUntilImageQueueEmpty();
+ this.getInstrumentation().waitForIdleSync();
+ //assertFalse(mActivity.testHasNotification());
+
+ int n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(1, n_new_files);
+ }
+
+ /* Start in photo mode with auto focus:
+ * - go to video mode
+ * - then switch to front camera
+ * - then switch back to photo mode
+ * - then go to back camera
+ * Check focus mode has returned to auto.
+ * This test is important when front camera doesn't support focus modes, but back camera does - we won't be able to reset to auto focus for the front camera, but need to do so when returning to back camera
+ */
+ public void testFocusSwitchVideoSwitchCameras() {
+ Log.d(TAG, "testFocusSwitchVideoSwitchCameras");
+
+ setToDefault();
+
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() <= 1 ) {
+ return;
+ }
+
+ if( !mPreview.supportsFocus() ) {
+ return;
+ }
+
+ int cameraId = mPreview.getCameraId();
+
+ switchToFocusValue("focus_mode_auto");
+
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ String focus_value = mPreview.getCameraController().getFocusValue();
+ Log.d(TAG, "video focus_value: "+ focus_value);
+ assertEquals("focus_mode_continuous_video", focus_value);
+
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+ // camera becomes invalid when switching cameras
+ focus_value = mPreview.getCameraController().getFocusValue();
+ Log.d(TAG, "front video focus_value: "+ focus_value);
+ // don't care when focus mode is for front camera (focus may not be supported for front camera)
+
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ focus_value = mPreview.getCameraController().getFocusValue();
+ Log.d(TAG, "front focus_value: "+ focus_value);
+ // don't care when focus mode is for front camera (focus may not be supported for front camera)
+
+ switchToCamera(cameraId);
+
+ focus_value = mPreview.getCameraController().getFocusValue();
+ Log.d(TAG, "end focus_value: "+ focus_value);
+ assertEquals("focus_mode_auto", focus_value);
+ }
+
+ /* Start in photo mode with non-default focus mode:
+ * - switch to front camera
+ * - switch to back camera
+ * Check focus mode is still what we set.
+ * This test is important when front camera doesn't support focus modes, but back camera does - need to remain in same focus mode for the back camera.
+ */
+ public void testFocusRemainMacroSwitchCamera() {
+ Log.d(TAG, "testFocusRemainMacroSwitchCamera");
+
+ setToDefault();
+
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() <= 1 ) {
+ return;
+ }
+
+ if( !mPreview.supportsFocus() ) {
+ return;
+ }
+
+ String non_default_focus_mode = getNonDefaultFocus();
+ switchToFocusValue(non_default_focus_mode);
+
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ // n.b., switch to front then to back
+ int cameraId = mPreview.getCameraId();
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+ switchToCamera(cameraId);
+
+ String focus_value = mPreview.getCameraController().getFocusValue();
+ Log.d(TAG, "focus_value: "+ focus_value);
+ assertEquals(focus_value, non_default_focus_mode);
+ }
+
+ /* Start in photo mode with focus auto:
+ * - switch to video mode
+ * - switch to non-default focus mode
+ * - switch to picture mode
+ * Check focus mode is now auto.
+ * As of 1.26, we now remember the focus mode for photos.
+ */
+ public void testFocusRemainMacroSwitchPhoto() {
+ Log.d(TAG, "testFocusRemainMacroSwitchPhoto");
+
+ setToDefault();
+
+ if( !mPreview.supportsFocus() ) {
+ return;
+ }
+
+ switchToFocusValue("focus_mode_auto");
+
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ String focus_value = mPreview.getCameraController().getFocusValue();
+ Log.d(TAG, "focus_value after switching to video mode: "+ focus_value);
+ assertEquals("focus_mode_continuous_video", focus_value);
+
+ String non_default_focus_mode = getNonDefaultFocus();
+ switchToFocusValue(non_default_focus_mode);
+
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+
+ focus_value = mPreview.getCameraController().getFocusValue();
+ Log.d(TAG, "focus_value after switching to picture mode: " + focus_value);
+ assertEquals("focus_mode_auto", focus_value);
+ }
+
+ /* Start in photo mode with focus auto:
+ * - switch to non-default focus mode
+ * - switch to video mode
+ * - switch to picture mode
+ * Check focus mode is still what we set.
+ * As of 1.26, we now remember the focus mode for photos.
+ */
+ public void testFocusSaveMacroSwitchPhoto() {
+ Log.d(TAG, "testFocusSaveMacroSwitchPhoto");
+
+ setToDefault();
+
+ if( !mPreview.supportsFocus() ) {
+ return;
+ }
+
+ String non_default_focus_mode = getNonDefaultFocus();
+ switchToFocusValue(non_default_focus_mode);
+
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ String focus_value = mPreview.getCameraController().getFocusValue();
+ Log.d(TAG, "focus_value after switching to video mode: "+ focus_value);
+ assertEquals("focus_mode_continuous_video", focus_value);
+
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+
+ focus_value = mPreview.getCameraController().getFocusValue();
+ Log.d(TAG, "focus_value after switching to picture mode: " + focus_value);
+ assertEquals(focus_value, non_default_focus_mode);
+ }
+
+ /* Start in photo mode with auto focus:
+ * - go to video mode
+ * - check in continuous focus mode
+ * - switch to auto focus mode
+ * - then pause and resume
+ * - then check still in video mode, still in auto focus mode
+ * - then repeat with restarting instead
+ * (Note the name is a bit misleading - it used to be that we reset to continuous mode, now we don't.)
+ */
+ public void testFocusSwitchVideoResetContinuous() {
+ Log.d(TAG, "testFocusSwitchVideoResetContinuous");
+
+ setToDefault();
+
+ if( !mPreview.supportsFocus() ) {
+ return;
+ }
+
+ switchToFocusValue("focus_mode_auto");
+
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ String focus_value = mPreview.getCameraController().getFocusValue();
+ assertEquals("focus_mode_continuous_video", focus_value);
+
+ switchToFocusValue("focus_mode_auto");
+ focus_value = mPreview.getCameraController().getFocusValue();
+ assertEquals("focus_mode_auto", focus_value);
+
+ this.pauseAndResume();
+ assertTrue(mPreview.isVideo());
+
+ focus_value = mPreview.getCameraController().getFocusValue();
+ assertEquals("focus_mode_auto", focus_value);
+
+ // now with restart
+
+ switchToFocusValue("focus_mode_auto");
+ focus_value = mPreview.getCameraController().getFocusValue();
+ assertEquals("focus_mode_auto", focus_value);
+
+ restart();
+ assertTrue(mPreview.isVideo());
+
+ focus_value = mPreview.getCameraController().getFocusValue();
+ assertEquals("focus_mode_auto", focus_value);
+ }
+
+ private void subTestISOButtonAvailability() {
+ if( mPreview.isVideoRecording() ) {
+ // shouldn't show ISO buttons when video recording
+ subTestPopupButtonAvailability("TEST_ISO", "auto", false);
+ subTestPopupButtonAvailability("TEST_ISO", "100", false);
+ subTestPopupButtonAvailability("TEST_ISO", "200", false);
+ subTestPopupButtonAvailability("TEST_ISO", "400", false);
+ subTestPopupButtonAvailability("TEST_ISO", "800", false);
+ subTestPopupButtonAvailability("TEST_ISO", "1600", false);
+ }
+ else if( mPreview.supportsISORange() ) {
+ subTestPopupButtonAvailability("TEST_ISO", "auto", true);
+ int [] test_isos = {0, 50, 100, 200, 400, 800, 1600, 3200, 6400};
+ int min_iso = mPreview.getMinimumISO();
+ int max_iso = mPreview.getMaximumISO();
+ for(int test_iso : test_isos) {
+ subTestPopupButtonAvailability("TEST_ISO", String.valueOf(test_iso), test_iso >= min_iso && test_iso <= max_iso);
+ }
+ subTestPopupButtonAvailability("TEST_ISO", String.valueOf(min_iso - 1), false);
+ subTestPopupButtonAvailability("TEST_ISO", String.valueOf(min_iso), true);
+ subTestPopupButtonAvailability("TEST_ISO", String.valueOf(max_iso), true);
+ subTestPopupButtonAvailability("TEST_ISO", String.valueOf(max_iso + 1), false);
+ }
+ else {
+ List supported_iso_values = mPreview.getSupportedISOs();
+ subTestPopupButtonAvailability("TEST_ISO", "auto", supported_iso_values);
+ subTestPopupButtonAvailability("TEST_ISO", "100", supported_iso_values);
+ subTestPopupButtonAvailability("TEST_ISO", "200", supported_iso_values);
+ subTestPopupButtonAvailability("TEST_ISO", "400", supported_iso_values);
+ subTestPopupButtonAvailability("TEST_ISO", "800", supported_iso_values);
+ subTestPopupButtonAvailability("TEST_ISO", "1600", supported_iso_values);
+ }
+ }
+
+ /* Tests enabling and disabling the preview bitmap.
+ */
+ public void testPreviewBitmap() throws InterruptedException {
+ Log.d(TAG, "testPreviewBitmap");
+
+ if( !mActivity.supportsPreviewBitmaps() ) {
+ Log.d(TAG, "preview bitmaps not supported");
+ return;
+ }
+
+ setToDefault();
+ Thread.sleep(1000);
+
+ long [] delays = {20, 50, 100, 1000};
+ int [] n_iters = {50, 30, 30, 3};
+ if( TestUtils.isEmulator() ) {
+ // this takes much longer to run on emulator, due to taking ~15s for the first waitForIdleSync() in the loop
+ n_iters = new int[]{1, 1, 1, 1};
+ }
+ assertEquals(delays.length, n_iters.length);
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+
+ assertFalse(mPreview.isPreviewBitmapEnabled());
+ assertFalse(mPreview.refreshPreviewBitmapTaskIsRunning());
+
+ for(int i=0;i>> i = " + i + " delay: " + delays[i]);
+ for(int j=0;j>> j = " + j + " / " + n_iters[i]);
+ SharedPreferences.Editor editor = settings.edit();
+
+ editor.putString(PreferenceKeys.HistogramPreferenceKey, "preference_histogram_rgb");
+ editor.apply();
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mActivity.getApplicationInterface().getDrawPreview().updateSettings();
+ }
+ });
+ Log.d(TAG, " wait for idle sync");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, " about to sleep for: " + delays[i]);
+ Thread.sleep(delays[i]);
+ Log.d(TAG, " done sleep");
+ if (delays[i] >= 1000) {
+ assertTrue(mPreview.isPreviewBitmapEnabled());
+ }
+
+ editor.putString(PreferenceKeys.HistogramPreferenceKey, "preference_histogram_off");
+ editor.apply();
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mActivity.getApplicationInterface().getDrawPreview().updateSettings();
+ }
+ });
+ Log.d(TAG, " wait for idle sync again");
+ this.getInstrumentation().waitForIdleSync();
+ Thread.sleep(delays[i]);
+ Log.d(TAG, " done wait for idle sync");
+ if (delays[i] >= 1000) {
+ assertFalse(mPreview.isPreviewBitmapEnabled());
+ assertFalse(mPreview.refreshPreviewBitmapTaskIsRunning());
+ }
+ }
+ }
+
+ Thread.sleep(500);
+ assertFalse(mPreview.isPreviewBitmapEnabled());
+ assertFalse(mPreview.refreshPreviewBitmapTaskIsRunning());
+ }
+
+ public void testTakePhotoExposureCompensation() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoExposureCompensation");
+ setToDefault();
+
+ View exposureButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureContainer = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure_container);
+ SeekBar seekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure_seekbar);
+ assertEquals(exposureButton.getVisibility(), (mPreview.supportsExposures() ? View.VISIBLE : View.GONE));
+ assertEquals(exposureContainer.getVisibility(), View.GONE);
+
+ if( !mPreview.supportsExposures() ) {
+ return;
+ }
+
+ clickView(exposureButton);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.VISIBLE);
+
+ subTestISOButtonAvailability();
+
+ //assertEquals(mPreview.getMaximumExposure() - mPreview.getMinimumExposure(), seekBar.getMax());
+ //assertEquals(mPreview.getCurrentExposure() - mPreview.getMinimumExposure(), seekBar.getProgress());
+ // need to allow for repeated zero values in seekbar:
+ assertTrue(seekBar.getMax() > mPreview.getMaximumExposure() - mPreview.getMinimumExposure());
+ assertEquals(mActivity.getExposureSeekbarProgressZero(), seekBar.getProgress());
+ assertEquals(mPreview.getCurrentExposure(), mActivity.getExposureSeekbarValue(seekBar.getProgress()));
+ Log.d(TAG, "change exposure to 1");
+ mActivity.changeExposure(1);
+ this.getInstrumentation().waitForIdleSync();
+ assertEquals(1, mPreview.getCurrentExposure());
+ //assertEquals(mPreview.getCurrentExposure() - mPreview.getMinimumExposure(), seekBar.getProgress());
+ assertEquals(mPreview.getCurrentExposure(), mActivity.getExposureSeekbarValue(seekBar.getProgress()));
+ Log.d(TAG, "set exposure to min");
+ seekBar.setProgress(0);
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "actual exposure is now " + mPreview.getCurrentExposure());
+ Log.d(TAG, "expected exposure to be " + mPreview.getMinimumExposure());
+ assertEquals(mPreview.getCurrentExposure(), mPreview.getMinimumExposure());
+ //assertEquals(mPreview.getCurrentExposure() - mPreview.getMinimumExposure(), seekBar.getProgress());
+ assertEquals(0, seekBar.getProgress());
+ assertEquals(mPreview.getCurrentExposure(), mActivity.getExposureSeekbarValue(seekBar.getProgress()));
+
+ // test volume keys
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.VolumeKeysPreferenceKey, "volume_exposure");
+ editor.apply();
+ // volume up
+ while( mPreview.getCurrentExposure() < mPreview.getMaximumExposure() ) {
+ Log.d(TAG, "use volume key to increase exposure");
+ int exposure = mPreview.getCurrentExposure();
+ this.getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_VOLUME_UP);
+ assertEquals(exposure+1, mPreview.getCurrentExposure());
+ }
+ // one more shouldn't change exposure
+ this.getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_VOLUME_UP);
+ assertEquals(mPreview.getMaximumExposure(), mPreview.getCurrentExposure());
+ // volume down
+ while( mPreview.getCurrentExposure() > mPreview.getMinimumExposure() ) {
+ Log.d(TAG, "use volume key to decrease exposure");
+ int exposure = mPreview.getCurrentExposure();
+ this.getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_VOLUME_DOWN);
+ assertEquals(exposure-1, mPreview.getCurrentExposure());
+ }
+ // one more shouldn't change exposure
+ this.getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_VOLUME_DOWN);
+ assertEquals(mPreview.getMinimumExposure(), mPreview.getCurrentExposure());
+
+ // test the exposure button clears and reopens without changing exposure level
+ clickView(exposureButton);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.GONE);
+ clickView(exposureButton);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.VISIBLE);
+ assertEquals(mPreview.getCurrentExposure(), mPreview.getMinimumExposure());
+ //assertEquals(mPreview.getCurrentExposure() - mPreview.getMinimumExposure(), seekBar.getProgress());
+ assertEquals(0, seekBar.getProgress());
+ assertEquals(mPreview.getCurrentExposure(), mActivity.getExposureSeekbarValue(seekBar.getProgress()));
+
+ // test touch to focus clears the exposure controls
+ int [] gui_location = new int[2];
+ mPreview.getView().getLocationOnScreen(gui_location);
+ final float scale = mActivity.getResources().getDisplayMetrics().density;
+ final int large_step_dist_c = (int) (80 * scale + 0.5f); // convert dps to pixels
+ final int step_count_c = 10;
+ TouchUtils.drag(MainActivityTest.this, gui_location[0]+large_step_dist_c, gui_location[0], gui_location[1]+large_step_dist_c, gui_location[1], step_count_c);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.GONE);
+ clickView(exposureButton);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.VISIBLE);
+ assertEquals(mPreview.getCurrentExposure(), mPreview.getMinimumExposure());
+ //assertEquals(mPreview.getCurrentExposure() - mPreview.getMinimumExposure(), seekBar.getProgress());
+ assertEquals(0, seekBar.getProgress());
+ assertEquals(mPreview.getCurrentExposure(), mActivity.getExposureSeekbarValue(seekBar.getProgress()));
+
+ Log.d(TAG, "set exposure to -1");
+ seekBar.setProgress(-1 - mPreview.getMinimumExposure());
+ this.getInstrumentation().waitForIdleSync();
+ assertEquals(mPreview.getCurrentExposure(), -1);
+ assertEquals(mPreview.getCurrentExposure() - mPreview.getMinimumExposure(), seekBar.getProgress()); // fine as -1 is below the repeated zeroes
+ assertEquals(mPreview.getCurrentExposure(), mActivity.getExposureSeekbarValue(seekBar.getProgress()));
+
+ // clear again so as to not interfere with take photo routine
+ TouchUtils.drag(MainActivityTest.this, gui_location[0]+large_step_dist_c, gui_location[0], gui_location[1]+large_step_dist_c, gui_location[1], step_count_c);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.GONE);
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+
+ // test that switching to video mode removes the ISO buttons
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ assertFalse(mPreview.isVideo());
+ Log.d(TAG, "switch to video mode");
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertTrue(mPreview.isVideo());
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.GONE);
+
+ clickView(exposureButton);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.VISIBLE);
+ subTestISOButtonAvailability(); // check that ISO buttons are shown
+
+ assertFalse(mPreview.isVideoRecording());
+ Log.d(TAG, "about to click take video");
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take video");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ assertTrue(mPreview.isVideoRecording());
+
+ Thread.sleep(100);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.VISIBLE);
+ subTestISOButtonAvailability(); // check that ISO buttons are not shown
+
+ Thread.sleep(3000);
+
+ assertTrue(mPreview.isVideoRecording());
+ Log.d(TAG, "about to click stop video");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking stop video");
+ this.getInstrumentation().waitForIdleSync();
+ assertFalse(mPreview.isVideoRecording());
+
+ assertTrue(mPreview.isVideo());
+ Log.d(TAG, "switch to photo mode");
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertFalse(mPreview.isVideo());
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.GONE);
+
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ) {
+ Log.d(TAG, "switch camera");
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+
+ assertEquals(exposureButton.getVisibility(), (mPreview.supportsExposures() ? View.VISIBLE : View.GONE));
+ assertEquals(exposureContainer.getVisibility(), View.GONE);
+
+ if( mPreview.supportsExposures() ) {
+ assertEquals(mPreview.getCurrentExposure(), -1);
+ assertEquals(mPreview.getCurrentExposure() - mPreview.getMinimumExposure(), seekBar.getProgress()); // fine as -1 is below the repeated zeroes
+ assertEquals(mPreview.getCurrentExposure(), mActivity.getExposureSeekbarValue(seekBar.getProgress()));
+
+ clickView(exposureButton);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.VISIBLE);
+ assertEquals(mPreview.getCurrentExposure(), -1);
+ assertEquals(mPreview.getCurrentExposure() - mPreview.getMinimumExposure(), seekBar.getProgress()); // fine as -1 is below the repeated zeroes
+ assertEquals(mPreview.getCurrentExposure(), mActivity.getExposureSeekbarValue(seekBar.getProgress()));
+ }
+ }
+ }
+
+ public void testTakePhotoManualISOExposure() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoManualISOExposure");
+ setToDefault();
+
+ if( !mPreview.usingCamera2API() ) {
+ Log.d(TAG, "test requires camera2 api");
+ return;
+ }
+ else if( !mPreview.supportsISORange() ) {
+ Log.d(TAG, "test requires manual iso range");
+ return;
+ }
+
+ switchToISO(100);
+
+ View exposureButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureContainer = mActivity.findViewById(net.sourceforge.opencamera.R.id.manual_exposure_container);
+ SeekBar isoSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.iso_seekbar);
+ SeekBar exposureTimeSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure_time_seekbar);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.GONE);
+
+ clickView(exposureButton);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.VISIBLE);
+ assertEquals(isoSeekBar.getVisibility(), View.VISIBLE);
+ assertEquals(exposureTimeSeekBar.getVisibility(), (mPreview.supportsExposureTime() ? View.VISIBLE : View.GONE));
+ subTestISOButtonAvailability();
+
+ /*final int manual_n = 1000; // should match MainActivity.manual_n
+ assertTrue( isoSeekBar.getMax() == manual_n );
+ if( mPreview.supportsExposureTime() )
+ assertTrue( exposureTimeSeekBar.getMax() == manual_n );*/
+
+ Log.d(TAG, "change ISO to min");
+ isoSeekBar.setProgress(0);
+ this.getInstrumentation().waitForIdleSync();
+ assertEquals(mPreview.getCameraController().getISO(), mPreview.getMinimumISO());
+
+ if( mPreview.supportsExposureTime() ) {
+ Log.d(TAG, "change exposure time to min");
+ exposureTimeSeekBar.setProgress(0);
+ this.getInstrumentation().waitForIdleSync();
+ assertEquals(mPreview.getCameraController().getISO(), mPreview.getMinimumISO());
+ assertEquals(mPreview.getCameraController().getExposureTime(), mPreview.getMinimumExposureTime());
+ }
+
+ Log.d(TAG, "camera_controller ISO: " + mPreview.getCameraController().getISO());
+ Log.d(TAG, "change ISO to max");
+ isoSeekBar.setProgress(isoSeekBar.getMax());
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "camera_controller ISO: " + mPreview.getCameraController().getISO());
+ Log.d(TAG, "reported max ISO: " + mPreview.getMaximumISO());
+ assertEquals(mPreview.getCameraController().getISO(), mPreview.getMaximumISO());
+
+ // n.b., currently don't test this on devices with long shutter times (e.g., OnePlus 3T)
+ if( mPreview.supportsExposureTime() && mPreview.getMaximumExposureTime() < 1000000000 ) {
+ Log.d(TAG, "change exposure time to max");
+ exposureTimeSeekBar.setProgress(exposureTimeSeekBar.getMax());
+ this.getInstrumentation().waitForIdleSync();
+ assertEquals(mPreview.getCameraController().getISO(), mPreview.getMaximumISO());
+ assertEquals(mPreview.getCameraController().getExposureTime(), mPreview.getMaximumExposureTime());
+ }
+ else {
+ Log.d(TAG, "change exposure time to middle");
+ //mActivity.setProgressSeekbarExponential(exposureTimeSeekBar, mPreview.getMinimumExposureTime(), mPreview.getMaximumExposureTime(), 1000000000);
+ exposureTimeSeekBar.setProgress(exposureTimeSeekBar.getMax()/2);
+ this.getInstrumentation().waitForIdleSync();
+ assertEquals(mPreview.getCameraController().getISO(), mPreview.getMaximumISO());
+ assertTrue( mPreview.getCameraController().getExposureTime() != mPreview.getMaximumExposureTime() );
+ }
+ long saved_exposure_time = mPreview.getCameraController().getExposureTime();
+
+ // test the exposure button clears and reopens without changing exposure level
+ clickView(exposureButton);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.GONE);
+ clickView(exposureButton);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.VISIBLE);
+ assertEquals(isoSeekBar.getVisibility(), View.VISIBLE);
+ assertEquals(exposureTimeSeekBar.getVisibility(), (mPreview.supportsExposureTime() ? View.VISIBLE : View.GONE));
+ assertEquals(mPreview.getCameraController().getISO(), mPreview.getMaximumISO());
+ if( mPreview.supportsExposureTime() )
+ assertEquals(mPreview.getCameraController().getExposureTime(), saved_exposure_time);
+
+ // test touch to focus clears the exposure controls
+ int [] gui_location = new int[2];
+ mPreview.getView().getLocationOnScreen(gui_location);
+ final int step_dist_c = 2;
+ final float scale = mActivity.getResources().getDisplayMetrics().density;
+ final int offset_dist_c = (int) (80 * scale + 0.5f); // convert dps to pixels
+ final int step_count_c = 10;
+ TouchUtils.drag(MainActivityTest.this, gui_location[0] + offset_dist_c + step_dist_c, gui_location[0] + offset_dist_c, gui_location[1] + offset_dist_c + step_dist_c, gui_location[1] + offset_dist_c, step_count_c);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.GONE);
+ clickView(exposureButton);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.VISIBLE);
+ assertEquals(isoSeekBar.getVisibility(), View.VISIBLE);
+ assertEquals(exposureTimeSeekBar.getVisibility(), (mPreview.supportsExposureTime() ? View.VISIBLE : View.GONE));
+ assertEquals(mPreview.getCameraController().getISO(), mPreview.getMaximumISO());
+ if( mPreview.supportsExposureTime() )
+ assertEquals(mPreview.getCameraController().getExposureTime(), saved_exposure_time);
+
+ // clear again so as to not interfere with take photo routine
+ TouchUtils.drag(MainActivityTest.this, gui_location[0] + offset_dist_c + step_dist_c, gui_location[0] + offset_dist_c, gui_location[1] + offset_dist_c + step_dist_c, gui_location[1] + offset_dist_c, step_count_c);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.GONE);
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ) {
+ Log.d(TAG, "switch camera");
+ int old_max = mPreview.getMaximumISO();
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.GONE);
+ // we use same ISO for all cameras, but if new camera has lower max, it should automatically reduce
+ assertEquals(Math.min(old_max, mPreview.getMaximumISO()), mPreview.getCameraController().getISO());
+ if( mPreview.supportsExposureTime() ) {
+ Log.d(TAG, "exposure time: " + mPreview.getCameraController().getExposureTime());
+ Log.d(TAG, "min exposure time: " + mPreview.getMinimumExposureTime());
+ Log.d(TAG, "max exposure time: " + mPreview.getMaximumExposureTime());
+ if( saved_exposure_time < mPreview.getMinimumExposureTime() )
+ saved_exposure_time = mPreview.getMinimumExposureTime();
+ if( saved_exposure_time > mPreview.getMaximumExposureTime() )
+ saved_exposure_time = mPreview.getMaximumExposureTime();
+ assertEquals(mPreview.getCameraController().getExposureTime(), saved_exposure_time);
+ }
+
+ clickView(exposureButton);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.VISIBLE);
+ assertEquals(isoSeekBar.getVisibility(), View.VISIBLE);
+ assertEquals(exposureTimeSeekBar.getVisibility(), (mPreview.supportsExposureTime() ? View.VISIBLE : View.GONE));
+ assertEquals(Math.min(old_max, mPreview.getMaximumISO()), mPreview.getCameraController().getISO());
+ if( mPreview.supportsExposureTime() )
+ assertEquals(mPreview.getCameraController().getExposureTime(), saved_exposure_time);
+ }
+ }
+
+ public void testTakePhotoManualWB() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoManualWB");
+ setToDefault();
+
+ if( !mPreview.usingCamera2API() ) {
+ return;
+ }
+ if( !mPreview.supportsWhiteBalanceTemperature() ) {
+ return;
+ }
+
+ View exposureButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureContainer = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure_container);
+ View manualWBContainer = mActivity.findViewById(net.sourceforge.opencamera.R.id.manual_white_balance_container);
+ // check manual exposure icon is available
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ // check exposure UI starts off closed
+ assertEquals(exposureContainer.getVisibility(), View.GONE);
+ assertEquals(manualWBContainer.getVisibility(), View.GONE);
+
+ assertEquals("auto", mPreview.getCameraController().getWhiteBalance());
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ int initial_temperature = mPreview.getCameraController().getWhiteBalanceTemperature();
+ int initial_temperature_setting = settings.getInt(PreferenceKeys.WhiteBalanceTemperaturePreferenceKey, 5000);
+ assertEquals(initial_temperature, initial_temperature_setting);
+ SeekBar white_balance_seek_bar = mActivity.findViewById(net.sourceforge.opencamera.R.id.white_balance_seekbar);
+ int initial_white_balance_seek_bar_pos = white_balance_seek_bar.getProgress();
+
+ /*SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.getWhiteBalancePreferenceKey(), "manual");
+ editor.apply();
+ updateForSettings();*/
+
+ // simulate having changed this through popup view:
+ openPopupMenu();
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.WhiteBalancePreferenceKey, "manual");
+ editor.apply();
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mActivity.getMainUI().getPopupView().switchToWhiteBalance("manual");
+ }
+ });
+ this.getInstrumentation().waitForIdleSync();
+
+ /*openPopupMenu();
+ // first need to open the white balance sub-menu
+ View wbButton = mActivity.getUIButton("TEST_WHITE_BALANCE");
+ assertTrue(wbButton != null);
+ ScrollView popupContainer = (ScrollView)mActivity.findViewById(net.sourceforge.opencamera.R.id.popup_container);
+ popupContainer.scrollTo(0, wbButton.getBottom());
+ this.getInstrumentation().waitForIdleSync();
+ Thread.sleep(1000);
+
+ clickView(wbButton);
+ Log.d(TAG, "clicked wb button");
+ // check popup still opened
+ assertTrue( mActivity.popupIsOpen() );
+
+ RadioButton manualWBButton = (RadioButton)mActivity.getUIButton("TEST_WHITE_BALANCE_manual");
+ assertTrue(manualWBButton != null);
+ assertTrue(!manualWBButton.isChecked());
+ clickView(manualWBButton);
+ Log.d(TAG, "clicked manual wb button");
+ // check popup still opened
+ assertTrue( mActivity.popupIsOpen() );
+ // check now selected
+ assertTrue(manualWBButton.isChecked());
+ */
+
+ // check we switched to manual mode
+ assertEquals("manual", mPreview.getCameraController().getWhiteBalance());
+
+ // check exposure UI automatically opened
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.VISIBLE);
+ assertEquals(manualWBContainer.getVisibility(), View.VISIBLE);
+
+ // check that the wb temperature has been updated, both in preferences, and the camera controller
+ int new_temperature = mPreview.getCameraController().getWhiteBalanceTemperature();
+ int new_temperature_setting = settings.getInt(PreferenceKeys.WhiteBalanceTemperaturePreferenceKey, 5000);
+ assertEquals(new_temperature, new_temperature_setting);
+ Log.d(TAG, "initial_temperature: " + initial_temperature);
+ Log.d(TAG, "new_temperature: " + new_temperature);
+ assertTrue(new_temperature != initial_temperature);
+ // check we moved the wb slider too
+ int new_white_balance_seek_bar_pos = white_balance_seek_bar.getProgress();
+ Log.d(TAG, "initial_white_balance_seek_bar_pos: " + initial_white_balance_seek_bar_pos);
+ Log.d(TAG, "new_white_balance_seek_bar_pos: " + new_white_balance_seek_bar_pos);
+ assertTrue(new_white_balance_seek_bar_pos != initial_white_balance_seek_bar_pos);
+
+ // close exposure UI
+ clickView(exposureButton);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.GONE);
+ assertEquals(manualWBContainer.getVisibility(), View.GONE);
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+
+ SeekBar seekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure_seekbar);
+ SeekBar seekBarWB = mActivity.findViewById(net.sourceforge.opencamera.R.id.white_balance_seekbar);
+
+ assertEquals(exposureButton.getVisibility(), (mPreview.supportsExposures() ? View.VISIBLE : View.GONE));
+ assertEquals(exposureContainer.getVisibility(), View.GONE);
+ assertEquals(manualWBContainer.getVisibility(), View.GONE);
+
+ if( !mPreview.supportsExposures() ) {
+ return;
+ }
+
+ // reopen exposure UI
+ clickView(exposureButton);
+ subTestISOButtonAvailability();
+
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureContainer.getVisibility(), View.VISIBLE);
+ assertEquals(seekBar.getVisibility(), View.VISIBLE);
+ assertEquals(manualWBContainer.getVisibility(), View.VISIBLE);
+ assertEquals(seekBarWB.getVisibility(), View.VISIBLE);
+ }
+
+ /** Tests that the audio control icon is visible or not as expect (guards against bug fixed in 1.30)
+ */
+ public void testAudioControlIcon() {
+ Log.d(TAG, "testAudioControlIcon");
+
+ setToDefault();
+
+ View audioControlButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.audio_control);
+ assertEquals(audioControlButton.getVisibility(), View.GONE);
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.AudioControlPreferenceKey, "noise");
+ editor.apply();
+ updateForSettings();
+ assertEquals(audioControlButton.getVisibility(), View.VISIBLE);
+
+ restart();
+ // reset due to restarting!
+ settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ editor = settings.edit();
+ audioControlButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.audio_control);
+
+ assertEquals(audioControlButton.getVisibility(), View.VISIBLE);
+
+ editor.putString(PreferenceKeys.AudioControlPreferenceKey, "none");
+ editor.apply();
+ updateForSettings();
+ Log.d(TAG, "visibility is now: " + audioControlButton.getVisibility());
+ assertEquals(audioControlButton.getVisibility(), View.GONE);
+
+ /*editor.putString(PreferenceKeys.AudioControlPreferenceKey, "voice");
+ editor.apply();
+ updateForSettings();
+ assertEquals(audioControlButton.getVisibility(), View.VISIBLE);*/
+
+ editor.putString(PreferenceKeys.AudioControlPreferenceKey, "none");
+ editor.apply();
+ updateForSettings();
+ Log.d(TAG, "visibility is now: " + audioControlButton.getVisibility());
+ assertEquals(audioControlButton.getVisibility(), View.GONE);
+ }
+
+ /** Test for on-screen icon. Cycles through cameras and checks that the visibility of
+ * the icons matches whether available for that camera - currently tests for flash and RAW.
+ * For multi-camera devices, this tests the behaviour with
+ * PreferenceKeys.MultiCamButtonPreferenceKey devices, so the switch camera icon still cycles
+ * through all cameras.
+ */
+ public void testIconsAgainstCameras() {
+ Log.d(TAG, "testIconsAgainstCameras");
+ setToDefault();
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.ShowCycleFlashPreferenceKey, true);
+ editor.putBoolean(PreferenceKeys.ShowCycleRawPreferenceKey, true);
+ if( mActivity.isMultiCamEnabled() ) {
+ editor.putBoolean(PreferenceKeys.MultiCamButtonPreferenceKey, false);
+ }
+ editor.apply();
+ updateForSettings();
+
+ for(int i=0;i visited_camera_ids) throws InterruptedException {
+ if( mActivity.showSwitchMultiCamIcon() ) {
+ final int cameraId = mPreview.getCameraId();
+ CameraController.Facing facing = mPreview.getCameraControllerManager().getFacing(cameraId);
+
+ List logical_camera_ids = mActivity.getSameFacingLogicalCameras(cameraId);
+ Set physical_cameras = mPreview.getPhysicalCameras(); // physical cameras for logical cameraId
+ assertEquals(cameraId, (int)logical_camera_ids.get(0));
+
+ // test all logical cameras with same-facing
+ for(int id : logical_camera_ids) {
+ Log.d(TAG, "testing multi id: " + id);
+
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mActivity.userSwitchToCamera(id, null);
+ }
+ });
+ // need to wait for UI code to finish
+ this.getInstrumentation().waitForIdleSync();
+ waitUntilCameraOpened();
+
+ int new_cameraId = mPreview.getCameraId();
+ Log.d(TAG, "multi cam button switched to " + new_cameraId);
+ Log.d(TAG, "cameraId: " + cameraId);
+ Log.d(TAG, "visited_camera_ids was: " + visited_camera_ids);
+ assertEquals(id, new_cameraId);
+ if( id != cameraId ) {
+ assertFalse(visited_camera_ids.contains(new_cameraId));
+ visited_camera_ids.add(new_cameraId);
+ }
+
+ CameraController.Facing new_facing = mPreview.getCameraControllerManager().getFacing(new_cameraId);
+ assertEquals(facing, new_facing);
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ }
+
+ if( physical_cameras != null ) {
+ // test all physical cameras for cameraId
+ for(String physical_id : physical_cameras) {
+ Log.d(TAG, "testing physical id: " + physical_id);
+
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mActivity.userSwitchToCamera(cameraId, physical_id);
+ }
+ });
+ // need to wait for UI code to finish
+ this.getInstrumentation().waitForIdleSync();
+ waitUntilCameraOpened();
+
+ int new_cameraId = mPreview.getCameraId();
+ assertEquals(cameraId, new_cameraId);
+
+ CameraController.Facing new_facing = mPreview.getCameraControllerManager().getFacing(new_cameraId);
+ assertEquals(facing, new_facing);
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ }
+ }
+
+ // old code for multi-cam button:
+ /*do {
+ View switchMultiCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_multi_camera);
+ clickView(switchMultiCameraButton);
+ waitUntilCameraOpened();
+
+ int new_cameraId = mPreview.getCameraId();
+ Log.d(TAG, "multi cam button switched to " + new_cameraId);
+ Log.d(TAG, "cameraId: " + cameraId);
+ Log.d(TAG, "visited_camera_ids was: " + visited_camera_ids);
+ assertTrue(new_cameraId != cameraId);
+ assertFalse(visited_camera_ids.contains(new_cameraId));
+ visited_camera_ids.add(new_cameraId);
+
+ CameraController.Facing new_facing = mPreview.getCameraControllerManager().getFacing(new_cameraId);
+ assertEquals(facing, new_facing);
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ }
+ while( mActivity.testGetNextMultiCameraId() != cameraId );*/
+
+ /*do {
+ int next_multi_cameraId = mActivity.testGetNextMultiCameraId();
+ assertTrue(next_multi_cameraId != cameraId);
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mActivity.userSwitchToCamera(next_multi_cameraId, null);
+ }
+ });
+ // need to wait for UI code to finish
+ this.getInstrumentation().waitForIdleSync();
+ waitUntilCameraOpened();
+
+ int new_cameraId = mPreview.getCameraId();
+ Log.d(TAG, "multi cam button switched to " + new_cameraId);
+ Log.d(TAG, "cameraId: " + cameraId);
+ Log.d(TAG, "visited_camera_ids was: " + visited_camera_ids);
+ assertTrue(new_cameraId != cameraId);
+ assertTrue(new_cameraId == next_multi_cameraId);
+ assertFalse(visited_camera_ids.contains(new_cameraId));
+ visited_camera_ids.add(new_cameraId);
+
+ CameraController.Facing new_facing = mPreview.getCameraControllerManager().getFacing(new_cameraId);
+ assertEquals(facing, new_facing);
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ }
+ while( mActivity.testGetNextMultiCameraId() != cameraId );*/
+ }
+ }
+
+ /** Tests taking a photo with multiple cameras.
+ * Also tests the content descriptions for switch camera button.
+ * And tests that we save the current camera when pausing and resuming.
+ * @param cycle_all_cameras If true, expect that the Switch Camera icon cycles through all
+ * cameras.
+ * @param test_multi_cam If true, also test cycling through cameras using the switch multi
+ * camera icon. If true, then cycle_all_cameras must be false. Should
+ * only be true on multi-camera devices.
+ */
+ private void subTestTakePhotoMultiCameras(boolean cycle_all_cameras, boolean test_multi_cam) throws InterruptedException {
+ Log.d(TAG, "subTestTakePhotoMultiCameras");
+
+ int n_cameras = mPreview.getCameraControllerManager().getNumberOfCameras();
+ if( n_cameras <= 1 ) {
+ return;
+ }
+
+ if( test_multi_cam ) {
+ assertFalse(cycle_all_cameras);
+ }
+
+ int orig_cameraId = mPreview.getCameraId();
+ Set visited_camera_ids = new HashSet<>();
+ visited_camera_ids.add(orig_cameraId);
+
+ boolean done_front_test = false;
+ for(int i=0;i<(cycle_all_cameras ? n_cameras-1 : 1);i++) {
+ Log.d(TAG, "i: " + i);
+ int cameraId = mPreview.getCameraId();
+
+ CameraController.Facing facing = mPreview.getCameraControllerManager().getFacing(cameraId);
+ if( i == 0 ) {
+ assertEquals(CameraController.Facing.FACING_BACK, facing);
+ }
+
+ if( test_multi_cam ) {
+ // first test cycling through the cameras with this facing
+ subTestCycleMultiCameras(visited_camera_ids);
+ }
+
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ CharSequence contentDescription = switchCameraButton.getContentDescription();
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+
+ int new_cameraId = mPreview.getCameraId();
+ assertTrue(new_cameraId != cameraId);
+ if( cycle_all_cameras ) {
+ // in this mode, we should just be iterating over the camera IDs
+ assertEquals((cameraId + 1) % n_cameras, new_cameraId);
+ }
+ assertFalse(visited_camera_ids.contains(new_cameraId));
+ visited_camera_ids.add(new_cameraId);
+
+ CameraController.Facing new_facing = mPreview.getCameraControllerManager().getFacing(new_cameraId);
+ CharSequence new_contentDescription = switchCameraButton.getContentDescription();
+ if( n_cameras == 2 || !cycle_all_cameras ) {
+ assertEquals(facing==CameraController.Facing.FACING_BACK ? CameraController.Facing.FACING_FRONT : CameraController.Facing.FACING_BACK, new_facing);
+ }
+
+ //int next_cameraId = (new_cameraId+1) % n_cameras;
+ int next_cameraId = mActivity.getNextCameraId();
+ assertTrue(next_cameraId != new_cameraId);
+ if( cycle_all_cameras ) {
+ // in this mode, we should just be iterating over the camera IDs
+ assertEquals((new_cameraId + 1) % n_cameras, next_cameraId);
+ }
+ if( i==n_cameras-1 || !cycle_all_cameras ) {
+ // should have returned to the start
+ assertEquals(cameraId, next_cameraId);
+ }
+ CameraController.Facing next_facing = mPreview.getCameraControllerManager().getFacing(next_cameraId);
+ if( n_cameras == 2 || !cycle_all_cameras ) {
+ assertEquals(facing, next_facing);
+ }
+
+ Log.d(TAG, "cameraId: " + cameraId);
+ Log.d(TAG, "facing: " + facing);
+ Log.d(TAG, "contentDescription: " + contentDescription);
+ Log.d(TAG, "new_cameraId: " + new_cameraId);
+ Log.d(TAG, "new_facing: " + new_facing);
+ Log.d(TAG, "new_contentDescription: " + new_contentDescription);
+ Log.d(TAG, "next_cameraId: " + next_cameraId);
+ Log.d(TAG, "next_facing: " + next_facing);
+
+ switch( new_facing ) {
+ case FACING_FRONT:
+ assertEquals(contentDescription, mActivity.getResources().getString(net.sourceforge.opencamera.R.string.switch_to_front_camera));
+ break;
+ case FACING_BACK:
+ assertEquals(contentDescription, mActivity.getResources().getString(net.sourceforge.opencamera.R.string.switch_to_back_camera));
+ break;
+ case FACING_EXTERNAL:
+ assertEquals(contentDescription, mActivity.getResources().getString(net.sourceforge.opencamera.R.string.switch_to_external_camera));
+ break;
+ default:
+ fail();
+ }
+ switch( next_facing ) {
+ case FACING_FRONT:
+ assertEquals(new_contentDescription, mActivity.getResources().getString(net.sourceforge.opencamera.R.string.switch_to_front_camera));
+ break;
+ case FACING_BACK:
+ assertEquals(new_contentDescription, mActivity.getResources().getString(net.sourceforge.opencamera.R.string.switch_to_back_camera));
+ break;
+ case FACING_EXTERNAL:
+ assertEquals(new_contentDescription, mActivity.getResources().getString(net.sourceforge.opencamera.R.string.switch_to_external_camera));
+ break;
+ default:
+ fail();
+ }
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+
+ if( !done_front_test && new_facing == CameraController.Facing.FACING_FRONT ) {
+ done_front_test = true;
+
+ // check still front camera after pause/resume
+ pauseAndResume();
+
+ int restart_cameraId = mPreview.getCameraId();
+ CharSequence restart_contentDescription = switchCameraButton.getContentDescription();
+ Log.d(TAG, "restart_contentDescription: " + restart_contentDescription);
+ assertEquals(restart_cameraId, new_cameraId);
+ switch( next_facing ) {
+ case FACING_FRONT:
+ assertEquals(restart_contentDescription, mActivity.getResources().getString(net.sourceforge.opencamera.R.string.switch_to_front_camera));
+ break;
+ case FACING_BACK:
+ assertEquals(restart_contentDescription, mActivity.getResources().getString(net.sourceforge.opencamera.R.string.switch_to_back_camera));
+ break;
+ case FACING_EXTERNAL:
+ assertEquals(restart_contentDescription, mActivity.getResources().getString(net.sourceforge.opencamera.R.string.switch_to_external_camera));
+ break;
+ default:
+ fail();
+ }
+
+ // now test mirror mode
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.FrontCameraMirrorKey, "preference_front_camera_mirror_photo");
+ editor.apply();
+ updateForSettings();
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ // disable mirror mode again
+ settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ editor = settings.edit();
+ editor.putString(PreferenceKeys.FrontCameraMirrorKey, "preference_front_camera_mirror_no");
+ editor.apply();
+ updateForSettings();
+ }
+ }
+
+ if( test_multi_cam ) {
+ subTestCycleMultiCameras(visited_camera_ids);
+ }
+
+ if( cycle_all_cameras || test_multi_cam ) {
+ // test we visited all cameras
+ assertEquals(n_cameras, visited_camera_ids.size());
+ }
+
+ // now check we really do return to the first camera
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+
+ int final_cameraId = mPreview.getCameraId();
+ assertEquals(orig_cameraId, final_cameraId);
+ }
+
+ /* Tests taking a photo with all non-default cameras.
+ * For multi-camera devices, this tests the behaviour with
+ * PreferenceKeys.MultiCamButtonPreferenceKey devices, so the switch camera icon still cycles
+ * through all cameras.
+ * Can be unstable on Android emulator if the time taken to focus means we've already switched
+ * back from auto to continuous focus (after touch to focus).
+ */
+ public void testTakePhotoFrontCameraAll() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoFrontCameraAll");
+ setToDefault();
+
+ if( mActivity.isMultiCamEnabled() ) {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.MultiCamButtonPreferenceKey, false);
+ editor.apply();
+ updateForSettings();
+ }
+
+ subTestTakePhotoMultiCameras(true, false);
+ }
+
+ /* Tests taking a photo on multi-camera devices with front and back cameras.
+ */
+ public void testTakePhotoFrontCamera() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoFrontCamera");
+ setToDefault();
+
+ if( !mActivity.isMultiCamEnabled() ) {
+ return; // no point running, as will be same as testTakePhotoFrontCameraAll
+ }
+
+ subTestTakePhotoMultiCameras(false, false);
+ }
+
+ /* Tests taking a photo on multi-camera devices, using both icons to switch between cameras.
+ */
+ public void testTakePhotoFrontCameraMulti() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoFrontCameraMulti");
+ setToDefault();
+
+ /*if( !mActivity.isMultiCamEnabled() ) {
+ return;
+ }*/
+
+ subTestTakePhotoMultiCameras(false, true);
+ }
+
+ /** Tests taking a photo with front camera and screen flash.
+ * Note this test fails on Android emulator with old camera API, because on front camera when
+ * we switch from continuous to auto focus from touch to focus, we're still in continuous focus
+ * mode, despite both focus modes being supported for front camera - I confirmed that we do
+ * switch to auto focus, and haven't reset to continuous! Could be a threading/synchronization
+ * issue from trying to read the camera parameters from the test thread?
+ */
+ public void testTakePhotoFrontCameraScreenFlash() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoFrontCameraScreenFlash");
+ setToDefault();
+
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() <= 1 ) {
+ return;
+ }
+
+ int cameraId = mPreview.getCameraId();
+
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ this.getInstrumentation().waitForIdleSync();
+ waitUntilCameraOpened();
+
+ int new_cameraId = mPreview.getCameraId();
+
+ Log.d(TAG, "cameraId: " + cameraId);
+ Log.d(TAG, "new_cameraId: " + new_cameraId);
+
+ assertTrue(cameraId != new_cameraId);
+
+ switchToFlashValue("flash_frontscreen_on");
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ }
+
+ /** Take a photo in auto focus mode.
+ */
+ public void testTakePhotoAutoFocus() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoAutoFocus");
+ setToDefault();
+ switchToFocusValue("focus_mode_auto");
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+
+ assertEquals(0, mPreview.getCameraController().test_af_state_null_focus);
+ }
+
+ /** Take a photo for Camera2 API when camera is released on UI thread whilst photo is taken on background thread (via
+ * autofocus callback).
+ */
+ public void testTakePhotoAutoFocusReleaseDuringPhoto() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoAutoFocusReleaseDuringPhoto");
+
+ if( !mPreview.usingCamera2API() ) {
+ Log.d(TAG, "test requires camera2 api");
+ return;
+ }
+
+ setToDefault();
+
+ if( !mPreview.supportsFocus() ) {
+ // if no focus, then the photo will be taken on the UI thread
+ Log.d(TAG, "test requires focus");
+ return;
+ }
+
+ switchToFocusValue("focus_mode_auto");
+
+ mPreview.getCameraController().test_release_during_photo = true;
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ assertFalse( mActivity.hasThumbnailAnimation() );
+ Log.d(TAG, "about to click take photo");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take photo");
+
+ Thread.sleep(5000);
+ }
+
+ public void testTakePhotoLockedFocus() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoLockedFocus");
+ setToDefault();
+ switchToFocusValue("focus_mode_locked");
+ subTestTakePhoto(true, false, true, true, false, false, false, false);
+ }
+
+ public void testTakePhotoManualFocus() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoManualFocus");
+ setToDefault();
+
+ if( !mPreview.supportsFocus() || !mPreview.getSupportedFocusValues().contains("focus_mode_manual2") ) {
+ return;
+ }
+ SeekBar seekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.focus_seekbar);
+ assertEquals(seekBar.getVisibility(), View.GONE);
+ switchToFocusValue("focus_mode_manual2");
+ assertEquals(seekBar.getVisibility(), View.VISIBLE);
+ seekBar.setProgress( (int)(0.25*(seekBar.getMax()-1)) );
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ }
+
+ public void testTakePhotoLockedLandscape() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoLockedLandscape");
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.LockOrientationPreferenceKey, "landscape");
+ editor.apply();
+ updateForSettings();
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ }
+
+ public void testTakePhotoLockedPortrait() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoLockedPortrait");
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.LockOrientationPreferenceKey, "portrait");
+ editor.apply();
+ updateForSettings();
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ }
+
+ // If this test fails, make sure we've manually selected that folder (as permission can't be given through the test framework).
+ public void testTakePhotoSAF() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoSAF");
+
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ) {
+ Log.d(TAG, "SAF requires Android Lollipop or better");
+ return;
+ }
+
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.UsingSAFPreferenceKey, true);
+ editor.putString(PreferenceKeys.SaveLocationSAFPreferenceKey, "content://com.android.externalstorage.documents/tree/primary%3ADCIM%2FOpenCamera");
+ editor.apply();
+ updateForSettings();
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ }
+
+ public void testTakePhotoAudioButton() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoAudioButton");
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.AudioControlPreferenceKey, "noise");
+ editor.apply();
+ updateForSettings();
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ }
+
+ // If this fails with a SecurityException about needing INJECT_EVENTS permission, this seems to be due to the "help popup" that Android shows - can be fixed by clearing that manually, then rerunning the test.
+ public void testImmersiveMode() throws InterruptedException {
+ Log.d(TAG, "testImmersiveMode");
+
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT ) {
+ Log.d(TAG, "immersive mode requires Android Kitkat or better");
+ return;
+ }
+
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.ImmersiveModePreferenceKey, "immersive_mode_gui");
+ editor.putString(PreferenceKeys.AudioControlPreferenceKey, "noise");
+ editor.apply();
+ updateForSettings();
+
+ boolean has_audio_control_button = true;
+ boolean has_zoom = mPreview.supportsZoom();
+
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ View switchMultiCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_multi_camera);
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ View exposureButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureLockButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure_lock);
+ View audioControlButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.audio_control);
+ View popupButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.popup);
+ View trashButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.trash);
+ View shareButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.share);
+ View zoomSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.zoom_seekbar);
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ View pauseVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.pause_video);
+ View takePhotoVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo_when_video_recording);
+ SeekBar seekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.focus_seekbar);
+ SeekBar targetSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.focus_bracketing_target_seekbar);
+
+ assertEquals(switchCameraButton.getVisibility(), (mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE));
+ assertEquals(switchMultiCameraButton.getVisibility(), (mActivity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE));
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+ int exposureVisibility = exposureButton.getVisibility();
+ int exposureLockVisibility = exposureLockButton.getVisibility();
+ assertEquals(audioControlButton.getVisibility(), (has_audio_control_button ? View.VISIBLE : View.GONE));
+ assertEquals(popupButton.getVisibility(), View.VISIBLE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ assertEquals(zoomSeekBar.getVisibility(), has_zoom ? View.VISIBLE : View.INVISIBLE);
+ assertEquals(takePhotoButton.getVisibility(), View.VISIBLE);
+ assertEquals(pauseVideoButton.getVisibility(), View.GONE);
+ assertEquals(takePhotoVideoButton.getVisibility(), View.GONE);
+ assertEquals(seekBar.getVisibility(), View.GONE);
+ assertEquals(targetSeekBar.getVisibility(), View.GONE);
+
+ // now wait for immersive mode to kick in
+ Thread.sleep(6000);
+ assertEquals(switchCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchMultiCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchVideoButton.getVisibility(), View.GONE);
+ assertEquals(exposureButton.getVisibility(), View.GONE);
+ assertEquals(exposureLockButton.getVisibility(), View.GONE);
+ assertEquals(audioControlButton.getVisibility(), View.GONE);
+ assertEquals(popupButton.getVisibility(), View.GONE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ assertEquals(zoomSeekBar.getVisibility(), has_zoom ? View.GONE : View.INVISIBLE);
+ assertEquals(takePhotoButton.getVisibility(), View.VISIBLE);
+ assertEquals(pauseVideoButton.getVisibility(), View.GONE);
+ assertEquals(takePhotoVideoButton.getVisibility(), View.GONE);
+ assertEquals(seekBar.getVisibility(), View.GONE);
+ assertEquals(targetSeekBar.getVisibility(), View.GONE);
+
+ subTestTakePhoto(false, true, true, true, false, false, false, false);
+
+ // test now exited immersive mode
+ assertEquals(switchCameraButton.getVisibility(), (mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE));
+ assertEquals(switchMultiCameraButton.getVisibility(), (mActivity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE));
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureButton.getVisibility(), exposureVisibility);
+ assertEquals(exposureLockButton.getVisibility(), exposureLockVisibility);
+ assertEquals(audioControlButton.getVisibility(), (has_audio_control_button ? View.VISIBLE : View.GONE));
+ assertEquals(popupButton.getVisibility(), View.VISIBLE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ assertEquals(zoomSeekBar.getVisibility(), has_zoom ? View.VISIBLE : View.INVISIBLE);
+ assertEquals(takePhotoButton.getVisibility(), View.VISIBLE);
+ assertEquals(pauseVideoButton.getVisibility(), View.GONE);
+ assertEquals(takePhotoVideoButton.getVisibility(), View.GONE);
+ assertEquals(seekBar.getVisibility(), View.GONE);
+ assertEquals(targetSeekBar.getVisibility(), View.GONE);
+
+ // wait for immersive mode to kick in again
+ Thread.sleep(6000);
+ assertEquals(switchCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchMultiCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchVideoButton.getVisibility(), View.GONE);
+ assertEquals(exposureButton.getVisibility(), View.GONE);
+ assertEquals(exposureLockButton.getVisibility(), View.GONE);
+ assertEquals(audioControlButton.getVisibility(), View.GONE);
+ assertEquals(popupButton.getVisibility(), View.GONE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ assertEquals(zoomSeekBar.getVisibility(), has_zoom ? View.GONE : View.INVISIBLE);
+ assertEquals(takePhotoButton.getVisibility(), View.VISIBLE);
+ assertEquals(pauseVideoButton.getVisibility(), View.GONE);
+ assertEquals(takePhotoVideoButton.getVisibility(), View.GONE);
+ assertEquals(seekBar.getVisibility(), View.GONE);
+ assertEquals(targetSeekBar.getVisibility(), View.GONE);
+
+ subTestTakePhotoPreviewPaused(true, false);
+
+ // test now exited immersive mode
+ assertEquals(switchCameraButton.getVisibility(), (mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE));
+ assertEquals(switchMultiCameraButton.getVisibility(), (mActivity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE));
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureButton.getVisibility(), exposureVisibility);
+ assertEquals(exposureLockButton.getVisibility(), exposureLockVisibility);
+ assertEquals(audioControlButton.getVisibility(), (has_audio_control_button ? View.VISIBLE : View.GONE));
+ assertEquals(popupButton.getVisibility(), View.VISIBLE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ assertEquals(zoomSeekBar.getVisibility(), has_zoom ? View.VISIBLE : View.INVISIBLE);
+ assertEquals(takePhotoButton.getVisibility(), View.VISIBLE);
+ assertEquals(pauseVideoButton.getVisibility(), View.GONE);
+ assertEquals(takePhotoVideoButton.getVisibility(), View.GONE);
+ assertEquals(seekBar.getVisibility(), View.GONE);
+ assertEquals(targetSeekBar.getVisibility(), View.GONE);
+
+ // need to switch video before going back to immersive mode
+ if( !mPreview.isVideo() ) {
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ }
+ // test now exited immersive mode
+ assertEquals(switchCameraButton.getVisibility(), (mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE));
+ assertEquals(switchMultiCameraButton.getVisibility(), (mActivity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE));
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureButton.getVisibility(), exposureVisibility);
+ assertEquals(exposureLockButton.getVisibility(), exposureLockVisibility);
+ assertEquals(audioControlButton.getVisibility(), (has_audio_control_button ? View.VISIBLE : View.GONE));
+ assertEquals(popupButton.getVisibility(), View.VISIBLE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ assertEquals(zoomSeekBar.getVisibility(), has_zoom ? View.VISIBLE : View.INVISIBLE);
+ assertEquals(takePhotoButton.getVisibility(), View.VISIBLE);
+ assertEquals(pauseVideoButton.getVisibility(), View.GONE);
+ assertEquals(takePhotoVideoButton.getVisibility(), View.GONE);
+ assertEquals(seekBar.getVisibility(), View.GONE);
+ assertEquals(targetSeekBar.getVisibility(), View.GONE);
+
+ // wait for immersive mode to kick in again
+ Thread.sleep(6000);
+ assertEquals(switchCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchMultiCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchVideoButton.getVisibility(), View.GONE);
+ assertEquals(exposureButton.getVisibility(), View.GONE);
+ assertEquals(exposureLockButton.getVisibility(), View.GONE);
+ assertEquals(audioControlButton.getVisibility(), View.GONE);
+ assertEquals(popupButton.getVisibility(), View.GONE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ assertEquals(zoomSeekBar.getVisibility(), has_zoom ? View.GONE : View.INVISIBLE);
+ assertEquals(takePhotoButton.getVisibility(), View.VISIBLE);
+ assertEquals(pauseVideoButton.getVisibility(), View.GONE);
+ assertEquals(takePhotoVideoButton.getVisibility(), View.GONE);
+ assertEquals(seekBar.getVisibility(), View.GONE);
+ assertEquals(targetSeekBar.getVisibility(), View.GONE);
+
+ subTestTakeVideo(false, false, false, true, null, 5000, false, 0);
+
+ // test touch exits immersive mode
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ assertEquals(switchCameraButton.getVisibility(), (mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE));
+ assertEquals(switchMultiCameraButton.getVisibility(), (mActivity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE));
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureButton.getVisibility(), exposureVisibility);
+ assertEquals(exposureLockButton.getVisibility(), exposureLockVisibility);
+ assertEquals(audioControlButton.getVisibility(), (has_audio_control_button ? View.VISIBLE : View.GONE));
+ assertEquals(popupButton.getVisibility(), View.VISIBLE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ assertEquals(zoomSeekBar.getVisibility(), has_zoom ? View.VISIBLE : View.INVISIBLE);
+ assertEquals(takePhotoButton.getVisibility(), View.VISIBLE);
+ assertEquals(pauseVideoButton.getVisibility(), View.GONE);
+ assertEquals(takePhotoVideoButton.getVisibility(), View.GONE);
+ assertEquals(seekBar.getVisibility(), View.GONE);
+ assertEquals(targetSeekBar.getVisibility(), View.GONE);
+
+ // switch back to photo mode
+ if( mPreview.isVideo() ) {
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ }
+
+ if( mPreview.usingCamera2API() && mPreview.supportsFocus() && mPreview.getSupportedFocusValues().contains("focus_mode_manual2") ) {
+ // now test manual focus seekbar disappears
+ assertEquals(seekBar.getVisibility(), View.GONE);
+ switchToFocusValue("focus_mode_manual2");
+ assertEquals(seekBar.getVisibility(), View.VISIBLE);
+
+ // wait for immersive mode to kick in again
+ Thread.sleep(6000);
+ assertEquals(switchCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchMultiCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchVideoButton.getVisibility(), View.GONE);
+ assertEquals(exposureButton.getVisibility(), View.GONE);
+ assertEquals(exposureLockButton.getVisibility(), View.GONE);
+ assertEquals(audioControlButton.getVisibility(), View.GONE);
+ assertEquals(popupButton.getVisibility(), View.GONE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ assertEquals(zoomSeekBar.getVisibility(), has_zoom ? View.GONE : View.INVISIBLE);
+ assertEquals(takePhotoButton.getVisibility(), View.VISIBLE);
+ assertEquals(pauseVideoButton.getVisibility(), View.GONE);
+ assertEquals(takePhotoVideoButton.getVisibility(), View.GONE);
+ assertEquals(seekBar.getVisibility(), View.GONE);
+ assertEquals(targetSeekBar.getVisibility(), View.GONE);
+
+ // test touch exits immersive mode
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ assertEquals(switchCameraButton.getVisibility(), (mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE));
+ assertEquals(switchMultiCameraButton.getVisibility(), (mActivity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE));
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureButton.getVisibility(), exposureVisibility);
+ assertEquals(exposureLockButton.getVisibility(), exposureLockVisibility);
+ assertEquals(audioControlButton.getVisibility(), (has_audio_control_button ? View.VISIBLE : View.GONE));
+ assertEquals(popupButton.getVisibility(), View.VISIBLE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ assertEquals(zoomSeekBar.getVisibility(), has_zoom ? View.VISIBLE : View.INVISIBLE);
+ assertEquals(takePhotoButton.getVisibility(), View.VISIBLE);
+ assertEquals(pauseVideoButton.getVisibility(), View.GONE);
+ assertEquals(takePhotoVideoButton.getVisibility(), View.GONE);
+ assertEquals(seekBar.getVisibility(), View.VISIBLE);
+ assertEquals(targetSeekBar.getVisibility(), View.GONE);
+
+ switchToFocusValue("focus_mode_continuous_picture");
+ }
+
+ if( mPreview.usingCamera2API() && mActivity.supportsFocusBracketing() ) {
+ // now test focus bracketing seekbars disappear
+ assertEquals(seekBar.getVisibility(), View.GONE);
+ assertEquals(targetSeekBar.getVisibility(), View.GONE);
+
+ settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_focus_bracketing");
+ editor.apply();
+ updateForSettings();
+
+ assertEquals(seekBar.getVisibility(), View.VISIBLE);
+ assertEquals(targetSeekBar.getVisibility(), View.VISIBLE);
+
+ // wait for immersive mode to kick in again
+ Thread.sleep(6000);
+ assertEquals(switchCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchMultiCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchVideoButton.getVisibility(), View.GONE);
+ assertEquals(exposureButton.getVisibility(), View.GONE);
+ assertEquals(exposureLockButton.getVisibility(), View.GONE);
+ assertEquals(audioControlButton.getVisibility(), View.GONE);
+ assertEquals(popupButton.getVisibility(), View.GONE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ assertEquals(zoomSeekBar.getVisibility(), has_zoom ? View.GONE : View.INVISIBLE);
+ assertEquals(takePhotoButton.getVisibility(), View.VISIBLE);
+ assertEquals(pauseVideoButton.getVisibility(), View.GONE);
+ assertEquals(takePhotoVideoButton.getVisibility(), View.GONE);
+ assertEquals(seekBar.getVisibility(), View.GONE);
+ assertEquals(targetSeekBar.getVisibility(), View.GONE);
+
+ // test touch exits immersive mode
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ assertEquals(switchCameraButton.getVisibility(), (mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE));
+ assertEquals(switchMultiCameraButton.getVisibility(), (mActivity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE));
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureButton.getVisibility(), exposureVisibility);
+ assertEquals(exposureLockButton.getVisibility(), exposureLockVisibility);
+ assertEquals(audioControlButton.getVisibility(), (has_audio_control_button ? View.VISIBLE : View.GONE));
+ assertEquals(popupButton.getVisibility(), View.VISIBLE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ assertEquals(zoomSeekBar.getVisibility(), has_zoom ? View.VISIBLE : View.INVISIBLE);
+ assertEquals(takePhotoButton.getVisibility(), View.VISIBLE);
+ assertEquals(pauseVideoButton.getVisibility(), View.GONE);
+ assertEquals(takePhotoVideoButton.getVisibility(), View.GONE);
+ assertEquals(seekBar.getVisibility(), View.VISIBLE);
+ assertEquals(targetSeekBar.getVisibility(), View.VISIBLE);
+
+ settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "photo_mode_std");
+ editor.apply();
+ updateForSettings();
+ }
+
+ if( mPreview.usingCamera2API() && mPreview.supportsISORange() ) {
+ // now test exposure button disappears when in manual ISO mode
+ switchToISO(100);
+
+ // wait for immersive mode to kick in again
+ Thread.sleep(6000);
+ assertEquals(switchCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchMultiCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchVideoButton.getVisibility(), View.GONE);
+ assertEquals(exposureButton.getVisibility(), View.GONE);
+ assertEquals(exposureLockButton.getVisibility(), View.GONE);
+ assertEquals(audioControlButton.getVisibility(), View.GONE);
+ assertEquals(popupButton.getVisibility(), View.GONE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ assertEquals(zoomSeekBar.getVisibility(), has_zoom ? View.GONE : View.INVISIBLE);
+ assertEquals(takePhotoButton.getVisibility(), View.VISIBLE);
+ assertEquals(pauseVideoButton.getVisibility(), View.GONE);
+ assertEquals(takePhotoVideoButton.getVisibility(), View.GONE);
+ assertEquals(seekBar.getVisibility(), View.GONE);
+ assertEquals(targetSeekBar.getVisibility(), View.GONE);
+ }
+ }
+
+ // See note under testImmersiveMode() if this fails with a SecurityException about needing INJECT_EVENTS permission.
+ public void testImmersiveModeEverything() throws InterruptedException {
+ Log.d(TAG, "testImmersiveModeEverything");
+
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT ) {
+ Log.d(TAG, "immersive mode requires Android Kitkat or better");
+ return;
+ }
+
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.ImmersiveModePreferenceKey, "immersive_mode_everything");
+ editor.apply();
+ updateForSettings();
+
+ boolean has_zoom = mPreview.supportsZoom();
+
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ View switchMultiCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_multi_camera);
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ View exposureButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureLockButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure_lock);
+ View popupButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.popup);
+ View trashButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.trash);
+ View shareButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.share);
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ View pauseVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.pause_video);
+ View takePhotoVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo_when_video_recording);
+ View zoomSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.zoom_seekbar);
+ assertEquals(switchCameraButton.getVisibility(), (mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE));
+ assertEquals(switchMultiCameraButton.getVisibility(), (mActivity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE));
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+ int exposureVisibility = exposureButton.getVisibility();
+ int exposureLockVisibility = exposureLockButton.getVisibility();
+ assertEquals(popupButton.getVisibility(), View.VISIBLE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ assertEquals(zoomSeekBar.getVisibility(), has_zoom ? View.VISIBLE : View.INVISIBLE);
+ assertEquals(takePhotoButton.getVisibility(), View.VISIBLE);
+ assertEquals(pauseVideoButton.getVisibility(), View.GONE);
+ assertEquals(takePhotoVideoButton.getVisibility(), View.GONE);
+
+ // now wait for immersive mode to kick in
+ Thread.sleep(6000);
+ assertEquals(switchCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchMultiCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchVideoButton.getVisibility(), View.GONE);
+ assertEquals(exposureButton.getVisibility(), View.GONE);
+ assertEquals(exposureLockButton.getVisibility(), View.GONE);
+ assertEquals(popupButton.getVisibility(), View.GONE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ assertEquals(zoomSeekBar.getVisibility(), has_zoom ? View.GONE : View.INVISIBLE);
+ assertEquals(takePhotoButton.getVisibility(), View.GONE);
+ assertEquals(pauseVideoButton.getVisibility(), View.GONE);
+ assertEquals(takePhotoVideoButton.getVisibility(), View.GONE);
+
+ // now touch to exit immersive mode
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ Thread.sleep(500);
+
+ // test now exited immersive mode
+ assertEquals(switchCameraButton.getVisibility(), (mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE));
+ assertEquals(switchMultiCameraButton.getVisibility(), (mActivity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE));
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureButton.getVisibility(), exposureVisibility);
+ assertEquals(exposureLockButton.getVisibility(), exposureLockVisibility);
+ assertEquals(popupButton.getVisibility(), View.VISIBLE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ assertEquals(zoomSeekBar.getVisibility(), has_zoom ? View.VISIBLE : View.INVISIBLE);
+ assertEquals(takePhotoButton.getVisibility(), View.VISIBLE);
+ assertEquals(pauseVideoButton.getVisibility(), View.GONE);
+ assertEquals(takePhotoVideoButton.getVisibility(), View.GONE);
+ }
+
+ /** Tests the use of the FLAG_LAYOUT_NO_LIMITS flag introduced in 1.48.
+ * In 1.49 this was replaced with View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION.
+ * In 1.54 this was replaced with WindowCompat.setDecorFitsSystemWindows().
+ * In 1.54, on Android 15+ we now support always running in edge-to-edge mode (MainActivity.edge_to_edge_mode).
+ */
+ public void testLayoutNoLimits() throws InterruptedException {
+ Log.d(TAG, "testLayoutNoLimits");
+
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ) {
+ // we don't support FLAG_LAYOUT_NO_LIMITS
+ return;
+ }
+
+ MainActivity.test_preview_want_no_limits = true;
+ MainActivity.test_preview_want_no_limits_value = false;
+ // need to restart for test_preview_want_no_limits static to take effect
+ restart();
+
+ setToDefault();
+
+ boolean edge_to_edge_mode = mActivity.getEdgeToEdgeMode();
+
+ Thread.sleep(1000);
+ assertEquals(0, mActivity.getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+ assertFalse(mActivity.test_set_show_under_navigation);
+ assertEquals(0, mActivity.getWindow().getDecorView().getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+ assertEquals(mActivity.getNavigationGap(), mActivity.getMainUI().test_navigation_gap);
+ assertEquals(mActivity.getNavigationGapLandscape(), mActivity.getMainUI().test_navigation_gap_landscape);
+ assertEquals(mActivity.getNavigationGapReverseLandscape(), mActivity.getMainUI().test_navigation_gap_reversed_landscape);
+ int initial_navigation_gap = mActivity.getMainUI().test_navigation_gap;
+ if( !edge_to_edge_mode ) {
+ assertEquals(0, mActivity.getMainUI().test_navigation_gap);
+ }
+ int initial_navigation_gap_landscape = mActivity.getMainUI().test_navigation_gap_landscape;
+ int initial_navigation_gap_reversed_landscape = mActivity.getMainUI().test_navigation_gap_reversed_landscape;
+ if( edge_to_edge_mode ) {
+ // exactly one navigation gap should be non-zero
+ int count = 0;
+ if( initial_navigation_gap > 0 )
+ count++;
+ if( initial_navigation_gap_landscape > 0 )
+ count++;
+ if( initial_navigation_gap_reversed_landscape > 0 )
+ count++;
+ assertEquals(1, count);
+ }
+ else {
+ assertEquals(0, initial_navigation_gap_landscape);
+ assertEquals(0, initial_navigation_gap_reversed_landscape);
+ }
+
+ // test changing resolution
+ MainActivity.test_preview_want_no_limits_value = true;
+ updateForSettings();
+ Thread.sleep(1000);
+ //final boolean supports_no_limits = mActivity.getNavigationGap() != 0;
+ final boolean supports_no_limits = false;
+ // on Android 11+, WindowCompat.setDecorFitsSystemWindows() uses Window.setDecorFitsSystemWindows() instead of SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION, and
+ // doesn't seem possible to read back that value, so instead on Android 11+ we rely on mActivity.test_set_show_under_navigation)
+ final boolean supports_hide_navigation = Build.VERSION.SDK_INT < Build.VERSION_CODES.R && mActivity.getNavigationGap() != 0;
+ Log.d(TAG, "supports_no_limits: " + supports_no_limits);
+ Log.d(TAG, "supports_hide_navigation: " + supports_hide_navigation);
+
+ assertEquals(supports_no_limits ? WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS : 0, mActivity.getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+ if( edge_to_edge_mode ) {
+ assertFalse(mActivity.test_set_show_under_navigation);
+ }
+ else {
+ assertTrue(mActivity.test_set_show_under_navigation);
+ }
+ assertEquals(supports_hide_navigation ? View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION : 0, mActivity.getWindow().getDecorView().getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+ assertEquals(mActivity.getNavigationGap(), mActivity.getMainUI().test_navigation_gap);
+ assertEquals(mActivity.getNavigationGapLandscape(), mActivity.getMainUI().test_navigation_gap_landscape);
+ assertEquals(mActivity.getNavigationGapReverseLandscape(), mActivity.getMainUI().test_navigation_gap_reversed_landscape);
+ if( edge_to_edge_mode ) {
+ assertEquals(initial_navigation_gap, mActivity.getMainUI().test_navigation_gap);
+ }
+ assertEquals(initial_navigation_gap_landscape, mActivity.getMainUI().test_navigation_gap_landscape);
+ assertEquals(initial_navigation_gap_reversed_landscape, mActivity.getMainUI().test_navigation_gap_reversed_landscape);
+ MainActivity.test_preview_want_no_limits_value = false;
+ updateForSettings();
+ Thread.sleep(1000);
+ assertEquals(0, mActivity.getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+ assertFalse(mActivity.test_set_show_under_navigation);
+ assertEquals(0, mActivity.getWindow().getDecorView().getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+ if( edge_to_edge_mode ) {
+ assertEquals(initial_navigation_gap, mActivity.getMainUI().test_navigation_gap);
+ }
+ else {
+ assertEquals(0, mActivity.getMainUI().test_navigation_gap);
+ }
+ assertEquals(initial_navigation_gap_landscape, mActivity.getMainUI().test_navigation_gap_landscape);
+ assertEquals(initial_navigation_gap_reversed_landscape, mActivity.getMainUI().test_navigation_gap_reversed_landscape);
+
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ) {
+ // test switching camera
+ MainActivity.test_preview_want_no_limits_value = true;
+ switchToCamera(1);
+ Thread.sleep(1000);
+ assertEquals(supports_no_limits ? WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS : 0, mActivity.getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+ if( edge_to_edge_mode ) {
+ assertFalse(mActivity.test_set_show_under_navigation);
+ }
+ else {
+ assertTrue(mActivity.test_set_show_under_navigation);
+ }
+ assertEquals(supports_hide_navigation ? View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION : 0, mActivity.getWindow().getDecorView().getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+ assertEquals(mActivity.getNavigationGap(), mActivity.getMainUI().test_navigation_gap);
+ assertEquals(mActivity.getNavigationGapLandscape(), mActivity.getMainUI().test_navigation_gap_landscape);
+ assertEquals(mActivity.getNavigationGapReverseLandscape(), mActivity.getMainUI().test_navigation_gap_reversed_landscape);
+ if( edge_to_edge_mode ) {
+ assertEquals(initial_navigation_gap, mActivity.getMainUI().test_navigation_gap);
+ }
+ assertEquals(initial_navigation_gap_landscape, mActivity.getMainUI().test_navigation_gap_landscape);
+ assertEquals(initial_navigation_gap_reversed_landscape, mActivity.getMainUI().test_navigation_gap_reversed_landscape);
+ MainActivity.test_preview_want_no_limits_value = false;
+ switchToCamera(0);
+ Thread.sleep(1000);
+ assertEquals(0, mActivity.getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+ assertFalse(mActivity.test_set_show_under_navigation);
+ assertEquals(0, mActivity.getWindow().getDecorView().getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+ if( edge_to_edge_mode ) {
+ assertEquals(initial_navigation_gap, mActivity.getMainUI().test_navigation_gap);
+ }
+ else {
+ assertEquals(0, mActivity.getMainUI().test_navigation_gap);
+ }
+ assertEquals(initial_navigation_gap_landscape, mActivity.getMainUI().test_navigation_gap_landscape);
+ assertEquals(initial_navigation_gap_reversed_landscape, mActivity.getMainUI().test_navigation_gap_reversed_landscape);
+ }
+
+ // test switching to video and back
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ MainActivity.test_preview_want_no_limits_value = true;
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertTrue(mPreview.isVideo());
+ Thread.sleep(1000);
+ assertEquals(supports_no_limits ? WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS : 0, mActivity.getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+ if( edge_to_edge_mode ) {
+ assertFalse(mActivity.test_set_show_under_navigation);
+ }
+ else {
+ assertTrue(mActivity.test_set_show_under_navigation);
+ }
+ assertEquals(supports_hide_navigation ? View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION : 0, mActivity.getWindow().getDecorView().getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+ assertEquals(mActivity.getNavigationGap(), mActivity.getMainUI().test_navigation_gap);
+ assertEquals(mActivity.getNavigationGapLandscape(), mActivity.getMainUI().test_navigation_gap_landscape);
+ assertEquals(mActivity.getNavigationGapReverseLandscape(), mActivity.getMainUI().test_navigation_gap_reversed_landscape);
+ if( edge_to_edge_mode ) {
+ assertEquals(initial_navigation_gap, mActivity.getMainUI().test_navigation_gap);
+ }
+ assertEquals(initial_navigation_gap_landscape, mActivity.getMainUI().test_navigation_gap_landscape);
+ assertEquals(initial_navigation_gap_reversed_landscape, mActivity.getMainUI().test_navigation_gap_reversed_landscape);
+ MainActivity.test_preview_want_no_limits_value = false;
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertFalse(mPreview.isVideo());
+ Thread.sleep(1000);
+ assertEquals(0, mActivity.getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+ assertFalse(mActivity.test_set_show_under_navigation);
+ assertEquals(0, mActivity.getWindow().getDecorView().getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+ if( edge_to_edge_mode ) {
+ assertEquals(initial_navigation_gap, mActivity.getMainUI().test_navigation_gap);
+ }
+ else {
+ assertEquals(0, mActivity.getMainUI().test_navigation_gap);
+ }
+ assertEquals(initial_navigation_gap_landscape, mActivity.getMainUI().test_navigation_gap_landscape);
+ assertEquals(initial_navigation_gap_reversed_landscape, mActivity.getMainUI().test_navigation_gap_reversed_landscape);
+
+ // test after restart
+ MainActivity.test_preview_want_no_limits_value = true;
+ restart();
+ Thread.sleep(1000);
+ assertEquals(supports_no_limits ? WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS : 0, mActivity.getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+ if( edge_to_edge_mode ) {
+ assertFalse(mActivity.test_set_show_under_navigation);
+ }
+ else {
+ assertTrue(mActivity.test_set_show_under_navigation);
+ }
+ assertEquals(supports_hide_navigation ? View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION : 0, mActivity.getWindow().getDecorView().getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+ assertEquals(mActivity.getNavigationGap(), mActivity.getMainUI().test_navigation_gap);
+ assertEquals(mActivity.getNavigationGapLandscape(), mActivity.getMainUI().test_navigation_gap_landscape);
+ assertEquals(mActivity.getNavigationGapReverseLandscape(), mActivity.getMainUI().test_navigation_gap_reversed_landscape);
+ if( edge_to_edge_mode ) {
+ assertEquals(initial_navigation_gap, mActivity.getMainUI().test_navigation_gap);
+ }
+ assertEquals(initial_navigation_gap_landscape, mActivity.getMainUI().test_navigation_gap_landscape);
+ assertEquals(initial_navigation_gap_reversed_landscape, mActivity.getMainUI().test_navigation_gap_reversed_landscape);
+ }
+
+ /** Tests the use of the FLAG_LAYOUT_NO_LIMITS flag introduced in 1.48, with the mode set from startup.
+ * In 1.49 this was replaced with View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION.
+ * In 1.54 this was replaced with WindowCompat.setDecorFitsSystemWindows().
+ */
+ public void testLayoutNoLimitsStartup() throws InterruptedException {
+ Log.d(TAG, "testLayoutNoLimitsStartup");
+
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ) {
+ // we don't support FLAG_LAYOUT_NO_LIMITS
+ return;
+ }
+
+ MainActivity.test_preview_want_no_limits = true;
+ MainActivity.test_preview_want_no_limits_value = true;
+ // need to restart for test_preview_want_no_limits static to take effect
+ restart();
+
+ setToDefault();
+
+ boolean edge_to_edge_mode = mActivity.getEdgeToEdgeMode();
+
+ Thread.sleep(1000);
+ //boolean supports_no_limits = mActivity.getNavigationGap() != 0;
+ final boolean supports_no_limits = false;
+ // on Android 11+, WindowCompat.setDecorFitsSystemWindows() uses Window.setDecorFitsSystemWindows() instead of SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION, and
+ // doesn't seem possible to read back that value, so instead on Android 11+ we rely on mActivity.test_set_show_under_navigation)
+ final boolean supports_hide_navigation = Build.VERSION.SDK_INT < Build.VERSION_CODES.R && mActivity.getNavigationGap() != 0;
+ Log.d(TAG, "supports_no_limits: " + supports_no_limits);
+ Log.d(TAG, "supports_hide_navigation: " + supports_hide_navigation);
+
+ Log.d(TAG, "check FLAG_LAYOUT_NO_LIMITS");
+ Log.d(TAG, "test_navigation_gap: " + mActivity.getMainUI().test_navigation_gap);
+ assertEquals(supports_no_limits ? WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS : 0, mActivity.getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+ if( edge_to_edge_mode ) {
+ assertFalse(mActivity.test_set_show_under_navigation);
+ }
+ else {
+ assertTrue(mActivity.test_set_show_under_navigation);
+ }
+ assertEquals(supports_hide_navigation ? View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION : 0, mActivity.getWindow().getDecorView().getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+ assertEquals(mActivity.getNavigationGap(), mActivity.getMainUI().test_navigation_gap);
+ }
+
+ private void subTestTakePhotoPreviewPaused(boolean immersive_mode, boolean is_raw) throws InterruptedException {
+ mPreview.count_cameraTakePicture = 0;
+
+ // count initial files in folder
+ int n_files = getNFiles();
+ Log.d(TAG, "n_files at start: " + n_files);
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.PausePreviewPreferenceKey, true);
+ editor.apply();
+
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ boolean has_audio_control_button = !sharedPreferences.getString(PreferenceKeys.AudioControlPreferenceKey, "none").equals("none");
+
+ Log.d(TAG, "check if preview is started");
+ Thread.sleep(500); // needed for Pixel 6 Pro with Camera 2 API
+ assertTrue(mPreview.isPreviewStarted());
+
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ View switchMultiCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_multi_camera);
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ //View flashButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.flash);
+ //View focusButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.focus_mode);
+ View exposureButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureLockButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure_lock);
+ View audioControlButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.audio_control);
+ View popupButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.popup);
+ View trashButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.trash);
+ View shareButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.share);
+ assertEquals(switchCameraButton.getVisibility(), (immersive_mode ? View.GONE : (mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE)));
+ assertEquals(switchMultiCameraButton.getVisibility(), (immersive_mode ? View.GONE : (mActivity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE)));
+ assertEquals(switchVideoButton.getVisibility(), (immersive_mode ? View.GONE : View.VISIBLE));
+ // store status to compare with later
+ int exposureVisibility = exposureButton.getVisibility();
+ int exposureLockVisibility = exposureLockButton.getVisibility();
+ assertEquals(audioControlButton.getVisibility(), ((has_audio_control_button && !immersive_mode) ? View.VISIBLE : View.GONE));
+ assertEquals(popupButton.getVisibility(), (immersive_mode ? View.GONE : View.VISIBLE));
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ Log.d(TAG, "about to click take photo");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take photo");
+
+ waitForTakePhoto();
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ assertEquals(1, mPreview.count_cameraTakePicture);
+
+ // don't need to wait until image queue empty, as Open Camera shouldn't use background thread for preview pause option
+
+ Bitmap thumbnail = mActivity.gallery_bitmap;
+ assertNotNull(thumbnail);
+
+ int n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ int exp_n_new_files = is_raw ? 2 : 1;
+ Log.d(TAG, "exp_n_new_files: " + exp_n_new_files);
+ assertEquals(n_new_files, exp_n_new_files);
+
+ // now preview should be paused
+ assertFalse(mPreview.isPreviewStarted()); // check preview paused
+ assertEquals(switchCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchMultiCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchVideoButton.getVisibility(), View.GONE);
+ assertEquals(exposureButton.getVisibility(), View.GONE);
+ assertEquals(exposureLockButton.getVisibility(), View.GONE);
+ assertEquals(audioControlButton.getVisibility(), View.GONE);
+ assertEquals(popupButton.getVisibility(), View.GONE);
+ assertEquals(trashButton.getVisibility(), View.VISIBLE);
+ assertEquals(shareButton.getVisibility(), View.VISIBLE);
+
+ Thread.sleep(1000); // needed for Galaxy S10e
+ Log.d(TAG, "about to click preview");
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ Log.d(TAG, "done click preview");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync 3");
+
+ // check photo not deleted
+ n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ Log.d(TAG, "exp_n_new_files: " + exp_n_new_files);
+ assertEquals(n_new_files, exp_n_new_files);
+
+ assertTrue(mPreview.isPreviewStarted()); // check preview restarted
+ assertEquals(switchCameraButton.getVisibility(), (mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE));
+ assertEquals(switchMultiCameraButton.getVisibility(), (mActivity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE));
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+ //assertTrue(flashButton.getVisibility() == flashVisibility);
+ //assertTrue(focusButton.getVisibility() == focusVisibility);
+ if( !immersive_mode ) {
+ assertEquals(exposureButton.getVisibility(), exposureVisibility);
+ assertEquals(exposureLockButton.getVisibility(), exposureLockVisibility);
+ }
+ assertEquals(audioControlButton.getVisibility(), (has_audio_control_button ? View.VISIBLE : View.GONE));
+ assertEquals(popupButton.getVisibility(), View.VISIBLE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+
+ // check still same icon even after a delay
+ Log.d(TAG, "thumbnail:" + thumbnail);
+ Log.d(TAG, "mActivity.gallery_bitmap: " + mActivity.gallery_bitmap);
+ assertSame(mActivity.gallery_bitmap, thumbnail);
+ Thread.sleep(1000);
+ Log.d(TAG, "thumbnail:" + thumbnail);
+ Log.d(TAG, "mActivity.gallery_bitmap: " + mActivity.gallery_bitmap);
+ assertSame(mActivity.gallery_bitmap, thumbnail);
+
+ mActivity.waitUntilImageQueueEmpty();
+ }
+
+ public void testTakePhotoPreviewPaused() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoPreviewPaused");
+ setToDefault();
+ subTestTakePhotoPreviewPaused(false, false);
+ }
+
+ public void testTakePhotoPreviewPausedAudioButton() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoPreviewPausedAudioButton");
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.AudioControlPreferenceKey, "noise");
+ editor.apply();
+ updateForSettings();
+
+ subTestTakePhotoPreviewPaused(false, false);
+ }
+
+ // If this test fails, make sure we've manually selected that folder (as permission can't be given through the test framework).
+ public void testTakePhotoPreviewPausedSAF() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoPreviewPausedSAF");
+
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ) {
+ Log.d(TAG, "SAF requires Android Lollipop or better");
+ return;
+ }
+
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.UsingSAFPreferenceKey, true);
+ editor.putString(PreferenceKeys.SaveLocationSAFPreferenceKey, "content://com.android.externalstorage.documents/tree/primary%3ADCIM%2FOpenCamera");
+ editor.apply();
+ updateForSettings();
+
+ subTestTakePhotoPreviewPaused(false, false);
+ }
+
+ /** Tests pause preview option.
+ * @param share If true, share the image; else, trash it. A test with share==true should be the
+ * last test if run in a suite, as sharing the image may sometimes cause later
+ * tests to hang.
+ */
+ private void subTestTakePhotoPreviewPausedShareTrash(boolean is_raw, boolean share) throws InterruptedException {
+ // count initial files in folder
+ int n_files = getNFiles();
+ Log.d(TAG, "n_files at start: " + n_files);
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.PausePreviewPreferenceKey, true);
+ editor.apply();
+
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ boolean has_audio_control_button = !sharedPreferences.getString(PreferenceKeys.AudioControlPreferenceKey, "none").equals("none");
+
+ Thread.sleep(500); // needed for Pixel 6 Pro with Camera 2 API
+ assertTrue(mPreview.isPreviewStarted());
+
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ View switchMultiCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_multi_camera);
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ //View flashButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.flash);
+ //View focusButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.focus_mode);
+ View exposureButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureLockButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure_lock);
+ View audioControlButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.audio_control);
+ View popupButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.popup);
+ View trashButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.trash);
+ View shareButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.share);
+ assertEquals(switchCameraButton.getVisibility(), (mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE));
+ assertEquals(switchMultiCameraButton.getVisibility(), (mActivity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE));
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+ // flash and focus etc default visibility tested in another test
+ // but store status to compare with later
+ //int flashVisibility = flashButton.getVisibility();
+ //int focusVisibility = focusButton.getVisibility();
+ int exposureVisibility = exposureButton.getVisibility();
+ int exposureLockVisibility = exposureLockButton.getVisibility();
+ assertEquals(audioControlButton.getVisibility(), (has_audio_control_button ? View.VISIBLE : View.GONE));
+ assertEquals(popupButton.getVisibility(), View.VISIBLE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ Log.d(TAG, "about to click take photo");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take photo");
+
+ waitForTakePhoto();
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ Log.d(TAG, "count_cameraTakePicture: " + mPreview.count_cameraTakePicture);
+ assertEquals(1, mPreview.count_cameraTakePicture);
+
+ // don't need to wait until image queue empty, as Open Camera shouldn't use background thread for preview pause option
+
+ Bitmap thumbnail = mActivity.gallery_bitmap;
+ assertNotNull(thumbnail);
+
+ int n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ int exp_n_new_files = is_raw ? 2 : 1;
+ Log.d(TAG, "exp_n_new_files: " + exp_n_new_files);
+ assertEquals(n_new_files, exp_n_new_files);
+
+ // now preview should be paused
+ assertFalse(mPreview.isPreviewStarted()); // check preview restarted
+ assertEquals(switchCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchMultiCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchVideoButton.getVisibility(), View.GONE);
+ //assertTrue(flashButton.getVisibility() == View.GONE);
+ //assertTrue(focusButton.getVisibility() == View.GONE);
+ assertEquals(exposureButton.getVisibility(), View.GONE);
+ assertEquals(exposureLockButton.getVisibility(), View.GONE);
+ assertEquals(audioControlButton.getVisibility(), View.GONE);
+ assertEquals(popupButton.getVisibility(), View.GONE);
+ assertEquals(trashButton.getVisibility(), View.VISIBLE);
+ assertEquals(shareButton.getVisibility(), View.VISIBLE);
+
+ if( share ) {
+ Log.d(TAG, "about to click share");
+ clickView(shareButton);
+ Log.d(TAG, "done click share");
+
+ // check photo(s) not deleted
+ n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(n_new_files, exp_n_new_files);
+ }
+ else {
+ Log.d(TAG, "about to click trash");
+ clickView(trashButton);
+ Log.d(TAG, "done click trash");
+
+ // check photo(s) deleted
+ n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(0, n_new_files);
+
+ assertTrue(mPreview.isPreviewStarted()); // check preview restarted
+ assertEquals(switchCameraButton.getVisibility(), (mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE));
+ assertEquals(switchMultiCameraButton.getVisibility(), (mActivity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE));
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+ //assertTrue(flashButton.getVisibility() == flashVisibility);
+ //assertTrue(focusButton.getVisibility() == focusVisibility);
+ assertEquals(exposureButton.getVisibility(), exposureVisibility);
+ assertEquals(exposureLockButton.getVisibility(), exposureLockVisibility);
+ assertEquals(audioControlButton.getVisibility(), (has_audio_control_button ? View.VISIBLE : View.GONE));
+ assertEquals(popupButton.getVisibility(), View.VISIBLE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+
+ // icon may be null, or have been set to another image - only changed after a delay
+ Thread.sleep(2000);
+ Log.d(TAG, "gallery_bitmap: " + mActivity.gallery_bitmap);
+ Log.d(TAG, "thumbnail: " + thumbnail);
+ assertNotSame(mActivity.gallery_bitmap, thumbnail);
+ }
+ mActivity.waitUntilImageQueueEmpty();
+ }
+
+ public void testTakePhotoPreviewPausedTrash() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoPreviewPausedTrash");
+ setToDefault();
+ subTestTakePhotoPreviewPausedShareTrash(false, false);
+ }
+
+ /** Equivalent of testTakePhotoPreviewPausedTrash(), but for Storage Access Framework.
+ * If this test fails, make sure we've manually selected that folder (as permission can't be given through the test framework).
+ */
+ public void testTakePhotoPreviewPausedTrashSAF() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoPreviewPausedTrashSAF");
+
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ) {
+ Log.d(TAG, "SAF requires Android Lollipop or better");
+ return;
+ }
+
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.UsingSAFPreferenceKey, true);
+ editor.putString(PreferenceKeys.SaveLocationSAFPreferenceKey, "content://com.android.externalstorage.documents/tree/primary%3ADCIM%2FOpenCamera");
+ editor.apply();
+ updateForSettings();
+
+ subTestTakePhotoPreviewPausedShareTrash(false, false);
+ }
+
+ /** Like testTakePhotoPreviewPausedTrash() but taking 2 photos, only deleting the most recent - make
+ * sure we don't delete both images!
+ */
+ public void testTakePhotoPreviewPausedTrash2() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoPreviewPausedTrash2");
+ setToDefault();
+
+ subTestTakePhotoPreviewPaused(false, false);
+
+ mPreview.count_cameraTakePicture = 0; // need to reset
+
+ subTestTakePhotoPreviewPausedShareTrash(false, false);
+ }
+
+ /** Equivalent of testTakePhotoPreviewPausedTrash(), but with Raw enabled.
+ */
+ public void testTakePhotoPreviewPausedTrashRaw() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoPreviewPausedTrashRaw");
+ setToDefault();
+
+ if( !mPreview.supportsRaw() ) {
+ return;
+ }
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.RawPreferenceKey, "preference_raw_yes");
+ editor.apply();
+ updateForSettings();
+
+ subTestTakePhotoPreviewPausedShareTrash(true, false);
+ }
+
+ /** Take a photo with RAW that we keep, then take a photo without RAW that we delete, and ensure we
+ * don't delete the previous RAW image!
+ */
+ public void testTakePhotoPreviewPausedTrashRaw2() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoPreviewPausedTrashRaw2");
+ setToDefault();
+
+ if( !mPreview.supportsRaw() ) {
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.RawPreferenceKey, "preference_raw_yes");
+ editor.apply();
+ updateForSettings();
+
+ subTestTakePhotoPreviewPaused(false, true);
+
+ settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ editor = settings.edit();
+ editor.putString(PreferenceKeys.RawPreferenceKey, "preference_raw_no");
+ editor.apply();
+ updateForSettings();
+ mPreview.count_cameraTakePicture = 0; // need to reset
+
+ subTestTakePhotoPreviewPausedShareTrash(false, false);
+ }
+
+ /** Tests sharing an image. If run in a suite, this test should be last, as sharing the image
+ * may sometimes cause later tests to hang.
+ */
+ public void testTakePhotoPreviewPausedShare() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoPreviewPausedShare");
+ setToDefault();
+ subTestTakePhotoPreviewPausedShareTrash(false, true);
+ }
+
+ /* Tests that we don't do an extra autofocus when taking a photo, if recently touch-focused.
+ */
+ public void testTakePhotoQuickFocus() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoQuickFocus");
+ setToDefault();
+
+ assertTrue(mPreview.isPreviewStarted());
+
+ // touch to auto-focus with focus area
+ // autofocus shouldn't be immediately, but after a delay
+ // and Galaxy S10e needs a longer delay for some reason, for the subsequent touch of the preview view to register
+ Thread.sleep(2000);
+ int saved_count = mPreview.count_cameraAutoFocus;
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ Log.d(TAG, "1 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+
+ if( mPreview.supportsFocus() ) {
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count + 1);
+ assertTrue(mPreview.hasFocusArea());
+ assertNotNull(mPreview.getCameraController().getFocusAreas());
+ assertEquals(1, mPreview.getCameraController().getFocusAreas().size());
+ assertNotNull(mPreview.getCameraController().getMeteringAreas());
+ assertEquals(1, mPreview.getCameraController().getMeteringAreas().size());
+ }
+ else {
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count);
+ }
+
+ // wait 3s for auto-focus to complete
+ Thread.sleep(3000);
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ Log.d(TAG, "about to click take photo");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take photo");
+
+ Log.d(TAG, "wait until finished taking photo");
+ waitForTakePhoto();
+ Log.d(TAG, "done taking photo");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ assertEquals(1, mPreview.count_cameraTakePicture);
+
+ // taking photo shouldn't have done an auto-focus, and still have focus areas
+ Log.d(TAG, "2 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ if( mPreview.supportsFocus() ) {
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count + 1);
+ assertTrue(mPreview.hasFocusArea());
+ assertNotNull(mPreview.getCameraController().getFocusAreas());
+ assertEquals(1, mPreview.getCameraController().getFocusAreas().size());
+ assertNotNull(mPreview.getCameraController().getMeteringAreas());
+ assertEquals(1, mPreview.getCameraController().getMeteringAreas().size());
+ }
+ else {
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count);
+ }
+
+ mActivity.waitUntilImageQueueEmpty();
+ }
+
+ private void takePhotoRepeatFocus(boolean locked) throws InterruptedException {
+ Log.d(TAG, "takePhotoRepeatFocus");
+ setToDefault();
+
+ if( !mPreview.supportsFocus() ) {
+ Log.d(TAG, "test requires focus");
+ return;
+ }
+
+ if( locked ) {
+ switchToFocusValue("focus_mode_locked");
+ }
+ else {
+ switchToFocusValue("focus_mode_auto");
+ }
+
+ assertTrue(mPreview.isPreviewStarted());
+
+ // touch to auto-focus with focus area
+ // autofocus shouldn't be immediately, but after a delay
+ // and Galaxy S10e needs a longer delay for some reason, for the subsequent touch of the preview view to register
+ Thread.sleep(2000);
+ int saved_count = mPreview.count_cameraAutoFocus;
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ Log.d(TAG, "1 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count + 1);
+ assertTrue(mPreview.hasFocusArea());
+ assertNotNull(mPreview.getCameraController().getFocusAreas());
+ assertEquals(1, mPreview.getCameraController().getFocusAreas().size());
+ assertNotNull(mPreview.getCameraController().getMeteringAreas());
+ assertEquals(1, mPreview.getCameraController().getMeteringAreas().size());
+
+ // wait 3s for auto-focus to complete, and 5s to require additional auto-focus when taking a photo
+ // need a bit longer on Galaxy S10e
+ Thread.sleep(9000);
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ Log.d(TAG, "about to click take photo");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take photo");
+
+ Log.d(TAG, "wait until finished taking photo");
+ waitForTakePhoto();
+ Log.d(TAG, "done taking photo");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ assertEquals(1, mPreview.count_cameraTakePicture);
+
+ // taking photo should have done an auto-focus iff in automatic mode, and still have focus areas
+ Log.d(TAG, "2 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ assertEquals((locked ? saved_count + 1 : saved_count + 2), mPreview.count_cameraAutoFocus);
+ assertTrue(mPreview.hasFocusArea());
+ assertNotNull(mPreview.getCameraController().getFocusAreas());
+ assertEquals(1, mPreview.getCameraController().getFocusAreas().size());
+ assertNotNull(mPreview.getCameraController().getMeteringAreas());
+ assertEquals(1, mPreview.getCameraController().getMeteringAreas().size());
+
+ mActivity.waitUntilImageQueueEmpty();
+ }
+
+ /* Tests that we do an extra autofocus when taking a photo, if too long since last touch-focused.
+ */
+ public void testTakePhotoRepeatFocus() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoRepeatFocus");
+ takePhotoRepeatFocus(false);
+ }
+
+ /* Tests that we don't do an extra autofocus when taking a photo, if too long since last touch-focused, when in locked focus mode.
+ */
+ public void testTakePhotoRepeatFocusLocked() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoRepeatFocusLocked");
+ takePhotoRepeatFocus(true);
+ }
+
+ /* Tests taking a photo with animation and shutter disabled, and not setting focus areas
+ */
+ public void testTakePhotoAlt() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoAlt");
+ setToDefault();
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.ThumbnailAnimationPreferenceKey, false);
+ editor.putBoolean(PreferenceKeys.ShutterSoundPreferenceKey, false);
+ editor.apply();
+
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ boolean has_audio_control_button = !sharedPreferences.getString(PreferenceKeys.AudioControlPreferenceKey, "none").equals("none");
+
+ assertTrue(mPreview.isPreviewStarted());
+
+ // count initial files in folder
+ int n_files = getNFiles();
+ Log.d(TAG, "n_files at start: " + n_files);
+
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ View switchMultiCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_multi_camera);
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ //View flashButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.flash);
+ //View focusButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.focus_mode);
+ View exposureButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureLockButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure_lock);
+ View audioControlButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.audio_control);
+ View popupButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.popup);
+ View trashButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.trash);
+ View shareButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.share);
+ assertEquals(switchCameraButton.getVisibility(), (mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE));
+ assertEquals(switchMultiCameraButton.getVisibility(), (mActivity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE));
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+ // flash and focus etc default visibility tested in another test
+ // but store status to compare with later
+ //int flashVisibility = flashButton.getVisibility();
+ //int focusVisibility = focusButton.getVisibility();
+ int exposureVisibility = exposureButton.getVisibility();
+ int exposureLockVisibility = exposureLockButton.getVisibility();
+ assertEquals(audioControlButton.getVisibility(), (has_audio_control_button ? View.VISIBLE : View.GONE));
+ assertEquals(popupButton.getVisibility(), View.VISIBLE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+
+ // autofocus shouldn't be immediately, but after a delay
+ Thread.sleep(2000);
+ int saved_count = mPreview.count_cameraAutoFocus;
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ Log.d(TAG, "about to click take photo");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take photo");
+
+ waitForTakePhoto();
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ assertEquals(1, mPreview.count_cameraTakePicture);
+
+ mActivity.waitUntilImageQueueEmpty();
+
+ int n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(1, n_new_files);
+
+ Log.d(TAG, "2 count_cameraAutoFocus: " + mPreview.count_cameraAutoFocus);
+ Log.d(TAG, "saved_count: " + saved_count);
+ /*
+ // taking photo should have done an auto-focus, and no focus areas [focus auto]
+ assertTrue(mPreview.count_cameraAutoFocus == saved_count+1);
+ */
+ // taking photo shouldn't have done an auto-focus, and no focus areas [focus continuous]
+ assertEquals(mPreview.count_cameraAutoFocus, saved_count);
+ assertFalse(mPreview.hasFocusArea());
+ assertNull(mPreview.getCameraController().getFocusAreas());
+ assertNull(mPreview.getCameraController().getMeteringAreas());
+
+ // trash/share only shown when preview is paused after taking a photo
+
+ assertTrue(mPreview.isPreviewStarted()); // check preview restarted
+ assertEquals(switchCameraButton.getVisibility(), (mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE));
+ assertEquals(switchMultiCameraButton.getVisibility(), (mActivity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE));
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+ //assertTrue(flashButton.getVisibility() == flashVisibility);
+ //assertTrue(focusButton.getVisibility() == focusVisibility);
+ assertEquals(exposureButton.getVisibility(), exposureVisibility);
+ assertEquals(exposureLockButton.getVisibility(), exposureLockVisibility);
+ assertEquals(audioControlButton.getVisibility(), (has_audio_control_button ? View.VISIBLE : View.GONE));
+ assertEquals(popupButton.getVisibility(), View.VISIBLE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ }
+
+ private void takePhotoLoop(int count) {
+ // count initial files in folder
+ int n_files = getNFiles();
+ Log.d(TAG, "n_files at start: " + n_files);
+
+ int start_count = mPreview.count_cameraTakePicture;
+ for(int i=0;i 1 ) {
+ int cameraId = mPreview.getCameraId();
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ while( switchCameraButton.getVisibility() != View.VISIBLE ) {
+ // wait until photo is taken and button is visible again
+ }
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+ int new_cameraId = mPreview.getCameraId();
+ assertTrue(cameraId != new_cameraId);
+ takePhotoLoop(n_photos_c);
+ while( switchCameraButton.getVisibility() != View.VISIBLE ) {
+ // wait until photo is taken and button is visible again
+ }
+
+ // return to back camera
+ switchToCamera(cameraId);
+ }
+ }
+
+ /* Tests taking photos repeatedly with auto-stabilise enabled.
+ * Tests with front and back.
+ */
+ public void testTakePhotoAutoLevel() {
+ Log.d(TAG, "testTakePhotoAutoLevel");
+
+ subTestTakePhotoAutoLevel();
+ }
+
+ /* As testTakePhotoAutoLevel(), but with test_low_memory set.
+ */
+ public void testTakePhotoAutoLevelLowMemory() {
+ Log.d(TAG, "testTakePhotoAutoLevelLowMemory");
+
+ mActivity.test_low_memory = true;
+
+ subTestTakePhotoAutoLevel();
+ }
+
+ private void takePhotoLoopAngles(int [] angles) {
+ // count initial files in folder
+ mActivity.test_have_angle = true;
+ int n_files = getNFiles();
+ Log.d(TAG, "n_files at start: " + n_files);
+
+ int start_count = mPreview.count_cameraTakePicture;
+ for(int i=0;i 1 ) {
+ int cameraId = mPreview.getCameraId();
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ while( switchCameraButton.getVisibility() != View.VISIBLE ) {
+ // wait until photo is taken and button is visible again
+ }
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+ int new_cameraId = mPreview.getCameraId();
+ assertTrue(cameraId != new_cameraId);
+ takePhotoLoopAngles(angles);
+ while( switchCameraButton.getVisibility() != View.VISIBLE ) {
+ // wait until photo is taken and button is visible again
+ }
+
+ // return to back camera
+ switchToCamera(cameraId);
+ }
+ }
+
+ /* Tests taking photos repeatedly with auto-stabilise enabled, at various angles.
+ * Tests with front and back.
+ */
+ public void testTakePhotoAutoLevelAngles() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoAutoLevel");
+
+ subTestTakePhotoAutoLevelAngles();
+ }
+
+ /* As testTakePhotoAutoLevelAngles(), but with test_low_memory set.
+ */
+ public void testTakePhotoAutoLevelAnglesLowMemory() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoAutoLevelAnglesLowMemory");
+
+ mActivity.test_low_memory = true;
+
+ subTestTakePhotoAutoLevelAngles();
+ }
+
+ /**
+ * @return The number of resultant video files
+ */
+ private int subTestTakeVideo(boolean test_exposure_lock, boolean test_focus_area, boolean allow_failure, boolean immersive_mode, TestUtils.VideoTestCallback test_cb, long time_ms, boolean max_filesize, int n_non_video_files) throws InterruptedException {
+ if( test_exposure_lock && !mPreview.supportsExposureLock() ) {
+ return 0;
+ }
+
+ Thread.sleep(500); // needed for Pixel 6 Pro with Camera 2 API
+
+ TestUtils.preTakeVideoChecks(mActivity, immersive_mode);
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+
+ if( !mPreview.isVideo() ) {
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ }
+ assertTrue(mPreview.isVideo());
+ TestUtils.preTakeVideoChecks(mActivity, immersive_mode);
+
+ // reset:
+ mActivity.getApplicationInterface().test_n_videos_scanned = 0;
+
+ // count initial files in folder
+ int n_files = getNFiles();
+ Log.d(TAG, "n_files at start: " + n_files);
+
+ // store status to compare with later
+ View exposureButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureLockButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure_lock);
+ int exposureVisibility = exposureButton.getVisibility();
+ int exposureLockVisibility = exposureLockButton.getVisibility();
+
+ Log.d(TAG, "about to click take video");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take video");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+
+ if( mPreview.usingCamera2API() ) {
+ assertEquals(mPreview.getCurrentPreviewSize().width, mPreview.getCameraController().test_texture_view_buffer_w);
+ assertEquals(mPreview.getCurrentPreviewSize().height, mPreview.getCameraController().test_texture_view_buffer_h);
+ }
+
+ if( mPreview.isOnTimer() ) {
+ Log.d(TAG, "wait for timer");
+ while( mPreview.isOnTimer() ) {
+ }
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ }
+
+ int exp_n_new_files = 0;
+ boolean failed_to_start = false;
+ if( mPreview.isVideoRecording() ) {
+ TestUtils.takeVideoRecordingChecks(mActivity, immersive_mode, exposureVisibility, exposureLockVisibility);
+
+ if( test_cb == null ) {
+ if( !immersive_mode && time_ms > 500 ) {
+ // test turning torch on/off (if in immersive mode, popup button will be hidden)
+ switchToFlashValue("flash_torch");
+ Thread.sleep(500);
+ switchToFlashValue("flash_off");
+ }
+
+ Thread.sleep(time_ms);
+ TestUtils.takeVideoRecordingChecks(mActivity, immersive_mode, exposureVisibility, exposureLockVisibility);
+
+ assertFalse(mPreview.hasFocusArea());
+ if( !allow_failure ) {
+ assertNull(mPreview.getCameraController().getFocusAreas());
+ assertNull(mPreview.getCameraController().getMeteringAreas());
+ }
+
+ if( test_focus_area ) {
+ // touch to auto-focus with focus area
+ Log.d(TAG, "touch to focus");
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ Thread.sleep(1000); // wait for autofocus
+ if( mPreview.supportsFocus() ) {
+ assertTrue(mPreview.hasFocusArea());
+ assertNotNull(mPreview.getCameraController().getFocusAreas());
+ assertEquals(1, mPreview.getCameraController().getFocusAreas().size());
+ assertNotNull(mPreview.getCameraController().getMeteringAreas());
+ assertEquals(1, mPreview.getCameraController().getMeteringAreas().size());
+ }
+ Log.d(TAG, "done touch to focus");
+
+ // this time, don't wait
+ Log.d(TAG, "touch again to focus");
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ }
+
+ if( test_exposure_lock ) {
+ Log.d(TAG, "test exposure lock");
+ assertFalse(mPreview.getCameraController().getAutoExposureLock());
+ clickView(exposureLockButton);
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ assertTrue( mPreview.getCameraController().getAutoExposureLock() );
+ Thread.sleep(2000);
+ }
+
+ TestUtils.takeVideoRecordingChecks(mActivity, immersive_mode, exposureVisibility, exposureLockVisibility);
+
+ Log.d(TAG, "about to click stop video");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking stop video");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ }
+ else {
+ exp_n_new_files = test_cb.doTest();
+
+ if( mPreview.isVideoRecording() ) {
+ Log.d(TAG, "about to click stop video");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking stop video");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ }
+ }
+ }
+ else {
+ Log.d(TAG, "didn't start video");
+ assertTrue(allow_failure);
+ failed_to_start = true;
+ }
+
+ int n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ TestUtils.checkFilesAfterTakeVideo(mActivity, allow_failure, test_cb != null, time_ms, n_non_video_files, failed_to_start, exp_n_new_files, n_new_files);
+
+ TestUtils.postTakeVideoChecks(mActivity, immersive_mode, max_filesize, exposureVisibility, exposureLockVisibility);
+
+ return n_new_files;
+ }
+
+ public void testTakeVideo() throws InterruptedException {
+ Log.d(TAG, "testTakeVideo");
+
+ setToDefault();
+
+ int n_new_files = subTestTakeVideo(false, false, false, false, null, 5000, false, 0);
+
+ assertEquals(1, n_new_files);
+ }
+
+ public void testTakeVideoAudioControl() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoAudioControl");
+
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.AudioControlPreferenceKey, "noise");
+ editor.apply();
+ updateForSettings();
+
+ subTestTakeVideo(false, false, false, false, null, 5000, false, 0);
+ }
+
+ // If this test fails, make sure we've manually selected that folder (as permission can't be given through the test framework).
+ public void testTakeVideoSAF() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoSAF");
+
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ) {
+ Log.d(TAG, "SAF requires Android Lollipop or better");
+ return;
+ }
+
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.UsingSAFPreferenceKey, true);
+ editor.putString(PreferenceKeys.SaveLocationSAFPreferenceKey, "content://com.android.externalstorage.documents/tree/primary%3ADCIM%2FOpenCamera");
+ editor.apply();
+ updateForSettings();
+
+ int n_new_files = subTestTakeVideo(false, false, false, false, null, 5000, false, 0);
+
+ assertEquals(1, n_new_files);
+ }
+
+ /** Tests video subtitles option.
+ */
+ public void testTakeVideoSubtitles() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoSubtitles");
+
+ setToDefault();
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.VideoSubtitlePref, "preference_video_subtitle_yes");
+ editor.apply();
+ updateForSettings();
+ }
+
+ subTestTakeVideo(false, false, false, false, null, 5000, false,
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.R ? 0 : 1 // boo, Android 11 doesn't allow video subtitles to be saved with mediastore API!
+ );
+ }
+
+ /** Tests video subtitles option, when using Storage Access Framework.
+ */
+ public void testTakeVideoSubtitlesSAF() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoSubtitlesSAF");
+
+ setToDefault();
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.VideoSubtitlePref, "preference_video_subtitle_yes");
+ editor.putBoolean(PreferenceKeys.UsingSAFPreferenceKey, true);
+ editor.putString(PreferenceKeys.SaveLocationSAFPreferenceKey, "content://com.android.externalstorage.documents/tree/primary%3ADCIM%2FOpenCamera");
+ editor.apply();
+ updateForSettings();
+ }
+
+ subTestTakeVideo(false, false, false, false, null, 5000, false, 1);
+ }
+
+ /** Tests video subtitles option, including GPS - also tests losing the connection.
+ * Also test with Storage Access Framework, so this can run on Android 11+.
+ */
+ public void testTakeVideoSubtitlesGPSSAF() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoSubtitlesGPSSAF");
+
+ setToDefault();
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.VideoSubtitlePref, "preference_video_subtitle_yes");
+ editor.putBoolean(PreferenceKeys.LocationPreferenceKey, true);
+ editor.putBoolean(PreferenceKeys.UsingSAFPreferenceKey, true);
+ editor.putString(PreferenceKeys.SaveLocationSAFPreferenceKey, "content://com.android.externalstorage.documents/tree/primary%3ADCIM%2FOpenCamera");
+ editor.apply();
+ updateForSettings();
+ }
+
+ subTestTakeVideo(false, false, false, false, new TestUtils.VideoTestCallback() {
+ @Override
+ public int doTest() {
+ // wait for location
+ long start_t = System.currentTimeMillis();
+ while( !mActivity.getLocationSupplier().testHasReceivedLocation() ) {
+ getInstrumentation().waitForIdleSync();
+ if( System.currentTimeMillis() - start_t > 20000 ) {
+ // need to allow long time for testing devices without mobile network; will likely fail altogether if don't even have wifi
+ fail();
+ }
+ }
+ getInstrumentation().waitForIdleSync();
+ assertNotNull(mActivity.getLocationSupplier().getLocation());
+
+ Log.d(TAG, "have location");
+ try {
+ Thread.sleep(2000);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+
+ // now test losing gps
+ Log.d(TAG, "test losing location");
+ mActivity.getLocationSupplier().setForceNoLocation(true);
+
+ try {
+ Thread.sleep(2000);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+
+ return 2;
+ }
+ }, 5000, false, 1);
+ }
+
+ /** Test pausing and resuming video.
+ */
+ public void testTakeVideoPause() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoPause");
+
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.N ) {
+ Log.d(TAG, "pause video requires Android N or better");
+ return;
+ }
+
+ setToDefault();
+
+ final View pauseVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.pause_video);
+ assertEquals(pauseVideoButton.getVisibility(), View.GONE);
+
+ subTestTakeVideo(false, false, false, false, new TestUtils.VideoTestCallback() {
+ @Override
+ public int doTest() {
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ final long time_tol_ms = 1000;
+
+ Log.d(TAG, "wait before pausing");
+ try {
+ Thread.sleep(3000);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+ assertEquals(takePhotoButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.stop_video));
+ assertEquals(pauseVideoButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.pause_video));
+ assertEquals(pauseVideoButton.getVisibility(), View.VISIBLE);
+ assertTrue( mPreview.isVideoRecording() );
+ assertFalse(mPreview.isVideoRecordingPaused());
+ long video_time = mPreview.getVideoTime(false);
+ Log.d(TAG, "video time: " + video_time);
+ assertTrue( video_time >= 3000 - time_tol_ms );
+ assertTrue( video_time <= 3000 + time_tol_ms );
+
+ Log.d(TAG, "about to click pause video");
+ clickView(pauseVideoButton);
+ Log.d(TAG, "done clicking pause video");
+ getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+
+ assertEquals(pauseVideoButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.resume_video));
+ assertEquals(pauseVideoButton.getVisibility(), View.VISIBLE);
+ assertTrue( mPreview.isVideoRecording() );
+ assertTrue( mPreview.isVideoRecordingPaused() );
+
+ Log.d(TAG, "wait before resuming");
+ try {
+ Thread.sleep(3000);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+ assertEquals(pauseVideoButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.resume_video));
+ assertEquals(pauseVideoButton.getVisibility(), View.VISIBLE);
+ assertTrue( mPreview.isVideoRecording() );
+ assertTrue( mPreview.isVideoRecordingPaused() );
+ video_time = mPreview.getVideoTime(false);
+ Log.d(TAG, "video time: " + video_time);
+ assertTrue( video_time >= 3000 - time_tol_ms );
+ assertTrue( video_time <= 3000 + time_tol_ms );
+
+ Log.d(TAG, "about to click resume video");
+ clickView(pauseVideoButton);
+ Log.d(TAG, "done clicking resume video");
+ getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+
+ assertEquals(pauseVideoButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.pause_video));
+ assertEquals(pauseVideoButton.getVisibility(), View.VISIBLE);
+ assertTrue( mPreview.isVideoRecording() );
+ assertFalse(mPreview.isVideoRecordingPaused());
+
+ Log.d(TAG, "wait before stopping");
+ try {
+ Thread.sleep(3000);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+ Log.d(TAG, "takePhotoButton description: " + takePhotoButton.getContentDescription());
+ assertEquals(takePhotoButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.stop_video));
+ assertEquals(pauseVideoButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.pause_video));
+ assertEquals(pauseVideoButton.getVisibility(), View.VISIBLE);
+ assertTrue( mPreview.isVideoRecording() );
+ assertFalse(mPreview.isVideoRecordingPaused());
+ video_time = mPreview.getVideoTime(false);
+ Log.d(TAG, "video time: " + video_time);
+ assertTrue( video_time >= 6000 - time_tol_ms );
+ assertTrue( video_time <= 6000 + time_tol_ms );
+
+ Log.d(TAG, "about to click stop video");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking stop video");
+ getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+
+ return 1;
+ }
+ }, 5000, false, 0);
+ }
+
+ /** Test pausing and stopping video.
+ */
+ public void testTakeVideoPauseStop() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoPauseStop");
+
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.N ) {
+ Log.d(TAG, "pause video requires Android N or better");
+ return;
+ }
+
+ setToDefault();
+
+ final View pauseVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.pause_video);
+ assertEquals(pauseVideoButton.getVisibility(), View.GONE);
+
+ subTestTakeVideo(false, false, false, false, new TestUtils.VideoTestCallback() {
+ @Override
+ public int doTest() {
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ final long time_tol_ms = 1000;
+
+ Log.d(TAG, "wait before pausing");
+ try {
+ Thread.sleep(3000);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+ assertEquals(takePhotoButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.stop_video));
+ assertEquals(pauseVideoButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.pause_video));
+ assertEquals(pauseVideoButton.getVisibility(), View.VISIBLE);
+ assertTrue( mPreview.isVideoRecording() );
+ assertFalse(mPreview.isVideoRecordingPaused());
+ long video_time = mPreview.getVideoTime(false);
+ Log.d(TAG, "video time: " + video_time);
+ assertTrue( video_time >= 3000 - time_tol_ms );
+ assertTrue( video_time <= 3000 + time_tol_ms );
+
+ Log.d(TAG, "about to click pause video");
+ clickView(pauseVideoButton);
+ Log.d(TAG, "done clicking pause video");
+ getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+
+ assertEquals(pauseVideoButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.resume_video));
+ assertEquals(pauseVideoButton.getVisibility(), View.VISIBLE);
+ assertTrue( mPreview.isVideoRecording() );
+ assertTrue( mPreview.isVideoRecordingPaused() );
+
+ Log.d(TAG, "wait before stopping");
+ try {
+ Thread.sleep(3000);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+ Log.d(TAG, "takePhotoButton description: " + takePhotoButton.getContentDescription());
+ assertEquals(takePhotoButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.stop_video));
+ assertEquals(pauseVideoButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.resume_video));
+ assertEquals(pauseVideoButton.getVisibility(), View.VISIBLE);
+ assertTrue( mPreview.isVideoRecording() );
+ assertTrue( mPreview.isVideoRecordingPaused() );
+ video_time = mPreview.getVideoTime(false);
+ Log.d(TAG, "video time: " + video_time);
+ assertTrue( video_time >= 3000 - time_tol_ms );
+ assertTrue( video_time <= 3000 + time_tol_ms );
+
+ Log.d(TAG, "about to click stop video");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking stop video");
+ getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+
+ return 1;
+ }
+ }, 5000, false, 0);
+ }
+
+ private void subTestTakeVideoSnapshot() throws InterruptedException {
+ Log.d(TAG, "subTestTakeVideoSnapshot");
+
+ final View takePhotoVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo_when_video_recording);
+ assertEquals(takePhotoVideoButton.getVisibility(), View.GONE);
+
+ subTestTakeVideo(false, false, false, false, new TestUtils.VideoTestCallback() {
+ @Override
+ public int doTest() {
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+
+ Log.d(TAG, "wait before taking photo");
+ try {
+ Thread.sleep(3000);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+ assertEquals(takePhotoButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.stop_video));
+ assertEquals(takePhotoVideoButton.getVisibility(), View.VISIBLE);
+ assertTrue( mPreview.isVideoRecording() );
+ assertFalse(mPreview.isVideoRecordingPaused());
+
+ Log.d(TAG, "about to click take photo snapshot");
+ clickView(takePhotoVideoButton);
+ Log.d(TAG, "done clicking take photo snapshot");
+ getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+
+ waitForTakePhoto();
+
+ assertEquals(takePhotoVideoButton.getVisibility(), View.VISIBLE);
+ assertTrue( mPreview.isVideoRecording() );
+ assertFalse(mPreview.isVideoRecordingPaused());
+
+ Log.d(TAG, "wait before stopping");
+ try {
+ Thread.sleep(3000);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+ assertEquals(takePhotoButton.getContentDescription(), mActivity.getResources().getString(net.sourceforge.opencamera.R.string.stop_video));
+ assertEquals(takePhotoVideoButton.getVisibility(), View.VISIBLE);
+ assertTrue( mPreview.isVideoRecording() );
+
+ Log.d(TAG, "about to click stop video");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking stop video");
+ getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+
+ return 2;
+ }
+ }, 5000, false, 1);
+
+ mActivity.waitUntilImageQueueEmpty();
+ }
+
+ /** Test taking photo while recording video.
+ */
+ public void testTakeVideoSnapshot() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoSnapshot");
+
+ setToDefault();
+
+ if( !mPreview.supportsPhotoVideoRecording() ) {
+ Log.d(TAG, "video snapshot not supported");
+ return;
+ }
+
+ subTestTakeVideoSnapshot();
+ }
+
+ /** Test taking photo while recording video, with timer.
+ */
+ public void testTakeVideoSnapshotTimer() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoSnapshotTimer");
+
+ setToDefault();
+
+ if( !mPreview.supportsPhotoVideoRecording() ) {
+ Log.d(TAG, "video snapshot not supported");
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.TimerPreferenceKey, "5");
+ editor.putBoolean(PreferenceKeys.TimerBeepPreferenceKey, false);
+ editor.apply();
+
+ subTestTakeVideoSnapshot();
+ }
+
+ /** Test taking photo while recording video, with pause preview.
+ */
+ public void testTakeVideoSnapshotPausePreview() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoSnapshotPausePreview");
+
+ setToDefault();
+
+ if( !mPreview.supportsPhotoVideoRecording() ) {
+ Log.d(TAG, "video snapshot not supported");
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.PausePreviewPreferenceKey, true);
+ editor.apply();
+
+ subTestTakeVideoSnapshot();
+ }
+
+ /** Test taking photo while recording video at max video quality.
+ */
+ public void testTakeVideoSnapshotMax() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoSnapshotMax");
+
+ setToDefault();
+
+ if( !mPreview.supportsPhotoVideoRecording() ) {
+ Log.d(TAG, "video snapshot not supported");
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.getVideoQualityPreferenceKey(mPreview.getCameraId(), mActivity.getApplicationInterface().getCameraIdSPhysicalPref(), false), "" + CamcorderProfile.QUALITY_HIGH); // set to highest quality (4K on Nexus 6 or OnePlus 3T)
+ editor.apply();
+ updateForSettings();
+
+ subTestTakeVideoSnapshot();
+ }
+
+ /** Set available memory to make sure that we stop before running out of memory.
+ * This test is fine-tuned to Nexus 6, OnePlus 3T, Nokia 8, Galaxy S10e as we measure hitting max filesize based on time.
+ */
+ public void testTakeVideoAvailableMemory() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoAvailableMemory");
+
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ) {
+ // as not fine-tuned to pre-Android 5 devices
+ return;
+ }
+ setToDefault();
+
+ mActivity.getApplicationInterface().test_set_available_memory = true;
+ mActivity.getApplicationInterface().test_available_memory = 50000000;
+ boolean is_nokia = Build.MANUFACTURER.toLowerCase(Locale.US).contains("hmd global");
+ boolean is_samsung = Build.MANUFACTURER.toLowerCase(Locale.US).contains("samsung");
+ if( is_nokia || is_samsung )
+ {
+ // Nokia 8 has much smaller video sizes, at least when recording with phone face down, so we both set
+ // 4K, and lower test_available_memory.
+ mActivity.getApplicationInterface().test_available_memory = 21000000; // must be at least MyApplicationInterface.getVideoMaxFileSizePref().min_free_filesize
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.getVideoQualityPreferenceKey(mPreview.getCameraId(), mActivity.getApplicationInterface().getCameraIdSPhysicalPref(), false), "" + CamcorderProfile.QUALITY_HIGH); // set to highest quality (4K on Nexus 6 or OnePlus 3T)
+ editor.apply();
+ updateForSettings();
+ }
+
+ subTestTakeVideo(false, false, false, false, new TestUtils.VideoTestCallback() {
+ @Override
+ public int doTest() {
+ // wait until automatically stops
+ Log.d(TAG, "wait until video recording stops");
+ long time_s = System.currentTimeMillis();
+ long video_time_s = mPreview.getVideoTime(false);
+ // simulate remaining memory now being reduced, so we don't keep trying to restart
+ mActivity.getApplicationInterface().test_available_memory = 10000000;
+ while( mPreview.isVideoRecording() ) {
+ assertTrue( System.currentTimeMillis() - time_s <= 35000 );
+ long video_time = mPreview.getVideoTime(false);
+ assertTrue( video_time >= video_time_s );
+ }
+ Log.d(TAG, "video recording now stopped");
+ // now allow time for video recording to properly shut down
+ try {
+ Thread.sleep(1000);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+ Log.d(TAG, "done waiting");
+
+ return 1;
+ }
+ }, 5000, true, 0);
+ }
+
+ /** Set available memory small enough to make sure we don't even attempt to record video.
+ */
+ public void testTakeVideoAvailableMemory2() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoAvailableMemory2");
+
+ setToDefault();
+
+ mActivity.getApplicationInterface().test_set_available_memory = true;
+ mActivity.getApplicationInterface().test_available_memory = 5000000;
+
+ subTestTakeVideo(false, false, true, false, new TestUtils.VideoTestCallback() {
+ @Override
+ public int doTest() {
+ // wait until automatically stops
+ Log.d(TAG, "wait until video recording stops");
+ assertFalse( mPreview.isVideoRecording() );
+ Log.d(TAG, "video recording now stopped");
+ return 0;
+ }
+ }, 5000, true, 0);
+ }
+
+ /** Set maximum filesize so that we get approx 3s of video time. Check that recording stops and restarts within 10s.
+ * Then check recording stops again within 10s.
+ * On Android 8+, we use MediaRecorder.setNextOutputFile() (see Preview.onVideoInfo()), so instead we just wait 10s and
+ * check video is still recording, then expect at least 2 resultant video files. If this fails on Android 8+, ensure
+ * that the video lengths aren't too short (if less than 3s, we sometimes seem to fall back to the pre-Android 8
+ * behaviour, presumably because setNextOutputFile() can't take effect in time).
+ * This test is fine-tuned to Nexus 6, OnePlus 3T, Nokia 8, Galaxy S10e, as we measure hitting max filesize based on time.
+ */
+ public void testTakeVideoMaxFileSize1() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoMaxFileSize1");
+
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ) {
+ // as not fine-tuned to pre-Android 5 devices
+ return;
+ }
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ boolean is_nokia = Build.MANUFACTURER.toLowerCase(Locale.US).contains("hmd global");
+ boolean is_samsung = Build.MANUFACTURER.toLowerCase(Locale.US).contains("samsung");
+ if( is_nokia || is_samsung ) {
+ // Nokia 8 has much smaller video sizes, at least when recording with phone face down, so we also set 4K
+ editor.putString(PreferenceKeys.getVideoQualityPreferenceKey(mPreview.getCameraId(), mActivity.getApplicationInterface().getCameraIdSPhysicalPref(), false), "" + CamcorderProfile.QUALITY_HIGH); // set to highest quality (4K on Nexus 6)
+ //editor.putString(PreferenceKeys.VideoMaxFileSizePreferenceKey, "2000000"); // approx 3s on Nokia 8 at 4K
+ editor.putString(PreferenceKeys.VideoMaxFileSizePreferenceKey, "10000000"); // approx 3s on Nokia 8 at 4K
+ }
+ else {
+ //editor.putString(PreferenceKeys.getVideoQualityPreferenceKey(mPreview.getCameraId()), "" + CamcorderProfile.QUALITY_HIGH); // set to highest quality (4K on Nexus 6)
+ //editor.putString(PreferenceKeys.VideoMaxFileSizePreferenceKey, "15728640"); // approx 3-4s on Nexus 6 at 4K
+ editor.putString(PreferenceKeys.VideoMaxFileSizePreferenceKey, "9437184"); // approx 3-4s on Nexus 6 at FullHD
+ }
+ editor.apply();
+ updateForSettings();
+
+ int n_new_files = subTestTakeVideo(false, false, false, false, new TestUtils.VideoTestCallback() {
+ @Override
+ public int doTest() {
+ if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ) {
+ assertTrue(mPreview.isVideoRecording());
+
+ long video_time = mPreview.getVideoTime(false);
+ long video_time_this_file = mPreview.getVideoTime(true);
+ assertEquals(video_time, video_time_this_file);
+
+ Log.d(TAG, "wait");
+ try {
+ Thread.sleep(10000);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+ Log.d(TAG, "check still recording");
+ assertTrue(mPreview.isVideoRecording());
+ return -1; // the number of videos recorded can vary, as the max duration corresponding to max filesize can vary wildly, so we check the number of files afterwards (below)
+ }
+
+ // pre-Android 8 code:
+
+ // wait until automatically stops
+ Log.d(TAG, "wait until video recording stops");
+ long time_s = System.currentTimeMillis();
+ long video_time_s = mPreview.getVideoTime(false);
+ while( mPreview.isVideoRecording() ) {
+ assertTrue( System.currentTimeMillis() - time_s <= 8000 );
+ long video_time = mPreview.getVideoTime(false);
+ assertTrue( video_time >= video_time_s );
+ }
+ Log.d(TAG, "video recording now stopped - wait for restart");
+ video_time_s = mPreview.getVideoAccumulatedTime();
+ Log.d(TAG, "video_time_s: " + video_time_s);
+ // now ensure we'll restart within a reasonable time
+ time_s = System.currentTimeMillis();
+ while( !mPreview.isVideoRecording() ) {
+ long c_time = System.currentTimeMillis();
+ if( c_time - time_s > 10000 ) {
+ Log.e(TAG, "time: " + (c_time - time_s));
+ }
+ assertTrue( c_time - time_s <= 10000 );
+ }
+ // wait for stop again
+ time_s = System.currentTimeMillis();
+ while( mPreview.isVideoRecording() ) {
+ long c_time = System.currentTimeMillis();
+ if( c_time - time_s > 10000 ) {
+ Log.e(TAG, "time: " + (c_time - time_s));
+ }
+ assertTrue( c_time - time_s <= 10000 );
+ long video_time = mPreview.getVideoTime(false);
+ if( video_time < video_time_s )
+ Log.d(TAG, "compare: " + video_time_s + " to " + video_time);
+ assertTrue( video_time + 1 >= video_time_s );
+ }
+ Log.d(TAG, "video recording now stopped again");
+
+ // now start again
+ time_s = System.currentTimeMillis();
+ while( !mPreview.isVideoRecording() ) {
+ long c_time = System.currentTimeMillis();
+ if( c_time - time_s > 10000 ) {
+ Log.e(TAG, "time: " + (c_time - time_s));
+ }
+ assertTrue( c_time - time_s <= 10000 );
+ }
+ try {
+ Thread.sleep(1000);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+
+ // now properly stop - need to wait first so that stopping video isn't ignored (due to too quick after video start)
+ try {
+ Thread.sleep(1000);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ Log.d(TAG, "about to click stop video");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking stop video");
+ getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ Log.d(TAG, "wait for stop");
+ try {
+ Thread.sleep(1000);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+ Log.d(TAG, "done wait for stop");
+ return -1; // the number of videos recorded can vary, as the max duration corresponding to max filesize can vary widly
+ }
+ }, 5000, true, 0);
+
+ if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ) {
+ assertTrue( n_new_files >= 2 );
+ }
+
+ // if we've restarted, the total video time should be longer than the video time for the most recent file
+ long video_time = mPreview.getVideoTime(false);
+ long video_time_this_file = mPreview.getVideoTime(true);
+ Log.d(TAG, "video_time: " + video_time);
+ Log.d(TAG, "video_time_this_file: " + video_time_this_file);
+ assertTrue(video_time > video_time_this_file + 1000);
+ }
+
+ /** Max filesize is for ~4.5s, and max duration is 5s, check we only get 1 video.
+ * This test is fine-tuned to OnePlus 3T, as we measure hitting max filesize based on time.
+ */
+ public void testTakeVideoMaxFileSize2() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoMaxFileSize2");
+
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ) {
+ // as not fine-tuned to pre-Android 5 devices
+ return;
+ }
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.getVideoQualityPreferenceKey(mPreview.getCameraId(), mActivity.getApplicationInterface().getCameraIdSPhysicalPref(), false), "" + CamcorderProfile.QUALITY_HIGH); // set to highest quality (4K on Nexus 6 or OnePlus 3T)
+ //editor.putString(PreferenceKeys.VideoMaxFileSizePreferenceKey, "23592960"); // approx 4.5s on Nexus 6 at 4K
+ editor.putString(PreferenceKeys.VideoMaxFileSizePreferenceKey, "35389440"); // approx 4.5s on OnePlus 3T at 4K
+ editor.putString(PreferenceKeys.VideoMaxDurationPreferenceKey, "5");
+ editor.apply();
+ updateForSettings();
+
+ subTestTakeVideo(false, false, false, false, new TestUtils.VideoTestCallback() {
+ @Override
+ public int doTest() {
+ // wait until automatically stops
+ Log.d(TAG, "wait until video recording stops");
+ long time_s = System.currentTimeMillis();
+ long video_time_s = mPreview.getVideoTime(false);
+ while( mPreview.isVideoRecording() ) {
+ assertTrue( System.currentTimeMillis() - time_s <= 8000 );
+ long video_time = mPreview.getVideoTime(false);
+ assertTrue( video_time >= video_time_s );
+ }
+ Log.d(TAG, "video recording now stopped - check we don't restart");
+ video_time_s = mPreview.getVideoAccumulatedTime();
+ Log.d(TAG, "video_time_s: " + video_time_s);
+ // now ensure we don't restart
+ time_s = System.currentTimeMillis();
+ while( System.currentTimeMillis() - time_s <= 5000 ) {
+ assertFalse( mPreview.isVideoRecording() );
+ }
+ return 1;
+ }
+ }, 5000, true, 0);
+ }
+
+ /* Max filesize for ~5s, max duration 7s, max n_repeats 1 - to ensure we're not repeating indefinitely.
+ * This test is fine-tuned to OnePlus 3T, as we measure hitting max filesize based on time.
+ */
+ public void testTakeVideoMaxFileSize3() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoMaxFileSize3");
+
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ) {
+ // as not fine-tuned to pre-Android 5 devices
+ return;
+ }
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.getVideoQualityPreferenceKey(mPreview.getCameraId(), mActivity.getApplicationInterface().getCameraIdSPhysicalPref(), false), "" + CamcorderProfile.QUALITY_HIGH); // set to highest quality (4K on Nexus 6)
+ //editor.putString(PreferenceKeys.VideoMaxFileSizePreferenceKey, "26214400"); // approx 5s on Nexus 6 at 4K
+ //editor.putString(PreferenceKeys.VideoMaxFileSizePreferenceKey, "15728640"); // approx 5s on Nexus 6 at 4K
+ editor.putString(PreferenceKeys.VideoMaxFileSizePreferenceKey, "26214400"); // approx 5s on OnePlus 3T at 4K
+ editor.putString(PreferenceKeys.VideoMaxDurationPreferenceKey, "7");
+ editor.putString(PreferenceKeys.VideoRestartPreferenceKey, "1");
+ editor.apply();
+ updateForSettings();
+
+ subTestTakeVideo(false, false, false, false, new TestUtils.VideoTestCallback() {
+ @Override
+ public int doTest() {
+ // wait until we should have stopped - 2x7s, but add 6s for each of 4 restarts
+ Log.d(TAG, "wait until video recording completely stopped");
+ try {
+ Thread.sleep(38000);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+ Log.d(TAG, "ensure we've really stopped");
+ long time_s = System.currentTimeMillis();
+ while( System.currentTimeMillis() - time_s <= 5000 ) {
+ assertFalse( mPreview.isVideoRecording() );
+ }
+ return -1; // the number of videos recorded can very, as the max duration corresponding to max filesize can vary widly
+ }
+ }, 5000, true, 0);
+ }
+
+
+ private void subTestTakeVideoMaxFileSize4() throws InterruptedException {
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.O ) {
+ // as this tests Android 8+'s seamless restart
+ return;
+ }
+ if( TestUtils.isEmulator() ) {
+ // as test takes about 6.5 minutes on emulator (possibly due to unusual video file sizes), even though it does eventually pass
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.getVideoQualityPreferenceKey(mPreview.getCameraId(), mActivity.getApplicationInterface().getCameraIdSPhysicalPref(), false), "" + CamcorderProfile.QUALITY_HIGH); // set to highest quality (4K on Galaxy S10e)
+ editor.putString(PreferenceKeys.VideoMaxFileSizePreferenceKey, "30000000"); // about 19s on Galaxy S10e at 4K
+ editor.apply();
+ updateForSettings();
+
+ subTestTakeVideo(false, false, false, false, new TestUtils.VideoTestCallback() {
+ @Override
+ public int doTest() {
+ assertTrue(mPreview.isVideoRecording());
+ assertFalse(mPreview.test_started_next_output_file);
+
+ while( !mPreview.test_called_next_output_file ) {
+ Log.d(TAG, "waiting for test_called_next_output_file");
+ try {
+ Thread.sleep(100);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+ }
+
+ Log.d(TAG, "test_called_next_output_file is now set");
+ assertTrue(mPreview.isVideoRecording());
+
+ // If this fails, it means we already started recording on the next output test, so
+ // can't test what we wanted to test (i.e., that we don't create a zero-length video
+ // file):
+ assertFalse(mPreview.test_started_next_output_file);
+
+ // wait a little bit longer... but needs to be before seamless restart actually occurs!
+ try {
+ Thread.sleep(100);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+
+ // If this fails, it means we already started recording on the next output test, so
+ // can't test what we wanted to test (i.e., that we don't create a zero-length video
+ // file):
+ assertFalse(mPreview.test_started_next_output_file);
+
+ return 1;
+ }
+ }, 5000, true, 0);
+ }
+
+ /** Tests stopping video when MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING has been received, but before
+ * we receive MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED. Tests that we delete the leftover zero-length file
+ * that would have been created.
+ */
+ public void testTakeVideoMaxFileSize4() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoMaxFileSize4");
+
+ setToDefault();
+
+ subTestTakeVideoMaxFileSize4();
+ }
+
+ /** As testTakeVideoMaxFileSize4(), but using Storage Access Framework.
+ * N.B., failing on Android 12+ (e.g., Galaxy S10e, Pixel 6 Pro) due to receiving MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED
+ * shortly after MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING. Seems to be issue specific to using SAF with too small
+ * maximum file sizes.
+ */
+ public void testTakeVideoMaxFileSize4SAF() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoMaxFileSize4SAF");
+
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ) {
+ Log.d(TAG, "SAF requires Android Lollipop or better");
+ return;
+ }
+
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.UsingSAFPreferenceKey, true);
+ editor.putString(PreferenceKeys.SaveLocationSAFPreferenceKey, "content://com.android.externalstorage.documents/tree/primary%3ADCIM%2FOpenCamera");
+ editor.apply();
+ updateForSettings();
+
+ subTestTakeVideoMaxFileSize4();
+ }
+
+ public void testTakeVideoStabilization() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoStabilization");
+
+ setToDefault();
+
+ if( !mPreview.supportsVideoStabilization() ) {
+ Log.d(TAG, "video stabilization not supported");
+ return;
+ }
+ boolean supports_ois = mPreview.supportsOpticalStabilization();
+
+ assertFalse(mPreview.getCameraController().getVideoStabilization());
+ assertEquals(supports_ois, mPreview.getOpticalStabilization()); // OIS should be on if supported
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.VideoStabilizationPreferenceKey, true);
+ editor.apply();
+ updateForSettings();
+
+ // video stabilization should only actually be enabled when in video mode
+ assertFalse(mPreview.isVideo());
+ assertFalse(mPreview.getCameraController().getVideoStabilization());
+ assertEquals(supports_ois, mPreview.getOpticalStabilization()); // OIS should be on if supported
+
+ // now switch to video mode
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertTrue(mPreview.isVideo());
+ assertTrue(mPreview.getCameraController().getVideoStabilization());
+ assertFalse(mPreview.getCameraController().getOpticalStabilization()); // OIS should always be disabled when using digital video stabilization
+
+ subTestTakeVideo(false, false, false, false, null, 5000, false, 0);
+
+ assertTrue(mPreview.isVideo());
+ assertTrue(mPreview.getCameraController().getVideoStabilization());
+ assertFalse(mPreview.getCameraController().getOpticalStabilization()); // OIS should always be disabled when using digital video stabilization
+
+ // restart when in video mode, and ensure still as expected
+ restart();
+ switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ Thread.sleep(1000);
+ assertTrue(mPreview.isVideo());
+ assertTrue(mPreview.getCameraController().getVideoStabilization());
+ assertFalse(mPreview.getCameraController().getOpticalStabilization()); // OIS should always be disabled when using digital video stabilization
+
+ // now switch back to photo mode
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertFalse(mPreview.isVideo());
+ assertFalse(mPreview.getCameraController().getVideoStabilization());
+ assertEquals(supports_ois, mPreview.getOpticalStabilization()); // OIS should be on if supported
+ }
+
+ public void testTakeVideoExposureLock() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoExposureLock");
+
+ setToDefault();
+
+ subTestTakeVideo(true, false, false, false, null, 5000, false, 0);
+ }
+
+ public void testTakeVideoFocusArea() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoFocusArea");
+
+ setToDefault();
+
+ subTestTakeVideo(false, true, false, false, null, 5000, false, 0);
+ }
+
+ /** Tests starting and stopping video quickly, to simulate failing to create a video (but needs Open Camera to delete
+ * the corrupt resultant video).
+ */
+ public void testTakeVideoQuick() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoQuick");
+
+ setToDefault();
+
+ mPreview.test_runtime_on_video_stop = true; // as RuntimeException on short delay doesn't seem to occur on Galaxy S10e at least, for 500ms delay
+
+ // still need a short delay (at least 500ms, otherwise Open Camera will ignore the repeated stop)
+ subTestTakeVideo(false, false, false, false, null, 500, false, 0);
+ }
+
+ // If this test fails, make sure we've manually selected that folder (as permission can't be given through the test framework).
+ public void testTakeVideoQuickSAF() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoQuickSAF");
+
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ) {
+ Log.d(TAG, "SAF requires Android Lollipop or better");
+ return;
+ }
+
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.UsingSAFPreferenceKey, true);
+ editor.putString(PreferenceKeys.SaveLocationSAFPreferenceKey, "content://com.android.externalstorage.documents/tree/primary%3ADCIM%2FOpenCamera");
+ editor.apply();
+ updateForSettings();
+
+ mPreview.test_runtime_on_video_stop = true; // as RuntimeException on short delay doesn't seem to occur on Galaxy S10e at least, for 500ms delay
+
+ // still need a short delay (at least 500ms, otherwise Open Camera will ignore the repeated stop)
+ subTestTakeVideo(false, false, false, false, null, 500, false, 0);
+ }
+
+ public void testTakeVideoForceFailure() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoForceFailure");
+
+ setToDefault();
+
+ mActivity.getPreview().test_video_failure = true;
+ subTestTakeVideo(false, false, true, false, null, 5000, false, 0);
+ }
+
+ public void testTakeVideoForceFailureSAF() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoForceFailureSAF");
+
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ) {
+ Log.d(TAG, "SAF requires Android Lollipop or better");
+ return;
+ }
+
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.UsingSAFPreferenceKey, true);
+ editor.putString(PreferenceKeys.SaveLocationSAFPreferenceKey, "content://com.android.externalstorage.documents/tree/primary%3ADCIM%2FOpenCamera");
+ editor.apply();
+ updateForSettings();
+
+ mActivity.getPreview().test_video_failure = true;
+ subTestTakeVideo(false, false, true, false, null, 5000, false, 0);
+ }
+
+ public void testTakeVideoForceIOException() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoForceIOException");
+
+ setToDefault();
+
+ mActivity.getPreview().test_video_ioexception = true;
+ subTestTakeVideo(false, false, true, false, null, 5000, false, 0);
+ }
+
+ public void testTakeVideoForceCameraControllerException() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoForceCameraControllerException");
+
+ setToDefault();
+
+ mActivity.getPreview().test_video_cameracontrollerexception = true;
+ subTestTakeVideo(false, false, true, false, null, 5000, false, 0);
+ }
+
+ /* Test can be reliable on some devices, test no longer run as part of test suites.
+ */
+ public void testTakeVideo4K() throws InterruptedException {
+ Log.d(TAG, "testTakeVideo4K");
+
+ setToDefault();
+
+ if( !mActivity.supportsForceVideo4K() ) {
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.ForceVideo4KPreferenceKey, true);
+ editor.apply();
+ updateForSettings();
+
+ subTestTakeVideo(false, false, true, false, null, 5000, false, 0);
+ }
+
+ /** Will likely be unreliable on OnePlus 3T and Galaxy S10e with Camera2.
+ * Also hangs with old camera API on Pixel 6 Pro.
+ */
+ public void testTakeVideoFPS() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoFPS");
+
+ setToDefault();
+ // different frame rates only reliable for Camera2, but at least make sure we don't crash on old api
+ final int [] fps_values = mPreview.usingCamera2API() ? new int[]{15, 25, 30, 60, 120, 240} : new int[]{30};
+ for(int fps_value : fps_values) {
+ if( mPreview.usingCamera2API() ) {
+ if( mPreview.getVideoQualityHander().videoSupportsFrameRate(fps_value) ) {
+ Log.d(TAG, "fps supported at normal speed: " + fps_value);
+ }
+ else if( mPreview.getVideoQualityHander().videoSupportsFrameRateHighSpeed(fps_value) ) {
+ Log.d(TAG, "fps supported at HIGH SPEED: " + fps_value);
+ }
+ else {
+ Log.d(TAG, "fps is NOT supported: " + fps_value);
+ continue;
+ }
+ boolean expect_high_speed;
+ boolean is_samsung = Build.MANUFACTURER.toLowerCase(Locale.US).contains("samsung");
+ boolean is_google = Build.MANUFACTURER.toLowerCase(Locale.US).contains("google");
+ if( is_samsung || is_google ) {
+ // tested on Galaxy S10e, Galaxy S24+, Pixel 6 Pro at least
+ expect_high_speed = (fps_value > 60);
+ }
+ else {
+ expect_high_speed = (fps_value >= 60);
+ }
+ assertEquals(expect_high_speed, mPreview.fpsIsHighSpeed(String.valueOf(fps_value)));
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.getVideoFPSPreferenceKey(mPreview.getCameraId(), mActivity.getApplicationInterface().getCameraIdSPhysicalPref()), String.valueOf(fps_value));
+ editor.apply();
+ updateForSettings();
+
+ Log.d(TAG, "test video with fps: " + fps_value);
+ //boolean allow_failure = fps_value.equals("24") || fps_value.equals("25") || fps_value.equals("60");
+ boolean allow_failure = false;
+ subTestTakeVideo(false, false, allow_failure, false, null, 5000, false, 0);
+ }
+ }
+
+ /** Will likely be unreliable on OnePlus 3T, Galaxy S10e.
+ * Manual mode should be ignored by high speed video, but check this doesn't crash at least!
+ */
+ public void testTakeVideoFPSHighSpeedManual() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoFPSHighSpeedManual");
+
+ setToDefault();
+
+ if( !mPreview.usingCamera2API() ) {
+ return;
+ }
+ else if( !mPreview.supportsISORange() ) {
+ return;
+ }
+
+ int fps_value = 120;
+ if( mPreview.getVideoQualityHander().videoSupportsFrameRate(fps_value) ) {
+ Log.d(TAG, "fps supported at normal speed: " + fps_value);
+ return;
+ }
+ else if( !mPreview.getVideoQualityHander().videoSupportsFrameRateHighSpeed(fps_value) ) {
+ Log.d(TAG, "fps is NOT supported: " + fps_value);
+ return;
+ }
+
+ assertTrue( mPreview.fpsIsHighSpeed(String.valueOf(fps_value)) );
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.getVideoFPSPreferenceKey(mPreview.getCameraId(), mActivity.getApplicationInterface().getCameraIdSPhysicalPref()), String.valueOf(fps_value));
+ editor.apply();
+ updateForSettings();
+
+ Log.d(TAG, "test video with fps: " + fps_value);
+
+ switchToISO(100);
+
+ View exposureButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+
+ // switch to video mode, ensure that exposure button disappears due to high speed video
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertTrue(mPreview.isVideo());
+ assertEquals(exposureButton.getVisibility(), View.GONE);
+
+ // test recording video
+ subTestTakeVideo(false, false, false, false, null, 5000, false, 0);
+
+ // switch to photo mode, ensure that exposure button re-appears
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertFalse(mPreview.isVideo());
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ }
+
+ /** Tests that video resolutions are stored separately for high speed fps, for Camera2.
+ */
+ public void testVideoFPSHighSpeed() {
+ Log.d(TAG, "testVideoFPSHighSpeed");
+
+ setToDefault();
+
+ if( !mPreview.usingCamera2API() ) {
+ return;
+ }
+
+ int fps_value = 120;
+ if( mPreview.getVideoQualityHander().videoSupportsFrameRate(fps_value) ) {
+ Log.d(TAG, "fps supported at normal speed: " + fps_value);
+ return;
+ }
+ else if( !mPreview.getVideoQualityHander().videoSupportsFrameRateHighSpeed(fps_value) ) {
+ Log.d(TAG, "fps is NOT supported: " + fps_value);
+ return;
+ }
+
+ assertTrue( mPreview.fpsIsHighSpeed(String.valueOf(fps_value)) );
+
+ // switch to video mode
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertTrue(mPreview.isVideo());
+
+ // get initial video resolution, for non-high-speed
+ String saved_quality = mActivity.getApplicationInterface().getVideoQualityPref();
+ VideoProfile profile = mPreview.getVideoProfile();
+ int saved_video_width = profile.videoFrameWidth;
+ int saved_video_height = profile.videoFrameHeight;
+ Log.d(TAG, "saved_quality: " + saved_quality);
+ Log.d(TAG, "saved_video_width: " + saved_video_width);
+ Log.d(TAG, "saved_video_height: " + saved_video_height);
+
+ // switch to high speed fps
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.getVideoFPSPreferenceKey(mPreview.getCameraId(), mActivity.getApplicationInterface().getCameraIdSPhysicalPref()), String.valueOf(fps_value));
+ editor.apply();
+ updateForSettings();
+
+ Log.d(TAG, "test video with fps: " + fps_value);
+
+ // change video resolution
+ List video_sizes = mPreview.getSupportedVideoQuality(mActivity.getApplicationInterface().getVideoFPSPref());
+ // find current index
+ int video_size_index = -1;
+ for(int i=0;i video_size_index+1);
+ video_size_index++;
+ String quality = video_sizes.get(video_size_index);
+ settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ editor = settings.edit();
+ editor.putString(PreferenceKeys.getVideoQualityPreferenceKey(mPreview.getCameraId(), mActivity.getApplicationInterface().getCameraIdSPhysicalPref(), mActivity.getApplicationInterface().fpsIsHighSpeed()), quality);
+ editor.apply();
+ updateForSettings();
+
+ quality = mActivity.getApplicationInterface().getVideoQualityPref();
+ profile = mPreview.getVideoProfile();
+ int video_width = profile.videoFrameWidth;
+ int video_height = profile.videoFrameHeight;
+ Log.d(TAG, "quality: " + quality);
+ Log.d(TAG, "video_width: " + video_width);
+ Log.d(TAG, "video_height: " + video_height);
+ assertNotEquals(saved_quality, quality);
+ assertFalse(video_width == saved_video_width && video_height == saved_video_height);
+ String high_speed_quality = quality;
+ int high_speed_video_width = video_width;
+ int high_speed_video_height = video_height;
+
+ // switch to normal fps
+ Log.d(TAG, "switch to normal fps");
+ settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ editor = settings.edit();
+ editor.putString(PreferenceKeys.getVideoFPSPreferenceKey(mPreview.getCameraId(), mActivity.getApplicationInterface().getCameraIdSPhysicalPref()), "30");
+ editor.apply();
+ updateForSettings();
+
+ // check resolution reverts to original
+ quality = mActivity.getApplicationInterface().getVideoQualityPref();
+ profile = mPreview.getVideoProfile();
+ video_width = profile.videoFrameWidth;
+ video_height = profile.videoFrameHeight;
+ Log.d(TAG, "quality: " + quality);
+ Log.d(TAG, "video_width: " + video_width);
+ Log.d(TAG, "video_height: " + video_height);
+ assertEquals(saved_quality, quality);
+ assertTrue(video_width == saved_video_width && video_height == saved_video_height);
+
+ // switch to high speed fps again
+ Log.d(TAG, "switch to high speed fps again");
+ settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ editor = settings.edit();
+ editor.putString(PreferenceKeys.getVideoFPSPreferenceKey(mPreview.getCameraId(), mActivity.getApplicationInterface().getCameraIdSPhysicalPref()), String.valueOf(fps_value));
+ editor.apply();
+ updateForSettings();
+
+ // check resolution reverts to high speed
+ quality = mActivity.getApplicationInterface().getVideoQualityPref();
+ profile = mPreview.getVideoProfile();
+ video_width = profile.videoFrameWidth;
+ video_height = profile.videoFrameHeight;
+ Log.d(TAG, "quality: " + quality);
+ Log.d(TAG, "video_width: " + video_width);
+ Log.d(TAG, "video_height: " + video_height);
+ assertEquals(high_speed_quality, quality);
+ assertTrue(video_width == high_speed_video_width && video_height == high_speed_video_height);
+ }
+
+ /** Will likely be unreliable on OnePlus 3T, Galaxy S10e.
+ */
+ public void testTakeVideoSlowMotion() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoSlowMotion");
+
+ setToDefault();
+
+ if( !mPreview.usingCamera2API() ) {
+ return;
+ }
+
+ List supported_capture_rates = mActivity.getApplicationInterface().getSupportedVideoCaptureRates();
+ if( supported_capture_rates.size() <= 1 ) {
+ Log.d(TAG, "slow motion not supported");
+ return;
+ }
+
+ float capture_rate = supported_capture_rates.get(0);
+ if( capture_rate > 1.0f-1.0e-5f ) {
+ Log.d(TAG, "slow motion not supported");
+ return;
+ }
+ Log.d(TAG, "capture_rate: " + capture_rate);
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putFloat(PreferenceKeys.getVideoCaptureRatePreferenceKey(mPreview.getCameraId(), mActivity.getApplicationInterface().getCameraIdSPhysicalPref()), capture_rate);
+ editor.apply();
+ updateForSettings();
+
+ // switch to video, and check we've set a high speed fps
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertTrue(mPreview.isVideo());
+
+ String fps_value = mActivity.getApplicationInterface().getVideoFPSPref();
+ int fps = Integer.parseInt(fps_value);
+ Log.d(TAG, "fps: " + fps);
+ assertTrue(fps >= 60);
+ assertTrue(mPreview.isVideoHighSpeed());
+
+ // check video profile
+ VideoProfile profile = mPreview.getVideoProfile();
+ assertEquals(profile.videoCaptureRate, fps, 1.0e-5);
+ assertEquals((float)profile.videoFrameRate, (float)(profile.videoCaptureRate*capture_rate), 1.0e-5);
+
+ boolean allow_failure = false;
+ subTestTakeVideo(false, false, allow_failure, false, null, 5000, false, 0);
+ }
+
+ /** Take video with timelapse mode.
+ * Fails on Pixel 6 Pro with old camera API.
+ */
+ public void testTakeVideoTimeLapse() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoTimeLapse");
+
+ setToDefault();
+
+ if( TestUtils.isEmulator() ) {
+ // fails on Android emulator (at least for Android 7.1 on VirtualScene camera) - and can also leave camera in a state that can't be opened for subsequent tests
+ return;
+ }
+
+ List supported_capture_rates = mActivity.getApplicationInterface().getSupportedVideoCaptureRates();
+ if( supported_capture_rates.size() <= 1 ) {
+ Log.d(TAG, "timelapse not supported");
+ return;
+ }
+
+ float capture_rate = -1.0f;
+ // find the first timelapse rate
+ for(float this_capture_rate : supported_capture_rates) {
+ if( this_capture_rate > 1.0f+1.0e-5f ) {
+ capture_rate = this_capture_rate;
+ break;
+ }
+ }
+ if( capture_rate < 0.0f ) {
+ Log.d(TAG, "timelapse not supported");
+ return;
+ }
+ Log.d(TAG, "capture_rate: " + capture_rate);
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putFloat(PreferenceKeys.getVideoCaptureRatePreferenceKey(mPreview.getCameraId(), mActivity.getApplicationInterface().getCameraIdSPhysicalPref()), capture_rate);
+ editor.apply();
+ updateForSettings();
+
+ // switch to video, and check we've set a non-high speed fps
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertTrue(mPreview.isVideo());
+
+ String fps_value = mActivity.getApplicationInterface().getVideoFPSPref();
+ Log.d(TAG, "fps_value: " + fps_value);
+ assertEquals("default", fps_value);
+ assertFalse(mPreview.isVideoHighSpeed());
+
+ // check video profile
+ VideoProfile profile = mPreview.getVideoProfile();
+ // note, need to allow a larger delta, due to the fudge factor applied for 2x timelapse
+ assertEquals((float)profile.videoFrameRate, (float)(profile.videoCaptureRate*capture_rate), 5.0e-3);
+
+ boolean allow_failure = false;
+ subTestTakeVideo(false, false, allow_failure, false, null, 5000, false, 0);
+ }
+
+ /* Test can be reliable on some devices, test no longer run as part of test suites.
+ */
+ public void testTakeVideoBitrate() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoBitrate");
+
+ setToDefault();
+ final String [] bitrate_values = new String[]{"1000000", "10000000", "20000000", "50000000"};
+ //final String [] bitrate_values = new String[]{"1000000", "10000000", "20000000", "30000000"};
+ for(String bitrate_value : bitrate_values) {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.VideoBitratePreferenceKey, bitrate_value);
+ editor.apply();
+ updateForSettings();
+
+ Log.d(TAG, "test video with bitrate: " + bitrate_value);
+ boolean allow_failure = bitrate_value.equals("30000000") || bitrate_value.equals("50000000");
+ subTestTakeVideo(false, false, allow_failure, false, null, 5000, false, 0);
+ }
+ }
+
+ /* Test recording video with a flat (log) profile.
+ */
+ public void testVideoLogProfile() throws InterruptedException {
+ Log.d(TAG, "testVideoLogProfile");
+
+ setToDefault();
+
+ if( !mPreview.supportsTonemapCurve() ) {
+ Log.d(TAG, "test requires tonemap curve");
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.VideoLogPreferenceKey, "strong");
+ editor.apply();
+ updateForSettings();
+
+ subTestTakeVideo(false, false, true, false, null, 5000, false, 0);
+
+ assertTrue( mPreview.getCameraController().test_used_tonemap_curve );
+ }
+
+ /* Test recording video with a flat (jtlog) profile.
+ */
+ public void testVideoJTLogProfile() throws InterruptedException {
+ Log.d(TAG, "testVideoJTLogProfile");
+
+ setToDefault();
+
+ if( !mPreview.supportsTonemapCurve() ) {
+ Log.d(TAG, "test requires tonemap curve");
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.VideoLogPreferenceKey, "jtlog");
+ editor.apply();
+ updateForSettings();
+
+ subTestTakeVideo(false, false, true, false, null, 5000, false, 0);
+
+ assertTrue( mPreview.getCameraController().test_used_tonemap_curve );
+ }
+
+ /* Test recording video with custom gamma profile.
+ */
+ public void testVideoGammaProfile() throws InterruptedException {
+ Log.d(TAG, "testVideoGammaProfile");
+
+ setToDefault();
+
+ if( !mPreview.supportsTonemapCurve() ) {
+ Log.d(TAG, "test requires tonemap curve");
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.VideoLogPreferenceKey, "gamma");
+ editor.putString(PreferenceKeys.VideoProfileGammaPreferenceKey, "3.0");
+ editor.apply();
+ updateForSettings();
+
+ subTestTakeVideo(false, false, true, false, null, 5000, false, 0);
+
+ assertTrue( mPreview.getCameraController().test_used_tonemap_curve );
+ }
+
+ /* Test recording video with non-default edge and noise reduction modes.
+ */
+ public void testVideoEdgeModeNoiseReductionMode() throws InterruptedException {
+ Log.d(TAG, "testVideoEdgeModeNoiseReductionMode");
+
+ setToDefault();
+
+ if( !mPreview.usingCamera2API() ) {
+ Log.d(TAG, "test requires camera2 api");
+ return;
+ }
+
+ CameraController2 camera_controller2 = (CameraController2)mPreview.getCameraController();
+ CaptureRequest.Builder previewBuilder = camera_controller2.testGetPreviewBuilder();
+ Integer default_edge_mode = previewBuilder.get(CaptureRequest.EDGE_MODE);
+ Integer default_noise_reduction_mode = previewBuilder.get(CaptureRequest.NOISE_REDUCTION_MODE);
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.EdgeModePreferenceKey, "off");
+ editor.putString(PreferenceKeys.CameraNoiseReductionModePreferenceKey, "fast");
+ editor.apply();
+ updateForSettings();
+
+ if( mPreview.getSupportedEdgeModes() != null ) {
+ Integer new_edge_mode = previewBuilder.get(CaptureRequest.EDGE_MODE);
+ assertEquals(CameraMetadata.EDGE_MODE_OFF, new_edge_mode.intValue());
+ }
+ if( mPreview.getSupportedNoiseReductionModes() != null ) {
+ Integer new_noise_reduction_mode = previewBuilder.get(CaptureRequest.NOISE_REDUCTION_MODE);
+ assertEquals(CameraMetadata.NOISE_REDUCTION_MODE_FAST, new_noise_reduction_mode.intValue());
+ }
+
+ subTestTakeVideo(false, false, true, false, null, 5000, false, 0);
+
+ editor = settings.edit();
+ editor.putString(PreferenceKeys.EdgeModePreferenceKey, "default");
+ editor.putString(PreferenceKeys.CameraNoiseReductionModePreferenceKey, "default");
+ editor.apply();
+ updateForSettings();
+
+ camera_controller2 = (CameraController2)mPreview.getCameraController();
+ previewBuilder = camera_controller2.testGetPreviewBuilder();
+ if( mPreview.getSupportedEdgeModes() != null ) {
+ Integer new_edge_mode = previewBuilder.get(CaptureRequest.EDGE_MODE);
+ assertEquals(default_edge_mode, new_edge_mode);
+ }
+ if( mPreview.getSupportedNoiseReductionModes() != null ) {
+ Integer new_noise_reduction_mode = previewBuilder.get(CaptureRequest.NOISE_REDUCTION_MODE);
+ assertEquals(default_noise_reduction_mode, new_noise_reduction_mode);
+ }
+ }
+
+ private void subTestTakeVideoMaxDuration(boolean restart, boolean interrupt) throws InterruptedException {
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.VideoMaxDurationPreferenceKey, "15");
+ if( restart ) {
+ editor.putString(PreferenceKeys.VideoRestartPreferenceKey, "1");
+ }
+ editor.apply();
+ }
+
+ assertTrue(mPreview.isPreviewStarted());
+
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ if( !mPreview.isVideo() ) {
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ }
+ assertTrue(mPreview.isVideo());
+ assertTrue(mPreview.isPreviewStarted());
+
+ // count initial files in folder
+ int n_files = getNFiles();
+ Log.d(TAG, "n_files at start: " + n_files);
+
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ boolean has_audio_control_button = !sharedPreferences.getString(PreferenceKeys.AudioControlPreferenceKey, "none").equals("none");
+
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ View switchMultiCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_multi_camera);
+ //View flashButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.flash);
+ //View focusButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.focus_mode);
+ View exposureButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureLockButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure_lock);
+ View audioControlButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.audio_control);
+ View popupButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.popup);
+ View trashButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.trash);
+ View shareButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.share);
+ assertEquals(switchCameraButton.getVisibility(), (mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE));
+ assertEquals(switchMultiCameraButton.getVisibility(), (mActivity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE));
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+ // flash and focus etc default visibility tested in another test
+ // but store status to compare with later
+ //int flashVisibility = flashButton.getVisibility();
+ //int focusVisibility = focusButton.getVisibility();
+ int exposureVisibility = exposureButton.getVisibility();
+ int exposureLockVisibility = exposureLockButton.getVisibility();
+ assertEquals(audioControlButton.getVisibility(), (has_audio_control_button ? View.VISIBLE : View.GONE));
+ assertEquals(popupButton.getVisibility(), View.VISIBLE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+
+ // workaround for Android 7.1 bug at https://stackoverflow.com/questions/47548317/what-belong-is-badtokenexception-at-classes-of-project
+ // without this, we get a crash due to that problem on Nexus (old API at least) in testTakeVideoMaxDuration
+ Thread.sleep(1000);
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ Log.d(TAG, "about to click take video");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take video");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+
+ assertTrue( mPreview.isVideoRecording() );
+
+ assertEquals(switchCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchMultiCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchVideoButton.getVisibility(), View.GONE);
+ //assertTrue(flashButton.getVisibility() == flashVisibility);
+ //assertTrue(focusButton.getVisibility() == View.GONE);
+ assertEquals(exposureButton.getVisibility(), exposureVisibility);
+ assertEquals(exposureLockButton.getVisibility(), exposureLockVisibility);
+ assertEquals(audioControlButton.getVisibility(), View.GONE);
+ assertEquals(popupButton.getVisibility(), (mPreview.supportsFlash() ? View.VISIBLE : View.GONE)); // popup button only visible when recording video if flash supported
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+
+ Thread.sleep(10000);
+ Log.d(TAG, "check still taking video");
+ assertTrue( mPreview.isVideoRecording() );
+
+ int n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ // note, if using scoped storage without SAF (i.e., mediastore API), then the video file won't show up until after we've finished recording (IS_PENDING is set to 0)
+ assertEquals(MainActivity.useScopedStorage() && !mActivity.getStorageUtils().isUsingSAF() ? 0 : 1, n_new_files);
+
+ if( restart ) {
+ if( interrupt ) {
+ Thread.sleep(5100);
+ restart();
+ Log.d(TAG, "done restart");
+ // now wait, and check we don't crash
+ Thread.sleep(5000);
+ return;
+ }
+ else {
+ Thread.sleep(10000);
+ Log.d(TAG, "check restarted video");
+ assertTrue( mPreview.isVideoRecording() );
+ n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ // as noted above, with mediastore API then the latest file won't be visible yet
+ assertEquals(MainActivity.useScopedStorage() && !mActivity.getStorageUtils().isUsingSAF() ? 1 : 2, n_new_files);
+
+ Thread.sleep(15000);
+ }
+ }
+ else {
+ Thread.sleep(8000);
+ }
+ Log.d(TAG, "check stopped taking video");
+ assertFalse(mPreview.isVideoRecording());
+
+ n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(n_new_files, (restart ? 2 : 1));
+
+ // trash/share only shown when preview is paused after taking a photo
+
+ assertTrue(mPreview.isPreviewStarted()); // check preview restarted
+ assertEquals(switchCameraButton.getVisibility(), (mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE));
+ assertEquals(switchMultiCameraButton.getVisibility(), (mActivity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE));
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+ //assertTrue(flashButton.getVisibility() == flashVisibility);
+ //assertTrue(focusButton.getVisibility() == focusVisibility);
+ assertEquals(exposureButton.getVisibility(), exposureVisibility);
+ assertEquals(exposureLockButton.getVisibility(), exposureLockVisibility);
+ assertEquals(audioControlButton.getVisibility(), (has_audio_control_button ? View.VISIBLE : View.GONE));
+ assertEquals(popupButton.getVisibility(), View.VISIBLE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ }
+
+ /**
+ * Fails on Android emulator, for some reason EXTRA_DURATION_LIMIT makes the video stop due to
+ * hitting max duration immediately.
+ */
+ public void testTakeVideoMaxDuration() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoMaxDuration");
+
+ setToDefault();
+
+ subTestTakeVideoMaxDuration(false, false);
+ }
+
+ /**
+ * Fails on Android emulator, for some reason EXTRA_DURATION_LIMIT makes the video stop due to
+ * hitting max duration immediately.
+ */
+ public void testTakeVideoMaxDurationRestart() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoMaxDurationRestart");
+
+ setToDefault();
+
+ subTestTakeVideoMaxDuration(true, false);
+ }
+
+ /**
+ * Fails on Android emulator, for some reason EXTRA_DURATION_LIMIT makes the video stop due to
+ * hitting max duration immediately.
+ */
+ public void testTakeVideoMaxDurationRestartInterrupt() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoMaxDurationRestartInterrupt");
+
+ setToDefault();
+
+ subTestTakeVideoMaxDuration(true, true);
+ }
+
+ public void testTakeVideoSettings() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoSettings");
+
+ setToDefault();
+
+ assertTrue(mPreview.isPreviewStarted());
+
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ if( !mPreview.isVideo() ) {
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ }
+ assertTrue(mPreview.isVideo());
+ assertTrue(mPreview.isPreviewStarted());
+
+ // count initial files in folder
+ int n_files = getNFiles();
+ Log.d(TAG, "n_files at start: " + n_files);
+
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ Log.d(TAG, "about to click take video");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take video");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+
+ assertTrue( mPreview.isVideoRecording() );
+
+ Thread.sleep(2000);
+ Log.d(TAG, "check still taking video");
+ assertTrue( mPreview.isVideoRecording() );
+
+ int n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ // note, if using scoped storage without SAF (i.e., mediastore API), then the video file won't show up until after we've finished recording (IS_PENDING is set to 0)
+ assertEquals(MainActivity.useScopedStorage() && !mActivity.getStorageUtils().isUsingSAF() ? 0 : 1, n_new_files);
+
+ // now go to settings
+ View settingsButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.settings);
+ Log.d(TAG, "about to click settings");
+ clickView(settingsButton);
+ Log.d(TAG, "done clicking settings");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ assertFalse(mPreview.isVideoRecording());
+
+ n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(1, n_new_files);
+
+ Thread.sleep(500);
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ Log.d(TAG, "on back pressed...");
+ mActivity.onBackPressed();
+ }
+ });
+ // need to wait for UI code to finish before leaving
+ this.getInstrumentation().waitForIdleSync();
+ Thread.sleep(500);
+ assertFalse(mPreview.isVideoRecording());
+
+ Log.d(TAG, "about to click take video");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take video");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+
+ assertTrue( mPreview.isVideoRecording() );
+
+ n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ // see note above about mediastore API
+ assertEquals(MainActivity.useScopedStorage() && !mActivity.getStorageUtils().isUsingSAF() ? 1 : 2, n_new_files);
+
+ }
+
+ /** Switch to non-default focus, go to settings, check still in focus mode that we set, then test recording.
+ */
+ public void testTakeVideoMacro() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoMacro");
+
+ setToDefault();
+
+ if( !mPreview.supportsFocus() ) {
+ return;
+ }
+
+ assertTrue(mPreview.isPreviewStarted());
+
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ if( !mPreview.isVideo() ) {
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ }
+ assertTrue(mPreview.isVideo());
+ assertTrue(mPreview.isPreviewStarted());
+
+ String non_default_focus_mode = getNonDefaultFocus();
+ switchToFocusValue(non_default_focus_mode);
+
+ // now go to settings
+ View settingsButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.settings);
+ Log.d(TAG, "about to click settings");
+ clickView(settingsButton);
+ Log.d(TAG, "done clicking settings");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ assertFalse(mPreview.isVideoRecording());
+
+ Thread.sleep(500);
+
+ // camera should be closed in settings
+ assertNull(mPreview.getCameraController());
+
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ Log.d(TAG, "on back pressed...");
+ mActivity.onBackPressed();
+ }
+ });
+ // need to wait for UI code to finish before leaving
+ this.getInstrumentation().waitForIdleSync();
+ Thread.sleep(500);
+
+ assertEquals(mPreview.getCurrentFocusValue(), non_default_focus_mode);
+
+ // count initial files in folder
+ int n_files = getNFiles();
+ Log.d(TAG, "n_files at start: " + n_files);
+
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ Log.d(TAG, "about to click take video");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take video");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+
+ assertTrue( mPreview.isVideoRecording() );
+
+ Thread.sleep(2000);
+ Log.d(TAG, "check still taking video");
+ assertTrue( mPreview.isVideoRecording() );
+
+ int n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ // note, if using scoped storage without SAF (i.e., mediastore API), then the video file won't show up until after we've finished recording (IS_PENDING is set to 0)
+ assertEquals(MainActivity.useScopedStorage() && !mActivity.getStorageUtils().isUsingSAF() ? 0 : 1, n_new_files);
+ }
+
+ public void testTakeVideoFlashVideo() throws InterruptedException {
+ Log.d(TAG, "testTakeVideoFlashVideo");
+
+ setToDefault();
+
+ if( !mPreview.supportsFlash() ) {
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.VideoFlashPreferenceKey, true);
+ editor.apply();
+ updateForSettings();
+
+ assertTrue(mPreview.isPreviewStarted());
+
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ if( !mPreview.isVideo() ) {
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ }
+ assertTrue(mPreview.isVideo());
+ assertTrue(mPreview.isPreviewStarted());
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ Log.d(TAG, "about to click take video");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take video");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+
+ assertTrue( mPreview.isVideoRecording() );
+
+ Thread.sleep(1500);
+ Log.d(TAG, "check still taking video");
+ assertTrue( mPreview.isVideoRecording() );
+
+ // wait until flash off
+ long time_s = System.currentTimeMillis();
+ while (mPreview.getCameraController().getFlashValue().equals("flash_torch")) {
+ assertTrue(System.currentTimeMillis() - time_s <= 200);
+ }
+
+ // wait until flash on
+ time_s = System.currentTimeMillis();
+ while (!mPreview.getCameraController().getFlashValue().equals("flash_torch")) {
+ assertTrue(System.currentTimeMillis() - time_s <= 1100);
+ }
+
+ // wait until flash off
+ time_s = System.currentTimeMillis();
+ while (mPreview.getCameraController().getFlashValue().equals("flash_torch")) {
+ assertTrue(System.currentTimeMillis() - time_s <= 200);
+ }
+
+ // wait until flash on
+ time_s = System.currentTimeMillis();
+ while (!mPreview.getCameraController().getFlashValue().equals("flash_torch")) {
+ assertTrue(System.currentTimeMillis() - time_s <= 1100);
+ }
+
+ Log.d(TAG, "about to click stop video");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking stop video");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+
+ // test flash now off
+ assertNotEquals("flash_torch", mPreview.getCameraController().getFlashValue());
+ }
+
+ // type: 0 - go to background; 1 - go to settings; 2 - go to popup
+ private void subTestTimer(int type) {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.TimerPreferenceKey, "10");
+ editor.putBoolean(PreferenceKeys.TimerBeepPreferenceKey, false);
+ editor.apply();
+
+ assertFalse(mPreview.isOnTimer());
+
+ // count initial files in folder
+ int n_files = getNFiles();
+ Log.d(TAG, "n_files at start: " + n_files);
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ Log.d(TAG, "about to click take photo");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take photo");
+ assertTrue(mPreview.isOnTimer());
+ assertEquals(0, mPreview.count_cameraTakePicture);
+
+ try {
+ // wait 2s, and check we are still on timer, and not yet taken a photo
+ Thread.sleep(2000);
+ assertTrue(mPreview.isOnTimer());
+ assertEquals(0, mPreview.count_cameraTakePicture);
+ // quit and resume
+ if( type == 0 )
+ restart();
+ else if( type == 1 ) {
+ View settingsButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.settings);
+ Log.d(TAG, "about to click settings");
+ clickView(settingsButton);
+ Log.d(TAG, "done clicking settings");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ Log.d(TAG, "on back pressed...");
+ mActivity.onBackPressed();
+ }
+ });
+ // need to wait for UI code to finish before leaving
+ this.getInstrumentation().waitForIdleSync();
+ Thread.sleep(1000); // need at least 1000ms for Nexus 7
+ }
+ else {
+ openPopupMenu();
+ }
+ takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ // check timer cancelled, and not yet taken a photo
+ assertFalse(mPreview.isOnTimer());
+ assertEquals(0, mPreview.count_cameraTakePicture);
+ int n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(0, n_new_files);
+
+ // start timer again
+ Log.d(TAG, "about to click take photo");
+ assertNotNull(mPreview.getCameraController());
+ clickView(takePhotoButton);
+ assertNotNull(mPreview.getCameraController());
+ Log.d(TAG, "done clicking take photo");
+ assertTrue(mPreview.isOnTimer());
+ assertEquals(0, mPreview.count_cameraTakePicture);
+ n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(0, n_new_files);
+
+ // wait 15s, and ensure we took a photo
+ Thread.sleep(15000);
+ Log.d(TAG, "waited, count now " + mPreview.count_cameraTakePicture);
+ assertFalse(mPreview.isOnTimer());
+ assertEquals(1, mPreview.count_cameraTakePicture);
+ n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(1, n_new_files);
+
+ // now set timer to 5s, and turn on pause_preview
+ editor.putString(PreferenceKeys.TimerPreferenceKey, "5");
+ editor.putBoolean(PreferenceKeys.PausePreviewPreferenceKey, true);
+ editor.apply();
+
+ Log.d(TAG, "about to click take photo");
+ assertNotNull(mPreview.getCameraController());
+ clickView(takePhotoButton);
+ assertNotNull(mPreview.getCameraController());
+ Log.d(TAG, "done clicking take photo");
+ assertTrue(mPreview.isOnTimer());
+ assertEquals(1, mPreview.count_cameraTakePicture);
+ n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(1, n_new_files);
+
+ // wait 10s, and ensure we took a photo
+ Thread.sleep(10000);
+ Log.d(TAG, "waited, count now " + mPreview.count_cameraTakePicture);
+ assertFalse(mPreview.isOnTimer());
+ assertEquals(2, mPreview.count_cameraTakePicture);
+ n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(2, n_new_files);
+
+ // now test cancelling
+ Log.d(TAG, "about to click take photo");
+ assertNotNull(mPreview.getCameraController());
+ clickView(takePhotoButton);
+ assertNotNull(mPreview.getCameraController());
+ Log.d(TAG, "done clicking take photo");
+ assertTrue(mPreview.isOnTimer());
+ assertEquals(2, mPreview.count_cameraTakePicture);
+ n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(2, n_new_files);
+
+ // wait 2s, and cancel
+ Thread.sleep(2000);
+ Log.d(TAG, "about to click take photo to cance");
+ assertNotNull(mPreview.getCameraController());
+ clickView(takePhotoButton);
+ assertNotNull(mPreview.getCameraController());
+ Log.d(TAG, "done clicking take photo to cancel");
+ assertFalse(mPreview.isOnTimer());
+ assertEquals(2, mPreview.count_cameraTakePicture);
+ n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(2, n_new_files);
+
+ // wait 8s, and ensure we didn't take a photo
+ Thread.sleep(8000);
+ Log.d(TAG, "waited, count now " + mPreview.count_cameraTakePicture);
+ assertFalse(mPreview.isOnTimer());
+ assertEquals(2, mPreview.count_cameraTakePicture);
+ n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(2, n_new_files);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+ }
+
+ /* Test with 10s timer, start a photo, go to background, then back, then take another photo. We should only take 1 photo - the original countdown should not be active (nor should we crash)!
+ */
+ public void testTimerBackground() {
+ Log.d(TAG, "testTimerBackground");
+ setToDefault();
+
+ subTestTimer(0);
+ }
+
+ /* Test and going to settings.
+ */
+ public void testTimerSettings() {
+ Log.d(TAG, "testTimerSettings");
+ setToDefault();
+
+ subTestTimer(1);
+ }
+
+ /* Test and going to popup.
+ */
+ public void testTimerPopup() {
+ Log.d(TAG, "testTimerPopup");
+ setToDefault();
+
+ subTestTimer(2);
+ }
+
+ /* Takes video on a timer, but interrupts with restart.
+ */
+ public void testVideoTimerInterrupt() {
+ Log.d(TAG, "testVideoTimerInterrupt");
+ setToDefault();
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.TimerPreferenceKey, "5");
+ editor.putBoolean(PreferenceKeys.TimerBeepPreferenceKey, false);
+ editor.apply();
+
+ assertFalse(mPreview.isOnTimer());
+
+ // count initial files in folder
+ int n_files = getNFiles();
+ Log.d(TAG, "n_files at start: " + n_files);
+
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertTrue(mPreview.isVideo());
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ Log.d(TAG, "about to click take photo");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take photo");
+ assertTrue(mPreview.isOnTimer());
+ assertEquals(0, mPreview.count_cameraTakePicture);
+
+ try {
+ // wait a moment after 5s, then restart
+ Thread.sleep(5100);
+ assertEquals(0, mPreview.count_cameraTakePicture);
+ // quit and resume
+ restart();
+ Log.d(TAG, "done restart");
+
+ // check timer cancelled; may or may not have managed to take a photo
+ assertFalse(mPreview.isOnTimer());
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+ }
+
+ /* Tests that selecting a new flash and focus option, then reopening the popup menu, still has the correct option highlighted.
+ */
+ public void testPopup() {
+ Log.d(TAG, "testPopup");
+ setToDefault();
+
+ switchToFlashValue("flash_off");
+ switchToFlashValue("flash_on");
+
+ if( mPreview.supportsFocus() ) {
+ if( mPreview.getSupportedFocusValues().contains("focus_mode_macro") ) {
+ switchToFocusValue("focus_mode_macro");
+ }
+ else if( mPreview.getSupportedFocusValues().contains("focus_mode_infinity") ) {
+ switchToFocusValue("focus_mode_infinity");
+ }
+
+ if( mPreview.getSupportedFocusValues().contains("focus_mode_auto") ) {
+ switchToFocusValue("focus_mode_auto");
+ }
+ }
+
+ // now open popup, pause and resume, then reopen popup
+ // this tests against a crash, if we don't remove the popup from the popup container in MainUI.destroyPopup()
+ openPopupMenu();
+
+ pauseAndResume();
+ openPopupMenu();
+ }
+
+ /* Tests against a bug where popup wouldn't show with left UI placement, due to 0 popup view height.
+ */
+ public void testPopupLeftLayout() {
+ Log.d(TAG, "testPopupLeftLayout");
+
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.UIPlacementPreferenceKey, "ui_left");
+ editor.apply();
+ updateForSettings();
+
+ View popup_view = mActivity.findViewById(net.sourceforge.opencamera.R.id.popup_container);
+
+ openPopupMenu();
+
+ int popup_width = popup_view.getWidth();
+ int popup_height = popup_view.getHeight();
+ int test_popup_width = mActivity.getMainUI().test_saved_popup_width;
+ int test_popup_height = mActivity.getMainUI().test_saved_popup_height;
+ Log.d(TAG, "popup_width: " + popup_width);
+ Log.d(TAG, "popup_height: " + popup_height);
+ Log.d(TAG, "test_popup_width: " + test_popup_width);
+ Log.d(TAG, "test_popup_height: " + test_popup_height);
+ assertTrue(popup_width > 0);
+ assertTrue(popup_height > 0);
+ assertEquals(popup_width, test_popup_width);
+ assertEquals(popup_height, test_popup_height);
+
+ // now reopen popup view, and check the same dimensions
+ closePopupMenu();
+
+ openPopupMenu();
+
+ int new_popup_width = popup_view.getWidth();
+ int new_popup_height = popup_view.getHeight();
+ test_popup_width = mActivity.getMainUI().test_saved_popup_width;
+ test_popup_height = mActivity.getMainUI().test_saved_popup_height;
+ Log.d(TAG, "new_popup_width: " + new_popup_width);
+ Log.d(TAG, "new_popup_height: " + new_popup_height);
+ Log.d(TAG, "test_popup_width: " + test_popup_width);
+ Log.d(TAG, "test_popup_height: " + test_popup_height);
+ assertEquals(popup_width, new_popup_width);
+ assertEquals(popup_height, new_popup_height);
+ assertEquals(popup_width, test_popup_width);
+ assertEquals(popup_height, test_popup_height);
+ }
+
+ /* Tests with ui_right vs ui_top layout.
+ */
+ public void testRightLayout() {
+ Log.d(TAG, "testRightLayout");
+
+ setToDefault();
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.UIPlacementPreferenceKey, "ui_right");
+ editor.apply();
+ updateForSettings();
+ }
+
+ openPopupMenu();
+
+ Point display_size = new Point();
+ {
+ // call with exclude_insets==true, as in this test we're measuring things about the UI
+ mActivity.getApplicationInterface().getDisplaySize(display_size, true);
+ Log.d(TAG, "display_size: " + display_size.x + " x " + display_size.y);
+ }
+ View settingsButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.settings);
+ View galleryButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.gallery);
+
+ Log.d(TAG, "settings right: " + settingsButton.getRight());
+ Log.d(TAG, "settings top: " + settingsButton.getTop());
+ Log.d(TAG, "gallery right: " + galleryButton.getRight());
+ Log.d(TAG, "gallery top: " + galleryButton.getTop());
+
+ final float scale = mActivity.getResources().getDisplayMetrics().density;
+ int expected_gap = (int) (MainUI.privacy_indicator_gap_dp * scale + 0.5f); // convert dps to pixels;
+ if( mActivity.getSystemOrientation() == MainActivity.SystemOrientation.PORTRAIT ) {
+ assertTrue(settingsButton.getBottom() > (int)(0.8*display_size.y));
+ assertEquals(display_size.x, settingsButton.getRight());
+ // position may be 1 coordinate different on some devices, e.g., Galaxy Nexus
+ // have 14 pixel gap on Pixel 6 Pro
+ assertEquals(display_size.y-1-expected_gap, galleryButton.getBottom(), 14.0+1.0e-5);
+ assertEquals(display_size.x-expected_gap, galleryButton.getRight());
+ }
+ else {
+ assertTrue(settingsButton.getRight() > (int)(0.8*display_size.x));
+ assertEquals(0, settingsButton.getTop());
+ assertEquals(display_size.x-expected_gap, galleryButton.getRight());
+ assertEquals(expected_gap, galleryButton.getTop());
+ }
+
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.UIPlacementPreferenceKey, "ui_top");
+ editor.apply();
+ updateForSettings();
+ }
+
+ Log.d(TAG, "settings right: " + settingsButton.getRight());
+ Log.d(TAG, "settings top: " + settingsButton.getTop());
+ Log.d(TAG, "gallery right: " + galleryButton.getRight());
+ Log.d(TAG, "gallery top: " + galleryButton.getTop());
+
+ if( mActivity.getSystemOrientation() == MainActivity.SystemOrientation.PORTRAIT ) {
+ assertTrue(settingsButton.getBottom() < (int)(0.2*display_size.y));
+ assertEquals(display_size.x, settingsButton.getRight());
+ // position may be 1 coordinate different on some devices, e.g., Galaxy Nexus
+ // have 14 pixel gap on Pixel 6 Pro
+ assertEquals(display_size.y-1-expected_gap, galleryButton.getBottom(), 14.0+1.0e-5);
+ assertEquals(display_size.x-expected_gap, galleryButton.getRight());
+ }
+ else {
+ assertTrue(settingsButton.getRight() < (int)(0.2*display_size.x));
+ assertEquals(0, settingsButton.getTop());
+ assertEquals(display_size.x-expected_gap, galleryButton.getRight());
+ assertEquals(expected_gap, galleryButton.getTop());
+ }
+
+ openPopupMenu();
+ }
+
+ /* Tests layout bug with popup menu.
+ * Note, in practice this doesn't seem to reproduce the problem, but keep the test anyway.
+ * Currently not autotested as the problem isn't fixed, and this would just be a test that
+ * occasionally fails (instead we work round the problem but not caching the popup when the
+ * bug occurs).
+ */
+ public void testPopupLayout() throws InterruptedException {
+ Log.d(TAG, "testPopupLayout");
+ setToDefault();
+
+ for(int i=0;i<50;i++) {
+ View popup_container = mActivity.findViewById(net.sourceforge.opencamera.R.id.popup_container);
+ View popupButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.popup);
+ final float scale = mActivity.getResources().getDisplayMetrics().density;
+ int max_width = (int) (280 * scale + 0.5f); // convert dps to pixels;
+
+ Thread.sleep(400);
+
+ // open popup
+ openPopupMenu();
+
+ // check popup width is not larger than expected
+ int popup_container_width = popup_container.getWidth();
+ Log.d(TAG, "i = : " + i);
+ Log.d(TAG, " popup_container_width: " + popup_container_width);
+ Log.d(TAG, " max_width: " + max_width);
+ assertTrue(popup_container_width <= max_width);
+
+ /*View settingsButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.settings);
+ Log.d(TAG, "about to click settings");
+ clickView(settingsButton);
+ Log.d(TAG, "done clicking settings");
+ this.getInstrumentation().waitForIdleSync();*/
+
+ if( i % 10 == 0 ) {
+ restart();
+ }
+ else {
+ pauseAndResume();
+ }
+ }
+ }
+
+ /* Tests to do with video and popup menu.
+ */
+ private void subTestVideoPopup(boolean on_timer) {
+ Log.d(TAG, "subTestVideoPopup");
+
+ assertFalse(mPreview.isOnTimer());
+ assertFalse(mActivity.popupIsOpen());
+ View popupButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.popup);
+
+ if( !mPreview.isVideo() ) {
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertTrue(mPreview.isVideo());
+ }
+
+ if( !on_timer ) {
+ // open popup now
+ openPopupMenu();
+ }
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ Log.d(TAG, "about to click take photo");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take photo");
+ if( on_timer ) {
+ assertTrue(mPreview.isOnTimer());
+ }
+
+ try {
+ if( on_timer ) {
+ Thread.sleep(2000);
+
+ // now open popup
+ openPopupMenu();
+
+ // check timer is cancelled
+ assertFalse(mPreview.isOnTimer());
+
+ // wait for timer (if it was still going)
+ Thread.sleep(4000);
+
+ // now check we still aren't recording, and that popup is still open
+ assertTrue( mPreview.isVideo() );
+ assertFalse(mPreview.isVideoRecording());
+ assertFalse(mPreview.isOnTimer());
+ assertTrue( mActivity.popupIsOpen() );
+ }
+ else {
+ Thread.sleep(1000);
+
+ // now check we are recording video, and that popup is closed
+ assertTrue( mPreview.isVideo() );
+ assertTrue( mPreview.isVideoRecording() );
+ assertFalse(mActivity.popupIsOpen());
+ }
+
+ if( !on_timer ) {
+ // (if on timer, the video will have stopped)
+ List supported_flash_values = mPreview.getSupportedFlashValues();
+ if( supported_flash_values == null ) {
+ // button shouldn't show at all
+ assertEquals(popupButton.getVisibility(), View.GONE);
+ }
+ else {
+ // now open popup again
+ openPopupMenu();
+ subTestPopupButtonAvailability("TEST_FLASH", "flash_off", supported_flash_values);
+ subTestPopupButtonAvailability("TEST_FLASH", "flash_auto", supported_flash_values);
+ subTestPopupButtonAvailability("TEST_FLASH", "flash_on", supported_flash_values);
+ subTestPopupButtonAvailability("TEST_FLASH", "flash_torch", supported_flash_values);
+ subTestPopupButtonAvailability("TEST_FLASH", "flash_red_eye", supported_flash_values);
+ subTestPopupButtonAvailability("TEST_FLASH", "flash_frontscreen_auto", supported_flash_values);
+ subTestPopupButtonAvailability("TEST_FLASH", "flash_frontscreen_on", supported_flash_values);
+ // only flash should be available
+ subTestPopupButtonAvailability("TEST_FOCUS", "focus_mode_auto", null);
+ subTestPopupButtonAvailability("TEST_FOCUS", "focus_mode_locked", null);
+ subTestPopupButtonAvailability("TEST_FOCUS", "focus_mode_infinity", null);
+ subTestPopupButtonAvailability("TEST_FOCUS", "focus_mode_macro", null);
+ subTestPopupButtonAvailability("TEST_FOCUS", "focus_mode_fixed", null);
+ subTestPopupButtonAvailability("TEST_FOCUS", "focus_mode_edof", null);
+ subTestPopupButtonAvailability("TEST_FOCUS", "focus_mode_continuous_picture", null);
+ subTestPopupButtonAvailability("TEST_FOCUS", "focus_mode_continuous_video", null);
+ subTestPopupButtonAvailability("TEST_ISO", "auto", null);
+ subTestPopupButtonAvailability("TEST_ISO", "100", null);
+ subTestPopupButtonAvailability("TEST_ISO", "200", null);
+ subTestPopupButtonAvailability("TEST_ISO", "400", null);
+ subTestPopupButtonAvailability("TEST_ISO", "800", null);
+ subTestPopupButtonAvailability("TEST_ISO", "1600", null);
+ subTestPopupButtonAvailability("TEST_WHITE_BALANCE", false);
+ subTestPopupButtonAvailability("TEST_SCENE_MODE", false);
+ subTestPopupButtonAvailability("TEST_COLOR_EFFECT", false);
+ }
+ }
+
+ Log.d(TAG, "now stop video");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking stop video");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ assertFalse(mPreview.isVideoRecording());
+ assertFalse(mActivity.popupIsOpen());
+
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+
+ // now open popup again
+ openPopupMenu();
+ subTestPopupButtonAvailability();
+ }
+
+ /* Tests that popup menu closes when we record video; then tests behaviour of popup.
+ */
+ public void testVideoPopup() {
+ Log.d(TAG, "testVideoPopup");
+ setToDefault();
+
+ subTestVideoPopup(false);
+
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ) {
+ Log.d(TAG, "switch camera");
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+ subTestVideoPopup(false);
+ }
+ }
+
+ /* Takes video on a timer, but checks that the popup menu stops video timer; then tests behaviour of popup.
+ */
+ public void testVideoTimerPopup() {
+ Log.d(TAG, "testVideoTimerPopup");
+ setToDefault();
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.TimerPreferenceKey, "5");
+ editor.putBoolean(PreferenceKeys.TimerBeepPreferenceKey, false);
+ editor.apply();
+
+ subTestVideoPopup(true);
+
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ) {
+ Log.d(TAG, "switch camera");
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+ subTestVideoPopup(true);
+ }
+ }
+
+ /* Tests for USB/bluetooth keyboard controls.
+ */
+ public void testKeyboardControls() throws InterruptedException {
+ Log.d(TAG, "testKeyboardControls");
+
+ setToDefault();
+
+ if( !mPreview.supportsFlash() ) {
+ Log.d(TAG, "doesn't support flash");
+ return;
+ }
+ else if( !mPreview.supportsFocus() ) {
+ Log.d(TAG, "doesn't support focus");
+ return;
+ }
+
+ switchToFlashValue("flash_auto");
+
+ // open popup
+ assertFalse( mActivity.popupIsOpen() );
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_FUNCTION);
+ getInstrumentation().waitForIdleSync();
+ assertTrue( mActivity.popupIsOpen() );
+
+ // arrow down
+ assertFalse(mActivity.getMainUI().testGetRemoteControlMode());
+ assertFalse(mActivity.getMainUI().selectingLines());
+ assertFalse(mActivity.getMainUI().selectingIcons());
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+ getInstrumentation().waitForIdleSync();
+ assertTrue(mActivity.getMainUI().testGetRemoteControlMode());
+ assertTrue(mActivity.getMainUI().selectingLines());
+ assertFalse(mActivity.getMainUI().selectingIcons());
+ assertEquals(0, mActivity.getMainUI().testGetPopupLine());
+ assertEquals(0, mActivity.getMainUI().testGetPopupIcon());
+ //Thread.sleep(3000); // test
+
+ // arrow down again
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_NUMPAD_2);
+ getInstrumentation().waitForIdleSync();
+ assertTrue(mActivity.getMainUI().testGetRemoteControlMode());
+ assertTrue(mActivity.getMainUI().selectingLines());
+ assertFalse(mActivity.getMainUI().selectingIcons());
+ //Thread.sleep(3000); // test
+ assertEquals(1, mActivity.getMainUI().testGetPopupLine());
+ assertEquals(0, mActivity.getMainUI().testGetPopupIcon());
+
+ // arrow down again
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_NUMPAD_2);
+ getInstrumentation().waitForIdleSync();
+ assertTrue(mActivity.getMainUI().testGetRemoteControlMode());
+ assertTrue(mActivity.getMainUI().selectingLines());
+ assertFalse(mActivity.getMainUI().selectingIcons());
+ assertEquals(3, mActivity.getMainUI().testGetPopupLine());
+ assertEquals(0, mActivity.getMainUI().testGetPopupIcon());
+
+ // arrow up
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_UP);
+ getInstrumentation().waitForIdleSync();
+ assertTrue(mActivity.getMainUI().testGetRemoteControlMode());
+ assertTrue(mActivity.getMainUI().selectingLines());
+ assertFalse(mActivity.getMainUI().selectingIcons());
+ assertEquals(1, mActivity.getMainUI().testGetPopupLine());
+ assertEquals(0, mActivity.getMainUI().testGetPopupIcon());
+
+ // arrow up again
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_NUMPAD_8);
+ getInstrumentation().waitForIdleSync();
+ assertTrue(mActivity.getMainUI().testGetRemoteControlMode());
+ assertTrue(mActivity.getMainUI().selectingLines());
+ assertFalse(mActivity.getMainUI().selectingIcons());
+ assertEquals(0, mActivity.getMainUI().testGetPopupLine());
+ assertEquals(0, mActivity.getMainUI().testGetPopupIcon());
+
+ // select
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_NUMPAD_5);
+ getInstrumentation().waitForIdleSync();
+ assertTrue(mActivity.getMainUI().testGetRemoteControlMode());
+ assertTrue(mActivity.getMainUI().selectingLines());
+ assertTrue(mActivity.getMainUI().selectingIcons());
+ assertEquals(0, mActivity.getMainUI().testGetPopupLine());
+ assertEquals(0, mActivity.getMainUI().testGetPopupIcon());
+
+ // arrow down
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+ getInstrumentation().waitForIdleSync();
+ assertTrue(mActivity.getMainUI().testGetRemoteControlMode());
+ assertTrue(mActivity.getMainUI().selectingLines());
+ assertTrue(mActivity.getMainUI().selectingIcons());
+ assertEquals(0, mActivity.getMainUI().testGetPopupLine());
+ assertEquals(1, mActivity.getMainUI().testGetPopupIcon());
+
+ // arrow down again
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_NUMPAD_2);
+ getInstrumentation().waitForIdleSync();
+ assertTrue(mActivity.getMainUI().testGetRemoteControlMode());
+ assertTrue(mActivity.getMainUI().selectingLines());
+ assertTrue(mActivity.getMainUI().selectingIcons());
+ assertEquals(0, mActivity.getMainUI().testGetPopupLine());
+ assertEquals(2, mActivity.getMainUI().testGetPopupIcon());
+
+ // arrow up
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_UP);
+ getInstrumentation().waitForIdleSync();
+ assertTrue(mActivity.getMainUI().testGetRemoteControlMode());
+ assertTrue(mActivity.getMainUI().selectingLines());
+ assertTrue(mActivity.getMainUI().selectingIcons());
+ assertEquals(0, mActivity.getMainUI().testGetPopupLine());
+ assertEquals(1, mActivity.getMainUI().testGetPopupIcon());
+
+ // arrow up again
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_NUMPAD_8);
+ getInstrumentation().waitForIdleSync();
+ assertTrue(mActivity.getMainUI().testGetRemoteControlMode());
+ assertTrue(mActivity.getMainUI().selectingLines());
+ assertTrue(mActivity.getMainUI().selectingIcons());
+ assertEquals(0, mActivity.getMainUI().testGetPopupLine());
+ assertEquals(0, mActivity.getMainUI().testGetPopupIcon());
+
+ // select
+ assertEquals("flash_auto", mPreview.getCurrentFlashValue());
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_NUMPAD_5);
+ getInstrumentation().waitForIdleSync();
+ Thread.sleep(500);
+ assertFalse( mActivity.popupIsOpen() );
+ assertEquals("flash_off", mPreview.getCurrentFlashValue());
+ assertFalse(mActivity.getMainUI().testGetRemoteControlMode());
+ assertFalse(mActivity.getMainUI().selectingLines());
+ assertFalse(mActivity.getMainUI().selectingIcons());
+
+ Thread.sleep(500);
+
+ // open exposure panel
+ assertFalse( mActivity.getMainUI().isExposureUIOpen() );
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_SLASH);
+ getInstrumentation().waitForIdleSync();
+ assertTrue( mActivity.getMainUI().isExposureUIOpen() );
+
+ assertFalse(mActivity.getMainUI().testGetRemoteControlMode());
+ if( mPreview.supportsISORange() || mPreview.getSupportedISOs() != null ) {
+ // need to skip past the ISO line
+ assertFalse(mActivity.getMainUI().selectingLines());
+ assertFalse(mActivity.getMainUI().selectingIcons());
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_NUMPAD_2);
+ getInstrumentation().waitForIdleSync();
+ assertTrue(mActivity.getMainUI().testGetRemoteControlMode());
+ assertFalse(mActivity.getMainUI().selectingLines());
+ assertFalse(mActivity.getMainUI().selectingIcons());
+ assertFalse(mActivity.getMainUI().isSelectingExposureUIElement());
+ assertEquals(0, mActivity.getMainUI().testGetPopupLine());
+ assertEquals(0, mActivity.getMainUI().testGetPopupIcon());
+ assertEquals(0, mActivity.getMainUI().testGetExposureLine());
+ }
+
+ // arrow down
+ assertFalse(mActivity.getMainUI().selectingLines());
+ assertFalse(mActivity.getMainUI().selectingIcons());
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+ getInstrumentation().waitForIdleSync();
+ assertTrue(mActivity.getMainUI().testGetRemoteControlMode());
+ assertFalse(mActivity.getMainUI().selectingLines());
+ assertFalse(mActivity.getMainUI().selectingIcons());
+ assertFalse(mActivity.getMainUI().isSelectingExposureUIElement());
+ assertEquals(0, mActivity.getMainUI().testGetPopupLine());
+ assertEquals(0, mActivity.getMainUI().testGetPopupIcon());
+ assertEquals(3, mActivity.getMainUI().testGetExposureLine());
+
+ // select
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_NUMPAD_5);
+ getInstrumentation().waitForIdleSync();
+ assertTrue(mActivity.getMainUI().testGetRemoteControlMode());
+ assertFalse(mActivity.getMainUI().selectingLines());
+ assertFalse(mActivity.getMainUI().selectingIcons());
+ assertTrue(mActivity.getMainUI().isSelectingExposureUIElement());
+ assertEquals(0, mActivity.getMainUI().testGetPopupLine());
+ assertEquals(0, mActivity.getMainUI().testGetPopupIcon());
+ assertEquals(3, mActivity.getMainUI().testGetExposureLine());
+
+ // arrow down
+ for(int i=0;i<6;i++) {
+ assertEquals(-i, mPreview.getCurrentExposure());
+ getInstrumentation().sendKeyDownUpSync((i%2==0) ? KeyEvent.KEYCODE_NUMPAD_2 : KeyEvent.KEYCODE_DPAD_DOWN);
+ getInstrumentation().waitForIdleSync();
+ assertEquals(-(i+1), mPreview.getCurrentExposure());
+ }
+
+ // arrow up
+ for(int i=0;i<6;i++) {
+ assertEquals(-6+i, mPreview.getCurrentExposure());
+ getInstrumentation().sendKeyDownUpSync((i%2==0) ? KeyEvent.KEYCODE_NUMPAD_8 : KeyEvent.KEYCODE_DPAD_UP);
+ getInstrumentation().waitForIdleSync();
+ assertEquals(-6+(i+1), mPreview.getCurrentExposure());
+ }
+
+ // close exposure panel
+ assertTrue( mActivity.getMainUI().isExposureUIOpen() );
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_SLASH);
+ getInstrumentation().waitForIdleSync();
+ assertFalse( mActivity.getMainUI().isExposureUIOpen() );
+
+ // take photo
+ assertEquals(0, mPreview.count_cameraTakePicture);
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_NUMPAD_5);
+ getInstrumentation().waitForIdleSync();
+ waitForTakePhoto();
+ assertEquals(1, mPreview.count_cameraTakePicture);
+ mActivity.waitUntilImageQueueEmpty();
+
+ // open settings
+ assertFalse(mActivity.isCameraInBackground());
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
+ this.getInstrumentation().waitForIdleSync();
+ assertTrue(mActivity.isCameraInBackground());
+
+ //Thread.sleep(3000);
+ Thread.sleep(500);
+ }
+
+ /* Tests taking photos repeatedly with auto-repeat method.
+ */
+ public void testTakePhotoRepeat() {
+ Log.d(TAG, "testTakePhotoRepeat");
+ setToDefault();
+
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.RepeatModePreferenceKey, "3");
+ editor.apply();
+ }
+
+ // count initial files in folder
+ int n_files = getNFiles();
+ Log.d(TAG, "n_files at start: " + n_files);
+
+ assertEquals(0, mPreview.count_cameraTakePicture);
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ Log.d(TAG, "about to click take photo");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take photo");
+ assertFalse(mPreview.isOnTimer());
+
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ View switchMultiCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_multi_camera);
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ View exposureButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureLockButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure_lock);
+ View popupButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.popup);
+ View trashButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.trash);
+ View shareButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.share);
+
+ try {
+ // wait 7s, and test that we've taken the photos by then
+ Thread.sleep(7000);
+ assertTrue(mPreview.isPreviewStarted()); // check preview restarted
+ Log.d(TAG, "count_cameraTakePicture: " + mPreview.count_cameraTakePicture);
+ assertEquals(3, mPreview.count_cameraTakePicture);
+ int n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(3, n_new_files);
+
+ // now test pausing and resuming
+ pauseAndResume();
+ // wait 5s, and test that we haven't taken any photos
+ Thread.sleep(5000);
+ assertTrue(mPreview.isPreviewStarted()); // check preview restarted
+ Log.d(TAG, "mPreview.count_cameraTakePicture: " + mPreview.count_cameraTakePicture);
+ assertEquals(3, mPreview.count_cameraTakePicture);
+ n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(3, n_new_files);
+
+ // test with preview paused
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.PausePreviewPreferenceKey, true);
+ editor.apply();
+ }
+ clickView(takePhotoButton);
+ Thread.sleep(7000);
+ assertEquals(6, mPreview.count_cameraTakePicture);
+ n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(6, n_new_files);
+ assertFalse(mPreview.isPreviewStarted()); // check preview paused
+
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ this.getInstrumentation().waitForIdleSync();
+ n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(6, n_new_files);
+ assertTrue(mPreview.isPreviewStarted()); // check preview restarted
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.PausePreviewPreferenceKey, false);
+ editor.apply();
+ }
+
+ // now test repeat interval
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.RepeatModePreferenceKey, "2");
+ editor.putString(PreferenceKeys.RepeatIntervalPreferenceKey, "3");
+ editor.putBoolean(PreferenceKeys.TimerBeepPreferenceKey, false);
+ editor.apply();
+ }
+ assertEquals(switchCameraButton.getVisibility(), (mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE));
+ assertEquals(switchMultiCameraButton.getVisibility(), (mActivity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE));
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureButton.getVisibility(), (mPreview.supportsExposures() ? View.VISIBLE : View.GONE));
+ assertEquals(exposureLockButton.getVisibility(), (mPreview.supportsExposureLock() ? View.VISIBLE : View.GONE));
+ assertEquals(popupButton.getVisibility(), View.VISIBLE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+
+ clickView(takePhotoButton);
+ waitForTakePhoto();
+ Log.d(TAG, "done taking 1st photo");
+ this.getInstrumentation().waitForIdleSync();
+ assertEquals(7, mPreview.count_cameraTakePicture);
+ mActivity.waitUntilImageQueueEmpty();
+ n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(7, n_new_files);
+
+ // wait 2s, should still not have taken another photo
+ Thread.sleep(2000);
+ assertEquals(7, mPreview.count_cameraTakePicture);
+ n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(7, n_new_files);
+ // check GUI has returned to correct state
+ assertEquals(switchCameraButton.getVisibility(), (mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE));
+ assertEquals(switchMultiCameraButton.getVisibility(), (mActivity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE));
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureButton.getVisibility(), (mPreview.supportsExposures() ? View.VISIBLE : View.GONE));
+ assertEquals(exposureLockButton.getVisibility(), (mPreview.supportsExposureLock() ? View.VISIBLE : View.GONE));
+ assertEquals(popupButton.getVisibility(), View.VISIBLE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+
+ // wait another 5s, should have taken another photo (need to allow time for the extra auto-focus)
+ Thread.sleep(5000);
+ assertEquals(8, mPreview.count_cameraTakePicture);
+ n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(8, n_new_files);
+ // wait 4s, should not have taken any more photos
+ Thread.sleep(4000);
+ assertEquals(8, mPreview.count_cameraTakePicture);
+ n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(8, n_new_files);
+ }
+ catch(InterruptedException e) {
+ Log.e(TAG, "InterruptedException from sleep", e);
+ fail();
+ }
+ }
+
+ /* Tests that saving quality (i.e., resolution) settings can be done per-camera. Also checks that the supported picture sizes is as expected.
+ */
+ public void testSaveQuality() {
+ Log.d(TAG, "testSaveQuality");
+
+ setToDefault();
+
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() <= 1 ) {
+ return;
+ }
+
+ List picture_sizes = mPreview.getSupportedPictureSizes(true);
+
+ // change back camera to the last size
+ CameraController.Size size = picture_sizes.get(picture_sizes.size()-1);
+ {
+ Log.d(TAG, "set size to " + size.width + " x " + size.height);
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.getResolutionPreferenceKey(mPreview.getCameraId(), mActivity.getApplicationInterface().getCameraIdSPhysicalPref()), size.width + " " + size.height);
+ editor.apply();
+ }
+
+ // need to resume activity for it to take effect (for camera to be reopened)
+ pauseAndResume();
+ CameraController.Size new_size = mPreview.getCameraController().getPictureSize();
+ Log.d(TAG, "size is now " + new_size.width + " x " + new_size.height);
+ assertEquals(size, new_size);
+
+ // switch camera to front
+ int cameraId = mPreview.getCameraId();
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+ int new_cameraId = mPreview.getCameraId();
+ assertTrue(cameraId != new_cameraId);
+
+ List front_picture_sizes = mPreview.getSupportedPictureSizes(true);
+
+ // change front camera to the last size
+ CameraController.Size front_size = front_picture_sizes.get(front_picture_sizes.size()-1);
+ {
+ Log.d(TAG, "set front_size to " + front_size.width + " x " + front_size.height);
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.getResolutionPreferenceKey(mPreview.getCameraId(), mActivity.getApplicationInterface().getCameraIdSPhysicalPref()), front_size.width + " " + front_size.height);
+ editor.apply();
+ }
+
+ // need to resume activity for it to take effect (for camera to be reopened)
+ pauseAndResume();
+ // check still on front camera
+ Log.d(TAG, "camera id " + mPreview.getCameraId());
+ assertEquals(mPreview.getCameraId(), new_cameraId);
+ CameraController.Size front_new_size = mPreview.getCameraController().getPictureSize();
+ Log.d(TAG, "front size is now " + front_new_size.width + " x " + front_new_size.height);
+ assertEquals(front_size, front_new_size);
+
+ // change front camera to the first size
+ front_size = front_picture_sizes.get(0);
+ {
+ Log.d(TAG, "set front_size to " + front_size.width + " x " + front_size.height);
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.getResolutionPreferenceKey(mPreview.getCameraId(), mActivity.getApplicationInterface().getCameraIdSPhysicalPref()), front_size.width + " " + front_size.height);
+ editor.apply();
+ }
+
+ // need to resume activity for it to take effect (for camera to be reopened)
+ pauseAndResume();
+ front_new_size = mPreview.getCameraController().getPictureSize();
+ Log.d(TAG, "front size is now " + front_new_size.width + " x " + front_new_size.height);
+ assertEquals(front_size, front_new_size);
+
+ // return to back camera
+ switchToCamera(cameraId);
+
+ // now back camera size should still be what it was
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ String settings_size = settings.getString(PreferenceKeys.getResolutionPreferenceKey(mPreview.getCameraId(), mActivity.getApplicationInterface().getCameraIdSPhysicalPref()), "");
+ Log.d(TAG, "settings key is " + PreferenceKeys.getResolutionPreferenceKey(mPreview.getCameraId(), mActivity.getApplicationInterface().getCameraIdSPhysicalPref()));
+ Log.d(TAG, "settings size is " + settings_size);
+ }
+ new_size = mPreview.getCameraController().getPictureSize();
+ Log.d(TAG, "size is now " + new_size.width + " x " + new_size.height);
+ assertEquals(size, new_size);
+ }
+
+ private void testExif(String file, Uri uri, boolean expect_device_tags, boolean expect_datetime, boolean expect_gps) throws IOException {
+ TestUtils.testExif(getActivity(), file, uri, expect_device_tags, expect_datetime, expect_gps);
+ }
+
+ private void subTestLocationOn(boolean gps_direction) throws IOException {
+ Log.d(TAG, "subTestLocationOn");
+
+ assertTrue(mActivity.getLocationSupplier().noLocationListeners());
+ assertFalse(mActivity.getLocationSupplier().testHasReceivedLocation());
+ assertNull(mActivity.getLocationSupplier().getLocation());
+ Log.d(TAG, "turn on location");
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.LocationPreferenceKey, true);
+ if( gps_direction ) {
+ editor.putBoolean(PreferenceKeys.GPSDirectionPreferenceKey, true);
+ }
+ editor.apply();
+ Log.d(TAG, "update settings after turning on location");
+ updateForSettings();
+ Log.d(TAG, "location should now be on");
+ }
+
+ assertTrue(mActivity.getLocationSupplier().hasLocationListeners());
+ Log.d(TAG, "wait until received location");
+
+ long start_t = System.currentTimeMillis();
+ while( !mActivity.getLocationSupplier().testHasReceivedLocation() ) {
+ this.getInstrumentation().waitForIdleSync();
+ if( System.currentTimeMillis() - start_t > 20000 ) {
+ // need to allow long time for testing devices without mobile network; will likely fail altogether if don't even have wifi
+ fail();
+ }
+ }
+ Log.d(TAG, "have received location");
+ this.getInstrumentation().waitForIdleSync();
+ assertNotNull(mActivity.getLocationSupplier().getLocation());
+ assertEquals(0, mPreview.count_cameraTakePicture);
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ mActivity.test_last_saved_image = null;
+ mActivity.test_last_saved_imageuri = null;
+ clickView(takePhotoButton);
+
+ Log.d(TAG, "wait until finished taking photo");
+ waitForTakePhoto();
+ this.getInstrumentation().waitForIdleSync();
+ assertEquals(1, mPreview.count_cameraTakePicture);
+ mActivity.waitUntilImageQueueEmpty();
+ testExif(mActivity.test_last_saved_image, mActivity.test_last_saved_imageuri, true, true, true);
+
+ // now test with auto-stabilise
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.AutoStabilisePreferenceKey, true);
+ editor.apply();
+ updateForSettings();
+ }
+ mActivity.test_last_saved_image = null;
+ mActivity.test_last_saved_imageuri = null;
+ clickView(takePhotoButton);
+
+ Log.d(TAG, "wait until finished taking photo");
+ waitForTakePhoto();
+ this.getInstrumentation().waitForIdleSync();
+ assertEquals(2, mPreview.count_cameraTakePicture);
+ mActivity.waitUntilImageQueueEmpty();
+ testExif(mActivity.test_last_saved_image, mActivity.test_last_saved_imageuri, true, true, true);
+
+ // switch to front camera
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ) {
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+ assertTrue(mActivity.getLocationSupplier().hasLocationListeners());
+ // shouldn't need to wait for test_has_received_location to be true, as should remember from before switching camera
+ assertNotNull(mActivity.getLocationSupplier().getLocation());
+ }
+ }
+
+ /* Tests we save location data; also tests that we save other exif data.
+ * May fail on devices without mobile network, especially if we don't even have wifi.
+ */
+ public void testLocationOn() throws IOException {
+ Log.d(TAG, "testLocationOn");
+ setToDefault();
+
+ subTestLocationOn(false);
+ }
+
+ /* Tests we save location and gps direction.
+ * May fail on devices without mobile network, especially if we don't even have wifi.
+ */
+ public void testLocationDirectionOn() throws IOException {
+ Log.d(TAG, "testLocationDirectionOn");
+ setToDefault();
+
+ subTestLocationOn(true);
+ }
+
+ /* As testLocationOn, but with SAF enabled.
+ * Important for Camera2 API at least to test the codepath for when
+ * ImageSaver.needGPSTimestampHack() returns true, when using SAF.
+ * May fail on devices without mobile network, especially if we don't even have wifi.
+ */
+ public void testLocationOnSAF() throws IOException {
+ Log.d(TAG, "testLocationOnSAF");
+
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ) {
+ Log.d(TAG, "SAF requires Android Lollipop or better");
+ return;
+ }
+
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.UsingSAFPreferenceKey, true);
+ editor.putString(PreferenceKeys.SaveLocationSAFPreferenceKey, "content://com.android.externalstorage.documents/tree/primary%3ADCIM%2FOpenCamera");
+ editor.apply();
+ updateForSettings();
+
+ subTestLocationOn(false);
+ }
+
+ /* Tests we don't save location data; also tests that we save other exif data.
+ */
+ private void subTestLocationOff(boolean gps_direction) throws IOException {
+ setToDefault();
+
+ if( gps_direction ) {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.GPSDirectionPreferenceKey, true);
+ editor.apply();
+ updateForSettings();
+ }
+ this.getInstrumentation().waitForIdleSync();
+ assertTrue(mActivity.getLocationSupplier().noLocationListeners());
+ assertFalse(mActivity.getLocationSupplier().testHasReceivedLocation());
+ assertNull(mActivity.getLocationSupplier().getLocation());
+ assertEquals(0, mPreview.count_cameraTakePicture);
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ mActivity.test_last_saved_image = null;
+ mActivity.test_last_saved_imageuri = null;
+ clickView(takePhotoButton);
+
+ assertTrue(mActivity.getLocationSupplier().noLocationListeners());
+ assertFalse(mActivity.getLocationSupplier().testHasReceivedLocation());
+ assertNull(mActivity.getLocationSupplier().getLocation());
+
+ Log.d(TAG, "wait until finished taking photo");
+ waitForTakePhoto();
+ this.getInstrumentation().waitForIdleSync();
+ assertEquals(1, mPreview.count_cameraTakePicture);
+
+ assertTrue(mActivity.getLocationSupplier().noLocationListeners());
+ assertFalse(mActivity.getLocationSupplier().testHasReceivedLocation());
+ assertNull(mActivity.getLocationSupplier().getLocation());
+
+ mActivity.waitUntilImageQueueEmpty();
+ testExif(mActivity.test_last_saved_image, mActivity.test_last_saved_imageuri, true, true, false);
+
+ assertTrue(mActivity.getLocationSupplier().noLocationListeners());
+ assertFalse(mActivity.getLocationSupplier().testHasReceivedLocation());
+ assertNull(mActivity.getLocationSupplier().getLocation());
+
+ // now test with auto-stabilise
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.AutoStabilisePreferenceKey, true);
+ editor.apply();
+ updateForSettings();
+ }
+ mActivity.test_last_saved_image = null;
+ mActivity.test_last_saved_imageuri = null;
+ clickView(takePhotoButton);
+
+ assertTrue(mActivity.getLocationSupplier().noLocationListeners());
+ assertFalse(mActivity.getLocationSupplier().testHasReceivedLocation());
+ assertNull(mActivity.getLocationSupplier().getLocation());
+
+ Log.d(TAG, "wait until finished taking photo");
+ waitForTakePhoto();
+ this.getInstrumentation().waitForIdleSync();
+ assertEquals(2, mPreview.count_cameraTakePicture);
+
+ assertTrue(mActivity.getLocationSupplier().noLocationListeners());
+ assertFalse(mActivity.getLocationSupplier().testHasReceivedLocation());
+ assertNull(mActivity.getLocationSupplier().getLocation());
+
+ mActivity.waitUntilImageQueueEmpty();
+ testExif(mActivity.test_last_saved_image, mActivity.test_last_saved_imageuri, true, true, false);
+
+ assertTrue(mActivity.getLocationSupplier().noLocationListeners());
+ assertFalse(mActivity.getLocationSupplier().testHasReceivedLocation());
+ assertNull(mActivity.getLocationSupplier().getLocation());
+
+ // switch to front camera
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ) {
+ int cameraId = mPreview.getCameraId();
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+ this.getInstrumentation().waitForIdleSync();
+
+ assertTrue(mActivity.getLocationSupplier().noLocationListeners());
+ assertFalse(mActivity.getLocationSupplier().testHasReceivedLocation());
+ assertNull(mActivity.getLocationSupplier().getLocation());
+
+ // return to back camera
+ switchToCamera(cameraId);
+ assertTrue(mActivity.getLocationSupplier().noLocationListeners());
+ assertFalse(mActivity.getLocationSupplier().testHasReceivedLocation());
+ assertNull(mActivity.getLocationSupplier().getLocation());
+ }
+
+ // now switch location back on
+ Log.d(TAG, "now switch location back on");
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.LocationPreferenceKey, true);
+ editor.apply();
+ updateForSettings();
+ }
+
+ long start_t = System.currentTimeMillis();
+ while( !mActivity.getLocationSupplier().testHasReceivedLocation() ) {
+ this.getInstrumentation().waitForIdleSync();
+ if( System.currentTimeMillis() - start_t > 20000 ) {
+ // need to allow long time for testing devices without mobile network; will likely fail altogether if don't even have wifi
+ fail();
+ }
+ }
+ this.getInstrumentation().waitForIdleSync();
+ assertNotNull(mActivity.getLocationSupplier().getLocation());
+
+ // switch to front camera
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ) {
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+ // shouldn't need to wait for test_has_received_location to be true, as should remember from before switching camera
+ assertNotNull(mActivity.getLocationSupplier().getLocation());
+ }
+ }
+
+ /* Tests we don't save location data; also tests that we save other exif data.
+ * May fail on devices without mobile network, especially if we don't even have wifi.
+ */
+ public void testLocationOff() throws IOException {
+ Log.d(TAG, "testLocationOff");
+ subTestLocationOff(false);
+ }
+
+ /* Tests we save gps direction.
+ * May fail on devices without mobile network, especially if we don't even have wifi.
+ */
+ public void testDirectionOn() throws IOException {
+ Log.d(TAG, "testDirectionOn");
+ subTestLocationOff(true);
+ }
+
+ /* As testDirectionOn() but for SAF.
+ * May fail on devices without mobile network, especially if we don't even have wifi.
+ * If this test fails, make sure we've manually selected that folder (as permission can't be given through the test
+ * framework).
+ */
+ public void testDirectionOnSAF() throws IOException {
+ Log.d(TAG, "testDirectionOnSAF");
+
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ) {
+ Log.d(TAG, "SAF requires Android Lollipop or better");
+ return;
+ }
+
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.UsingSAFPreferenceKey, true);
+ editor.putString(PreferenceKeys.SaveLocationSAFPreferenceKey, "content://com.android.externalstorage.documents/tree/primary%3ADCIM%2FOpenCamera");
+ editor.apply();
+ updateForSettings();
+
+ subTestLocationOff(true);
+ }
+
+ /* Tests we disable location when going to settings, but re-enable it when returning to camera.
+ * Also tests camera is turned off when going to settings.
+ * Fails on Android emulator because we immediately get location again after returning from settings.
+ */
+ public void testLocationSettings() throws InterruptedException {
+ Log.d(TAG, "testLocationSettings");
+ setToDefault();
+
+ assertTrue(mPreview.openCameraAttempted());
+ assertNotNull(mPreview.getCameraController());
+ assertTrue(mActivity.getLocationSupplier().noLocationListeners());
+ assertFalse(mActivity.getLocationSupplier().testHasReceivedLocation());
+ assertNull(mActivity.getLocationSupplier().getLocation());
+ Log.d(TAG, "turn on location");
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.LocationPreferenceKey, true);
+ editor.apply();
+ Log.d(TAG, "update settings after turning on location");
+ updateForSettings();
+ Log.d(TAG, "location should now be on");
+ }
+
+ assertTrue(mActivity.getLocationSupplier().hasLocationListeners());
+ Log.d(TAG, "wait until received location");
+
+ long start_t = System.currentTimeMillis();
+ while( !mActivity.getLocationSupplier().testHasReceivedLocation() ) {
+ this.getInstrumentation().waitForIdleSync();
+ if( System.currentTimeMillis() - start_t > 20000 ) {
+ // need to allow long time for testing devices without mobile network; will likely fail altogether if don't even have wifi
+ fail();
+ }
+ }
+ Log.d(TAG, "have received location");
+ this.getInstrumentation().waitForIdleSync();
+ assertNotNull(mActivity.getLocationSupplier().getLocation());
+ // check wasn't cached
+ LocationSupplier.LocationInfo locationInfo = new LocationSupplier.LocationInfo();
+ mActivity.getLocationSupplier().getLocation(locationInfo);
+ assertFalse(locationInfo.LocationWasCached());
+
+ assertTrue(mPreview.openCameraAttempted());
+ assertNotNull(mPreview.getCameraController());
+
+ // now go to settings
+ assertFalse(mActivity.isCameraInBackground());
+ View settingsButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.settings);
+ Log.d(TAG, "about to click settings");
+ clickView(settingsButton);
+ Log.d(TAG, "done clicking settings");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ assertTrue(mActivity.isCameraInBackground());
+
+ // now check we're not listening for location, or opening camera
+ start_t = System.currentTimeMillis();
+ int count = 0;
+ while( System.currentTimeMillis() - start_t <= 15000 ) {
+ assertFalse(mPreview.isOpeningCamera());
+ assertFalse(mPreview.openCameraAttempted());
+ assertNull(mPreview.getCameraController());
+
+ assertTrue(mActivity.getLocationSupplier().noLocationListeners());
+ assertFalse(mActivity.getLocationSupplier().testHasReceivedLocation());
+ assertNull(mActivity.getLocationSupplier().getLocation());
+ Thread.sleep(10);
+ if( count++ == 5 ) {
+ pauseAndResume(); // check we still don't listen for location after pause and resume
+ }
+ }
+
+ // now go back
+ assertTrue(mActivity.isCameraInBackground());
+ Log.d(TAG, "go back");
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mActivity.onBackPressed();
+ }
+ });
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ assertFalse(mActivity.isCameraInBackground());
+
+ // check camera is reopening
+ assertTrue(mPreview.isOpeningCamera() || mPreview.openCameraAttempted());
+
+ // check we start listening again
+ // first should have a cached location
+ assertTrue(mActivity.getLocationSupplier().hasLocationListeners());
+ assertFalse(mActivity.getLocationSupplier().testHasReceivedLocation());
+ assertNotNull(mActivity.getLocationSupplier().getLocation());
+ locationInfo = new LocationSupplier.LocationInfo();
+ mActivity.getLocationSupplier().getLocation(locationInfo);
+ assertTrue(locationInfo.LocationWasCached());
+
+ // check camera is opened after a pause
+ Thread.sleep(1000);
+ assertTrue(mPreview.openCameraAttempted());
+ assertNotNull(mPreview.getCameraController());
+
+ // check we get a non-cached location
+ while( !mActivity.getLocationSupplier().testHasReceivedLocation() ) {
+ this.getInstrumentation().waitForIdleSync();
+ if( System.currentTimeMillis() - start_t > 25000 ) {
+ // need to allow long time for testing devices without mobile network; will likely fail altogether if don't even have wifi
+ fail();
+ }
+ }
+ Log.d(TAG, "have received location");
+ this.getInstrumentation().waitForIdleSync();
+ assertNotNull(mActivity.getLocationSupplier().getLocation());
+ // check wasn't cached
+ locationInfo = new LocationSupplier.LocationInfo();
+ mActivity.getLocationSupplier().getLocation(locationInfo);
+ assertFalse(locationInfo.LocationWasCached());
+
+ // now test repeatedly going to settings and back - guard against crash we had where onLocationChanged got called one more time after
+ // location listeners had been freed
+ for(int i=0;i<20;i++) {
+ assertTrue(mActivity.getLocationSupplier().hasLocationListeners());
+ Thread.sleep((i % 5) * 100);
+
+ // go to settings
+ assertFalse(mActivity.isCameraInBackground());
+ Log.d(TAG, "about to click settings");
+ clickView(settingsButton);
+ Log.d(TAG, "done clicking settings");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ assertTrue(mActivity.isCameraInBackground());
+
+ Thread.sleep(100);
+ assertTrue(mActivity.getLocationSupplier().noLocationListeners());
+ assertFalse(mActivity.getLocationSupplier().testHasReceivedLocation());
+ assertNull(mActivity.getLocationSupplier().getLocation());
+
+ Thread.sleep(200);
+ assertTrue(mActivity.getLocationSupplier().noLocationListeners());
+ assertFalse(mActivity.getLocationSupplier().testHasReceivedLocation());
+ assertNull(mActivity.getLocationSupplier().getLocation());
+
+ // go back
+ assertTrue(mActivity.isCameraInBackground());
+ Log.d(TAG, "go back");
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mActivity.onBackPressed();
+ }
+ });
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ assertFalse(mActivity.isCameraInBackground());
+ }
+
+ // check camera is opened after a pause
+ Thread.sleep(1000);
+ assertTrue(mPreview.openCameraAttempted());
+ assertNotNull(mPreview.getCameraController());
+ }
+
+ private void subTestPhotoStamp() throws IOException {
+ {
+ assertFalse(mActivity.getApplicationInterface().getDrawPreview().getStoredHasStampPref());
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.StampPreferenceKey, "preference_stamp_yes");
+ editor.apply();
+ updateForSettings();
+ assertTrue(mActivity.getApplicationInterface().getDrawPreview().getStoredHasStampPref());
+ }
+
+ assertEquals(0, mPreview.count_cameraTakePicture);
+
+ int n_old_files = getNFiles();
+ Log.d(TAG, "n_old_files: " + n_old_files);
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ clickView(takePhotoButton);
+
+ Log.d(TAG, "wait until finished taking photo");
+ waitForTakePhoto();
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "photo count: " + mPreview.count_cameraTakePicture);
+ assertEquals(1, mPreview.count_cameraTakePicture);
+ mActivity.waitUntilImageQueueEmpty();
+ int n_files = getNFiles() - n_old_files;
+ Log.d(TAG, "n_files: " + n_files);
+ assertEquals(1, n_files);
+ testExif(mActivity.test_last_saved_image, mActivity.test_last_saved_imageuri, true, true, false);
+
+ // now again with location
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.LocationPreferenceKey, true);
+ editor.apply();
+ updateForSettings();
+ }
+
+ assertTrue( mActivity.getLocationSupplier().hasLocationListeners() );
+ long start_t = System.currentTimeMillis();
+ while( !mActivity.getLocationSupplier().testHasReceivedLocation() ) {
+ this.getInstrumentation().waitForIdleSync();
+ if( System.currentTimeMillis() - start_t > 20000 ) {
+ // need to allow long time for testing devices without mobile network; will likely fail altogether if don't even have wifi
+ fail();
+ }
+ }
+ this.getInstrumentation().waitForIdleSync();
+ assertNotNull(mActivity.getLocationSupplier().getLocation());
+
+ clickView(takePhotoButton);
+
+ Log.d(TAG, "wait until finished taking photo");
+ waitForTakePhoto();
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "photo count: " + mPreview.count_cameraTakePicture);
+ assertEquals(2, mPreview.count_cameraTakePicture);
+ mActivity.waitUntilImageQueueEmpty();
+ n_files = getNFiles() - n_old_files;
+ Log.d(TAG, "n_files: " + n_files);
+ assertEquals(2, n_files);
+ testExif(mActivity.test_last_saved_image, mActivity.test_last_saved_imageuri, true, true, true);
+
+ // now again with location and custom text
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.LocationPreferenceKey, true);
+ editor.putString(PreferenceKeys.TextStampPreferenceKey, "Test stamp!£$");
+ editor.apply();
+ updateForSettings();
+ }
+
+ assertTrue( mActivity.getLocationSupplier().hasLocationListeners() );
+ while( !mActivity.getLocationSupplier().testHasReceivedLocation() ) {
+ }
+ this.getInstrumentation().waitForIdleSync();
+ assertNotNull(mActivity.getLocationSupplier().getLocation());
+
+ clickView(takePhotoButton);
+
+ Log.d(TAG, "wait until finished taking photo");
+ waitForTakePhoto();
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "photo count: " + mPreview.count_cameraTakePicture);
+ assertEquals(3, mPreview.count_cameraTakePicture);
+ mActivity.waitUntilImageQueueEmpty();
+ n_files = getNFiles() - n_old_files;
+ Log.d(TAG, "n_files: " + n_files);
+ assertEquals(3, n_files);
+ testExif(mActivity.test_last_saved_image, mActivity.test_last_saved_imageuri, true, true, true);
+
+ // now test with auto-stabilise
+ {
+ assertFalse(mActivity.getApplicationInterface().getDrawPreview().getStoredAutoStabilisePref());
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.TextStampPreferenceKey, "");
+ editor.putBoolean(PreferenceKeys.AutoStabilisePreferenceKey, true);
+ editor.apply();
+ updateForSettings();
+ assertTrue(mActivity.getApplicationInterface().getDrawPreview().getStoredAutoStabilisePref());
+ }
+
+ clickView(takePhotoButton);
+
+ Log.d(TAG, "wait until finished taking photo");
+ waitForTakePhoto();
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "photo count: " + mPreview.count_cameraTakePicture);
+ assertEquals(4, mPreview.count_cameraTakePicture);
+ mActivity.waitUntilImageQueueEmpty();
+ n_files = getNFiles() - n_old_files;
+ Log.d(TAG, "n_files: " + n_files);
+ assertEquals(4, n_files);
+ testExif(mActivity.test_last_saved_image, mActivity.test_last_saved_imageuri, true, true, true);
+
+ // now again with auto-stabilise angle 0
+
+ mActivity.test_have_angle = true;
+ mActivity.test_angle = 0.0f;
+
+ clickView(takePhotoButton);
+
+ Log.d(TAG, "wait until finished taking photo");
+ waitForTakePhoto();
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "photo count: " + mPreview.count_cameraTakePicture);
+ assertEquals(5, mPreview.count_cameraTakePicture);
+ mActivity.waitUntilImageQueueEmpty();
+ n_files = getNFiles() - n_old_files;
+ Log.d(TAG, "n_files: " + n_files);
+ assertEquals(5, n_files);
+ testExif(mActivity.test_last_saved_image, mActivity.test_last_saved_imageuri, true, true, true);
+
+ mActivity.test_have_angle = false;
+ }
+
+ /* Tests we can stamp date/time and location to photo.
+ * May fail on devices without mobile network, especially if we don't even have wifi.
+ */
+ public void testPhotoStamp() throws IOException {
+ Log.d(TAG, "testPhotoStamp");
+
+ setToDefault();
+
+ subTestPhotoStamp();
+ }
+
+ /** As testPhotoStamp() but with SAF.
+ * If this test fails, make sure we've manually selected that folder (as permission can't be given through the test
+ * framework).
+ */
+ public void testPhotoStampSAF() throws IOException {
+ Log.d(TAG, "testPhotoStampSAF");
+
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ) {
+ Log.d(TAG, "SAF requires Android Lollipop or better");
+ return;
+ }
+
+ setToDefault();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.UsingSAFPreferenceKey, true);
+ editor.putString(PreferenceKeys.SaveLocationSAFPreferenceKey, "content://com.android.externalstorage.documents/tree/primary%3ADCIM%2FOpenCamera");
+ editor.apply();
+ updateForSettings();
+
+ subTestPhotoStamp();
+ }
+
+ /* Tests we can stamp custom text to photo.
+ */
+ public void testCustomTextStamp() {
+ Log.d(TAG, "testCustomTextStamp");
+
+ setToDefault();
+
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.TextStampPreferenceKey, "Test stamp!£$");
+ editor.apply();
+ updateForSettings();
+ }
+
+ assertEquals(0, mPreview.count_cameraTakePicture);
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ clickView(takePhotoButton);
+
+ Log.d(TAG, "wait until finished taking photo");
+ waitForTakePhoto();
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "photo count: " + mPreview.count_cameraTakePicture);
+ assertEquals(1, mPreview.count_cameraTakePicture);
+
+ // now test with auto-stabilise
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(PreferenceKeys.AutoStabilisePreferenceKey, true);
+ editor.apply();
+ updateForSettings();
+ }
+
+ clickView(takePhotoButton);
+
+ Log.d(TAG, "wait until finished taking photo");
+ waitForTakePhoto();
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "photo count: " + mPreview.count_cameraTakePicture);
+ assertEquals(2, mPreview.count_cameraTakePicture);
+
+ mActivity.waitUntilImageQueueEmpty();
+ }
+
+ /* Tests zoom.
+ */
+ public void testZoom() throws InterruptedException {
+ Log.d(TAG, "testZoom");
+ setToDefault();
+
+ if( !mPreview.supportsZoom() ) {
+ Log.d(TAG, "zoom not supported");
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+
+ final SeekBar zoomSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.zoom_seekbar);
+ assertEquals(zoomSeekBar.getVisibility(), View.VISIBLE);
+ int max_zoom = mPreview.getMaxZoom();
+ assertEquals(zoomSeekBar.getMax(), max_zoom);
+ Log.d(TAG, "zoomSeekBar progress = " + zoomSeekBar.getProgress());
+ Log.d(TAG, "actual zoom = " + mPreview.getCameraController().getZoom());
+ assertEquals(max_zoom - zoomSeekBar.getProgress(), mPreview.getCameraController().getZoom());
+
+ // test we can find 1x zoom (which won't be the first entry for devices that exposure ultra-wide
+ // cameras via the zoom)
+ boolean found_1xzoom = false;
+ for(int i=0;i 1.0f);
+
+ // check zoom values are non-decreasing
+ for(int i=0;i " + zoom1);
+ assertTrue(zoom1 >= zoom0);
+ }
+
+ if( mPreview.supportsFocus() ) {
+ assertFalse(mPreview.hasFocusArea());
+ assertNull(mPreview.getCameraController().getFocusAreas());
+ assertNull(mPreview.getCameraController().getMeteringAreas());
+
+ // touch to auto-focus with focus area
+ Thread.sleep(2000); // needed for Galaxy S10e for the touch to register
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ assertTrue(mPreview.hasFocusArea());
+ assertNotNull(mPreview.getCameraController().getFocusAreas());
+ assertEquals(1, mPreview.getCameraController().getFocusAreas().size());
+ assertNotNull(mPreview.getCameraController().getMeteringAreas());
+ assertEquals(1, mPreview.getCameraController().getMeteringAreas().size());
+ }
+
+ int zoom = mPreview.getCameraController().getZoom();
+
+ // now test multitouch zoom
+ mPreview.scaleZoom(2.0f);
+ this.getInstrumentation().waitForIdleSync();
+ Thread.sleep(500); // need to wait for zoom transition (for Camera2 API)
+ Log.d(TAG, "compare actual zoom " + mPreview.getCameraController().getZoom() + " to zoom " + zoom);
+ assertTrue(mPreview.getCameraController().getZoom() > zoom);
+ assertEquals(max_zoom - zoomSeekBar.getProgress(), mPreview.getCameraController().getZoom());
+
+ mPreview.scaleZoom(0.5f);
+ this.getInstrumentation().waitForIdleSync();
+ Thread.sleep(500); // need to wait for zoom transition (for Camera2 API)
+ Log.d(TAG, "compare actual zoom " + mPreview.getCameraController().getZoom() + " to zoom " + zoom);
+ // here test the zoom ratio values themselves rather than the indices, as we may have repeated zoom_ratios entries for 1x zoom
+ assertEquals( mPreview.getZoomRatio(mPreview.getCameraController().getZoom()), mPreview.getZoomRatio(zoom) );
+ assertEquals(max_zoom - zoomSeekBar.getProgress(), mPreview.getCameraController().getZoom());
+
+ // test to max/min
+ mPreview.scaleZoom(10000.0f);
+ this.getInstrumentation().waitForIdleSync();
+ Thread.sleep(500); // need to wait for zoom transition (for Camera2 API)
+ Log.d(TAG, "compare actual zoom " + mPreview.getCameraController().getZoom() + " to max_zoom " + max_zoom);
+ assertEquals(mPreview.getCameraController().getZoom(), max_zoom);
+ assertEquals(max_zoom - zoomSeekBar.getProgress(), mPreview.getCameraController().getZoom());
+
+ mPreview.scaleZoom(1.0f/10000.0f);
+ this.getInstrumentation().waitForIdleSync();
+ Thread.sleep(500); // need to wait for zoom transition (for Camera2 API)
+ Log.d(TAG, "compare actual zoom " + mPreview.getCameraController().getZoom() + " to zero");
+ assertEquals(0, mPreview.getCameraController().getZoom());
+ assertEquals(max_zoom - zoomSeekBar.getProgress(), mPreview.getCameraController().getZoom());
+
+ // use seekbar to zoom
+ Log.d(TAG, "zoom to max");
+ Log.d(TAG, "progress was: " + zoomSeekBar.getProgress());
+ zoomSeekBar.setProgress(0);
+ this.getInstrumentation().waitForIdleSync();
+ Thread.sleep(500); // need to wait for zoom transition (for Camera2 API)
+ Log.d(TAG, "compare actual zoom " + mPreview.getCameraController().getZoom() + " to max_zoom " + max_zoom);
+ assertEquals(mPreview.getCameraController().getZoom(), max_zoom);
+ assertEquals(max_zoom - zoomSeekBar.getProgress(), mPreview.getCameraController().getZoom());
+ if( mPreview.supportsFocus() ) {
+ // check that focus areas cleared
+ assertFalse(mPreview.hasFocusArea());
+ assertNull(mPreview.getCameraController().getFocusAreas());
+ assertNull(mPreview.getCameraController().getMeteringAreas());
+ }
+
+ Log.d(TAG, "zoom to min");
+ Log.d(TAG, "progress was: " + zoomSeekBar.getProgress());
+ zoomSeekBar.setProgress(zoomSeekBar.getMax());
+ this.getInstrumentation().waitForIdleSync();
+ Thread.sleep(500); // need to wait for zoom transition (for Camera2 API)
+ Log.d(TAG, "compare actual zoom " + mPreview.getCameraController().getZoom() + " to zoom " + zoom);
+ assertEquals(mPreview.getCameraController().getZoom(), 0);
+ assertEquals(max_zoom - zoomSeekBar.getProgress(), mPreview.getCameraController().getZoom());
+
+ // use volume keys to zoom in/out
+ editor.putString(PreferenceKeys.VolumeKeysPreferenceKey, "volume_zoom");
+ editor.apply();
+
+ Log.d(TAG, "zoom in with volume keys");
+ this.getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_VOLUME_UP);
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "compare actual zoom " + mPreview.getCameraController().getZoom() + " to zoom " + zoom);
+ assertEquals(mPreview.getCameraController().getZoom(), 1);
+ assertEquals(max_zoom - zoomSeekBar.getProgress(), mPreview.getCameraController().getZoom());
+
+ Log.d(TAG, "zoom out with volume keys");
+ this.getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_VOLUME_DOWN);
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "compare actual zoom " + mPreview.getCameraController().getZoom() + " to zoom " + zoom);
+ assertEquals(mPreview.getCameraController().getZoom(), 0);
+ assertEquals(max_zoom - zoomSeekBar.getProgress(), mPreview.getCameraController().getZoom());
+
+ // now test with -/+ control
+
+ Log.d(TAG, "zoom in");
+ mActivity.zoomIn();
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "compare actual zoom " + mPreview.getCameraController().getZoom() + " to zoom " + zoom);
+ assertEquals(mPreview.getCameraController().getZoom(), 1);
+ assertEquals(max_zoom - zoomSeekBar.getProgress(), mPreview.getCameraController().getZoom());
+ if( mPreview.supportsFocus() ) {
+ // check that focus areas cleared
+ assertFalse(mPreview.hasFocusArea());
+ assertNull(mPreview.getCameraController().getFocusAreas());
+ assertNull(mPreview.getCameraController().getMeteringAreas());
+
+ // touch to auto-focus with focus area
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ assertTrue(mPreview.hasFocusArea());
+ assertNotNull(mPreview.getCameraController().getFocusAreas());
+ assertEquals(1, mPreview.getCameraController().getFocusAreas().size());
+ assertNotNull(mPreview.getCameraController().getMeteringAreas());
+ assertEquals(1, mPreview.getCameraController().getMeteringAreas().size());
+ }
+
+ Log.d(TAG, "zoom out");
+ mActivity.zoomOut();
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "compare actual zoom " + mPreview.getCameraController().getZoom() + " to zoom " + zoom);
+ assertEquals(mPreview.getCameraController().getZoom(), 0);
+ assertEquals(max_zoom - zoomSeekBar.getProgress(), mPreview.getCameraController().getZoom());
+ if( mPreview.supportsFocus() ) {
+ // check that focus areas cleared
+ assertFalse(mPreview.hasFocusArea());
+ assertNull(mPreview.getCameraController().getFocusAreas());
+ assertNull(mPreview.getCameraController().getMeteringAreas());
+
+ // touch to auto-focus with focus area
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ assertTrue(mPreview.hasFocusArea());
+ assertNotNull(mPreview.getCameraController().getFocusAreas());
+ assertEquals(1, mPreview.getCameraController().getFocusAreas().size());
+ assertNotNull(mPreview.getCameraController().getMeteringAreas());
+ assertEquals(1, mPreview.getCameraController().getMeteringAreas().size());
+ }
+
+ // now test with slider invisible
+
+ editor.putBoolean(PreferenceKeys.ShowZoomSliderControlsPreferenceKey, false);
+ editor.apply();
+ updateForSettings();
+
+ assertEquals(zoomSeekBar.getVisibility(), View.INVISIBLE);
+
+ Log.d(TAG, "zoom in");
+ mActivity.zoomIn();
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "compare actual zoom " + mPreview.getCameraController().getZoom() + " to zoom " + zoom);
+ assertEquals(mPreview.getCameraController().getZoom(), 1);
+ assertEquals(max_zoom - zoomSeekBar.getProgress(), mPreview.getCameraController().getZoom());
+
+ Log.d(TAG, "zoom out");
+ mActivity.zoomOut();
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "compare actual zoom " + mPreview.getCameraController().getZoom() + " to zoom " + zoom);
+ assertEquals(mPreview.getCameraController().getZoom(), 0);
+ assertEquals(max_zoom - zoomSeekBar.getProgress(), mPreview.getCameraController().getZoom());
+ }
+
+ public void testZoomIdle() throws InterruptedException {
+ Log.d(TAG, "testZoomIdle");
+ setToDefault();
+
+ if( !mPreview.supportsZoom() ) {
+ Log.d(TAG, "zoom not supported");
+ return;
+ }
+
+ final SeekBar zoomSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.zoom_seekbar);
+ assertEquals(zoomSeekBar.getVisibility(), View.VISIBLE);
+ int init_zoom = mPreview.getCameraController().getZoom();
+ int max_zoom = mPreview.getMaxZoom();
+ zoomSeekBar.setProgress(0);
+ this.getInstrumentation().waitForIdleSync();
+ Thread.sleep(500); // need to wait for zoom transition (for Camera2 API)
+ Log.d(TAG, "compare actual zoom " + mPreview.getCameraController().getZoom() + " to zoom " + max_zoom);
+ assertEquals(mPreview.getCameraController().getZoom(), max_zoom);
+ assertEquals(max_zoom - zoomSeekBar.getProgress(), mPreview.getCameraController().getZoom());
+
+ pauseAndResume();
+ Log.d(TAG, "after pause and resume: compare actual zoom " + mPreview.getCameraController().getZoom() + " to zoom " + max_zoom);
+ // as of Open Camera v1.43, zoom is reset when pause/resuming
+ //assertTrue(mPreview.getCameraController().getZoom() == max_zoom);
+ assertEquals(mPreview.getCameraController().getZoom(), init_zoom);
+ assertEquals(max_zoom - zoomSeekBar.getProgress(), mPreview.getCameraController().getZoom());
+ }
+
+ public void testZoomSwitchCamera() throws InterruptedException {
+ Log.d(TAG, "testZoomSwitchCamera");
+ setToDefault();
+
+ if( !mPreview.supportsZoom() ) {
+ Log.d(TAG, "zoom not supported");
+ return;
+ }
+ else if( mPreview.getCameraControllerManager().getNumberOfCameras() <= 1 ) {
+ return;
+ }
+
+ final SeekBar zoomSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.zoom_seekbar);
+ assertEquals(zoomSeekBar.getVisibility(), View.VISIBLE);
+ int init_zoom = mPreview.getCameraController().getZoom();
+ float init_zoom_ratio = mPreview.getZoomRatio(init_zoom);
+ int max_zoom = mPreview.getMaxZoom();
+ zoomSeekBar.setProgress(0);
+ this.getInstrumentation().waitForIdleSync();
+ Thread.sleep(500); // need to wait for zoom transition (for Camera2 API)
+ Log.d(TAG, "compare actual zoom " + mPreview.getCameraController().getZoom() + " to zoom " + max_zoom);
+ assertEquals(mPreview.getCameraController().getZoom(), max_zoom);
+ assertEquals(max_zoom - zoomSeekBar.getProgress(), mPreview.getCameraController().getZoom());
+
+ int cameraId = mPreview.getCameraId();
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+ int new_cameraId = mPreview.getCameraId();
+ Log.d(TAG, "cameraId: " + cameraId);
+ Log.d(TAG, "new_cameraId: " + new_cameraId);
+ assertTrue(cameraId != new_cameraId);
+
+ max_zoom = mPreview.getMaxZoom();
+ Log.d(TAG, "after pause and resume: compare actual zoom " + mPreview.getCameraController().getZoom() + " to zoom " + max_zoom);
+ // as of Open Camera v1.43, zoom is reset when pause/resuming
+ //assertTrue(mPreview.getCameraController().getZoom() == max_zoom);
+ assertEquals(mPreview.getZoomRatio(mPreview.getCameraController().getZoom()), init_zoom_ratio);
+ assertEquals(max_zoom - zoomSeekBar.getProgress(), mPreview.getCameraController().getZoom());
+ }
+
+ /** Switch to front camera, pause and resume, check still on the front camera.
+ */
+ public void testSwitchCameraIdle() {
+ Log.d(TAG, "testSwitchCameraIdle");
+ setToDefault();
+
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() <= 1 ) {
+ return;
+ }
+
+ int cameraId = mPreview.getCameraId();
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+
+ int new_cameraId = mPreview.getCameraId();
+ assertTrue(cameraId != new_cameraId);
+
+ pauseAndResume();
+
+ int new2_cameraId = mPreview.getCameraId();
+ assertEquals(new2_cameraId, new_cameraId);
+
+ }
+
+ /** Tests touching the screen before camera has opened.
+ */
+ public void testTouchFocusQuick() {
+ Log.d(TAG, "testTouchFocusQuick");
+ setToDefault();
+
+ pauseAndResume(false); // don't wait for camera to be reopened, as we want to test touch focus whilst it's opening
+
+ for(int i=0;i<10;i++) {
+ TouchUtils.clickView(MainActivityTest.this, mPreview.getView());
+ }
+ }
+
+ /** Tests trying to switch camera repeatedly, without waiting for camera to open.
+ */
+ public void testSwitchCameraRepeat() {
+ Log.d(TAG, "testSwitchCameraRepeat");
+ setToDefault();
+
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() <= 1 ) {
+ return;
+ }
+
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ for(int i=0;i<100;i++) {
+ clickView(switchCameraButton);
+ }
+ waitUntilCameraOpened();
+ // n.b., don't check the new camera Id, as it's ill-defined which camera will be open
+ // the main point of this test is to check we don't crash due to opening camera on background thread
+ }
+
+ /* Tests repeatedly switching camera, waiting for camera to reopen each time.
+ * Guards against a bug fixed in 1.44 where we would crash due to memory leak in
+ * OrientationEventListener.enable() (from Preview.cameraOpened()) when called too many times.
+ * Note, takes a while (over 1m) to run, test may look like it's hung whilst running!
+ */
+ public void testSwitchCameraRepeat2() {
+ Log.d(TAG, "testSwitchCameraRepeat2");
+ setToDefault();
+
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() <= 1 ) {
+ return;
+ }
+
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ int cameraId = mPreview.getCameraId();
+
+ for(int i=0;i<130;i++) {
+ Log.d(TAG, "i = " + i);
+
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+
+ int new_cameraId = mPreview.getCameraId();
+ assertTrue(new_cameraId != cameraId);
+ cameraId = new_cameraId;
+ }
+ }
+
+ /* Tests going to gallery.
+ */
+ public void testGallery() {
+ Log.d(TAG, "testGallery");
+ setToDefault();
+
+ View galleryButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.gallery);
+ clickView(galleryButton);
+
+ }
+
+ /* Tests going to settings, and back.
+ */
+ public void testSettings() throws InterruptedException {
+ Log.d(TAG, "testSettings");
+ setToDefault();
+
+ restart(false); // so we test going to settings even without waiting for preview to start (for Camera2 API)
+
+ assertFalse(mActivity.isCameraInBackground());
+ View settingsButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.settings);
+ clickView(settingsButton);
+ this.getInstrumentation().waitForIdleSync();
+ assertTrue(mActivity.isCameraInBackground());
+
+ Thread.sleep(500);
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ Log.d(TAG, "on back pressed...");
+ mActivity.onBackPressed();
+ }
+ });
+ // need to wait for UI code to finish before leaving
+ this.getInstrumentation().waitForIdleSync();
+ Thread.sleep(500);
+
+ // check preview starts up
+ waitUntilPreviewStarted();
+ }
+
+ /* Tests going to settings and opening the privacy policy window.
+ */
+ public void testSettingsPrivacyPolicy() throws InterruptedException {
+ Log.d(TAG, "testSettingsPrivacyPolicy");
+ setToDefault();
+
+ assertFalse(mActivity.isCameraInBackground());
+ View settingsButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.settings);
+ clickView(settingsButton);
+ this.getInstrumentation().waitForIdleSync();
+ assertTrue(mActivity.isCameraInBackground());
+ Thread.sleep(500);
+
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ MyPreferenceFragment fragment = mActivity.getPreferenceFragment();
+ assertNotNull(fragment);
+ fragment.clickedPrivacyPolicy();
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ Thread.sleep(1000);
+ }
+
+ /* Tests save and load settings.
+ */
+ public void testSettingsSaveLoad() throws InterruptedException {
+ Log.d(TAG, "testSettingsSaveLoad");
+ setToDefault();
+
+ final String test_string = "Test stamp!£$ <&"; // intentionally include characters that need escaping in xml
+
+ // set a non-default setting
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.TextStampPreferenceKey, test_string);
+ editor.apply();
+ updateForSettings();
+ }
+
+ mActivity.getSettingsManager().saveSettings("test_testSettingsSaveLoad.xml");
+ assertNotNull(mActivity.test_save_settings_file);
+
+ // now modify the aforementioned setting
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.TextStampPreferenceKey, "");
+ editor.apply();
+ updateForSettings();
+ }
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ String new_string = settings.getString(PreferenceKeys.TextStampPreferenceKey, "");
+ assertEquals("", new_string);
+
+ // now load settings
+ assertTrue( mActivity.getSettingsManager().loadSettings(mActivity.test_save_settings_file) );
+
+ // wait - n.b., loadSettings() won't restart due to being test code
+ Thread.sleep(3000);
+ /*mActivity = getActivity();
+ Log.d(TAG, "mActivity is now: " + mActivity);
+ mPreview = mActivity.getPreview();
+ Log.d(TAG, "mPreview is now: " + mPreview);*/
+
+ // now check setting is as expected
+ settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ new_string = settings.getString(PreferenceKeys.TextStampPreferenceKey, "");
+ Log.d(TAG, "new_string: " + new_string);
+ assertEquals(test_string, new_string);
+
+ // check again after a restart
+ restart();
+ settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ new_string = settings.getString(PreferenceKeys.TextStampPreferenceKey, "");
+ Log.d(TAG, "new_string: " + new_string);
+ assertEquals(test_string, new_string);
+ }
+
+ private void subTestCreateSaveFolder(boolean use_saf, String save_folder, boolean delete_folder) {
+ setToDefault();
+
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ if( use_saf ) {
+ editor.putBoolean(PreferenceKeys.UsingSAFPreferenceKey, true);
+ editor.putString(PreferenceKeys.SaveLocationSAFPreferenceKey, save_folder);
+ }
+ else {
+ editor.putString(PreferenceKeys.SaveLocationPreferenceKey, save_folder);
+ }
+ editor.apply();
+ updateForSettings();
+ if( use_saf ) {
+ // need to call this directly, as we don't call mActivity.onActivityResult
+ mActivity.updateFolderHistorySAF(save_folder);
+ }
+ }
+
+ SaveLocationHistory save_location_history = use_saf ? mActivity.getSaveLocationHistorySAF() : mActivity.getSaveLocationHistory();
+ assertTrue(save_location_history.size() > 0);
+ assertTrue(save_location_history.contains(save_folder));
+ assertEquals(save_location_history.get(save_location_history.size() - 1), save_folder);
+
+ File folder = mActivity.getImageFolder();
+ if( folder.exists() && delete_folder ) {
+ // Note when using scoped storage, this won't actually work (although the test code won't fail)
+ // It's not possible to delete folders with scoped storage unless via SAF which would need permission to have been
+ // given to such folders.
+ assertTrue(folder.isDirectory());
+ // delete folder - need to delete contents first
+ if( folder.isDirectory() ) {
+ String [] children = folder.list();
+ if( children != null ) {
+ for(String child : children) {
+ File file = new File(folder, child);
+ //noinspection ResultOfMethodCallIgnored
+ file.delete();
+ MediaScannerConnection.scanFile(mActivity, new String[] { file.getAbsolutePath() }, null, null);
+ }
+ }
+ }
+ //noinspection ResultOfMethodCallIgnored
+ folder.delete();
+ }
+ int n_old_files = getNFiles();
+ Log.d(TAG, "n_old_files: " + n_old_files);
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ Log.d(TAG, "about to click take photo");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take photo");
+
+ Log.d(TAG, "wait until finished taking photo");
+ waitForTakePhoto();
+ Log.d(TAG, "done taking photo");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ assertEquals(1, mPreview.count_cameraTakePicture);
+
+ mActivity.waitUntilImageQueueEmpty();
+
+ int n_new_files = getNFiles();
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(n_new_files, n_old_files + 1);
+
+ // change back to default, so as to not be annoying
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ if( use_saf ) {
+ editor.putString(PreferenceKeys.SaveLocationSAFPreferenceKey, "content://com.android.externalstorage.documents/tree/primary%3ADCIM%2FOpenCamera");
+ }
+ else {
+ editor.putString(PreferenceKeys.SaveLocationPreferenceKey, "OpenCamera");
+ }
+ editor.apply();
+ }
+ }
+
+ /** Tests taking a photo with a new save folder.
+ */
+ public void testCreateSaveFolder1() {
+ Log.d(TAG, "testCreateSaveFolder1");
+ subTestCreateSaveFolder(false, "OpenCameraTest", true);
+ }
+
+ /** Tests taking a photo with a new save folder.
+ */
+ public void testCreateSaveFolder2() {
+ Log.d(TAG, "testCreateSaveFolder2");
+ subTestCreateSaveFolder(false, "OpenCameraTest/", true);
+ }
+
+ /** Tests taking a photo with a new save folder.
+ */
+ public void testCreateSaveFolder3() {
+ Log.d(TAG, "testCreateSaveFolder3");
+ subTestCreateSaveFolder(false, "OpenCameraTest_a/OpenCameraTest_b", true);
+ }
+
+ /** Tests taking a photo with a new save folder.
+ */
+ @SuppressLint("SdCardPath")
+ public void testCreateSaveFolder4() {
+ Log.d(TAG, "testCreateSaveFolder4");
+
+ if( MainActivity.useScopedStorage() ) {
+ // can't save outside DCIM when using scoped storage
+ return;
+ }
+
+ subTestCreateSaveFolder(false, "/sdcard/Pictures/OpenCameraTest", true);
+ }
+
+ /** Tests taking a photo with a new save folder.
+ */
+ public void testCreateSaveFolderUnicode() {
+ Log.d(TAG, "testCreateSaveFolderUnicode");
+ subTestCreateSaveFolder(false, "éúíóá!£$%^&()", true);
+ }
+
+ /** Tests taking a photo with a new save folder.
+ */
+ public void testCreateSaveFolderEmpty() {
+ Log.d(TAG, "testCreateSaveFolderEmpty");
+ subTestCreateSaveFolder(false, "", false);
+ }
+
+ /** Tests taking a photo with a new save folder.
+ * If this test fails, make sure we've manually selected that folder (as permission can't be given through the test framework).
+ */
+ public void testCreateSaveFolderSAF() {
+ Log.d(TAG, "testCreateSaveFolderSAF");
+
+ if( Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ) {
+ Log.d(TAG, "SAF requires Android Lollipop or better");
+ return;
+ }
+
+ subTestCreateSaveFolder(true, "content://com.android.externalstorage.documents/tree/primary%3ADCIM", true);
+ }
+
+ /** Tests code for checking existing non-SAF save locations, when updating to scoped storage.
+ * Case where save location is invalid for scoped storage.
+ */
+ public void testScopedStorageChecks1() {
+ Log.d(TAG, "testScopedStorageChecks1");
+ setToDefault();
+
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.SaveLocationPreferenceKey, "/storage/emulated/0/Pictures/Camera");
+ editor.putString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_0", "OpenCamera");
+ editor.putString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_1", "/storage/emulated/0/DCIM");
+ editor.putString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_2", "/storage/emulated/0/DCIM/OpenCameraéúíóá!£$%^&()/test");
+ editor.putString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_3", "Camera");
+ editor.putString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_4", "/storage/sdcard/DCIM/OpenCamera");
+ editor.putString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_5", "/storage/emulated/0/Pictures/Camera");
+ editor.putInt(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_size", 6);
+ editor.apply();
+ }
+
+ restart();
+
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ if( MainActivity.useScopedStorage() ) {
+ assertEquals("OpenCamera", sharedPreferences.getString(PreferenceKeys.SaveLocationPreferenceKey, "OpenCamera"));
+ // because the save folder is reset to "OpenCamera", we've also removed it from the original position in the history
+ assertEquals("", sharedPreferences.getString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_0", null));
+ assertEquals("OpenCameraéúíóá!£$%^&()/test", sharedPreferences.getString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_1", null));
+ assertEquals("Camera", sharedPreferences.getString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_2", null));
+ // invalid entry removed here
+ assertEquals("OpenCamera", sharedPreferences.getString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_3", null));
+ assertEquals(4, sharedPreferences.getInt(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_size", 0));
+ }
+ else {
+ // should be unchanged
+ assertEquals("/storage/emulated/0/Pictures/Camera", sharedPreferences.getString(PreferenceKeys.SaveLocationPreferenceKey, "OpenCamera"));
+ assertEquals("OpenCamera", sharedPreferences.getString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_0", null));
+ assertEquals("/storage/emulated/0/DCIM", sharedPreferences.getString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_1", null));
+ assertEquals("/storage/emulated/0/DCIM/OpenCameraéúíóá!£$%^&()/test", sharedPreferences.getString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_2", null));
+ assertEquals("Camera", sharedPreferences.getString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_3", null));
+ assertEquals("/storage/sdcard/DCIM/OpenCamera", sharedPreferences.getString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_4", null));
+ assertEquals("/storage/emulated/0/Pictures/Camera", sharedPreferences.getString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_5", null));
+ assertEquals(6, sharedPreferences.getInt(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_size", 0));
+ }
+ }
+
+ /** Tests code for checking existing non-SAF save locations, when updating to scoped storage.
+ * This test a version where everything is valid and shouldn't change.
+ */
+ public void testScopedStorageChecks2() {
+ Log.d(TAG, "testScopedStorageChecks2");
+ setToDefault();
+
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.SaveLocationPreferenceKey, "Camera");
+ editor.putString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_0", "OpenCamera");
+ editor.putString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_1", "test");
+ editor.putString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_2", "Camera");
+ editor.putInt(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_size", 3);
+ editor.apply();
+ }
+
+ restart();
+
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ assertEquals("Camera", sharedPreferences.getString(PreferenceKeys.SaveLocationPreferenceKey, "OpenCamera"));
+ assertEquals("OpenCamera", sharedPreferences.getString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_0", null));
+ assertEquals("test", sharedPreferences.getString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_1", null));
+ assertEquals("Camera", sharedPreferences.getString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_2", null));
+ assertEquals(3, sharedPreferences.getInt(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_size", 0));
+ }
+
+ /** Tests code for checking existing non-SAF save locations, when updating to scoped storage.
+ * This tests a version with default settings, where nothing should change.
+ */
+ public void testScopedStorageChecks3() {
+ Log.d(TAG, "testScopedStorageChecks3");
+ setToDefault();
+
+ restart();
+
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ assertEquals("OpenCamera", sharedPreferences.getString(PreferenceKeys.SaveLocationPreferenceKey, "OpenCamera"));
+ assertEquals("OpenCamera", sharedPreferences.getString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_0", null));
+ assertEquals(1, sharedPreferences.getInt(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_size", 0));
+ }
+
+ /** Tests code for checking existing non-SAF save locations, when updating to scoped storage.
+ * Case where save location contains sub-folder, so is valid but needs updating for scoped storage.
+ */
+ public void testScopedStorageChecks4() {
+ Log.d(TAG, "testScopedStorageChecks4");
+ setToDefault();
+
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.SaveLocationPreferenceKey, "/storage/emulated/0/DCIM/OpenCamera/subfolder");
+ editor.putString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_0", "/storage/emulated/0/DCIM/OpenCamera/subfolder/subfolder");
+ editor.putString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_1", "/storage/emulated/0/Pictures");
+ editor.putString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_2", "/storage/emulated/0");
+ editor.putString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_3", "OpenCamera");
+ editor.putString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_4", "/storage/emulated/0/DCIM/OpenCamera/subfolder");
+ editor.putInt(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_size", 5);
+ editor.apply();
+ }
+
+ restart();
+
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ if( MainActivity.useScopedStorage() ) {
+ assertEquals("OpenCamera/subfolder", sharedPreferences.getString(PreferenceKeys.SaveLocationPreferenceKey, "OpenCamera"));
+ assertEquals("OpenCamera/subfolder/subfolder", sharedPreferences.getString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_0", null));
+ // invalid entry removed here
+ // invalid entry removed here
+ assertEquals("OpenCamera", sharedPreferences.getString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_1", null));
+ assertEquals("OpenCamera/subfolder", sharedPreferences.getString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_2", null));
+ assertEquals(3, sharedPreferences.getInt(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_size", 0));
+ }
+ else {
+ // should be unchanged
+ assertEquals("/storage/emulated/0/DCIM/OpenCamera/subfolder", sharedPreferences.getString(PreferenceKeys.SaveLocationPreferenceKey, "OpenCamera"));
+ assertEquals("/storage/emulated/0/DCIM/OpenCamera/subfolder/subfolder", sharedPreferences.getString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_0", null));
+ assertEquals("/storage/emulated/0/Pictures", sharedPreferences.getString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_1", null));
+ assertEquals("/storage/emulated/0", sharedPreferences.getString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_2", null));
+ assertEquals("OpenCamera", sharedPreferences.getString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_3", null));
+ assertEquals("/storage/emulated/0/DCIM/OpenCamera/subfolder", sharedPreferences.getString(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_4", null));
+ assertEquals(5, sharedPreferences.getInt(PreferenceKeys.SaveLocationHistoryBasePreferenceKey + "_size", 0));
+ }
+ }
+
+ /** Tests launching the folder chooser on a new folder.
+ */
+ public void testFolderChooserNew() throws InterruptedException {
+ Log.d(TAG, "testFolderChooserNew");
+
+ if( MainActivity.useScopedStorage() ) {
+ Log.d(TAG, "folder chooser not relevant for scoped storage");
+ return;
+ }
+
+ setToDefault();
+
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.SaveLocationPreferenceKey, "OpenCameraTest");
+ editor.apply();
+ updateForSettings();
+ }
+
+ File folder = mActivity.getImageFolder();
+ if( folder.exists() ) {
+ assertTrue(folder.isDirectory());
+ // delete folder - need to delete contents first
+ if( folder.isDirectory() ) {
+ String [] children = folder.list();
+ if( children != null ) {
+ for(String child : children) {
+ File file = new File(folder, child);
+ //noinspection ResultOfMethodCallIgnored
+ file.delete();
+ MediaScannerConnection.scanFile(mActivity, new String[] { file.getAbsolutePath() }, null, null);
+ }
+ }
+ }
+ //noinspection ResultOfMethodCallIgnored
+ folder.delete();
+ }
+
+ FolderChooserDialog fragment = new FolderChooserDialog();
+ fragment.setStartFolder(mActivity.getImageFolder());
+ fragment.show(mActivity.getFragmentManager(), "FOLDER_FRAGMENT");
+ Thread.sleep(1000); // wait until folderchooser started up
+ Log.d(TAG, "started folderchooser");
+ assertNotNull(fragment.getCurrentFolder());
+ assertEquals(fragment.getCurrentFolder(), folder);
+ assertTrue(folder.exists());
+ }
+
+ /** Tests launching the folder chooser on a folder we don't have access to.
+ * (Shouldn't be possible to get into this state, but just in case.)
+ */
+ public void testFolderChooserInvalid() throws InterruptedException {
+ Log.d(TAG, "testFolderChooserInvalid");
+
+ if( MainActivity.useScopedStorage() ) {
+ Log.d(TAG, "folder chooser not relevant for scoped storage");
+ return;
+ }
+
+ setToDefault();
+
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.SaveLocationPreferenceKey, "/OpenCameraTest");
+ editor.apply();
+ updateForSettings();
+ }
+
+ FolderChooserDialog fragment = new FolderChooserDialog();
+ fragment.setStartFolder(mActivity.getImageFolder());
+ fragment.show(mActivity.getFragmentManager(), "FOLDER_FRAGMENT");
+ Thread.sleep(1000); // wait until folderchooser started up
+ Log.d(TAG, "started folderchooser");
+ assertNotNull(fragment.getCurrentFolder());
+ Log.d(TAG, "current folder: " + fragment.getCurrentFolder());
+ assertTrue(fragment.getCurrentFolder().exists());
+ }
+
+ private void subTestSaveFolderHistory(final boolean use_saf) {
+ // clearFolderHistory has code that must be run on UI thread
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ Log.d(TAG, "clearFolderHistory");
+ if( use_saf )
+ mActivity.clearFolderHistorySAF();
+ else
+ mActivity.clearFolderHistory();
+ }
+ });
+ // need to wait for UI code to finish before leaving
+ this.getInstrumentation().waitForIdleSync();
+ SaveLocationHistory save_location_history = use_saf ? mActivity.getSaveLocationHistorySAF() : mActivity.getSaveLocationHistory();
+ Log.d(TAG, "save_location_history size: " + save_location_history.size());
+ assertEquals(1, save_location_history.size());
+ String current_folder;
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ current_folder = use_saf ? settings.getString(PreferenceKeys.SaveLocationSAFPreferenceKey, "") : settings.getString(PreferenceKeys.SaveLocationPreferenceKey, "OpenCamera");
+ Log.d(TAG, "current_folder: " + current_folder);
+ Log.d(TAG, "save_location_history entry: " + save_location_history.get(0));
+ assertEquals(save_location_history.get(0), current_folder);
+ }
+
+ {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(use_saf ? PreferenceKeys.SaveLocationSAFPreferenceKey : PreferenceKeys.SaveLocationPreferenceKey, "new_folder_history_entry");
+ editor.apply();
+ updateForSettings();
+ if( use_saf ) {
+ // need to call this directly, as we don't call mActivity.onActivityResult
+ mActivity.updateFolderHistorySAF("new_folder_history_entry");
+ }
+ }
+ save_location_history = use_saf ? mActivity.getSaveLocationHistorySAF() : mActivity.getSaveLocationHistory();
+ Log.d(TAG, "save_location_history size: " + save_location_history.size());
+ for(int i=0;i scene_modes = mPreview.getSupportedSceneModes();
+ if( scene_modes == null ) {
+ return;
+ }
+ Log.d(TAG, "scene mode: " + mPreview.getCameraController().getSceneMode());
+ assertTrue( mPreview.getCameraController().getSceneMode() == null || mPreview.getCameraController().getSceneMode().equals(CameraController.SCENE_MODE_DEFAULT) );
+
+ String scene_mode = null;
+ // find a scene mode that isn't default
+ for(String this_scene_mode : scene_modes) {
+ if( !this_scene_mode.equals(CameraController.SCENE_MODE_DEFAULT) ) {
+ scene_mode = this_scene_mode;
+ break;
+ }
+ }
+ if( scene_mode == null ) {
+ return;
+ }
+ Log.d(TAG, "change to scene_mode: " + scene_mode);
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.SceneModePreferenceKey, scene_mode);
+ editor.apply();
+ updateForSettings();
+
+ String new_scene_mode = mPreview.getCameraController().getSceneMode();
+ Log.d(TAG, "scene_mode is now: " + new_scene_mode);
+ assertEquals(new_scene_mode, scene_mode);
+
+ // Now set back to default - important as on some devices, non-default scene modes may override e.g. what
+ // white balance mode can be set.
+ // This was needed to fix the test testCameraModes() on Galaxy Nexus, which started failing in
+ // April 2018 for v1.43. Earlier versions (e.g., 1.42) still had the problem despite previously
+ // testing fine, so something must have changed on the device?
+
+ settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ editor = settings.edit();
+ editor.putString(PreferenceKeys.SceneModePreferenceKey, CameraController.SCENE_MODE_DEFAULT);
+ editor.apply();
+ updateForSettings();
+
+ new_scene_mode = mPreview.getCameraController().getSceneMode();
+ Log.d(TAG, "scene_mode is now: " + new_scene_mode);
+ assertEquals(new_scene_mode, CameraController.SCENE_MODE_DEFAULT);
+ }
+
+ private void subTestColorEffect() {
+ Log.d(TAG, "subTestColorEffect");
+
+ setToDefault();
+
+ List color_effects = mPreview.getSupportedColorEffects();
+ if( color_effects == null ) {
+ return;
+ }
+ Log.d(TAG, "color effect: " + mPreview.getCameraController().getColorEffect());
+ assertTrue( mPreview.getCameraController().getColorEffect() == null || mPreview.getCameraController().getColorEffect().equals(CameraController.COLOR_EFFECT_DEFAULT) );
+
+ String color_effect = null;
+ // find a color effect that isn't default
+ for(String this_color_effect : color_effects) {
+ if( !this_color_effect.equals(CameraController.COLOR_EFFECT_DEFAULT) ) {
+ color_effect = this_color_effect;
+ break;
+ }
+ }
+ if( color_effect == null ) {
+ return;
+ }
+ Log.d(TAG, "change to color_effect: " + color_effect);
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.ColorEffectPreferenceKey, color_effect);
+ editor.apply();
+ updateForSettings();
+
+ String new_color_effect = mPreview.getCameraController().getColorEffect();
+ Log.d(TAG, "color_effect is now: " + new_color_effect);
+ assertEquals(new_color_effect, color_effect);
+ }
+
+ private void subTestWhiteBalance() {
+ Log.d(TAG, "subTestWhiteBalance");
+
+ setToDefault();
+
+ List white_balances = mPreview.getSupportedWhiteBalances();
+ if( white_balances == null ) {
+ return;
+ }
+ Log.d(TAG, "white balance: " + mPreview.getCameraController().getWhiteBalance());
+ assertTrue( mPreview.getCameraController().getWhiteBalance() == null || mPreview.getCameraController().getWhiteBalance().equals(CameraController.WHITE_BALANCE_DEFAULT) );
+
+ String white_balance = null;
+ // find a white balance that isn't default
+ for(String this_white_balances : white_balances) {
+ if( !this_white_balances.equals(CameraController.WHITE_BALANCE_DEFAULT) ) {
+ white_balance = this_white_balances;
+ break;
+ }
+ }
+ if( white_balance == null ) {
+ return;
+ }
+ Log.d(TAG, "change to white_balance: " + white_balance);
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.WhiteBalancePreferenceKey, white_balance);
+ editor.apply();
+ updateForSettings();
+
+ String new_white_balance = mPreview.getCameraController().getWhiteBalance();
+ Log.d(TAG, "white_balance is now: " + new_white_balance);
+ assertEquals(new_white_balance, white_balance);
+ }
+
+ private void subTestImageQuality() {
+ Log.d(TAG, "subTestImageQuality");
+
+ setToDefault();
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.QualityPreferenceKey, "100");
+ editor.apply();
+ updateForSettings();
+
+ int quality = mPreview.getCameraController().getJpegQuality();
+ Log.d(TAG, "quality is: " + quality);
+ assertEquals(100, quality);
+ }
+
+ /** Note this test fails on Android emulator with old camera API, because we get a
+ * RuntimeException from setParameters when trying to set white balance (we catch the
+ * exception, but the test fails because the white balance hasn't been changed to the expected
+ * value).
+ */
+ public void testCameraModes() {
+ Log.d(TAG, "testCameraModes");
+ subTestSceneMode();
+ subTestColorEffect();
+ subTestWhiteBalance();
+ subTestImageQuality();
+ }
+
+ /** Tests that changing resolutions doesn't close the popup.
+ */
+ public void testSwitchResolution() throws InterruptedException {
+ Log.d(TAG, "testSwitchResolution");
+
+ setToDefault();
+
+ View popupButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.popup);
+ CameraController.Size old_picture_size = mPreview.getCameraController().getPictureSize();
+
+ // open popup
+ openPopupMenu();
+
+ TextView photoResolutionButton = (TextView)mActivity.getUIButton("PHOTO_RESOLUTIONS");
+ assertNotNull(photoResolutionButton);
+ //String exp_size_string = old_picture_size.width + " x " + old_picture_size.height + " " + Preview.getMPString(old_picture_size.width, old_picture_size.height);
+ //String exp_size_string = old_picture_size.width + " x " + old_picture_size.height;
+ String exp_size_string = old_picture_size.width + " x " + old_picture_size.height + " (" + Preview.getMPString(old_picture_size.width, old_picture_size.height) + ")";
+ Log.d(TAG, "size string: " + photoResolutionButton.getText());
+ assertEquals(exp_size_string, photoResolutionButton.getText());
+
+ // change photo resolution
+ View photoResolutionChangeButton = mActivity.getUIButton("PHOTO_RESOLUTIONS_PREV");
+ assertNotNull(photoResolutionChangeButton);
+ this.getInstrumentation().waitForIdleSync();
+ clickView(photoResolutionChangeButton);
+
+ // check
+ Thread.sleep(2000);
+ CameraController.Size new_picture_size = mPreview.getCameraController().getPictureSize();
+ Log.d(TAG, "old picture size: " + old_picture_size.width + " x " + old_picture_size.height);
+ Log.d(TAG, "old new_picture_size size: " + new_picture_size.width + " x " + new_picture_size.height);
+ assertNotEquals(new_picture_size, old_picture_size);
+ assertTrue( mActivity.popupIsOpen() );
+
+ //exp_size_string = new_picture_size.width + " x " + new_picture_size.height + " " + Preview.getMPString(new_picture_size.width, new_picture_size.height);
+ //exp_size_string = new_picture_size.width + " x " + new_picture_size.height;
+ exp_size_string = new_picture_size.width + " x " + new_picture_size.height + " (" + Preview.getMPString(new_picture_size.width, new_picture_size.height) + ")";
+ Log.d(TAG, "size string: " + photoResolutionButton.getText());
+ assertEquals(photoResolutionButton.getText(), exp_size_string);
+
+ // switch to video mode
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertTrue(mPreview.isVideo());
+
+ // open popup
+ openPopupMenu();
+
+ TextView videoResolutionButton = (TextView)mActivity.getUIButton("VIDEO_RESOLUTIONS");
+ assertNotNull(videoResolutionButton);
+ CharSequence oldVideoResolutionString = videoResolutionButton.getText();
+
+ // change video resolution
+ View videoResolutionChangeButton = mActivity.getUIButton("VIDEO_RESOLUTIONS_PREV");
+ assertNotNull(videoResolutionChangeButton);
+ clickView(videoResolutionChangeButton);
+
+ // check
+ Thread.sleep(500);
+ assertTrue( mActivity.popupIsOpen() );
+ assertNotEquals(videoResolutionButton.getText(), oldVideoResolutionString);
+
+ }
+
+ /* Test for failing to open camera.
+ */
+ public void testFailOpenCamera() throws InterruptedException {
+ Log.d(TAG, "testFailOpenCamera");
+
+ setToDefault();
+
+ assertNotNull(mPreview.getCameraControllerManager());
+ assertNotNull(mPreview.getCameraController());
+ mPreview.test_fail_open_camera = true;
+
+ // can't test on startup, as camera is created when we create activity, so instead test by switching camera
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ) {
+ Log.d(TAG, "switch camera");
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ waitUntilCameraOpened(false);
+ assertNotNull(mPreview.getCameraControllerManager());
+ assertNull(mPreview.getCameraController());
+ this.getInstrumentation().waitForIdleSync();
+
+ assertFalse( mActivity.popupIsOpen() );
+ View popupButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.popup);
+ Log.d(TAG, "about to click popup");
+ clickView(popupButton);
+ Log.d(TAG, "done clicking popup");
+ Thread.sleep(500);
+ // if camera isn't opened, popup shouldn't open
+ assertFalse( mActivity.popupIsOpen() );
+
+ View settingsButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.settings);
+ Log.d(TAG, "about to click settings");
+ clickView(settingsButton);
+ Log.d(TAG, "done clicking settings");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.VolumeKeysPreferenceKey, "volume_exposure");
+ editor.apply();
+ this.getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_VOLUME_UP);
+ }
+
+ public void testTakePhotoDRO() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoDRO");
+
+ setToDefault();
+
+ if( !mActivity.supportsDRO() ) {
+ return;
+ }
+
+ assertEquals(90, mActivity.getApplicationInterface().getImageQualityPref());
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_dro");
+ editor.apply();
+ updateForSettings();
+
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.DRO);
+ assertEquals(100, mActivity.getApplicationInterface().getImageQualityPref());
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+
+ assertEquals(100, mActivity.getApplicationInterface().getImageQualityPref());
+
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+
+ assertEquals(90, mActivity.getApplicationInterface().getImageQualityPref());
+
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertEquals(100, mActivity.getApplicationInterface().getImageQualityPref());
+
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_std");
+ editor.apply();
+ updateForSettings();
+
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.Standard);
+ assertEquals(90, mActivity.getApplicationInterface().getImageQualityPref());
+ }
+
+ public void testTakePhotoDROPhotoStamp() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoDROPhotoStamp");
+
+ setToDefault();
+
+ if( !mActivity.supportsDRO() ) {
+ return;
+ }
+
+ assertEquals(90, mActivity.getApplicationInterface().getImageQualityPref());
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_dro");
+ editor.putString(PreferenceKeys.StampPreferenceKey, "preference_stamp_yes");
+ editor.apply();
+ updateForSettings();
+
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.DRO);
+ assertEquals(100, mActivity.getApplicationInterface().getImageQualityPref());
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+
+ assertEquals(100, mActivity.getApplicationInterface().getImageQualityPref());
+
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_std");
+ editor.apply();
+ updateForSettings();
+
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.Standard);
+ assertEquals(90, mActivity.getApplicationInterface().getImageQualityPref());
+ }
+
+ /** Tests restarting in HDR mode.
+ */
+ public void testHDRRestart() {
+ Log.d(TAG, "testHDRRestart");
+ setToDefault();
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.Standard);
+
+ if( !mActivity.supportsHDR() ) {
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_hdr");
+ editor.apply();
+
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.HDR);
+ restart();
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.HDR);
+ }
+
+ public void testTakePhotoHDR() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoHDR");
+
+ setToDefault();
+
+ if( !mActivity.supportsHDR() ) {
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_hdr");
+ editor.apply();
+ updateForSettings();
+
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.HDR);
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ if( mPreview.usingCamera2API() ) {
+ Log.d(TAG, "test_capture_results: " + mPreview.getCameraController().test_capture_results);
+ assertEquals(1, mPreview.getCameraController().test_capture_results);
+ }
+ }
+
+ /** Tests taking photo in HDR photo mode with fast expo/HDR burst disabled.
+ */
+ public void testTakePhotoHDRSlowBurst() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoHDRSlowBurst");
+
+ setToDefault();
+
+ if( !mActivity.supportsHDR() ) {
+ return;
+ }
+ if( !mPreview.usingCamera2API() ) {
+ Log.d(TAG, "test requires camera2 api");
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_hdr");
+ editor.putBoolean(PreferenceKeys.Camera2FastBurstPreferenceKey, false);
+ editor.apply();
+ updateForSettings();
+
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.HDR);
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ if( mPreview.usingCamera2API() ) {
+ Log.d(TAG, "test_capture_results: " + mPreview.getCameraController().test_capture_results);
+ assertEquals(1, mPreview.getCameraController().test_capture_results);
+ }
+ }
+
+ /** Tests taking photo in HDR photo mode with saving base expo images.
+ */
+ public void testTakePhotoHDRSaveExpo() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoHDRSaveExpo");
+
+ setToDefault();
+
+ if( !mActivity.supportsHDR() ) {
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_hdr");
+ editor.putBoolean(PreferenceKeys.HDRSaveExpoPreferenceKey, true);
+ editor.apply();
+ updateForSettings();
+
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.HDR);
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ if( mPreview.usingCamera2API() ) {
+ Log.d(TAG, "test_capture_results: " + mPreview.getCameraController().test_capture_results);
+ assertEquals(1, mPreview.getCameraController().test_capture_results);
+ }
+ }
+
+ /** Tests taking photo in HDR photo mode with saving base expo images, with RAW.
+ */
+ public void testTakePhotoHDRSaveExpoRaw() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoHDRSaveExpoRaw");
+
+ setToDefault();
+
+ if( !mActivity.supportsHDR() ) {
+ return;
+ }
+ if( !mPreview.supportsRaw() ) {
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.RawPreferenceKey, "preference_raw_yes");
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_hdr");
+ editor.putBoolean(PreferenceKeys.HDRSaveExpoPreferenceKey, true);
+ editor.apply();
+ updateForSettings();
+
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.HDR);
+ subTestTakePhoto(false, false, true, true, false, false, true, false);
+ if( mPreview.usingCamera2API() ) {
+ Log.d(TAG, "test_capture_results: " + mPreview.getCameraController().test_capture_results);
+ assertEquals(1, mPreview.getCameraController().test_capture_results);
+ }
+ }
+
+ /** Tests taking photo in HDR photo mode with saving base expo images, with RAW only.
+ */
+ public void testTakePhotoHDRSaveExpoRawOnly() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoHDRSaveExpoRawOnly");
+
+ setToDefault();
+
+ if( !mActivity.supportsHDR() ) {
+ return;
+ }
+ if( !mPreview.supportsRaw() ) {
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.RawPreferenceKey, "preference_raw_only");
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_hdr");
+ editor.putBoolean(PreferenceKeys.HDRSaveExpoPreferenceKey, true);
+ editor.apply();
+ updateForSettings();
+
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.HDR);
+ subTestTakePhoto(false, false, true, true, false, false, true, false);
+ if( mPreview.usingCamera2API() ) {
+ Log.d(TAG, "test_capture_results: " + mPreview.getCameraController().test_capture_results);
+ assertEquals(1, mPreview.getCameraController().test_capture_results);
+ }
+ }
+
+ /** Take photo in HDR mode with front camera.
+ * Note that this fails on OnePlus 3T with old camera API, due to bug where photo resolution changes when
+ * exposure compensation set for front camera.
+ */
+ public void testTakePhotoHDRFrontCamera() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoHDRFrontCamera");
+
+ setToDefault();
+
+ if( !mActivity.supportsHDR() ) {
+ return;
+ }
+ if( mPreview.getCameraControllerManager().getNumberOfCameras() <= 1 ) {
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_hdr");
+ editor.apply();
+ updateForSettings();
+
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.HDR);
+
+ int cameraId = mPreview.getCameraId();
+
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ clickView(switchCameraButton);
+ waitUntilCameraOpened();
+
+ int new_cameraId = mPreview.getCameraId();
+
+ Log.d(TAG, "cameraId: " + cameraId);
+ Log.d(TAG, "new_cameraId: " + new_cameraId);
+
+ assertTrue(cameraId != new_cameraId);
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ if( mPreview.usingCamera2API() ) {
+ Log.d(TAG, "test_capture_results: " + mPreview.getCameraController().test_capture_results);
+ assertEquals(1, mPreview.getCameraController().test_capture_results);
+ }
+ }
+
+ public void testTakePhotoHDRAutoStabilise() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoHDRAutoStabilise");
+
+ setToDefault();
+
+ if( !mActivity.supportsHDR() ) {
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_hdr");
+ editor.putBoolean(PreferenceKeys.AutoStabilisePreferenceKey, true);
+ editor.apply();
+ updateForSettings();
+
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.HDR);
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ if( mPreview.usingCamera2API() ) {
+ Log.d(TAG, "test_capture_results: " + mPreview.getCameraController().test_capture_results);
+ assertEquals(1, mPreview.getCameraController().test_capture_results);
+ }
+ }
+
+ public void testTakePhotoHDRPhotoStamp() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoHDRPhotoStamp");
+
+ setToDefault();
+
+ if( !mActivity.supportsHDR() ) {
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_hdr");
+ editor.putString(PreferenceKeys.StampPreferenceKey, "preference_stamp_yes");
+ editor.apply();
+ updateForSettings();
+
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.HDR);
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ if( mPreview.usingCamera2API() ) {
+ Log.d(TAG, "test_capture_results: " + mPreview.getCameraController().test_capture_results);
+ assertEquals(1, mPreview.getCameraController().test_capture_results);
+ }
+ }
+
+ /** Tests expo bracketing with default values.
+ */
+ public void testTakePhotoExpo() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoExpo");
+
+ setToDefault();
+
+ if( !mActivity.supportsExpoBracketing() ) {
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_expo_bracketing");
+ editor.apply();
+ updateForSettings();
+
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.ExpoBracketing);
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ if( mPreview.usingCamera2API() ) {
+ Log.d(TAG, "test_capture_results: " + mPreview.getCameraController().test_capture_results);
+ assertEquals(1, mPreview.getCameraController().test_capture_results);
+ }
+ }
+
+ /** Tests expo bracketing with 5 images, 1 stop.
+ */
+ public void testTakePhotoExpo5() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoExpo5");
+
+ setToDefault();
+
+ if( !mActivity.supportsExpoBracketing() ) {
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_expo_bracketing");
+ editor.putString(PreferenceKeys.ExpoBracketingNImagesPreferenceKey, "5");
+ editor.putString(PreferenceKeys.ExpoBracketingStopsPreferenceKey, "1");
+ editor.apply();
+ updateForSettings();
+
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.ExpoBracketing);
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ if( mPreview.usingCamera2API() ) {
+ Log.d(TAG, "test_capture_results: " + mPreview.getCameraController().test_capture_results);
+ assertEquals(1, mPreview.getCameraController().test_capture_results);
+ }
+ }
+
+ /* Sets focus bracketing seek bars to some test positions.
+ */
+ private void setUpFocusBracketing() throws InterruptedException {
+ SeekBar focusSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.focus_seekbar);
+ SeekBar focusTargetSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.focus_bracketing_target_seekbar);
+
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.FocusBracketing);
+ assertEquals(focusSeekBar.getVisibility(), View.VISIBLE);
+ focusSeekBar.setProgress( (int)(0.9*(focusSeekBar.getMax()-1)) );
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "source focus_distance: " + mPreview.getCameraController().getFocusDistance());
+ mPreview.stoppedSettingFocusDistance(false); // hack, since onStopTrackingTouch() isn't called programmatically!
+ this.getInstrumentation().waitForIdleSync();
+ Thread.sleep(500);
+
+ float initial_focus_distance = mPreview.getCameraController().getFocusDistance();
+ Log.d(TAG, "initial_focus_distance: " + initial_focus_distance);
+ CameraController2 camera_controller2 = (CameraController2)mPreview.getCameraController();
+ CaptureRequest.Builder previewBuilder = camera_controller2.testGetPreviewBuilder();
+ // need to use LENS_FOCUS_DISTANCE rather than mPreview.getCameraController().getFocusDistance(), as the latter
+ // will always return the source focus distance, even if the preview was set to something else
+ float actual_initial_focus_distance = previewBuilder.get(CaptureRequest.LENS_FOCUS_DISTANCE);
+ assertEquals(initial_focus_distance, actual_initial_focus_distance, 1.0e-5f);
+
+ assertEquals(focusTargetSeekBar.getVisibility(), View.VISIBLE);
+ focusTargetSeekBar.setProgress( (int)(0.25*(focusTargetSeekBar.getMax()-1)) );
+ this.getInstrumentation().waitForIdleSync();
+ // test that we temporarily set the focus to the target distance
+ float target_actual_focus_distance = previewBuilder.get(CaptureRequest.LENS_FOCUS_DISTANCE);
+ Log.d(TAG, "target_actual_focus_distance: " + target_actual_focus_distance);
+ assertTrue(Math.abs(initial_focus_distance - target_actual_focus_distance) > 1.0e-5f); // no assertNotEquals!
+ mPreview.stoppedSettingFocusDistance(true); // hack, since onStopTrackingTouch() isn't called programmatically!
+ this.getInstrumentation().waitForIdleSync();
+ Thread.sleep(500); // wait for initial focus to be set
+
+ // test that we've reset back to the source distance
+ float new_focus_distance = mPreview.getCameraController().getFocusDistance();
+ Log.d(TAG, "new_focus_distance: " + new_focus_distance);
+ assertEquals(initial_focus_distance, new_focus_distance, 1.0e-5f);
+
+ float new_actual_focus_distance = previewBuilder.get(CaptureRequest.LENS_FOCUS_DISTANCE);
+ Log.d(TAG, "new_actual_focus_distance: " + new_actual_focus_distance);
+ assertEquals(initial_focus_distance, new_actual_focus_distance, 1.0e-5f);
+ }
+
+ /** Tests taking a photo in focus bracketing mode.
+ */
+ public void testTakePhotoFocusBracketing() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoFocusBracketing");
+
+ setToDefault();
+
+ if( !mActivity.supportsFocusBracketing() ) {
+ Log.d(TAG, "test requires focus bracketing");
+ return;
+ }
+
+ SeekBar focusSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.focus_seekbar);
+ assertEquals(focusSeekBar.getVisibility(), View.GONE);
+ SeekBar focusTargetSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.focus_bracketing_target_seekbar);
+ assertEquals(focusTargetSeekBar.getVisibility(), View.GONE);
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_focus_bracketing");
+ editor.apply();
+ updateForSettings();
+
+ setUpFocusBracketing();
+
+ float initial_focus_distance = mPreview.getCameraController().getFocusDistance();
+ Log.d(TAG, "initial_focus_distance: " + initial_focus_distance);
+ CameraController2 camera_controller2 = (CameraController2)mPreview.getCameraController();
+ CaptureRequest.Builder previewBuilder = camera_controller2.testGetPreviewBuilder();
+ // need to use LENS_FOCUS_DISTANCE rather than mPreview.getCameraController().getFocusDistance(), as the latter
+ // will always return the source focus distance, even if the preview was set to something else
+ float actual_initial_focus_distance = previewBuilder.get(CaptureRequest.LENS_FOCUS_DISTANCE);
+ assertEquals(initial_focus_distance, actual_initial_focus_distance, 1.0e-5f);
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ Log.d(TAG, "test_capture_results: " + mPreview.getCameraController().test_capture_results);
+ assertEquals(1, mPreview.getCameraController().test_capture_results);
+
+ float new_focus_distance = mPreview.getCameraController().getFocusDistance();
+ Log.d(TAG, "new_focus_distance: " + new_focus_distance);
+ assertEquals(initial_focus_distance, new_focus_distance, 1.0e-5f);
+
+ float new_actual_focus_distance = previewBuilder.get(CaptureRequest.LENS_FOCUS_DISTANCE);
+ Log.d(TAG, "new_actual_focus_distance: " + new_actual_focus_distance);
+ assertEquals(initial_focus_distance, new_actual_focus_distance, 1.0e-5f);
+ }
+
+ /** Tests taking a photo in focus bracketing mode, with auto-level and 20 images.
+ */
+ public void testTakePhotoFocusBracketingHeavy() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoFocusBracketingHeavy");
+
+ setToDefault();
+
+ if( !mActivity.supportsFocusBracketing() ) {
+ Log.d(TAG, "test requires focus bracketing");
+ return;
+ }
+
+ ImageSaver.test_small_queue_size = true;
+ mActivity.getApplicationInterface().getImageSaver().test_slow_saving = true;
+ // need to restart for test_small_queue_size to take effect
+ restart();
+
+ SeekBar focusSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.focus_seekbar);
+ assertEquals(focusSeekBar.getVisibility(), View.GONE);
+ SeekBar focusTargetSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.focus_bracketing_target_seekbar);
+ assertEquals(focusTargetSeekBar.getVisibility(), View.GONE);
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_focus_bracketing");
+ editor.putString(PreferenceKeys.FocusBracketingNImagesPreferenceKey, "20");
+ editor.apply();
+ updateForSettings();
+
+ setUpFocusBracketing();
+
+ float initial_focus_distance = mPreview.getCameraController().getFocusDistance();
+ Log.d(TAG, "initial_focus_distance: " + initial_focus_distance);
+ CameraController2 camera_controller2 = (CameraController2)mPreview.getCameraController();
+ CaptureRequest.Builder previewBuilder = camera_controller2.testGetPreviewBuilder();
+ // need to use LENS_FOCUS_DISTANCE rather than mPreview.getCameraController().getFocusDistance(), as the latter
+ // will always return the source focus distance, even if the preview was set to something else
+ float actual_initial_focus_distance = previewBuilder.get(CaptureRequest.LENS_FOCUS_DISTANCE);
+ assertEquals(initial_focus_distance, actual_initial_focus_distance, 1.0e-5f);
+
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ Log.d(TAG, "test_capture_results: " + mPreview.getCameraController().test_capture_results);
+ assertEquals(1, mPreview.getCameraController().test_capture_results);
+
+ float new_focus_distance = mPreview.getCameraController().getFocusDistance();
+ Log.d(TAG, "new_focus_distance: " + new_focus_distance);
+ assertEquals(initial_focus_distance, new_focus_distance, 1.0e-5f);
+
+ float new_actual_focus_distance = previewBuilder.get(CaptureRequest.LENS_FOCUS_DISTANCE);
+ Log.d(TAG, "new_actual_focus_distance: " + new_actual_focus_distance);
+ assertEquals(initial_focus_distance, new_actual_focus_distance, 1.0e-5f);
+ }
+
+ /** Tests taking a photo in focus bracketing mode, but with cancelling.
+ */
+ public void testTakePhotoFocusBracketingCancel() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoFocusBracketingCancel");
+
+ setToDefault();
+
+ if( !mActivity.supportsFocusBracketing() ) {
+ Log.d(TAG, "test requires focus bracketing");
+ return;
+ }
+
+ SeekBar focusSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.focus_seekbar);
+ assertEquals(focusSeekBar.getVisibility(), View.GONE);
+ SeekBar focusTargetSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.focus_bracketing_target_seekbar);
+ assertEquals(focusTargetSeekBar.getVisibility(), View.GONE);
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_focus_bracketing");
+ editor.putString(PreferenceKeys.FocusBracketingNImagesPreferenceKey, "200");
+ editor.apply();
+ updateForSettings();
+
+ setUpFocusBracketing();
+
+ float initial_focus_distance = mPreview.getCameraController().getFocusDistance();
+ Log.d(TAG, "initial_focus_distance: " + initial_focus_distance);
+ CameraController2 camera_controller2 = (CameraController2)mPreview.getCameraController();
+ CaptureRequest.Builder previewBuilder = camera_controller2.testGetPreviewBuilder();
+ // need to use LENS_FOCUS_DISTANCE rather than mPreview.getCameraController().getFocusDistance(), as the latter
+ // will always return the source focus distance, even if the preview was set to something else
+ float actual_initial_focus_distance = previewBuilder.get(CaptureRequest.LENS_FOCUS_DISTANCE);
+ assertEquals(initial_focus_distance, actual_initial_focus_distance, 1.0e-5f);
+
+ assertFalse( mPreview.isTakingPhoto() );
+ assertTrue( mActivity.getApplicationInterface().canTakeNewPhoto() );
+
+ for(int i=0;i<2;i++) {
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ Log.d(TAG, "about to click take photo");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take photo");
+ assertTrue( mPreview.isTakingPhoto() );
+
+ Thread.sleep(i==0 ? 500 : 3000); // wait before cancelling
+ assertTrue( mPreview.isTakingPhoto() );
+
+ Log.d(TAG, "about to click take photo to cancel");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take photo cancel");
+
+ // need to wait until cancelled
+ Thread.sleep(3000);
+ assertFalse( mPreview.isTakingPhoto() );
+ assertTrue( mActivity.getApplicationInterface().canTakeNewPhoto() );
+
+ assertTrue(mPreview.isPreviewStarted()); // check preview restarted
+ Log.d(TAG, "count_cameraTakePicture: " + mPreview.count_cameraTakePicture);
+ assertEquals(mPreview.count_cameraTakePicture, i + 1);
+ Log.d(TAG, "test_capture_results: " + mPreview.getCameraController().test_capture_results);
+ assertEquals(mPreview.getCameraController().test_capture_results, i + 1);
+
+ float new_focus_distance = mPreview.getCameraController().getFocusDistance();
+ Log.d(TAG, "new_focus_distance: " + new_focus_distance);
+ assertEquals(initial_focus_distance, new_focus_distance, 1.0e-5f);
+
+ float new_actual_focus_distance = previewBuilder.get(CaptureRequest.LENS_FOCUS_DISTANCE);
+ Log.d(TAG, "new_actual_focus_distance: " + new_actual_focus_distance);
+ assertEquals(initial_focus_distance, new_actual_focus_distance, 1.0e-5f);
+ }
+ }
+
+ /** Tests taking a photo with RAW and focus bracketing mode.
+ */
+ public void testTakePhotoRawFocusBracketing() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoRawFocusBracketing");
+
+ setToDefault();
+
+ if( !mActivity.supportsFocusBracketing() ) {
+ Log.d(TAG, "test requires focus bracketing");
+ return;
+ }
+ if( !mPreview.supportsRaw() ) {
+ Log.d(TAG, "test requires RAW");
+ return;
+ }
+
+ SeekBar focusSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.focus_seekbar);
+ assertEquals(focusSeekBar.getVisibility(), View.GONE);
+ SeekBar focusTargetSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.focus_bracketing_target_seekbar);
+ assertEquals(focusTargetSeekBar.getVisibility(), View.GONE);
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.RawPreferenceKey, "preference_raw_yes");
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_focus_bracketing");
+ editor.apply();
+ updateForSettings();
+
+ setUpFocusBracketing();
+
+ float initial_focus_distance = mPreview.getCameraController().getFocusDistance();
+ Log.d(TAG, "initial_focus_distance: " + initial_focus_distance);
+ CameraController2 camera_controller2 = (CameraController2)mPreview.getCameraController();
+ CaptureRequest.Builder previewBuilder = camera_controller2.testGetPreviewBuilder();
+ // need to use LENS_FOCUS_DISTANCE rather than mPreview.getCameraController().getFocusDistance(), as the latter
+ // will always return the source focus distance, even if the preview was set to something else
+ float actual_initial_focus_distance = previewBuilder.get(CaptureRequest.LENS_FOCUS_DISTANCE);
+ assertEquals(initial_focus_distance, actual_initial_focus_distance, 1.0e-5f);
+
+ subTestTakePhoto(false, false, true, true, false, false, true, false);
+ Log.d(TAG, "test_capture_results: " + mPreview.getCameraController().test_capture_results);
+ assertEquals(1, mPreview.getCameraController().test_capture_results);
+
+ float new_focus_distance = mPreview.getCameraController().getFocusDistance();
+ Log.d(TAG, "new_focus_distance: " + new_focus_distance);
+ assertEquals(initial_focus_distance, new_focus_distance, 1.0e-5f);
+
+ float new_actual_focus_distance = previewBuilder.get(CaptureRequest.LENS_FOCUS_DISTANCE);
+ Log.d(TAG, "new_actual_focus_distance: " + new_actual_focus_distance);
+ assertEquals(initial_focus_distance, new_actual_focus_distance, 1.0e-5f);
+ }
+
+ /** Tests taking a photo with RAW only and focus bracketing mode.
+ */
+ public void testTakePhotoRawOnlyFocusBracketing() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoRawOnlyFocusBracketing");
+
+ setToDefault();
+
+ if( !mActivity.supportsFocusBracketing() ) {
+ Log.d(TAG, "test requires focus bracketing");
+ return;
+ }
+ if( !mPreview.supportsRaw() ) {
+ Log.d(TAG, "test requires RAW");
+ return;
+ }
+
+ SeekBar focusSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.focus_seekbar);
+ assertEquals(focusSeekBar.getVisibility(), View.GONE);
+ SeekBar focusTargetSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.focus_bracketing_target_seekbar);
+ assertEquals(focusTargetSeekBar.getVisibility(), View.GONE);
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.RawPreferenceKey, "preference_raw_only");
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_focus_bracketing");
+ editor.apply();
+ updateForSettings();
+
+ setUpFocusBracketing();
+
+ float initial_focus_distance = mPreview.getCameraController().getFocusDistance();
+ Log.d(TAG, "initial_focus_distance: " + initial_focus_distance);
+ CameraController2 camera_controller2 = (CameraController2)mPreview.getCameraController();
+ CaptureRequest.Builder previewBuilder = camera_controller2.testGetPreviewBuilder();
+ // need to use LENS_FOCUS_DISTANCE rather than mPreview.getCameraController().getFocusDistance(), as the latter
+ // will always return the source focus distance, even if the preview was set to something else
+ float actual_initial_focus_distance = previewBuilder.get(CaptureRequest.LENS_FOCUS_DISTANCE);
+ assertEquals(initial_focus_distance, actual_initial_focus_distance, 1.0e-5f);
+
+ subTestTakePhoto(false, false, true, true, false, false, true, false);
+ Log.d(TAG, "test_capture_results: " + mPreview.getCameraController().test_capture_results);
+ assertEquals(1, mPreview.getCameraController().test_capture_results);
+
+ float new_focus_distance = mPreview.getCameraController().getFocusDistance();
+ Log.d(TAG, "new_focus_distance: " + new_focus_distance);
+ assertEquals(initial_focus_distance, new_focus_distance, 1.0e-5f);
+
+ float new_actual_focus_distance = previewBuilder.get(CaptureRequest.LENS_FOCUS_DISTANCE);
+ Log.d(TAG, "new_actual_focus_distance: " + new_actual_focus_distance);
+ assertEquals(initial_focus_distance, new_actual_focus_distance, 1.0e-5f);
+ }
+
+ /** Tests NR photo mode.
+ */
+ public void testTakePhotoNR() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoNR");
+
+ setToDefault();
+
+ if( !mActivity.supportsNoiseReduction() ) {
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_noise_reduction");
+ editor.apply();
+ updateForSettings();
+
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.NoiseReduction);
+
+ final int n_back_photos = 3;
+ subTestTakePhoto(false, false, true, true, false, false, false, false);
+ Log.d(TAG, "test_capture_results: " + mPreview.getCameraController().test_capture_results);
+ assertEquals(1, mPreview.getCameraController().test_capture_results);
+ assertTrue(mActivity.getPreview().getCameraController().getBurstTotal() < CameraController.N_IMAGES_NR_DARK_LOW_LIGHT);
+
+ // then try again without waiting
+ for(int i=1;i= 3 && n_new_files <= 31);
+ }
+ else {
+ // at one photo per 100ms, should have approximately 30 - note that long press can take longer to kick in on some devices, e.g., OnePlus 3T
+ assertTrue(n_new_files >= 12 && n_new_files <= 31);
+ }
+
+ mActivity.waitUntilImageQueueEmpty();
+ }
+
+ /** Tests continuous burst.
+ * Fails on Android emulator with Camera2 API, due to a serious camera error occurring for
+ * fast burst with more than 5 images!
+ */
+ public void testTakePhotoContinuousBurst() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoContinuousBurst");
+
+ setToDefault();
+
+ if( !mActivity.supportsFastBurst() ) {
+ return;
+ }
+
+ subTestTakePhotoContinuousBurst(false);
+
+ Thread.sleep(1000);
+
+ // now take a regular photo
+ subTestTakePhoto(false, false, false, false, false, false, false, false);
+ }
+
+ /** Tests continuous burst, but with flags set for slow saving and shorter queue.
+ * Fails on Android emulator with Camera2 API, due to a serious camera error occurring for
+ * fast burst with more than 5 images!
+ */
+ public void testTakePhotoContinuousBurstSlow() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoContinuousBurstSlow");
+
+ setToDefault();
+
+ if( !mActivity.supportsFastBurst() ) {
+ return;
+ }
+
+ ImageSaver.test_small_queue_size = true;
+ mActivity.getApplicationInterface().getImageSaver().test_slow_saving = true;
+ // need to restart for test_small_queue_size to take effect
+ restart();
+
+ subTestTakePhotoContinuousBurst(true);
+ }
+
+ private void subTestTakePhotoPanorama(boolean to_max, boolean cancel, boolean cancel_by_settings) throws InterruptedException {
+ Log.d(TAG, "subTestTakePhotoPanorama");
+ setToDefault();
+
+ if( !mActivity.supportsPanorama() ) {
+ return;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(PreferenceKeys.PhotoModePreferenceKey, "preference_photo_mode_panorama");
+ //editor.putString(PreferenceKeys.PanoramaSaveExpoPreferenceKey, "preference_panorama_save_all"); // test/debug
+ editor.apply();
+ updateForSettings();
+
+ assertSame(mActivity.getApplicationInterface().getPhotoMode(), MyApplicationInterface.PhotoMode.Panorama);
+
+ // count initial files in folder
+ int n_files = getNFiles();
+ Log.d(TAG, "n_files at start: " + n_files);
+
+ Thread.sleep(1000);
+ assertEquals(0, mPreview.count_cameraTakePicture);
+
+ assertFalse( mActivity.getApplicationInterface().getGyroSensor().isRecording() );
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ View switchCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_camera);
+ View switchMultiCameraButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_multi_camera);
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ View exposureButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure);
+ View exposureLockButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.exposure_lock);
+ View audioControlButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.audio_control);
+ View popupButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.popup);
+ View trashButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.trash);
+ View shareButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.share);
+ View settingsButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.settings);
+ View cancelPanoramaButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.cancel_panorama);
+
+ assertEquals(takePhotoButton.getVisibility(), View.VISIBLE);
+ assertEquals(switchCameraButton.getVisibility(), (mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE));
+ assertEquals(switchMultiCameraButton.getVisibility(), (mActivity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE));
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureLockButton.getVisibility(), (mPreview.supportsExposureLock() ? View.VISIBLE : View.GONE));
+ assertEquals(audioControlButton.getVisibility(), View.GONE);
+ assertEquals(popupButton.getVisibility(), View.VISIBLE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ assertEquals(settingsButton.getVisibility(), View.VISIBLE);
+ assertEquals(cancelPanoramaButton.getVisibility(), View.GONE);
+
+ Log.d(TAG, "about to click take photo");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take photo");
+
+ Log.d(TAG, "wait until finished taking photo");
+ waitForTakePhoto();
+ Log.d(TAG, "done taking photo");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ assertEquals(1, mPreview.count_cameraTakePicture);
+
+ for(int i=0;i<(to_max ? MyApplicationInterface.max_panorama_pics_c-1 : 4);i++) {
+ Log.d(TAG, "i = " + i);
+ assertTrue( mActivity.getApplicationInterface().getGyroSensor().isRecording() );
+
+ assertEquals(takePhotoButton.getVisibility(), View.VISIBLE);
+ assertEquals(switchCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchMultiCameraButton.getVisibility(), View.GONE);
+ assertEquals(switchVideoButton.getVisibility(), View.GONE);
+ assertEquals(exposureButton.getVisibility(), View.GONE);
+ assertEquals(exposureLockButton.getVisibility(), View.GONE);
+ assertEquals(audioControlButton.getVisibility(), View.GONE);
+ assertEquals(popupButton.getVisibility(), View.GONE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ assertEquals(settingsButton.getVisibility(), View.GONE);
+ assertEquals(cancelPanoramaButton.getVisibility(), View.VISIBLE);
+
+ Thread.sleep(2000);
+
+ assertTrue( mActivity.getApplicationInterface().getGyroSensor().isRecording() );
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.getApplicationInterface().getGyroSensor().testForceTargetAchieved(0);
+ }
+ });
+ // need to wait for UI code to finish before leaving
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "wait for taking photo");
+ waitForTakePhoto();
+ Log.d(TAG, "done taking photo");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ Log.d(TAG, "take picture count: " + mPreview.count_cameraTakePicture);
+ assertEquals(mPreview.count_cameraTakePicture, i + 2);
+ }
+
+ Thread.sleep(2000);
+
+ if( !to_max ) {
+ assertTrue( mActivity.getApplicationInterface().getGyroSensor().isRecording() );
+
+ if( cancel ) {
+ if( cancel_by_settings ) {
+ Log.d(TAG, "about to click settings");
+ clickView(settingsButton);
+ Log.d(TAG, "done clicking settings");
+ this.getInstrumentation().waitForIdleSync();
+ }
+ else {
+ Log.d(TAG, "about to click cancel");
+ clickView(cancelPanoramaButton);
+ Log.d(TAG, "done clicking cancel");
+ this.getInstrumentation().waitForIdleSync();
+ }
+ }
+ else {
+ // finish panorama (if to_max, this should have happened automatically)
+ Log.d(TAG, "about to click take photo");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take photo");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ }
+ }
+
+ assertFalse( mActivity.getApplicationInterface().getGyroSensor().isRecording() );
+
+ assertEquals(takePhotoButton.getVisibility(), View.VISIBLE);
+ assertEquals(switchCameraButton.getVisibility(), (mPreview.getCameraControllerManager().getNumberOfCameras() > 1 ? View.VISIBLE : View.GONE));
+ assertEquals(switchMultiCameraButton.getVisibility(), (mActivity.showSwitchMultiCamIcon() ? View.VISIBLE : View.GONE));
+ assertEquals(switchVideoButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureButton.getVisibility(), View.VISIBLE);
+ assertEquals(exposureLockButton.getVisibility(), (mPreview.supportsExposureLock() ? View.VISIBLE : View.GONE));
+ assertEquals(audioControlButton.getVisibility(), View.GONE);
+ assertEquals(popupButton.getVisibility(), View.VISIBLE);
+ assertEquals(trashButton.getVisibility(), View.GONE);
+ assertEquals(shareButton.getVisibility(), View.GONE);
+ assertEquals(settingsButton.getVisibility(), View.VISIBLE);
+ assertEquals(cancelPanoramaButton.getVisibility(), View.GONE);
+
+ if( !cancel && !to_max ) {
+ // test trying to take another photo whilst saving
+ Thread.sleep(500);
+ assertTrue( mActivity.getApplicationInterface().getImageSaver().getNImagesToSave() > 0 );
+ Log.d(TAG, "about to click take photo whilst saving images");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take photo");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ assertFalse( mActivity.getApplicationInterface().getGyroSensor().isRecording() );
+
+ // and again (test for crash that occured in 1.47!)
+ Thread.sleep(500);
+ assertTrue( mActivity.getApplicationInterface().getImageSaver().getNImagesToSave() > 0 );
+ Log.d(TAG, "about to click take photo whilst saving images");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take photo");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+ assertFalse( mActivity.getApplicationInterface().getGyroSensor().isRecording() );
+ }
+
+ mActivity.waitUntilImageQueueEmpty();
+
+ int n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(n_new_files, (cancel ? 0 : 1));
+ }
+
+ /* Test for panorama photo mode.
+ * Can fail on Android emulator due to failing to create panorama image from identical set of
+ * images (since we don't actually move the camera).
+ */
+ public void testTakePhotoPanorama() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoPanorama");
+
+ boolean has_zoom = mPreview.supportsZoom(); // record before switching to panorama mode, so we check if zoom is available (this will be false once in panorama mode)
+
+ subTestTakePhotoPanorama(false, false, false);
+
+ // check zoom seekbar doesn't show
+ View zoomSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.zoom_seekbar);
+ assertEquals(zoomSeekBar.getVisibility(), View.INVISIBLE);
+
+ // switch to video mode, check zoom now shows
+ View switchVideoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.switch_video);
+ clickView(switchVideoButton);
+ waitUntilCameraOpened();
+ assertTrue(mPreview.isVideo());
+ assertEquals(zoomSeekBar.getVisibility(), has_zoom ? View.VISIBLE : View.INVISIBLE);
+
+ // pause/resume, check still in video mode, and zoom still available
+ pauseAndResume();
+ assertTrue(mPreview.isVideo());
+ zoomSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.zoom_seekbar);
+ assertEquals(zoomSeekBar.getVisibility(), has_zoom ? View.VISIBLE : View.INVISIBLE);
+
+ // restart, check still in video mode, and zoom still available
+ restart();
+ assertTrue(mPreview.isVideo());
+ zoomSeekBar = mActivity.findViewById(net.sourceforge.opencamera.R.id.zoom_seekbar);
+ assertEquals(zoomSeekBar.getVisibility(), has_zoom ? View.VISIBLE : View.INVISIBLE);
+ }
+
+ /* Test for panorama photo mode, taking max number of panorama shots.
+ * Can fail on Android emulator due to failing to create panorama image from identical set of
+ * images (since we don't actually move the camera).
+ */
+ public void testTakePhotoPanoramaMax() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoPanoramaMax");
+
+ subTestTakePhotoPanorama(true, false, false);
+ }
+
+ /* Test for panorama photo mode, but cancelling.
+ */
+ public void testTakePhotoPanoramaCancel() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoPanoramaCancel");
+
+ subTestTakePhotoPanorama(false, true, false);
+ }
+
+ /* Test for panorama photo mode, but cancelling by going to settings.
+ * No longer relevant now that we hide the Settings while taking photo.
+ */
+ /*public void testTakePhotoPanoramaCancelBySettings() throws InterruptedException {
+ Log.d(TAG, "testTakePhotoPanoramaCancelBySettings");
+
+ subTestTakePhotoPanorama(false, true, true);
+ }*/
+
+ /*private Bitmap getBitmapFromAssets(String filename) throws IOException {
+ Log.d(TAG, "getBitmapFromAssets: " + filename);
+ AssetManager assetManager = getInstrumentation().getContext().getResources().getAssets();
+ InputStream is = assetManager.open(filename);
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inMutable = true;
+ Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);
+ is.close();
+ Log.d(TAG, " done: " + bitmap);
+ return bitmap;
+ }*/
+
+ private Bitmap getBitmapFromFile(String filename) {
+ return TestUtils.getBitmapFromFile(mActivity, filename);
+ }
+
+ private Bitmap getBitmapFromFile(String filename, int inSampleSize) {
+ return TestUtils.getBitmapFromFile(mActivity, filename, inSampleSize);
+ }
+
+ /* Tests restarting a large number of times - can be useful for testing for memory/resource leaks.
+ */
+ public void testRestart() {
+ Log.d(TAG, "testRestart");
+ setToDefault();
+
+ final int n_restarts = 150;
+ for(int i=0;i video_quality = mActivity.getPreview().getVideoQualityHander().getSupportedVideoQuality();
+
+ assertFalse( mActivity.getApplicationInterface().isVideoPref() );
+ assertEquals( 0, mActivity.getApplicationInterface().getVideoMaxDurationPref() );
+ // n.b., will fail if not enough storage space on device!:
+ MyApplicationInterface.VideoMaxFileSize videomaxfilesize = mActivity.getApplicationInterface().getVideoMaxFileSizePref();
+ long max_filesize = videomaxfilesize.max_filesize;
+ assertTrue( max_filesize > 100000000);
+ assertTrue(videomaxfilesize.auto_restart);
+
+ Intent intent = createDefaultIntent();
+ intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE);
+ intent.putExtra(MediaStore.EXTRA_SIZE_LIMIT, 50123456L);
+ intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
+ setActivityIntent(intent);
+
+ restart();
+
+ assertTrue( mActivity.getApplicationInterface().isVideoPref() );
+ assertEquals( 0, mActivity.getApplicationInterface().getVideoMaxDurationPref() );
+ videomaxfilesize = mActivity.getApplicationInterface().getVideoMaxFileSizePref();
+ assertEquals( 50123456, videomaxfilesize.max_filesize );
+ assertFalse(videomaxfilesize.auto_restart);
+ assertEquals(video_quality.get(video_quality.size()-1), mActivity.getApplicationInterface().getVideoQualityPref());
+
+ intent = createDefaultIntent();
+ intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE);
+ intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
+ setActivityIntent(intent);
+
+ restart();
+
+ assertTrue( mActivity.getApplicationInterface().isVideoPref() );
+ assertEquals( 0, mActivity.getApplicationInterface().getVideoMaxDurationPref() );
+ videomaxfilesize = mActivity.getApplicationInterface().getVideoMaxFileSizePref();
+ assertTrue( videomaxfilesize.max_filesize > 100000000);
+ assertTrue( Math.abs(max_filesize - videomaxfilesize.max_filesize) < 5000000 ); // remaining storage may vary whilst test is running!
+ assertTrue(videomaxfilesize.auto_restart);
+
+ assertEquals(video_quality.get(0), mActivity.getApplicationInterface().getVideoQualityPref());
+ }
+
+ /* Tests launching with ACTION_VIDEO_CAPTURE intent, along with EXTRA_DURATION_LIMIT. The test
+ * then tests we actually record video with the duration limit set.
+ * Fails on Android emulator, for some reason EXTRA_DURATION_LIMIT makes the video stop due to
+ * hitting max duration immediately.
+ */
+ public void testIntentVideoDurationLimit() throws InterruptedException, ApplicationInterface.NoFreeStorageException {
+ Log.d(TAG, "testIntentVideoDurationLimit");
+
+ setToDefault();
+
+ assertFalse( mActivity.getApplicationInterface().isVideoPref() );
+ assertEquals( 0, mActivity.getApplicationInterface().getVideoMaxDurationPref() );
+ // n.b., will fail if not enough storage space on device!:
+ MyApplicationInterface.VideoMaxFileSize videomaxfilesize = mActivity.getApplicationInterface().getVideoMaxFileSizePref();
+ long max_filesize = videomaxfilesize.max_filesize;
+ assertTrue( max_filesize > 100000000);
+ assertTrue(videomaxfilesize.auto_restart);
+
+ Intent intent = createDefaultIntent();
+ intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE);
+ intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 5);
+ setActivityIntent(intent);
+
+ restart();
+
+ assertTrue( mActivity.getApplicationInterface().isVideoPref() );
+ assertEquals( 5000, mActivity.getApplicationInterface().getVideoMaxDurationPref() );
+ // note that max_filesize may vary if device filesize has changed whilst test is running
+ assertEquals( max_filesize, mActivity.getApplicationInterface().getVideoMaxFileSizePref().max_filesize, 5*1048576 );
+
+ // count initial files in folder
+ int n_files = getNFiles();
+ Log.d(TAG, "n_files at start: " + n_files);
+
+ View takePhotoButton = mActivity.findViewById(net.sourceforge.opencamera.R.id.take_photo);
+ Log.d(TAG, "about to click take video");
+ clickView(takePhotoButton);
+ Log.d(TAG, "done clicking take video");
+ this.getInstrumentation().waitForIdleSync();
+ Log.d(TAG, "after idle sync");
+
+ assertTrue( mPreview.isVideoRecording() );
+
+ Thread.sleep(4000);
+ Log.d(TAG, "check still taking video");
+ assertTrue( mPreview.isVideoRecording() );
+
+ int n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ // note, if using scoped storage without SAF (i.e., mediastore API), then the video file won't show up until after we've finished recording (IS_PENDING is set to 0)
+ assertEquals(MainActivity.useScopedStorage() && !mActivity.getStorageUtils().isUsingSAF() ? 0 : 1, n_new_files);
+
+ Thread.sleep(3000);
+
+ Log.d(TAG, "check stopped taking video");
+ assertFalse(mPreview.isVideoRecording());
+
+ n_new_files = getNFiles() - n_files;
+ Log.d(TAG, "n_new_files: " + n_new_files);
+ assertEquals(1, n_new_files);
+
+ }
+
+ /** Tests that we handle the upgrade from the preference boolean key "preference_use_camera2"
+ * to the string key PreferenceKeys.CameraAPIPreferenceKey that occured in v1.48.
+ */
+ public void testCamera2PrefUpgrade() {
+ Log.d(TAG, "testCamera2PrefUpgrade");
+
+ // n.b., don't bother calling setToDefault()
+ waitUntilCameraOpened();
+
+ if( !mActivity.supportsCamera2() ) {
+ Log.d(TAG, "test requires camera2 support");
+ return;
+ }
+
+ assertFalse(mPreview.usingCamera2API());
+
+ // test legacy key present, but set to old api
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.clear();
+ editor.putBoolean("preference_use_camera2", false);
+ editor.apply();
+ restart();
+ assertFalse(mPreview.usingCamera2API());
+
+ // now test legacy key present for camera2 api
+ settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ editor = settings.edit();
+ editor.clear();
+ editor.putBoolean("preference_use_camera2", true);
+ editor.apply();
+
+ for(int i=0;i<2;i++) {
+ restart();
+ assertTrue(mPreview.usingCamera2API());
+
+ // also check we switched over to the new key
+ settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ assertFalse(settings.contains("preference_use_camera2"));
+ assertTrue(settings.contains(PreferenceKeys.CameraAPIPreferenceKey));
+ assertEquals("preference_camera_api_camera2", settings.getString(PreferenceKeys.CameraAPIPreferenceKey, PreferenceKeys.CameraAPIPreferenceDefault));
+ }
+ }
+
+ private TestUtils.HistogramDetails checkHistogram(Bitmap bitmap) {
+ return TestUtils.checkHistogram(mActivity, bitmap);
+ }
+
+ private TestUtils.HistogramDetails subTestHDR(List inputs, String output_name, boolean test_dro, int iso, long exposure_time) {
+ return TestUtils.subTestHDR(mActivity, inputs, output_name, test_dro, iso, exposure_time);
+ }
+
+ private TestUtils.HistogramDetails subTestHDR(List inputs, String output_name, boolean test_dro, int iso, long exposure_time, HDRProcessor.TonemappingAlgorithm tonemapping_algorithm/*, HDRTestCallback test_callback*/) {
+ return TestUtils.subTestHDR(mActivity, inputs, output_name, test_dro, iso, exposure_time, tonemapping_algorithm);
+ }
+
+ private void checkHDROffsets(int [] exp_offsets_x, int [] exp_offsets_y) {
+ TestUtils.checkHDROffsets(mActivity, exp_offsets_x, exp_offsets_y);
+ }
+
+ private void checkHDROffsets(int [] exp_offsets_x, int [] exp_offsets_y, int scale) {
+ TestUtils.checkHDROffsets(mActivity, exp_offsets_x, exp_offsets_y, scale);
+ }
+
+ private static void checkHistogramDetails(TestUtils.HistogramDetails hdrHistogramDetails, int exp_min_value, int exp_median_value, int exp_max_value) {
+ TestUtils.checkHistogramDetails(hdrHistogramDetails, exp_min_value, exp_median_value, exp_max_value);
+ }
+
+ /** Tests HDR algorithm on test samples "saintpaul".
+ */
+ public void testHDR1() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR1");
+
+ setToDefault();
+
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( getBitmapFromFile(TestUtils.hdr_images_path + "saintpaul/input2.jpg") );
+ inputs.add( getBitmapFromFile(TestUtils.hdr_images_path + "saintpaul/input3.jpg") );
+ inputs.add( getBitmapFromFile(TestUtils.hdr_images_path + "saintpaul/input4.jpg") );
+
+ // actual ISO unknown, so guessing
+ TestUtils.HistogramDetails hdrHistogramDetails = subTestHDR(inputs, "testHDR1_output.jpg", false, 1600, 1000000000L);
+
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0};
+ checkHDROffsets(exp_offsets_x, exp_offsets_y);
+
+ //checkHistogramDetails(hdrHistogramDetails, 1, 39, 253);
+ //checkHistogramDetails(hdrHistogramDetails, 1, 44, 253);
+ //checkHistogramDetails(hdrHistogramDetails, 1, 42, 253);
+ //checkHistogramDetails(hdrHistogramDetails, 1, 24, 254);
+ checkHistogramDetails(hdrHistogramDetails, 2, 30, 254);
+ }
+
+ /** Tests HDR algorithm on test samples "saintpaul", but with 5 images.
+ */
+ public void testHDR1_exp5() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR1_exp5");
+
+ setToDefault();
+
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( getBitmapFromFile(TestUtils.hdr_images_path + "saintpaul/input1.jpg") );
+ inputs.add( getBitmapFromFile(TestUtils.hdr_images_path + "saintpaul/input2.jpg") );
+ inputs.add( getBitmapFromFile(TestUtils.hdr_images_path + "saintpaul/input3.jpg") );
+ inputs.add( getBitmapFromFile(TestUtils.hdr_images_path + "saintpaul/input4.jpg") );
+ inputs.add( getBitmapFromFile(TestUtils.hdr_images_path + "saintpaul/input5.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = subTestHDR(inputs, "testHDR1_exp5_output.jpg", false, -1, -1);
+
+ int [] exp_offsets_x = {0, 0, 0, 0, 0};
+ int [] exp_offsets_y = {0, 0, 0, 0, 0};
+ checkHDROffsets(exp_offsets_x, exp_offsets_y);
+
+ //checkHistogramDetails(hdrHistogramDetails, 3, 43, 251);
+ checkHistogramDetails(hdrHistogramDetails, 6, 42, 251);
+ }
+
+ /** Tests HDR algorithm on test samples "stlouis".
+ */
+ public void testHDR2() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR2");
+
+ setToDefault();
+
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( getBitmapFromFile(TestUtils.hdr_images_path + "stlouis/input1.jpg") );
+ inputs.add( getBitmapFromFile(TestUtils.hdr_images_path + "stlouis/input2.jpg") );
+ inputs.add( getBitmapFromFile(TestUtils.hdr_images_path + "stlouis/input3.jpg") );
+
+ // actual ISO unknown, so guessing
+ subTestHDR(inputs, "testHDR2_output.jpg", false, 1600, (long)(1000000000L*2.5));
+
+ int [] exp_offsets_x = {0, 0, 2};
+ int [] exp_offsets_y = {0, 0, 0};
+ checkHDROffsets(exp_offsets_x, exp_offsets_y);
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR3".
+ */
+ public void testHDR3() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR3");
+
+ setToDefault();
+
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( getBitmapFromFile(TestUtils.hdr_images_path + "testHDR3/input0.jpg") );
+ inputs.add( getBitmapFromFile(TestUtils.hdr_images_path + "testHDR3/input1.jpg") );
+ inputs.add( getBitmapFromFile(TestUtils.hdr_images_path + "testHDR3/input2.jpg") );
+
+ TestUtils.HistogramDetails hdrHistogramDetails = subTestHDR(inputs, "testHDR3_output.jpg", false, 40, 1000000000L/680);
+
+ int [] exp_offsets_x = {0, 0, 0};
+ int [] exp_offsets_y = {1, 0, -1};
+ checkHDROffsets(exp_offsets_x, exp_offsets_y);
+
+ //checkHistogramDetails(hdrHistogramDetails, 3, 104, 255);
+ //checkHistogramDetails(hdrHistogramDetails, 4, 113, 255);
+ checkHistogramDetails(hdrHistogramDetails, 8, 113, 255);
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR4".
+ */
+ public void testHDR4() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR4");
+
+ setToDefault();
+
+ // list assets
+ List inputs = new ArrayList<>();
+ inputs.add( getBitmapFromFile(TestUtils.hdr_images_path + "testHDR4/input0.jpg") );
+ inputs.add( getBitmapFromFile(TestUtils.hdr_images_path + "testHDR4/input1.jpg") );
+ inputs.add( getBitmapFromFile(TestUtils.hdr_images_path + "testHDR4/input2.jpg") );
+
+ subTestHDR(inputs, "testHDR4_output.jpg", true, 102, 1000000000L/60);
+
+ int [] exp_offsets_x = {-2, 0, 2};
+ int [] exp_offsets_y = {-1, 0, 1};
+ checkHDROffsets(exp_offsets_x, exp_offsets_y);
+ }
+
+ /** Tests HDR algorithm on test samples "testHDR5".
+ */
+ public void testHDR5() throws IOException, InterruptedException {
+ Log.d(TAG, "testHDR5");
+
+ setToDefault();
+
+ // list assets
+ List