From 46cfe8dd0fa86183ce300d105654f8dedd24cdf3 Mon Sep 17 00:00:00 2001 From: frankpreel Date: Thu, 28 Mar 2024 13:56:38 +0100 Subject: [PATCH 01/19] Replace boolean is_video() As now the switch button supports 3 mode enum FunctionalMode { PHOTO, VIDEO, QRCODE }; --- .../opencamera/preview/Preview.java | 121 ++++++++++-------- 1 file changed, 66 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java b/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java index eb4e623b3..e130b61f7 100644 --- a/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java +++ b/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java @@ -197,7 +197,15 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu private AsyncTask open_camera_task; // background task used for opening camera private CloseCameraTask close_camera_task; // background task used for closing camera private boolean has_permissions = true; // whether we have permissions necessary to operate the camera (camera, storage); assume true until we've been denied one of them - private boolean is_video; + + public static enum FunctionalMode { + PHOTO, + VIDEO, + QRCODE + }; + private FunctionalMode functionalMode; + private boolean is_video() {return functionalMode == FunctionalMode.VIDEO;}; + private volatile MediaRecorder video_recorder; // must be volatile for test project reading the state private volatile boolean video_start_time_set; // must be volatile for test project reading the state private long video_start_time; // system time when the video recording was started, or last resumed if it was paused @@ -686,14 +694,14 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu if( MyDebug.LOG ) Log.d(TAG, "handleSingleTouch"); - if( !this.is_video && this.isTakingPhotoOrOnTimer() ) { + if( !this.is_video() && this.isTakingPhotoOrOnTimer() ) { // if video, okay to refocus when recording return true; } // note, we always try to force start the preview (in case is_preview_paused has become false) // except if recording video (firstly, the preview should be running; secondly, we don't want to reset the phase!) - if (!this.is_video) { + if (!this.is_video()) { startCameraPreview(); } cancelAutoFocus(); @@ -1263,7 +1271,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu } } if (due_to_max_filesize || remaining_restart_video > 0) { - if (is_video) { + if (is_video()) { String toast = null; if (!due_to_max_filesize) toast = remaining_restart_video + " " + getContext().getResources().getString(R.string.repeats_to_go); @@ -1639,7 +1647,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu current_focus_index = -1; max_num_focus_areas = 0; applicationInterface.cameraInOperation(false, false); - if (is_video) + if (is_video()) applicationInterface.cameraInOperation(false, true); if (!this.has_surface) { if (MyDebug.LOG) { @@ -2025,7 +2033,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu saved_is_video = false; } // must switch video before setupCameraParameters(), and starting preview - if (saved_is_video != this.is_video) { + if (saved_is_video != this.is_video()) { if (MyDebug.LOG) Log.d(TAG, "switch video mode as not in correct mode"); this.switchVideo(true, false); @@ -2076,7 +2084,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu updateFlashForVideo(); if (take_photo) { - if (this.is_video) { + if (this.is_video()) { if (MyDebug.LOG) Log.d(TAG, "switch to video for take_photo widget"); this.switchVideo(true, true); @@ -2085,8 +2093,8 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu // must be done after switching to video mode (so is_video is set correctly) if (MyDebug.LOG) - Log.d(TAG, "is_video?: " + is_video); - if (this.is_video) { + Log.d(TAG, "is_video?: " + is_video()); + if (this.is_video()) { CameraController.TonemapProfile tonemap_profile = CameraController.TonemapProfile.TONEMAPPROFILE_OFF; if (supports_tonemap_curve) { tonemap_profile = applicationInterface.getVideoTonemapProfile(); @@ -2105,7 +2113,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu // Setup for high speed - must be done after setupCameraParameters() and switching to video mode, but before setPreviewSize() and startCameraPreview(). // In theory it shouldn't matter if we call setVideoHighSpeed(true) if is_video==false, as it should only have an effect // when recording video; but don't set high speed mode in photo mode just to be safe. - camera_controller.setVideoHighSpeed(is_video && video_high_speed); + camera_controller.setVideoHighSpeed(is_video() && video_high_speed); if (do_startup_focus && using_android_l && camera_controller.supportsAutoFocus()) { // need to switch flash off for autofocus - and for Android L, need to do this before starting preview (otherwise it won't work in time); for old camera API, need to do this after starting preview! @@ -2615,10 +2623,10 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu { if (MyDebug.LOG) { Log.d(TAG, "set up video stabilization"); - Log.d(TAG, "is_video?: " + is_video); + Log.d(TAG, "is_video?: " + is_video()); } if (this.supports_video_stabilization) { - boolean using_video_stabilization = is_video && applicationInterface.getVideoStabilizationPref(); + boolean using_video_stabilization = is_video() && applicationInterface.getVideoStabilizationPref(); if (MyDebug.LOG) Log.d(TAG, "using_video_stabilization?: " + using_video_stabilization); camera_controller.setVideoStabilization(using_video_stabilization); @@ -3107,7 +3115,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu Log.d(TAG, "video_high_speed?: " + video_high_speed); } - if (is_video && video_high_speed && supports_iso_range && is_manual_iso) { + if (is_video() && video_high_speed && supports_iso_range && is_manual_iso) { if (MyDebug.LOG) Log.d(TAG, "manual mode not supported for video_high_speed"); camera_controller.setManualISO(false, 0); @@ -3257,7 +3265,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu } // first set picture size (for photo mode, must be done now so we can set the picture size from this; for video, doesn't really matter when we set it) CameraController.Size new_size; - if (this.is_video) { + if (this.is_video()) { // see comments for getOptimalVideoPictureSize() VideoProfile profile = getVideoProfile(); if (MyDebug.LOG) @@ -3770,8 +3778,8 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu String preview_size = applicationInterface.getPreviewSizePref(); // should always use wysiwig for video mode, otherwise we get incorrect aspect ratio shown when recording video (at least on Galaxy Nexus, e.g., at 640x480) // also not using wysiwyg mode with video caused corruption on Samsung cameras (tested with Samsung S3, Android 4.3, front camera, infinity focus) - if (preview_size.equals("preference_preview_size_wysiwyg") || this.is_video) { - if (this.is_video) { + if (preview_size.equals("preference_preview_size_wysiwyg") || this.is_video()) { + if (this.is_video()) { if (MyDebug.LOG) Log.d(TAG, "set preview aspect ratio from video size (wysiwyg)"); VideoProfile profile = getVideoProfile(); @@ -3828,7 +3836,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu final double ASPECT_TOLERANCE = 0.05; if (sizes == null) return null; - if (is_video && video_high_speed) { + if (is_video() && video_high_speed) { VideoProfile profile = getVideoProfile(); if (MyDebug.LOG) Log.d(TAG, "video size: " + profile.videoFrameWidth + " x " + profile.videoFrameHeight); @@ -4648,7 +4656,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu // (important not to return here however - still want to call // camera_controller.clearPreviewFpsRange() to clear a previously set fps) } - else if( this.is_video ) { + else if( this.is_video() ) { // For Nexus 5 and Nexus 6, we need to set the preview fps using matchPreviewFpsToVideo to avoid problem of dark preview in low light, as described above. // When the video recording starts, the preview automatically adjusts, but still good to avoid too-dark preview before the user starts recording. // However I'm wary of changing the behaviour for all devices at the moment, since some devices can be @@ -4714,33 +4722,33 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu Log.d(TAG, "camera not opened!"); return; } - if (!is_video && !supports_video) { + if (!is_video() && !supports_video) { if (MyDebug.LOG) Log.d(TAG, "video not supported"); return; } - boolean old_is_video = is_video; - if (this.is_video) { + boolean old_is_video = is_video(); + if (this.is_video()) { if (video_recorder != null) { stopVideo(false); } - this.is_video = false; + this.functionalMode = FunctionalMode.PHOTO; } else { if (this.isOnTimer()) { cancelTimer(); ((MainActivity)getContext()).setDecorFitsSystemWindows(true); - this.is_video = true; + this.functionalMode = FunctionalMode.VIDEO; } else if (this.phase == PHASE_TAKING_PHOTO) { // wait until photo taken if (MyDebug.LOG) Log.d(TAG, "wait until photo taken"); } else { ((MainActivity)getContext()).setDecorFitsSystemWindows(true); - this.is_video = true; + this.functionalMode = FunctionalMode.VIDEO; } } - if (is_video != old_is_video) { + if (is_video() != old_is_video) { setFocusPref(false); // first restore the saved focus for the new photo/video mode; don't do autofocus, as it'll be cancelled when restarting preview /*if( !is_video ) { // changing from video to photo mode @@ -4749,7 +4757,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu if (change_user_pref) { // now save - applicationInterface.setVideoPref(is_video); + applicationInterface.setVideoPref(is_video()); } if (!during_startup) { // if during startup, updateFlashForVideo() needs to always be explicitly called anyway @@ -4776,7 +4784,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu // changing from photo to video mode setFocusPref(false); }*/ - if (is_video) { + if (is_video()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && applicationInterface.getRecordAudioPref()) { // check for audio permission now, rather than when user starts video recording // we restrict the checks to Android 6 or later just in case, see note in LocationSupplier.setupLocationListener() @@ -4804,7 +4812,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu private void setFocusPref(boolean auto_focus) { if (MyDebug.LOG) Log.d(TAG, "setFocusPref()"); - String focus_value = applicationInterface.getFocusPref(is_video); + String focus_value = applicationInterface.getFocusPref(is_video()); if (focus_value.length() > 0) { if (MyDebug.LOG) Log.d(TAG, "found existing focus_value: " + focus_value); @@ -4818,7 +4826,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu Log.d(TAG, "found no existing focus_value"); // here we set the default values for focus mode // note if updating default focus value for photo mode, also update MainActivityTest.setToDefault() - if( !updateFocus(is_video ? "focus_mode_continuous_video" : "focus_mode_continuous_picture", true, true, auto_focus) ) { + if( !updateFocus(is_video() ? "focus_mode_continuous_video" : "focus_mode_continuous_picture", true, true, auto_focus) ) { if( MyDebug.LOG ) Log.d(TAG, "continuous focus not supported, so fall back to first"); updateFocus(0, true, true, auto_focus); @@ -4839,12 +4847,12 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu if (MyDebug.LOG) Log.d(TAG, "updateFocusForVideo()"); String old_focus_mode = null; - if (this.supported_focus_values != null && camera_controller != null && is_video) { + if (this.supported_focus_values != null && camera_controller != null && is_video()) { boolean focus_is_video = focusIsVideo(); if (MyDebug.LOG) { - Log.d(TAG, "focus_is_video: " + focus_is_video + " , is_video: " + is_video); + Log.d(TAG, "focus_is_video: " + focus_is_video + " , is_video: " + is_video()); } - if (focus_is_video != is_video) { + if (focus_is_video != is_video()) { if (MyDebug.LOG) Log.d(TAG, "need to change focus mode"); old_focus_mode = this.getCurrentFocusValue(); @@ -4862,7 +4870,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu private void updateFlashForVideo() { if (MyDebug.LOG) Log.d(TAG, "updateFlashForVideo()"); - if (is_video) { + if (is_video()) { // check flash is not auto or on String current_flash = getCurrentFlashValue(); if (current_flash != null && !isFlashSupportedForVideo(current_flash)) { @@ -4926,7 +4934,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu public void updateFlash(String flash_value) { if (MyDebug.LOG) Log.d(TAG, "updateFlash(): " + flash_value); - if (this.phase == PHASE_TAKING_PHOTO && !is_video) { + if (this.phase == PHASE_TAKING_PHOTO && !is_video()) { // just to be safe - risk of cancelling the autofocus before taking a photo, or otherwise messing things up if (MyDebug.LOG) Log.d(TAG, "currently taking a photo"); @@ -4967,7 +4975,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu // don't bother setting done to false as we shouldn't have two torches in a row... } - if (is_video) { + if (is_video()) { // check supported for video String new_flash_value = supported_flash_values.get(new_flash_index); if (!isFlashSupportedForVideo(new_flash_value)) { @@ -5137,7 +5145,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu if (save) { // now save - applicationInterface.setFocusPref(focus_value, is_video); + applicationInterface.setFocusPref(focus_value, is_video()); } } } @@ -5187,7 +5195,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu String focus_value = current_focus_index != -1 ? supported_focus_values.get(current_focus_index) : null; if (MyDebug.LOG) Log.d(TAG, "focus_value is " + focus_value); - if (camera_controller != null && focus_value != null && focus_value.equals("focus_mode_continuous_picture") && !this.is_video) { + if (camera_controller != null && focus_value != null && focus_value.equals("focus_mode_continuous_picture") && !this.is_video()) { if (MyDebug.LOG) Log.d(TAG, "set continuous picture focus move callback"); camera_controller.setContinuousFocusMoveCallback(new CameraController.ContinuousFocusMoveCallback() { @@ -5272,7 +5280,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu this.phase = PHASE_NORMAL; return; } - if (is_video && continuous_fast_burst) { + if (is_video() && continuous_fast_burst) { Log.e(TAG, "continuous_fast_burst not supported for video mode"); this.phase = PHASE_NORMAL; return; @@ -5284,7 +5292,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu } //if( !photo_snapshot && this.phase == PHASE_TAKING_PHOTO ) { //if( (is_video && is_video_recording && !photo_snapshot) || this.phase == PHASE_TAKING_PHOTO ) { - if (is_video && isVideoRecording() && !photo_snapshot) { + if (is_video() && isVideoRecording() && !photo_snapshot) { // user requested stop video if (!video_start_time_set || System.currentTimeMillis() - video_start_time < 500) { // if user presses to stop too quickly, we ignore @@ -5296,7 +5304,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu stopVideo(false); } return; - } else if ((!is_video || photo_snapshot) && this.phase == PHASE_TAKING_PHOTO) { + } else if ((!is_video() || photo_snapshot) && this.phase == PHASE_TAKING_PHOTO) { // user requested take photo while already taking photo if (MyDebug.LOG) Log.d(TAG, "already taking a photo"); @@ -5304,14 +5312,14 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu cancelRepeat(); showToast(take_photo_toast, R.string.cancelled_repeat_mode, true); } - else if( !is_video && camera_controller.getBurstType() == CameraController.BurstType.BURSTTYPE_FOCUS && camera_controller.isCapturingBurst() ) { + else if( !is_video() && camera_controller.getBurstType() == CameraController.BurstType.BURSTTYPE_FOCUS && camera_controller.isCapturingBurst() ) { camera_controller.stopFocusBracketingBurst(); showToast(take_photo_toast, R.string.cancelled_focus_bracketing, true); } return; } - if (!is_video || photo_snapshot) { + if (!is_video() || photo_snapshot) { // check it's okay to take a photo if (!applicationInterface.canTakeNewPhoto()) { if (MyDebug.LOG) @@ -5584,7 +5592,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu if (MyDebug.LOG) Log.d(TAG, "takePicture"); //this.thumbnail_anim = false; - if (!is_video || photo_snapshot) + if (!is_video() || photo_snapshot) this.phase = PHASE_TAKING_PHOTO; else { if (phase == PHASE_TIMER) @@ -5599,7 +5607,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu Log.d(TAG, "camera not opened!"); this.phase = PHASE_NORMAL; applicationInterface.cameraInOperation(false, false); - if (is_video) + if (is_video()) applicationInterface.cameraInOperation(false, true); return; } @@ -5608,7 +5616,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu Log.d(TAG, "preview surface not yet available"); this.phase = PHASE_NORMAL; applicationInterface.cameraInOperation(false, false); - if (is_video) + if (is_video()) applicationInterface.cameraInOperation(false, true); return; } @@ -5623,17 +5631,17 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu if (MyDebug.LOG) Log.d(TAG, "location data required, but not available"); showToast(null, R.string.location_not_available, true); - if( !is_video || photo_snapshot ) + if( !is_video() || photo_snapshot ) this.phase = PHASE_NORMAL; applicationInterface.cameraInOperation(false, false); - if (is_video) + if (is_video()) applicationInterface.cameraInOperation(false, true); return; } } } - if (is_video && !photo_snapshot) { + if (is_video() && !photo_snapshot) { if (MyDebug.LOG) Log.d(TAG, "start video recording"); startVideoRecording(max_filesize_restart); @@ -6465,7 +6473,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu camera_controller.setRotation(getImageVideoRotation()); boolean enable_sound = applicationInterface.getShutterSoundPref(); - if (is_video && isVideoRecording()) + if (is_video() && isVideoRecording()) enable_sound = false; // always disable shutter sound if we're taking a photo while recording video if (MyDebug.LOG) Log.d(TAG, "enable_sound? " + enable_sound); @@ -6564,7 +6572,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu } else if (!this.is_preview_started) { if (MyDebug.LOG) Log.d(TAG, "preview not yet started"); - } else if (!(manual && this.is_video) && (this.isVideoRecording() || this.isTakingPhotoOrOnTimer())) { + } else if (!(manual && this.is_video()) && (this.isVideoRecording() || this.isTakingPhotoOrOnTimer())) { // if taking a video, we allow manual autofocuses // autofocus may cause problem if there is a video corruption problem, see testTakeVideoBitrate() on Nexus 7 at 30Mbs or 50Mbs, where the startup autofocus would cause a problem here if (MyDebug.LOG) @@ -6574,7 +6582,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu // remove any previous request to switch back to continuous removePendingContinuousFocusReset(); } - if (manual && !is_video && camera_controller.focusIsContinuous() && supportedFocusValue("focus_mode_auto")) { + if (manual && !is_video() && camera_controller.focusIsContinuous() && supportedFocusValue("focus_mode_auto")) { if (MyDebug.LOG) Log.d(TAG, "switch from continuous to autofocus mode for touch focus"); camera_controller.setFocusValue("focus_mode_auto"); // switch to autofocus @@ -6755,8 +6763,8 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu Log.d(TAG, "starting the camera preview"); { if (MyDebug.LOG) - Log.d(TAG, "setRecordingHint: " + is_video); - camera_controller.setRecordingHint(this.is_video); + Log.d(TAG, "setRecordingHint: " + is_video()); + camera_controller.setRecordingHint(this.is_video()); } setPreviewFps(); try { @@ -7057,7 +7065,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu public boolean isVideoHighSpeed() { if (MyDebug.LOG) Log.d(TAG, "isVideoHighSpeed"); - return is_video && video_high_speed; + return is_video() && video_high_speed; } public boolean canDisableShutterSound() { @@ -8583,8 +8591,11 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu } } + public boolean isQRCode() { + return functionalMode == FunctionalMode.QRCODE; + } public boolean isVideo() { - return is_video; + return functionalMode == FunctionalMode.VIDEO; } public boolean isVideoRecording() { -- GitLab From 421a3cff88c41716f50bf164d9bc010c40da7a4a Mon Sep 17 00:00:00 2001 From: frankpreel Date: Wed, 3 Apr 2024 10:13:47 +0200 Subject: [PATCH 02/19] Add QRCode toast --- .../sourceforge/opencamera/MainActivity.java | 153 +++++++++--------- 1 file changed, 78 insertions(+), 75 deletions(-) diff --git a/app/src/main/java/net/sourceforge/opencamera/MainActivity.java b/app/src/main/java/net/sourceforge/opencamera/MainActivity.java index 8b0bffe86..b1b553939 100644 --- a/app/src/main/java/net/sourceforge/opencamera/MainActivity.java +++ b/app/src/main/java/net/sourceforge/opencamera/MainActivity.java @@ -6258,7 +6258,7 @@ public class MainActivity extends AppCompatActivity { simple = false; } } - else { + else { //Camera if( photo_mode == MyApplicationInterface.PhotoMode.Panorama ) { // don't show resolution in panorama mode toast_string = ""; @@ -6292,89 +6292,92 @@ public class MainActivity extends AppCompatActivity { simple = false; } } - if( applicationInterface.getFaceDetectionPref() ) { - // important so that the user realises why touching for focus/metering areas won't work - easy to forget that face detection has been turned on! - toast_string += "\n" + getResources().getString(R.string.preference_face_detection); - simple = false; - } - if( !video_high_speed ) { - //manual ISO only supported for high speed video - String iso_value = applicationInterface.getISOPref(); - if( !iso_value.equals(CameraController.ISO_DEFAULT) ) { - toast_string += "\nISO: " + iso_value; - if( preview.supportsExposureTime() ) { - long exposure_time_value = applicationInterface.getExposureTimePref(); - toast_string += " " + preview.getExposureTimeString(exposure_time_value); - } - simple = false; - } - int current_exposure = camera_controller.getExposureCompensation(); - if( current_exposure != 0 ) { - toast_string += "\n" + preview.getExposureCompensationString(current_exposure); + if (preview.isQRCode()) { + toast_string = "QRCode"; + } else { //Camera || Video + if (applicationInterface.getFaceDetectionPref()) { + // important so that the user realises why touching for focus/metering areas won't work - easy to forget that face detection has been turned on! + toast_string += "\n" + getResources().getString(R.string.preference_face_detection); simple = false; } - } - try { - String scene_mode = camera_controller.getSceneMode(); - String white_balance = camera_controller.getWhiteBalance(); - String color_effect = camera_controller.getColorEffect(); - if( scene_mode != null && !scene_mode.equals(CameraController.SCENE_MODE_DEFAULT) ) { - toast_string += "\n" + getResources().getString(R.string.scene_mode) + ": " + mainUI.getEntryForSceneMode(scene_mode); - simple = false; - } - if( white_balance != null && !white_balance.equals(CameraController.WHITE_BALANCE_DEFAULT) ) { - toast_string += "\n" + getResources().getString(R.string.white_balance) + ": " + mainUI.getEntryForWhiteBalance(white_balance); - if( white_balance.equals("manual") && preview.supportsWhiteBalanceTemperature() ) { - toast_string += " " + camera_controller.getWhiteBalanceTemperature(); + if (!video_high_speed) { + //manual ISO only supported for high speed video + String iso_value = applicationInterface.getISOPref(); + if (!iso_value.equals(CameraController.ISO_DEFAULT)) { + toast_string += "\nISO: " + iso_value; + if (preview.supportsExposureTime()) { + long exposure_time_value = applicationInterface.getExposureTimePref(); + toast_string += " " + preview.getExposureTimeString(exposure_time_value); + } + simple = false; + } + int current_exposure = camera_controller.getExposureCompensation(); + if (current_exposure != 0) { + toast_string += "\n" + preview.getExposureCompensationString(current_exposure); + simple = false; } - simple = false; } - if( color_effect != null && !color_effect.equals(CameraController.COLOR_EFFECT_DEFAULT) ) { - toast_string += "\n" + getResources().getString(R.string.color_effect) + ": " + mainUI.getEntryForColorEffect(color_effect); - simple = false; + try { + String scene_mode = camera_controller.getSceneMode(); + String white_balance = camera_controller.getWhiteBalance(); + String color_effect = camera_controller.getColorEffect(); + if (scene_mode != null && !scene_mode.equals(CameraController.SCENE_MODE_DEFAULT)) { + toast_string += "\n" + getResources().getString(R.string.scene_mode) + ": " + mainUI.getEntryForSceneMode(scene_mode); + simple = false; + } + if (white_balance != null && !white_balance.equals(CameraController.WHITE_BALANCE_DEFAULT)) { + toast_string += "\n" + getResources().getString(R.string.white_balance) + ": " + mainUI.getEntryForWhiteBalance(white_balance); + if (white_balance.equals("manual") && preview.supportsWhiteBalanceTemperature()) { + toast_string += " " + camera_controller.getWhiteBalanceTemperature(); + } + simple = false; + } + if (color_effect != null && !color_effect.equals(CameraController.COLOR_EFFECT_DEFAULT)) { + toast_string += "\n" + getResources().getString(R.string.color_effect) + ": " + mainUI.getEntryForColorEffect(color_effect); + simple = false; + } + } catch (RuntimeException e) { + // catch runtime error from camera_controller old API from camera.getParameters() + e.printStackTrace(); } - } - catch(RuntimeException e) { - // catch runtime error from camera_controller old API from camera.getParameters() - e.printStackTrace(); - } - String lock_orientation = applicationInterface.getLockOrientationPref(); - if( !lock_orientation.equals("none") && photo_mode != MyApplicationInterface.PhotoMode.Panorama ) { - // panorama locks to portrait, but don't want to display that in the toast - String [] entries_array = getResources().getStringArray(R.array.preference_lock_orientation_entries); - String [] values_array = getResources().getStringArray(R.array.preference_lock_orientation_values); - int index = Arrays.asList(values_array).indexOf(lock_orientation); - if( index != -1 ) { // just in case! - String entry = entries_array[index]; - toast_string += "\n" + entry; - simple = false; + String lock_orientation = applicationInterface.getLockOrientationPref(); + if (!lock_orientation.equals("none") && photo_mode != MyApplicationInterface.PhotoMode.Panorama) { + // panorama locks to portrait, but don't want to display that in the toast + String[] entries_array = getResources().getStringArray(R.array.preference_lock_orientation_entries); + String[] values_array = getResources().getStringArray(R.array.preference_lock_orientation_values); + int index = Arrays.asList(values_array).indexOf(lock_orientation); + if (index != -1) { // just in case! + String entry = entries_array[index]; + toast_string += "\n" + entry; + simple = false; + } } - } - String timer = sharedPreferences.getString(PreferenceKeys.TimerPreferenceKey, "0"); - if( !timer.equals("0") && photo_mode != MyApplicationInterface.PhotoMode.Panorama ) { - String [] entries_array = getResources().getStringArray(R.array.preference_timer_entries); - String [] values_array = getResources().getStringArray(R.array.preference_timer_values); - int index = Arrays.asList(values_array).indexOf(timer); - if( index != -1 ) { // just in case! - String entry = entries_array[index]; - toast_string += "\n" + getResources().getString(R.string.preference_timer) + ": " + entry; - simple = false; + String timer = sharedPreferences.getString(PreferenceKeys.TimerPreferenceKey, "0"); + if (!timer.equals("0") && photo_mode != MyApplicationInterface.PhotoMode.Panorama) { + String[] entries_array = getResources().getStringArray(R.array.preference_timer_entries); + String[] values_array = getResources().getStringArray(R.array.preference_timer_values); + int index = Arrays.asList(values_array).indexOf(timer); + if (index != -1) { // just in case! + String entry = entries_array[index]; + toast_string += "\n" + getResources().getString(R.string.preference_timer) + ": " + entry; + simple = false; + } } - } - String repeat = applicationInterface.getRepeatPref(); - if( !repeat.equals("1") ) { - String [] entries_array = getResources().getStringArray(R.array.preference_burst_mode_entries); - String [] values_array = getResources().getStringArray(R.array.preference_burst_mode_values); - int index = Arrays.asList(values_array).indexOf(repeat); - if( index != -1 ) { // just in case! - String entry = entries_array[index]; - toast_string += "\n" + getResources().getString(R.string.preference_burst_mode) + ": " + entry; - simple = false; + String repeat = applicationInterface.getRepeatPref(); + if (!repeat.equals("1")) { + String[] entries_array = getResources().getStringArray(R.array.preference_burst_mode_entries); + String[] values_array = getResources().getStringArray(R.array.preference_burst_mode_values); + int index = Arrays.asList(values_array).indexOf(repeat); + if (index != -1) { // just in case! + String entry = entries_array[index]; + toast_string += "\n" + getResources().getString(R.string.preference_burst_mode) + ": " + entry; + simple = false; + } } + /*if( audio_listener != null ) { + toast_string += "\n" + getResources().getString(R.string.preference_audio_noise_control); + }*/ } - /*if( audio_listener != null ) { - toast_string += "\n" + getResources().getString(R.string.preference_audio_noise_control); - }*/ if( MyDebug.LOG ) { Log.d(TAG, "toast_string: " + toast_string); -- GitLab From 351300a2a7da6e398ac3a12f7f7dab9295540b07 Mon Sep 17 00:00:00 2001 From: frankpreel Date: Wed, 3 Apr 2024 10:22:41 +0200 Subject: [PATCH 03/19] QRCode support --- .../opencamera/preview/Preview.java | 20 ++++++++--- .../net/sourceforge/opencamera/ui/MainUI.java | 33 +++++++++++++++++-- app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java b/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java index e130b61f7..bd7c7318e 100644 --- a/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java +++ b/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java @@ -203,8 +203,10 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu VIDEO, QRCODE }; - private FunctionalMode functionalMode; + private FunctionalMode functionalMode = FunctionalMode.PHOTO; private boolean is_video() {return functionalMode == FunctionalMode.VIDEO;}; + private boolean is_qrcode() {return functionalMode == FunctionalMode.QRCODE;}; + private boolean is_photo() {return functionalMode == FunctionalMode.PHOTO;}; private volatile MediaRecorder video_recorder; // must be volatile for test project reading the state private volatile boolean video_start_time_set; // must be volatile for test project reading the state @@ -4728,12 +4730,16 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu return; } boolean old_is_video = is_video(); + boolean old_is_qrcode = is_qrcode(); if (this.is_video()) { if (video_recorder != null) { stopVideo(false); } + this.functionalMode = FunctionalMode.QRCODE; + } else if (this.is_qrcode()) { + // K1ZFP TODO Stop the qrcode // this.functionalMode = FunctionalMode.PHOTO; - } else { + } else if (this.is_photo()) { if (this.isOnTimer()) { cancelTimer(); ((MainActivity)getContext()).setDecorFitsSystemWindows(true); @@ -4748,7 +4754,10 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu } } - if (is_video() != old_is_video) { + if (is_qrcode() != old_is_qrcode) { + //K1ZFP TODO // + } + else if (is_video() != old_is_video) { setFocusPref(false); // first restore the saved focus for the new photo/video mode; don't do autofocus, as it'll be cancelled when restarting preview /*if( !is_video ) { // changing from video to photo mode @@ -4757,7 +4766,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu if (change_user_pref) { // now save - applicationInterface.setVideoPref(is_video()); + applicationInterface.setVideoPref(is_video()); // K1ZFP TODO Warn// } if (!during_startup) { // if during startup, updateFlashForVideo() needs to always be explicitly called anyway @@ -8597,6 +8606,9 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu public boolean isVideo() { return functionalMode == FunctionalMode.VIDEO; } + public boolean isPhoto() { + return functionalMode == FunctionalMode.PHOTO; + } public boolean isVideoRecording() { return video_recorder != null && video_start_time_set; diff --git a/app/src/main/java/net/sourceforge/opencamera/ui/MainUI.java b/app/src/main/java/net/sourceforge/opencamera/ui/MainUI.java index 44fd83b21..a7b18be07 100644 --- a/app/src/main/java/net/sourceforge/opencamera/ui/MainUI.java +++ b/app/src/main/java/net/sourceforge/opencamera/ui/MainUI.java @@ -1217,6 +1217,7 @@ public class MainUI { int resource; int content_description; int switch_video_content_description; + // The switch order is camera -> video -> qrcode -> camera... if (main_activity.getPreview().isVideo()) { if (MyDebug.LOG) Log.d(TAG, "set icon to video " + main_activity.getPreview().isVideoRecording()); @@ -1224,23 +1225,48 @@ public class MainUI { ? R.drawable.ic_camera_video_recording : R.drawable.ic_camera_video; content_description = main_activity.getPreview().isVideoRecording() ? R.string.stop_video : R.string.start_video; + switch_video_content_description = R.string.switch_to_qrcode; + } else if (main_activity.getPreview().isQRCode()) { + if (MyDebug.LOG) + Log.d(TAG, "set icon to qrcode"); + resource = R.drawable.empty; + content_description = 0; switch_video_content_description = R.string.switch_to_photo; - } else { + } else { // Video case if (MyDebug.LOG) Log.d(TAG, "set icon to photo"); resource = R.drawable.take_photo_selector; content_description = R.string.take_photo; switch_video_content_description = R.string.switch_to_video; } + view.setImageResource(resource); - view.setContentDescription(main_activity.getResources().getString(content_description)); + if (content_description==0) + view.setContentDescription(""); + else + view.setContentDescription(main_activity.getResources().getString(content_description)); view.setTag(resource); // for testing view = main_activity.findViewById(R.id.switch_video); view.setContentDescription(main_activity.getResources().getString(switch_video_content_description)); - resource = main_activity.getPreview().isVideo() ? R.drawable.ic_switch_camera : R.drawable.ic_switch_video; + + if (main_activity.getPreview().isVideo()) + resource = R.drawable.ic_switch_qrcode; + else if (main_activity.getPreview().isQRCode()) + resource = R.drawable.ic_switch_camera; + else // camera + resource = R.drawable.ic_switch_video; view.setImageResource(resource); view.setTag(resource); // for testing + + // Hide/Show gallery & switch camera icons. + if (main_activity.getPreview().isQRCode()) { + main_activity.findViewById(R.id.gallery).setVisibility(View.INVISIBLE); + main_activity.findViewById(R.id.switch_camera).setVisibility(View.INVISIBLE); + } else { + main_activity.findViewById(R.id.gallery).setVisibility(View.VISIBLE); + main_activity.findViewById(R.id.switch_camera).setVisibility(View.VISIBLE); + } } } @@ -2506,6 +2532,7 @@ public class MainUI { } public void setPopupIcon() { + if (MyDebug.LOG) Log.d(TAG, "setPopupIcon"); ImageButton popup = main_activity.findViewById(R.id.popup); diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index a48df3018..c0ab62b99 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -932,4 +932,5 @@ X-Bty Extension : Bokeh X-Bokeh + QR Code \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ec96043cd..d1b828e50 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1101,4 +1101,5 @@ Skip LENS + QRCode -- GitLab From 4634d8ae56bc1205032d55c763f3ebd3f31e2760 Mon Sep 17 00:00:00 2001 From: frankpreel Date: Wed, 3 Apr 2024 10:27:46 +0200 Subject: [PATCH 04/19] Empty bitmap --- app/src/main/res/drawable/empty.xml | 8 ++++++++ app/src/main/res/drawable/ic_switch_qrcode.xml | 16 ++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 app/src/main/res/drawable/empty.xml create mode 100644 app/src/main/res/drawable/ic_switch_qrcode.xml diff --git a/app/src/main/res/drawable/empty.xml b/app/src/main/res/drawable/empty.xml new file mode 100644 index 000000000..c886b6ada --- /dev/null +++ b/app/src/main/res/drawable/empty.xml @@ -0,0 +1,8 @@ + + + diff --git a/app/src/main/res/drawable/ic_switch_qrcode.xml b/app/src/main/res/drawable/ic_switch_qrcode.xml new file mode 100644 index 000000000..e42783ba6 --- /dev/null +++ b/app/src/main/res/drawable/ic_switch_qrcode.xml @@ -0,0 +1,16 @@ + + + + + + + -- GitLab From cae06472fb670eca2543b8ed19e50814a9d6d28b Mon Sep 17 00:00:00 2001 From: frankpreel Date: Wed, 3 Apr 2024 14:44:28 +0200 Subject: [PATCH 05/19] Typo --- app/src/main/java/net/sourceforge/opencamera/ui/MainUI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/net/sourceforge/opencamera/ui/MainUI.java b/app/src/main/java/net/sourceforge/opencamera/ui/MainUI.java index a7b18be07..929ce21a9 100644 --- a/app/src/main/java/net/sourceforge/opencamera/ui/MainUI.java +++ b/app/src/main/java/net/sourceforge/opencamera/ui/MainUI.java @@ -1232,7 +1232,7 @@ public class MainUI { resource = R.drawable.empty; content_description = 0; switch_video_content_description = R.string.switch_to_photo; - } else { // Video case + } else { // Camera case if (MyDebug.LOG) Log.d(TAG, "set icon to photo"); resource = R.drawable.take_photo_selector; -- GitLab From 8532ba368522f424f388179b779393e26107bfe6 Mon Sep 17 00:00:00 2001 From: frankpreel Date: Wed, 3 Apr 2024 14:48:29 +0200 Subject: [PATCH 06/19] Save current operating mode. Please note that QRCode mode is *NOT* part of the save. Only Video and Photo modes can be restored. If the application is exited in QRCode mode, Photo mode will be restored the next time the application is started. --- .../main/java/net/sourceforge/opencamera/preview/Preview.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java b/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java index bd7c7318e..3ef1ff6bd 100644 --- a/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java +++ b/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java @@ -4755,7 +4755,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu } if (is_qrcode() != old_is_qrcode) { - //K1ZFP TODO // + applicationInterface.setVideoPref(false); } else if (is_video() != old_is_video) { setFocusPref(false); // first restore the saved focus for the new photo/video mode; don't do autofocus, as it'll be cancelled when restarting preview @@ -4766,7 +4766,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu if (change_user_pref) { // now save - applicationInterface.setVideoPref(is_video()); // K1ZFP TODO Warn// + applicationInterface.setVideoPref(true); } if (!during_startup) { // if during startup, updateFlashForVideo() needs to always be explicitly called anyway -- GitLab From 8143d53030b1b8569fa1763d893cce1354565a02 Mon Sep 17 00:00:00 2001 From: frankpreel Date: Thu, 4 Apr 2024 09:17:25 +0200 Subject: [PATCH 07/19] Add dependencies. Dependencies are not the last one due to targetSdkVersion = 33 --- app/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/build.gradle b/app/build.gradle index 3900d3631..b44fb311a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -79,6 +79,9 @@ dependencies { implementation 'androidx.exifinterface:exifinterface:1.3.6' + implementation 'androidx.camera:camera-core:1.2.3' + implementation 'com.google.zxing:core:3.5.2' + testImplementation 'junit:junit:4.13.2' // newer AndroidJUnit4 InstrumentedTest -- GitLab From 9ef6fb7d431b3a6ebcae1feb52dcecd0647d9127 Mon Sep 17 00:00:00 2001 From: frankpreel Date: Thu, 4 Apr 2024 10:24:16 +0200 Subject: [PATCH 08/19] Increase minSdkVersion as TextClassification needs updated version --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index b44fb311a..aeef04060 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -22,7 +22,7 @@ android { defaultConfig { applicationId "foundation.e.camera" - minSdkVersion 25 + minSdkVersion 26 targetSdkVersion 33 renderscriptTargetApi 21 -- GitLab From 1809adddd360edf572c501069df7e057fdab5e1a Mon Sep 17 00:00:00 2001 From: frankpreel Date: Fri, 5 Apr 2024 14:23:43 +0200 Subject: [PATCH 09/19] Icons used in the QRCode flash process --- app/src/main/res/drawable/ic_book.xml | 15 +++++++++++++ .../main/res/drawable/ic_calendar_add_on.xml | 15 +++++++++++++ .../main/res/drawable/ic_contact_phone.xml | 15 +++++++++++++ app/src/main/res/drawable/ic_content_copy.xml | 16 ++++++++++++++ .../main/res/drawable/ic_directions_car.xml | 21 +++++++++++++++++++ app/src/main/res/drawable/ic_drag_handle.xml | 10 +++++++++ app/src/main/res/drawable/ic_email.xml | 15 +++++++++++++ app/src/main/res/drawable/ic_location_on.xml | 18 ++++++++++++++++ app/src/main/res/drawable/ic_network_wifi.xml | 14 +++++++++++++ app/src/main/res/drawable/ic_passkey.xml | 15 +++++++++++++ app/src/main/res/drawable/ic_phone.xml | 15 +++++++++++++ .../main/res/drawable/ic_shopping_cart.xml | 15 +++++++++++++ app/src/main/res/drawable/ic_sms.xml | 15 +++++++++++++ app/src/main/res/drawable/ic_text_snippet.xml | 16 ++++++++++++++ .../qr_bottom_sheet_action_button_divider.xml | 10 +++++++++ 15 files changed, 225 insertions(+) create mode 100644 app/src/main/res/drawable/ic_book.xml create mode 100644 app/src/main/res/drawable/ic_calendar_add_on.xml create mode 100644 app/src/main/res/drawable/ic_contact_phone.xml create mode 100644 app/src/main/res/drawable/ic_content_copy.xml create mode 100644 app/src/main/res/drawable/ic_directions_car.xml create mode 100644 app/src/main/res/drawable/ic_drag_handle.xml create mode 100644 app/src/main/res/drawable/ic_email.xml create mode 100644 app/src/main/res/drawable/ic_location_on.xml create mode 100644 app/src/main/res/drawable/ic_network_wifi.xml create mode 100644 app/src/main/res/drawable/ic_passkey.xml create mode 100644 app/src/main/res/drawable/ic_phone.xml create mode 100644 app/src/main/res/drawable/ic_shopping_cart.xml create mode 100644 app/src/main/res/drawable/ic_sms.xml create mode 100644 app/src/main/res/drawable/ic_text_snippet.xml create mode 100644 app/src/main/res/drawable/qr_bottom_sheet_action_button_divider.xml diff --git a/app/src/main/res/drawable/ic_book.xml b/app/src/main/res/drawable/ic_book.xml new file mode 100644 index 000000000..1c6d45296 --- /dev/null +++ b/app/src/main/res/drawable/ic_book.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_calendar_add_on.xml b/app/src/main/res/drawable/ic_calendar_add_on.xml new file mode 100644 index 000000000..d4cfef3e2 --- /dev/null +++ b/app/src/main/res/drawable/ic_calendar_add_on.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_contact_phone.xml b/app/src/main/res/drawable/ic_contact_phone.xml new file mode 100644 index 000000000..19d5ef77f --- /dev/null +++ b/app/src/main/res/drawable/ic_contact_phone.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_content_copy.xml b/app/src/main/res/drawable/ic_content_copy.xml new file mode 100644 index 000000000..66112fee7 --- /dev/null +++ b/app/src/main/res/drawable/ic_content_copy.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_directions_car.xml b/app/src/main/res/drawable/ic_directions_car.xml new file mode 100644 index 000000000..b39f53343 --- /dev/null +++ b/app/src/main/res/drawable/ic_directions_car.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_drag_handle.xml b/app/src/main/res/drawable/ic_drag_handle.xml new file mode 100644 index 000000000..602c9d3bc --- /dev/null +++ b/app/src/main/res/drawable/ic_drag_handle.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_email.xml b/app/src/main/res/drawable/ic_email.xml new file mode 100644 index 000000000..63a9bd89a --- /dev/null +++ b/app/src/main/res/drawable/ic_email.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_location_on.xml b/app/src/main/res/drawable/ic_location_on.xml new file mode 100644 index 000000000..f1db4bdbf --- /dev/null +++ b/app/src/main/res/drawable/ic_location_on.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_network_wifi.xml b/app/src/main/res/drawable/ic_network_wifi.xml new file mode 100644 index 000000000..6db18e1a0 --- /dev/null +++ b/app/src/main/res/drawable/ic_network_wifi.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_passkey.xml b/app/src/main/res/drawable/ic_passkey.xml new file mode 100644 index 000000000..b3c3dbb54 --- /dev/null +++ b/app/src/main/res/drawable/ic_passkey.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_phone.xml b/app/src/main/res/drawable/ic_phone.xml new file mode 100644 index 000000000..ae7ecad9c --- /dev/null +++ b/app/src/main/res/drawable/ic_phone.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_shopping_cart.xml b/app/src/main/res/drawable/ic_shopping_cart.xml new file mode 100644 index 000000000..3cd15b2b0 --- /dev/null +++ b/app/src/main/res/drawable/ic_shopping_cart.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_sms.xml b/app/src/main/res/drawable/ic_sms.xml new file mode 100644 index 000000000..b20d6f027 --- /dev/null +++ b/app/src/main/res/drawable/ic_sms.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_text_snippet.xml b/app/src/main/res/drawable/ic_text_snippet.xml new file mode 100644 index 000000000..137e2893e --- /dev/null +++ b/app/src/main/res/drawable/ic_text_snippet.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/app/src/main/res/drawable/qr_bottom_sheet_action_button_divider.xml b/app/src/main/res/drawable/qr_bottom_sheet_action_button_divider.xml new file mode 100644 index 000000000..06402726f --- /dev/null +++ b/app/src/main/res/drawable/qr_bottom_sheet_action_button_divider.xml @@ -0,0 +1,10 @@ + + + + + -- GitLab From 43844ae824b4ef51b98bd3d0551ab3bfc39348c1 Mon Sep 17 00:00:00 2001 From: frankpreel Date: Fri, 5 Apr 2024 14:25:29 +0200 Subject: [PATCH 10/19] Functional scan process --- .../sourceforge/opencamera/MainActivity.java | 21 ++ .../opencamera/ext/AddressBookParsedResult.kt | 130 ++++++++++ .../opencamera/ext/CalendarParsedResult.kt | 63 +++++ .../net/sourceforge/opencamera/ext/Context.kt | 17 ++ .../ext/EmailAddressParsedResult.kt | 52 ++++ .../opencamera/ext/GeoParsedResult.kt | 36 +++ .../opencamera/ext/ISBNParsedResult.kt | 38 +++ .../sourceforge/opencamera/ext/ImageProxy.kt | 42 ++++ .../net/sourceforge/opencamera/ext/Int.kt | 20 ++ .../opencamera/ext/ParsedResult.kt | 50 ++++ .../opencamera/ext/ProductParsedResult.kt | 38 +++ .../opencamera/ext/RemoteAction.kt | 44 ++++ .../opencamera/ext/SMSParsedResult.kt | 36 +++ .../opencamera/ext/TelParsedResult.kt | 36 +++ .../opencamera/ext/VINParsedResult.kt | 39 +++ .../opencamera/ext/WifiParsedResult.kt | 66 +++++ .../opencamera/preview/Preview.java | 40 ++++ .../opencamera/qr/QrImageAnalyzer.kt | 226 ++++++++++++++++++ .../opencamera/qr/QrTextClassifier.kt | 100 ++++++++ .../layout/qr_bottom_sheet_action_button.xml | 20 ++ .../res/layout/qr_bottom_sheet_dialog.xml | 154 ++++++++++++ app/src/main/res/values/colors.xml | 8 + 22 files changed, 1276 insertions(+) create mode 100644 app/src/main/java/net/sourceforge/opencamera/ext/AddressBookParsedResult.kt create mode 100644 app/src/main/java/net/sourceforge/opencamera/ext/CalendarParsedResult.kt create mode 100644 app/src/main/java/net/sourceforge/opencamera/ext/Context.kt create mode 100644 app/src/main/java/net/sourceforge/opencamera/ext/EmailAddressParsedResult.kt create mode 100644 app/src/main/java/net/sourceforge/opencamera/ext/GeoParsedResult.kt create mode 100644 app/src/main/java/net/sourceforge/opencamera/ext/ISBNParsedResult.kt create mode 100644 app/src/main/java/net/sourceforge/opencamera/ext/ImageProxy.kt create mode 100644 app/src/main/java/net/sourceforge/opencamera/ext/Int.kt create mode 100644 app/src/main/java/net/sourceforge/opencamera/ext/ParsedResult.kt create mode 100644 app/src/main/java/net/sourceforge/opencamera/ext/ProductParsedResult.kt create mode 100644 app/src/main/java/net/sourceforge/opencamera/ext/RemoteAction.kt create mode 100644 app/src/main/java/net/sourceforge/opencamera/ext/SMSParsedResult.kt create mode 100644 app/src/main/java/net/sourceforge/opencamera/ext/TelParsedResult.kt create mode 100644 app/src/main/java/net/sourceforge/opencamera/ext/VINParsedResult.kt create mode 100644 app/src/main/java/net/sourceforge/opencamera/ext/WifiParsedResult.kt create mode 100644 app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt create mode 100644 app/src/main/java/net/sourceforge/opencamera/qr/QrTextClassifier.kt create mode 100644 app/src/main/res/layout/qr_bottom_sheet_action_button.xml create mode 100644 app/src/main/res/layout/qr_bottom_sheet_dialog.xml diff --git a/app/src/main/java/net/sourceforge/opencamera/MainActivity.java b/app/src/main/java/net/sourceforge/opencamera/MainActivity.java index b1b553939..d1d7b827a 100644 --- a/app/src/main/java/net/sourceforge/opencamera/MainActivity.java +++ b/app/src/main/java/net/sourceforge/opencamera/MainActivity.java @@ -83,6 +83,7 @@ import net.sourceforge.opencamera.cameracontroller.CameraControllerManager; import net.sourceforge.opencamera.cameracontroller.CameraControllerManager2; import net.sourceforge.opencamera.preview.Preview; import net.sourceforge.opencamera.preview.VideoProfile; +import net.sourceforge.opencamera.qr.QrImageAnalyzer; import net.sourceforge.opencamera.remotecontrol.BluetoothRemoteControl; import net.sourceforge.opencamera.ui.CircleImageView; import net.sourceforge.opencamera.ui.DrawPreview; @@ -109,6 +110,11 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import foundation.e.camera.R; +import kotlin.coroutines.CoroutineContext; +import kotlinx.coroutines.CoroutineScope; +import kotlinx.coroutines.Dispatchers; +import kotlinx.coroutines.Job; +import kotlinx.coroutines.JobKt; /** * The main Activity for Open Camera. @@ -241,6 +247,11 @@ public class MainActivity extends AppCompatActivity { //public static final boolean lock_to_landscape = true; public static final boolean lock_to_landscape = false; + // QRCode + public QrImageAnalyzer qrImageAnalyzer; + private Job job; + private CoroutineScope coroutineScope; + // handling for lock_to_landscape==false: public enum SystemOrientation { @@ -273,6 +284,16 @@ public class MainActivity extends AppCompatActivity { Log.d(TAG, "activity_count: " + activity_count); super.onCreate(savedInstanceState); + //QRCode + job = JobKt.Job(null); + coroutineScope = new CoroutineScope() { + @Override + public CoroutineContext getCoroutineContext() { + return Dispatchers.getMain().plus(job); + } + }; + qrImageAnalyzer = new QrImageAnalyzer(this, coroutineScope); + setContentView(R.layout.activity_main); PreferenceManager.setDefaultValues(this, R.xml.preferences, false); // initialise any unset preferences to their default values diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/AddressBookParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/AddressBookParsedResult.kt new file mode 100644 index 000000000..49054372c --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/AddressBookParsedResult.kt @@ -0,0 +1,130 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.os.Build +import android.provider.ContactsContract +import android.view.textclassifier.TextClassification +import android.view.textclassifier.TextClassifier +import com.google.zxing.client.result.AddressBookParsedResult +import foundation.e.camera.R + +fun AddressBookParsedResult.createIntent() = Intent( + Intent.ACTION_INSERT, ContactsContract.Contacts.CONTENT_URI +).apply { + names.firstOrNull()?.let { + putExtra(ContactsContract.Intents.Insert.NAME, it) + } + + pronunciation?.let { + putExtra(ContactsContract.Intents.Insert.PHONETIC_NAME, it) + } + + phoneNumbers?.let { phoneNumbers -> + val phoneTypes = phoneTypes ?: arrayOf() + + for ((i, keys) in listOf( + listOf( + ContactsContract.Intents.Insert.PHONE, + ContactsContract.Intents.Insert.PHONE_TYPE, + ), + listOf( + ContactsContract.Intents.Insert.SECONDARY_PHONE, + ContactsContract.Intents.Insert.SECONDARY_PHONE_TYPE, + ), + listOf( + ContactsContract.Intents.Insert.TERTIARY_PHONE, + ContactsContract.Intents.Insert.TERTIARY_PHONE_TYPE, + ), + ).withIndex()) { + phoneNumbers.getOrNull(i)?.let { phone -> + putExtra(keys.first(), phone) + phoneTypes.getOrNull(i)?.let { + putExtra(keys.last(), it) + } + } + } + } + + emails?.let { emails -> + val emailTypes = emailTypes ?: arrayOf() + + for ((i, keys) in listOf( + listOf( + ContactsContract.Intents.Insert.EMAIL, + ContactsContract.Intents.Insert.EMAIL_TYPE, + ), + listOf( + ContactsContract.Intents.Insert.SECONDARY_EMAIL, + ContactsContract.Intents.Insert.SECONDARY_EMAIL_TYPE, + ), + listOf( + ContactsContract.Intents.Insert.TERTIARY_EMAIL, + ContactsContract.Intents.Insert.TERTIARY_EMAIL_TYPE, + ), + ).withIndex()) { + emails.getOrNull(i)?.let { phone -> + putExtra(keys.first(), phone) + emailTypes.getOrNull(i)?.let { + putExtra(keys.last(), it) + } + } + } + } + + instantMessenger?.let { + putExtra(ContactsContract.Intents.Insert.IM_HANDLE, it) + } + + note?.let { + putExtra(ContactsContract.Intents.Insert.NOTES, it) + } + + addresses?.let { emails -> + val addressTypes = addressTypes ?: arrayOf() + + for ((i, keys) in listOf( + listOf( + ContactsContract.Intents.Insert.POSTAL, + ContactsContract.Intents.Insert.POSTAL_TYPE, + ), + ).withIndex()) { + emails.getOrNull(i)?.let { phone -> + putExtra(keys.first(), phone) + addressTypes.getOrNull(i)?.let { + putExtra(keys.last(), it) + } + } + } + } + + org?.let { + putExtra(ContactsContract.Intents.Insert.COMPANY, it) + } +} + +fun AddressBookParsedResult.createTextClassification( + context: Context +) = TextClassification.Builder() + .setText(title ?: names.firstOrNull() ?: "") + .setEntityType(TextClassifier.TYPE_OTHER, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_contact_phone, + R.string.qr_address_title, + R.string.qr_address_content_description, + createIntent() + ) + ) + } + } + .build() diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/CalendarParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/CalendarParsedResult.kt new file mode 100644 index 000000000..09a45490c --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/CalendarParsedResult.kt @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.os.Build +import android.provider.CalendarContract +import android.view.textclassifier.TextClassification +import android.view.textclassifier.TextClassifier +import androidx.core.os.bundleOf +import com.google.zxing.client.result.CalendarParsedResult +import foundation.e.camera.R + +fun CalendarParsedResult.createIntent() = Intent( + Intent.ACTION_INSERT, CalendarContract.Events.CONTENT_URI +).apply { + summary?.let { + putExtra(CalendarContract.Events.TITLE, it) + } + description?.let { + putExtra(CalendarContract.Events.DESCRIPTION, it) + } + location?.let { + putExtra(CalendarContract.Events.EVENT_LOCATION, it) + } + organizer?.let { + putExtra(CalendarContract.Events.ORGANIZER, it) + } + attendees?.let { + putExtra(Intent.EXTRA_EMAIL, it.joinToString(",")) + } + + putExtras( + bundleOf( + CalendarContract.EXTRA_EVENT_BEGIN_TIME to startTimestamp, + CalendarContract.EXTRA_EVENT_END_TIME to endTimestamp, + CalendarContract.EXTRA_EVENT_ALL_DAY to (isStartAllDay && isEndAllDay), + ) + ) +} + +fun CalendarParsedResult.createTextClassification(context: Context) = TextClassification.Builder() + .setText(summary) + .setEntityType(TextClassifier.TYPE_OTHER, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_calendar_add_on, + R.string.qr_calendar_title, + R.string.qr_calendar_content_description, + createIntent() + ) + ) + } + } + .build() diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/Context.kt b/app/src/main/java/net/sourceforge/opencamera/ext/Context.kt new file mode 100644 index 000000000..4026fb52e --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/Context.kt @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.content.Context +import android.util.TypedValue +import androidx.annotation.AttrRes +import androidx.annotation.ColorInt + +@ColorInt +fun Context.getThemeColor(@AttrRes attribute: Int) = TypedValue().let { + theme.resolveAttribute(attribute, it, true) + it.data +} diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/EmailAddressParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/EmailAddressParsedResult.kt new file mode 100644 index 000000000..8f89a63d0 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/EmailAddressParsedResult.kt @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.view.textclassifier.TextClassification +import android.view.textclassifier.TextClassifier +import androidx.core.os.bundleOf +import com.google.zxing.client.result.EmailAddressParsedResult +import foundation.e.camera.R + +fun EmailAddressParsedResult.createIntent() = Intent( + Intent.ACTION_SENDTO, + Uri.parse("mailto:${tos?.firstOrNull() ?: ""}") +).apply { + putExtras( + bundleOf( + Intent.EXTRA_EMAIL to tos, + Intent.EXTRA_CC to cCs, + Intent.EXTRA_BCC to bcCs, + Intent.EXTRA_SUBJECT to subject, + Intent.EXTRA_TEXT to body, + ) + ) +} + +fun EmailAddressParsedResult.createTextClassification( + context: Context +) = TextClassification.Builder() + .setText(tos.joinToString()) + .setEntityType(TextClassifier.TYPE_EMAIL, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_email, + R.string.qr_email_title, + R.string.qr_email_content_description, + createIntent() + ) + ) + } + } + .build() diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/GeoParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/GeoParsedResult.kt new file mode 100644 index 000000000..1c7fb4597 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/GeoParsedResult.kt @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.view.textclassifier.TextClassification +import android.view.textclassifier.TextClassifier +import com.google.zxing.client.result.GeoParsedResult +import foundation.e.camera.R + +fun GeoParsedResult.createIntent() = Intent(Intent.ACTION_VIEW, Uri.parse(geoURI)) + +fun GeoParsedResult.createTextClassification(context: Context) = TextClassification.Builder() + .setText(displayResult) + .setEntityType(TextClassifier.TYPE_ADDRESS, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_location_on, + R.string.qr_geo_title, + R.string.qr_geo_content_description, + createIntent() + ) + ) + } + } + .build() diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/ISBNParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/ISBNParsedResult.kt new file mode 100644 index 000000000..f71f031e3 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/ISBNParsedResult.kt @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.view.textclassifier.TextClassification +import android.view.textclassifier.TextClassifier +import com.google.zxing.client.result.ISBNParsedResult +import foundation.e.camera.R + +fun ISBNParsedResult.createIntent() = Intent( + Intent.ACTION_VIEW, Uri.parse("https://isbnsearch.org/isbn/${isbn}") +) + +fun ISBNParsedResult.createTextClassification(context: Context) = TextClassification.Builder() + .setText(isbn) + .setEntityType(TextClassifier.TYPE_OTHER, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_book, + R.string.qr_isbn_title, + R.string.qr_isbn_content_description, + createIntent() + ) + ) + } + } + .build() diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/ImageProxy.kt b/app/src/main/java/net/sourceforge/opencamera/ext/ImageProxy.kt new file mode 100644 index 000000000..3b0d19456 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/ImageProxy.kt @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2022 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import androidx.camera.core.ImageProxy +import com.google.zxing.PlanarYUVLuminanceSource + +private fun rotateYUVLuminancePlane(data: ByteArray, width: Int, height: Int): ByteArray { + val yuv = ByteArray(width * height) + // Rotate the Y luma + var i = 0 + for (x in 0 until width) { + for (y in height - 1 downTo 0) { + yuv[i] = data[y * width + x] + i++ + } + } + return yuv +} + +internal val ImageProxy.planarYUVLuminanceSource: PlanarYUVLuminanceSource + get() { + val plane = planes[0] + val buffer = plane.buffer + var bytes = ByteArray(buffer.remaining()) + buffer.get(bytes) + + var width = width + var height = height + + if (imageInfo.rotationDegrees == 90 || imageInfo.rotationDegrees == 270) { + bytes = rotateYUVLuminancePlane(bytes, width, height) + width = height.also { height = width } + } + + return PlanarYUVLuminanceSource( + bytes, width, height, 0, 0, width, height, true + ) + } diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/Int.kt b/app/src/main/java/net/sourceforge/opencamera/ext/Int.kt new file mode 100644 index 000000000..e3e8d83ff --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/Int.kt @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2022 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.content.res.Resources.getSystem +import android.util.Range +import kotlin.math.roundToInt + +val Int.px + get() = (this * getSystem().displayMetrics.density).roundToInt() + +val Int.dp + get() = (this / getSystem().displayMetrics.density).roundToInt() + +internal fun Int.Companion.mapToRange(range: Range, percentage: Float): Int { + return (((range.upper - range.lower) * percentage) + range.lower).roundToInt() +} diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/ParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/ParsedResult.kt new file mode 100644 index 000000000..a35f89e48 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/ParsedResult.kt @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.content.Context +import com.google.zxing.client.result.AddressBookParsedResult +import com.google.zxing.client.result.CalendarParsedResult +import com.google.zxing.client.result.EmailAddressParsedResult +import com.google.zxing.client.result.GeoParsedResult +import com.google.zxing.client.result.ISBNParsedResult +import com.google.zxing.client.result.ParsedResult +import com.google.zxing.client.result.ProductParsedResult +import com.google.zxing.client.result.SMSParsedResult +import com.google.zxing.client.result.TelParsedResult +import com.google.zxing.client.result.TextParsedResult +import com.google.zxing.client.result.URIParsedResult +import com.google.zxing.client.result.VINParsedResult +import com.google.zxing.client.result.WifiParsedResult + +fun ParsedResult.createTextClassification(context: Context) = when (this) { + is AddressBookParsedResult -> createTextClassification(context) + + is CalendarParsedResult -> createTextClassification(context) + + is EmailAddressParsedResult -> createTextClassification(context) + + is GeoParsedResult -> createTextClassification(context) + + is ISBNParsedResult -> createTextClassification(context) + + is ProductParsedResult -> createTextClassification(context) + + is SMSParsedResult -> createTextClassification(context) + + is TelParsedResult -> createTextClassification(context) + + is TextParsedResult -> null // Try with the next methods + + is URIParsedResult -> null // We handle this manually + + is VINParsedResult -> createTextClassification(context) + + is WifiParsedResult -> createTextClassification(context) + + else -> null +} + diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/ProductParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/ProductParsedResult.kt new file mode 100644 index 000000000..43177e0cf --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/ProductParsedResult.kt @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.view.textclassifier.TextClassification +import android.view.textclassifier.TextClassifier +import com.google.zxing.client.result.ProductParsedResult +import foundation.e.camera.R + +fun ProductParsedResult.createIntent() = Intent( + Intent.ACTION_VIEW, Uri.parse("https://www.barcodelookup.com/${productID}") +) + +fun ProductParsedResult.createTextClassification(context: Context) = TextClassification.Builder() + .setText(productID) + .setEntityType(TextClassifier.TYPE_OTHER, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_shopping_cart, + R.string.qr_product_title, + R.string.qr_product_content_description, + createIntent() + ) + ) + } + } + .build() diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/RemoteAction.kt b/app/src/main/java/net/sourceforge/opencamera/ext/RemoteAction.kt new file mode 100644 index 000000000..bc5101101 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/RemoteAction.kt @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.app.PendingIntent +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.graphics.drawable.Icon +import androidx.annotation.AttrRes +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.appcompat.content.res.AppCompatResources +import androidx.core.graphics.drawable.DrawableCompat +import androidx.core.graphics.drawable.toBitmap +import kotlin.reflect.KClass + +fun KClass.build( + context: Context, + @DrawableRes iconRes: Int, + @StringRes titleRes: Int, + @StringRes contentDescriptionRes: Int, + intent: Intent, + requestCode: Int = 0, + flags: Int = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT, + @AttrRes iconTint: Int = com.google.android.material.R.attr.colorOnBackground, +) = RemoteAction( + Icon.createWithBitmap( + AppCompatResources.getDrawable(context, iconRes)?.let { + DrawableCompat.wrap(it.mutate()).apply { + DrawableCompat.setTint( + this, + context.getThemeColor(iconTint) + ) + } + }?.toBitmap() + ), + context.getString(titleRes), + context.getString(contentDescriptionRes), + PendingIntent.getActivity(context, requestCode, intent, flags) +) diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/SMSParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/SMSParsedResult.kt new file mode 100644 index 000000000..d12aea8f2 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/SMSParsedResult.kt @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.view.textclassifier.TextClassification +import android.view.textclassifier.TextClassifier +import com.google.zxing.client.result.SMSParsedResult +import foundation.e.camera.R + +fun SMSParsedResult.createIntent() = Intent(Intent.ACTION_SENDTO, Uri.parse(smsuri)) + +fun SMSParsedResult.createTextClassification(context: Context) = TextClassification.Builder() + .setText(numbers.first()) + .setEntityType(TextClassifier.TYPE_OTHER, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_sms, + R.string.qr_sms_title, + R.string.qr_sms_content_description, + createIntent() + ) + ) + } + } + .build() diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/TelParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/TelParsedResult.kt new file mode 100644 index 000000000..17d3c177b --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/TelParsedResult.kt @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.view.textclassifier.TextClassification +import android.view.textclassifier.TextClassifier +import com.google.zxing.client.result.TelParsedResult +import foundation.e.camera.R + +fun TelParsedResult.createIntent() = Intent(Intent.ACTION_SENDTO, Uri.parse(telURI)) + +fun TelParsedResult.createTextClassification(context: Context) = TextClassification.Builder() + .setText(number) + .setEntityType(TextClassifier.TYPE_PHONE, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_phone, + R.string.qr_tel_title, + R.string.qr_tel_content_description, + createIntent() + ) + ) + } + } + .build() diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/VINParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/VINParsedResult.kt new file mode 100644 index 000000000..2b4ca3f0f --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/VINParsedResult.kt @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + + +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.view.textclassifier.TextClassification +import android.view.textclassifier.TextClassifier +import com.google.zxing.client.result.VINParsedResult +import foundation.e.camera.R + +fun VINParsedResult.createIntent() = Intent( + Intent.ACTION_VIEW, Uri.parse("https://www.vindecoderz.com/EN/check-lookup/${vin}") +) + +fun VINParsedResult.createTextClassification(context: Context) = TextClassification.Builder() + .setText(vin) + .setEntityType(TextClassifier.TYPE_OTHER, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_directions_car, + R.string.qr_vin_title, + R.string.qr_vin_content_description, + createIntent() + ) + ) + } + } + .build() diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/WifiParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/WifiParsedResult.kt new file mode 100644 index 000000000..6b00f92c1 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/WifiParsedResult.kt @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + + +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.net.wifi.WifiNetworkSuggestion +import android.os.Build +import android.provider.Settings +import android.view.textclassifier.TextClassification +import android.view.textclassifier.TextClassifier +import androidx.annotation.RequiresApi +import com.google.zxing.client.result.WifiParsedResult +import foundation.e.camera.R + +@RequiresApi(Build.VERSION_CODES.R) +fun WifiParsedResult.createIntent() = Intent(Settings.ACTION_WIFI_ADD_NETWORKS).apply { + putExtra( + Settings.EXTRA_WIFI_NETWORK_LIST, + arrayListOf( + WifiNetworkSuggestion.Builder() + .setSsid(ssid) + .setIsHiddenSsid(isHidden) + .apply { + password?.let { + when (networkEncryption) { + "WPA" -> { + // Per specs, Wi-Fi QR codes are only used for + // WPA2 and WPA-Mixed networks, we can safely assume + // this networks supports WPA2 + setWpa2Passphrase(it) + } + + "SAE" -> { + setWpa3Passphrase(it) + } + } + } + } + .build() + ) + ) +} + +fun WifiParsedResult.createTextClassification(context: Context) = TextClassification.Builder() + .setText(ssid) + .setEntityType(TextClassifier.TYPE_OTHER, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_network_wifi, + R.string.qr_wifi_title, + R.string.qr_wifi_content_description, + createIntent() + ) + ) + } + } + .build() diff --git a/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java b/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java index 3ef1ff6bd..f70b262d6 100644 --- a/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java +++ b/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java @@ -95,6 +95,14 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import com.google.zxing.BinaryBitmap; +import com.google.zxing.LuminanceSource; +import com.google.zxing.MultiFormatReader; +import com.google.zxing.Reader; +import com.google.zxing.Result; +import com.google.zxing.common.HybridBinarizer; +import com.google.zxing.RGBLuminanceSource; + import foundation.e.camera.R; /** This class was originally named due to encapsulating the camera preview, @@ -1105,6 +1113,38 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu @Override public void onSurfaceTextureUpdated(@NonNull SurfaceTexture arg0) { refreshPreviewBitmap(); + + if (isQRCode()) { + enablePreviewBitmap(); + TextureView textureView = (TextureView) this.cameraSurface; + Bitmap bitmap = textureView.getBitmap(preview_bitmap); + + if (bitmap!=null) { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + int[] pixels = new int[width * height]; + bitmap.getPixels(pixels, 0, width, 0, 0, width, height); + + LuminanceSource source = new RGBLuminanceSource(width, height, pixels); + BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source)); + Reader reader = new MultiFormatReader(); + + try { + Result result = reader.decode(binaryBitmap); + String qrcodeContent = result.getText(); + MainActivity mActivity = (MainActivity) this.getContext(); + if (MyDebug.LOG) + Log.d(TAG, "Find QRCode qrcodeContent="+qrcodeContent ); + mActivity.qrImageAnalyzer.showQrDialog(result); + } catch (Exception e) { + // K1ZFP TODO Error 2 + } + } else { + // K1ZFP TODO Error 1 + } + } else { + disablePreviewBitmap(); + } } private void configureTransform() { diff --git a/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt b/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt new file mode 100644 index 000000000..4c85315ce --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt @@ -0,0 +1,226 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.qr + +import android.app.Activity +import android.app.KeyguardManager +import android.app.PendingIntent +import android.content.ClipData +import android.content.ClipDescription +import android.content.ClipboardManager +import android.content.Intent +import android.os.Build +import android.text.method.LinkMovementMethod +import android.view.textclassifier.TextClassificationManager +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import android.widget.Toast +import androidx.appcompat.content.res.AppCompatResources +import androidx.appcompat.widget.LinearLayoutCompat.LayoutParams +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageProxy +import androidx.cardview.widget.CardView +import androidx.core.graphics.drawable.DrawableCompat +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.button.MaterialButton +import com.google.zxing.BinaryBitmap +import com.google.zxing.MultiFormatReader +import com.google.zxing.Result +import com.google.zxing.common.HybridBinarizer +import foundation.e.camera.R +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import net.sourceforge.opencamera.ext.* +import kotlin.reflect.cast + +class QrImageAnalyzer(private val activity: Activity, private val scope: CoroutineScope) : + ImageAnalysis.Analyzer { + // Views + private val bottomSheetDialog by lazy { + BottomSheetDialog(activity).apply { + setContentView(R.layout.qr_bottom_sheet_dialog) + } + } + private val bottomSheetDialogCardView by lazy { + bottomSheetDialog.findViewById(R.id.cardView)!! + } + private val bottomSheetDialogTitle by lazy { + bottomSheetDialog.findViewById(R.id.title)!! + } + private val bottomSheetDialogData by lazy { + bottomSheetDialog.findViewById(R.id.data)!! + } + private val bottomSheetDialogIcon by lazy { + bottomSheetDialog.findViewById(R.id.icon)!! + } + private val bottomSheetDialogCopy by lazy { + bottomSheetDialog.findViewById(R.id.copy)!! + } + private val bottomSheetDialogShare by lazy { + bottomSheetDialog.findViewById(R.id.share)!! + } + private val bottomSheetDialogActionsLayout by lazy { + bottomSheetDialog.findViewById(R.id.actionsLayout)!! + } + + // System services + private val clipboardManager by lazy { activity.getSystemService(ClipboardManager::class.java) } + private val keyguardManager by lazy { activity.getSystemService(KeyguardManager::class.java) } + private val textClassificationManager by lazy { + activity.getSystemService(TextClassificationManager::class.java) + } + + // QR + private val reader by lazy { MultiFormatReader() } + + private val qrTextClassifier by lazy { + QrTextClassifier(activity, textClassificationManager.textClassifier) + } + + override fun analyze(image: ImageProxy) { + image.use { + val source = image.planarYUVLuminanceSource + + val result = runCatching { + reader.decodeWithState(BinaryBitmap(HybridBinarizer(source))) + }.getOrNull() ?: runCatching { + reader.decodeWithState(BinaryBitmap(HybridBinarizer(source.invert()))) + }.getOrNull() + + result?.let { + showQrDialog(it) + } + + reader.reset() + } + } + + public fun showQrDialog(result: Result) { + scope.launch(Dispatchers.Main) { + if (bottomSheetDialog.isShowing) { + return@launch + } + + val text = result.text ?: return@launch + bottomSheetDialogData.text = text + + // Classify message + val textClassification = withContext(Dispatchers.IO) { + qrTextClassifier.classifyText(result) + } + + bottomSheetDialogData.text = textClassification.text + bottomSheetDialogActionsLayout.removeAllViews() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && + textClassification.actions.isNotEmpty() + ) { + with(textClassification.actions[0]) { + bottomSheetDialogCardView.setOnClickListener { + try { + actionIntent.send() + } catch (e: PendingIntent.CanceledException) { + Toast.makeText( + activity, + R.string.qr_no_app_available_for_action, + Toast.LENGTH_SHORT + ).show() + } + } + bottomSheetDialogCardView.contentDescription = contentDescription + bottomSheetDialogData.movementMethod = null + bottomSheetDialogTitle.text = title + bottomSheetDialogIcon.setImageIcon(icon) + } + for (action in textClassification.actions.drop(1)) { + bottomSheetDialogActionsLayout.addView(inflateButton().apply { + setOnClickListener { + try { + action.actionIntent.send() + } catch (e: PendingIntent.CanceledException) { + Toast.makeText( + activity, + R.string.qr_no_app_available_for_action, + Toast.LENGTH_SHORT + ).show() + } + } + contentDescription = action.contentDescription + this.text = action.title + withContext(Dispatchers.IO) { + val drawable = action.icon.loadDrawable(activity)!! + drawable.setBounds(0, 0, 15.px, 15.px) + withContext(Dispatchers.Main) { + setCompoundDrawables( + drawable, null, null, null + ) + } + } + }) + } + } else { + bottomSheetDialogCardView.setOnClickListener {} + bottomSheetDialogTitle.text = activity.resources.getText(R.string.qr_text) + bottomSheetDialogIcon.setImageDrawable(AppCompatResources.getDrawable( + activity, R.drawable.ic_text_snippet + )?.let { + DrawableCompat.wrap(it.mutate()).apply { + DrawableCompat.setTint( + this, activity.getThemeColor( + com.google.android.material.R.attr.colorOnBackground + ) + ) + } + }) + } + + // Make links clickable if not on locked keyguard + bottomSheetDialogData.movementMethod = + if (!keyguardManager.isKeyguardLocked) LinkMovementMethod.getInstance() + else null + + // Set buttons + bottomSheetDialogCopy.setOnClickListener { + clipboardManager.setPrimaryClip( + ClipData.newPlainText( + "", text + ) + ) + } + + bottomSheetDialogShare.setOnClickListener { + activity.startActivity( + Intent.createChooser( + Intent().apply { + action = Intent.ACTION_SEND + type = ClipDescription.MIMETYPE_TEXT_PLAIN + putExtra( + Intent.EXTRA_TEXT, result.text + ) + }, + activity.getString(R.string.abc_shareactionprovider_share_with) + ) + ) + } + + // Show dialog + bottomSheetDialog.show() + } + } + + private fun inflateButton() = MaterialButton::class.cast( + activity.layoutInflater.inflate( + R.layout.qr_bottom_sheet_action_button, + bottomSheetDialogActionsLayout, + false + ) + ).apply { + layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) + } +} diff --git a/app/src/main/java/net/sourceforge/opencamera/qr/QrTextClassifier.kt b/app/src/main/java/net/sourceforge/opencamera/qr/QrTextClassifier.kt new file mode 100644 index 000000000..e51772bf4 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/qr/QrTextClassifier.kt @@ -0,0 +1,100 @@ +/* + * SPDX-FileCopyrightText: 2023 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.qr + +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.net.wifi.WifiManager +import android.os.Build +import android.os.LocaleList +import android.provider.Settings +import android.text.SpannableString +import android.view.textclassifier.TextClassification +import android.view.textclassifier.TextClassifier +import com.google.zxing.Result +import com.google.zxing.client.result.ResultParser +import com.google.zxing.client.result.URIParsedResult +import foundation.e.camera.R +import net.sourceforge.opencamera.ext.* +import kotlin.reflect.safeCast + +class QrTextClassifier( + private val context: Context, private val textClassifier: TextClassifier +) { + private val wifiManager by lazy { + runCatching { context.getSystemService(WifiManager::class.java) }.getOrNull() + } + + fun classifyText(result: Result): TextClassification { + // Try with ZXing parser + val parsedResult = ResultParser.parseResult(result) + parsedResult?.createTextClassification(context)?.let { + return it + } + + // We handle URIParsedResult here + val text = URIParsedResult::class.safeCast(parsedResult)?.uri ?: result.text + + // Try parsing it as a Uri + Uri.parse(text.toString()).let { uri -> + when (uri.scheme?.lowercase()) { + // Wi-Fi DPP + SCHEME_DPP -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && + wifiManager?.isEasyConnectSupported == true + ) { + return TextClassification.Builder() + .setText(context.getString(R.string.qr_dpp_description)) + .setEntityType(TextClassifier.TYPE_OTHER, 1.0f) + .addAction( + RemoteAction::class.build( + context, + R.drawable.ic_network_wifi, + R.string.qr_dpp_title, + R.string.qr_dpp_description, + Intent(Settings.ACTION_PROCESS_WIFI_EASY_CONNECT_URI).apply { + data = uri + } + ) + ) + .build() + } + + SCHEME_FIDO -> return TextClassification.Builder() + .setText(context.getString(R.string.qr_fido_content_description)) + .setEntityType(TextClassifier.TYPE_OTHER, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_passkey, + R.string.qr_fido_title, + R.string.qr_fido_content_description, + Intent(Intent.ACTION_VIEW).apply { + data = uri + } + ) + ) + } + } + .build() + } + } + + // Let Android classify it + val spannableString = SpannableString(text) + return textClassifier.classifyText( + spannableString, 0, spannableString.length, LocaleList.getDefault() + ) + } + + companion object { + private const val SCHEME_DPP = "dpp" + private const val SCHEME_FIDO = "fido" + } +} diff --git a/app/src/main/res/layout/qr_bottom_sheet_action_button.xml b/app/src/main/res/layout/qr_bottom_sheet_action_button.xml new file mode 100644 index 000000000..6154163e1 --- /dev/null +++ b/app/src/main/res/layout/qr_bottom_sheet_action_button.xml @@ -0,0 +1,20 @@ + + + diff --git a/app/src/main/res/layout/qr_bottom_sheet_dialog.xml b/app/src/main/res/layout/qr_bottom_sheet_dialog.xml new file mode 100644 index 000000000..c0b491489 --- /dev/null +++ b/app/src/main/res/layout/qr_bottom_sheet_dialog.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 013b4e094..761c80c43 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -17,4 +17,12 @@ @color/e_disabled_color_light @color/e_disabled_color_dark #E5000000 + + + #FFFFFF + #E5000000 + #8BC34A + #3F51B5 + #CB000000 + -- GitLab From f1b2f6e00fbd4530160a6fc4f8d68fa795b40231 Mon Sep 17 00:00:00 2001 From: frankpreel Date: Fri, 5 Apr 2024 14:45:38 +0200 Subject: [PATCH 11/19] Text resources associated with the QRCode scanning process (FR+EN) --- app/src/main/res/values-fr/strings.xml | 30 ++++++++++++++++++++++++++ app/src/main/res/values/strings.xml | 30 ++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index c0ab62b99..4b806de31 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -933,4 +933,34 @@ Extension : Bokeh X-Bokeh QR Code + Ajouter un contact + Ajouter un contact + Ajouter des évènements au calendrier + Ajouter cet évènement au calendrier + Ouvrir cette localisation + Ouvrir cette localisation + Envoyer un nouveau couriel + Composer un nouveau courriel pour les courriels spécifiés + Regarder cet ISBN + Recherchez cet ISBN sur isbnsearch.org + Rechercher un produit + Consulter le code-barres de l\'ID de ce produit + Envoyer un nouveau SMS + Envoyer un nouveau SMS aux destinataires spécifiés + Appeler le numéro de téléphone + Appeler le numéro de téléphone + Rechercher VIN + Recherche du numéro d\'identification du véhicule (VIN) + Se connecter à ce réseau Wi-Fi + Ajouter ce réseau Wi-Fi à la liste des réseaux connus et y connecter l\'appareil + Icône + Partager + Copier vers le presse papier + "Pas d'application disponible pour prendre en compte cette action " + Texte + Configurer cet appareil + Wi-Fi Easy Connect™ (DPP) + Manipuler ce code QR FIDO + Utiliser le mot de passe + Partager avec \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d1b828e50..a9737d854 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1102,4 +1102,34 @@ LENS QRCode + Add contact + Add contact + Add event to calendar + Add this event to the calendar + Open this location + Open this location + Send a new email + Compose a new email to the specified emails + Lookup this ISBN + Search this ISBN on isbnsearch.org + Lookup product + Lookup this product ID barcode + Send a new SMS + Send a new SMS to the specified recipients + Call phone number + Call the phone number + Lookup VIN + Lookup this Vehicle Identification Number (VIN) + Connect to this Wi-Fi network + Add this Wi-Fi network to the list of known networks and connect the device to it + Icon + Share + Copy to clipboard + No app available to handle this action + Text + Configure this device + Wi-Fi Easy Connect™ (DPP) + Handle this FIDO QR code + Use passkey + Share with -- GitLab From cdd4e54bbe5d541cd0a7746d9f3dc343c429264f Mon Sep 17 00:00:00 2001 From: frankpreel Date: Wed, 10 Apr 2024 11:20:18 +0200 Subject: [PATCH 12/19] Remove Analyzer as the analyze process is performed by the Preview class --- .../opencamera/qr/QrImageAnalyzer.kt | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt b/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt index 4c85315ce..8206bf297 100644 --- a/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt +++ b/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt @@ -12,8 +12,10 @@ import android.content.ClipData import android.content.ClipDescription import android.content.ClipboardManager import android.content.Intent +import android.content.res.Configuration import android.os.Build import android.text.method.LinkMovementMethod +import android.view.View import android.view.textclassifier.TextClassificationManager import android.widget.ImageButton import android.widget.ImageView @@ -26,6 +28,7 @@ import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageProxy import androidx.cardview.widget.CardView import androidx.core.graphics.drawable.DrawableCompat +import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.button.MaterialButton import com.google.zxing.BinaryBitmap @@ -40,14 +43,15 @@ import kotlinx.coroutines.withContext import net.sourceforge.opencamera.ext.* import kotlin.reflect.cast -class QrImageAnalyzer(private val activity: Activity, private val scope: CoroutineScope) : - ImageAnalysis.Analyzer { +class QrImageAnalyzer(private val activity: Activity, private val scope: CoroutineScope) { + // Views private val bottomSheetDialog by lazy { BottomSheetDialog(activity).apply { setContentView(R.layout.qr_bottom_sheet_dialog) } } + private val bottomSheetDialogCardView by lazy { bottomSheetDialog.findViewById(R.id.cardView)!! } @@ -84,30 +88,14 @@ class QrImageAnalyzer(private val activity: Activity, private val scope: Corouti QrTextClassifier(activity, textClassificationManager.textClassifier) } - override fun analyze(image: ImageProxy) { - image.use { - val source = image.planarYUVLuminanceSource - - val result = runCatching { - reader.decodeWithState(BinaryBitmap(HybridBinarizer(source))) - }.getOrNull() ?: runCatching { - reader.decodeWithState(BinaryBitmap(HybridBinarizer(source.invert()))) - }.getOrNull() - - result?.let { - showQrDialog(it) - } - - reader.reset() - } - } - public fun showQrDialog(result: Result) { scope.launch(Dispatchers.Main) { if (bottomSheetDialog.isShowing) { return@launch } + + val text = result.text ?: return@launch bottomSheetDialogData.text = text @@ -211,6 +199,7 @@ class QrImageAnalyzer(private val activity: Activity, private val scope: Corouti // Show dialog bottomSheetDialog.show() + } } @@ -223,4 +212,5 @@ class QrImageAnalyzer(private val activity: Activity, private val scope: Corouti ).apply { layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) } + } -- GitLab From cae47354dfe456d7e0e1aaf898afa6e62ed00afa Mon Sep 17 00:00:00 2001 From: frankpreel Date: Wed, 10 Apr 2024 11:20:39 +0200 Subject: [PATCH 13/19] Real QRCode icon --- .../main/res/drawable/ic_switch_qrcode.xml | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/app/src/main/res/drawable/ic_switch_qrcode.xml b/app/src/main/res/drawable/ic_switch_qrcode.xml index e42783ba6..8b31106f9 100644 --- a/app/src/main/res/drawable/ic_switch_qrcode.xml +++ b/app/src/main/res/drawable/ic_switch_qrcode.xml @@ -3,14 +3,15 @@ android:height="37dp" android:viewportWidth="37" android:viewportHeight="37"> - - - - - + + + + + -- GitLab From 7a1b83d549fc95b1002a8f5e11071b2504cf8fd2 Mon Sep 17 00:00:00 2001 From: frankpreel Date: Wed, 10 Apr 2024 11:24:10 +0200 Subject: [PATCH 14/19] Lock the orientation in Portrait mode --- .../net/sourceforge/opencamera/qr/QrImageAnalyzer.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt b/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt index 8206bf297..a4f82a666 100644 --- a/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt +++ b/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt @@ -12,6 +12,7 @@ import android.content.ClipData import android.content.ClipDescription import android.content.ClipboardManager import android.content.Intent +import android.content.pm.ActivityInfo import android.content.res.Configuration import android.os.Build import android.text.method.LinkMovementMethod @@ -94,8 +95,6 @@ class QrImageAnalyzer(private val activity: Activity, private val scope: Corouti return@launch } - - val text = result.text ?: return@launch bottomSheetDialogData.text = text @@ -198,8 +197,14 @@ class QrImageAnalyzer(private val activity: Activity, private val scope: Corouti } // Show dialog + activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + bottomSheetDialog.show() + bottomSheetDialog.setOnDismissListener { + activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + } + } } -- GitLab From 9dd518e0d7723e06f2e7b5dc8314b053b6fc609b Mon Sep 17 00:00:00 2001 From: frankpreel Date: Wed, 10 Apr 2024 11:24:58 +0200 Subject: [PATCH 15/19] Remove unused --- .../java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt b/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt index a4f82a666..87d8f79d9 100644 --- a/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt +++ b/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt @@ -13,10 +13,8 @@ import android.content.ClipDescription import android.content.ClipboardManager import android.content.Intent import android.content.pm.ActivityInfo -import android.content.res.Configuration import android.os.Build import android.text.method.LinkMovementMethod -import android.view.View import android.view.textclassifier.TextClassificationManager import android.widget.ImageButton import android.widget.ImageView @@ -25,17 +23,12 @@ import android.widget.TextView import android.widget.Toast import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.widget.LinearLayoutCompat.LayoutParams -import androidx.camera.core.ImageAnalysis -import androidx.camera.core.ImageProxy import androidx.cardview.widget.CardView import androidx.core.graphics.drawable.DrawableCompat -import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.button.MaterialButton -import com.google.zxing.BinaryBitmap import com.google.zxing.MultiFormatReader import com.google.zxing.Result -import com.google.zxing.common.HybridBinarizer import foundation.e.camera.R import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -- GitLab From ff09cae4266cccd93ce673e52ba632e739957877 Mon Sep 17 00:00:00 2001 From: frankpreel Date: Wed, 10 Apr 2024 11:25:34 +0200 Subject: [PATCH 16/19] Fix dimmed screen preview when leaving the QRCode --- .../main/java/net/sourceforge/opencamera/preview/Preview.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java b/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java index f70b262d6..f142aecb4 100644 --- a/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java +++ b/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java @@ -4777,7 +4777,6 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu } this.functionalMode = FunctionalMode.QRCODE; } else if (this.is_qrcode()) { - // K1ZFP TODO Stop the qrcode // this.functionalMode = FunctionalMode.PHOTO; } else if (this.is_photo()) { if (this.isOnTimer()) { @@ -4796,6 +4795,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu if (is_qrcode() != old_is_qrcode) { applicationInterface.setVideoPref(false); + this.reopenCamera(); } else if (is_video() != old_is_video) { setFocusPref(false); // first restore the saved focus for the new photo/video mode; don't do autofocus, as it'll be cancelled when restarting preview -- GitLab From 5b8c65624ea6e19e79e58f37b8fc14c2fa9b53a4 Mon Sep 17 00:00:00 2001 From: frankpreel Date: Thu, 11 Apr 2024 09:55:19 +0200 Subject: [PATCH 17/19] Automatic camera switch in QRCode mode --- .../sourceforge/opencamera/MainActivity.java | 39 +++++++++++++++++++ .../opencamera/preview/Preview.java | 5 +++ 2 files changed, 44 insertions(+) diff --git a/app/src/main/java/net/sourceforge/opencamera/MainActivity.java b/app/src/main/java/net/sourceforge/opencamera/MainActivity.java index d1d7b827a..692599dee 100644 --- a/app/src/main/java/net/sourceforge/opencamera/MainActivity.java +++ b/app/src/main/java/net/sourceforge/opencamera/MainActivity.java @@ -2682,6 +2682,45 @@ public class MainActivity extends AppCompatActivity { } } + /* getBetterQRCodeCameraID() + Returns the best camera ID, based on the fact that it's probably the first rear camera available. + The user always has the option of selecting with the lens switcher in case the choice is wrong. + Returns -1 if no camera available. In that case we do *NOT* trig any switch. + */ + public int getBetterQRCodeCameraID() { + int best_qrcode_camera = -1; + if( MyDebug.LOG ) + Log.d(TAG, "getBetterQRCodeCameraID"); + if( !isMultiCamEnabled() ) { + Log.e(TAG, "getBetterQRCodeCameraID switch multi camera icon shouldn't have been visible"); + return best_qrcode_camera; + } + if( preview.isOpeningCamera() ) { + if( MyDebug.LOG ) + Log.d(TAG, "getBetterQRCodeCameraID already opening camera in background thread"); + return best_qrcode_camera; + } + if( this.preview.canSwitchCamera() ) { + try { + CameraManager _cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); + for (String _cameraId : _cameraManager.getCameraIdList()) { + CameraCharacteristics characteristics = _cameraManager.getCameraCharacteristics(_cameraId); + Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); + if (facing != null && facing == CameraCharacteristics.LENS_FACING_BACK) { + best_qrcode_camera = Integer.parseInt(_cameraId); + if( MyDebug.LOG ) + Log.d(TAG, "best_qrcode_camera ="+best_qrcode_camera); + break; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return best_qrcode_camera; + } + + private void updateMultiCameraIcon() { Button multiCameraButton = findViewById(R.id.switch_multi_camera); diff --git a/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java b/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java index f142aecb4..e6f72256f 100644 --- a/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java +++ b/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java @@ -4776,6 +4776,11 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu stopVideo(false); } this.functionalMode = FunctionalMode.QRCODE; + int qrcodeCamId = ((MainActivity)getContext()).getBetterQRCodeCameraID(); + if (qrcodeCamId >= 0) { + applicationInterface.setCameraIdPref(qrcodeCamId); + //userSwitchToCamera(qrcodeCamId, true); + } } else if (this.is_qrcode()) { this.functionalMode = FunctionalMode.PHOTO; } else if (this.is_photo()) { -- GitLab From b619dc1f549e631463f024e64e127aed93eb11a9 Mon Sep 17 00:00:00 2001 From: althafvly Date: Fri, 19 Apr 2024 14:34:56 +0530 Subject: [PATCH 18/19] camera: Import only required --- .../main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt | 3 ++- .../java/net/sourceforge/opencamera/qr/QrTextClassifier.kt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt b/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt index 87d8f79d9..47273a370 100644 --- a/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt +++ b/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt @@ -34,7 +34,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import net.sourceforge.opencamera.ext.* +import net.sourceforge.opencamera.ext.getThemeColor +import net.sourceforge.opencamera.ext.px import kotlin.reflect.cast class QrImageAnalyzer(private val activity: Activity, private val scope: CoroutineScope) { diff --git a/app/src/main/java/net/sourceforge/opencamera/qr/QrTextClassifier.kt b/app/src/main/java/net/sourceforge/opencamera/qr/QrTextClassifier.kt index e51772bf4..89fc85169 100644 --- a/app/src/main/java/net/sourceforge/opencamera/qr/QrTextClassifier.kt +++ b/app/src/main/java/net/sourceforge/opencamera/qr/QrTextClassifier.kt @@ -20,7 +20,8 @@ import com.google.zxing.Result import com.google.zxing.client.result.ResultParser import com.google.zxing.client.result.URIParsedResult import foundation.e.camera.R -import net.sourceforge.opencamera.ext.* +import net.sourceforge.opencamera.ext.build +import net.sourceforge.opencamera.ext.createTextClassification import kotlin.reflect.safeCast class QrTextClassifier( -- GitLab From b3ca4df889859e7808fd0b1b3c8a95d9fde5df96 Mon Sep 17 00:00:00 2001 From: althafvly Date: Fri, 19 Apr 2024 14:37:00 +0530 Subject: [PATCH 19/19] minor changes --- .../opencamera/ext/AddressBookParsedResult.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/AddressBookParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/AddressBookParsedResult.kt index 49054372c..58bdeee36 100644 --- a/app/src/main/java/net/sourceforge/opencamera/ext/AddressBookParsedResult.kt +++ b/app/src/main/java/net/sourceforge/opencamera/ext/AddressBookParsedResult.kt @@ -29,7 +29,7 @@ fun AddressBookParsedResult.createIntent() = Intent( phoneNumbers?.let { phoneNumbers -> val phoneTypes = phoneTypes ?: arrayOf() - for ((i, keys) in listOf( + for ((key, keys) in listOf( listOf( ContactsContract.Intents.Insert.PHONE, ContactsContract.Intents.Insert.PHONE_TYPE, @@ -43,9 +43,9 @@ fun AddressBookParsedResult.createIntent() = Intent( ContactsContract.Intents.Insert.TERTIARY_PHONE_TYPE, ), ).withIndex()) { - phoneNumbers.getOrNull(i)?.let { phone -> + phoneNumbers.getOrNull(key)?.let { phone -> putExtra(keys.first(), phone) - phoneTypes.getOrNull(i)?.let { + phoneTypes.getOrNull(key)?.let { putExtra(keys.last(), it) } } @@ -55,7 +55,7 @@ fun AddressBookParsedResult.createIntent() = Intent( emails?.let { emails -> val emailTypes = emailTypes ?: arrayOf() - for ((i, keys) in listOf( + for ((key, keys) in listOf( listOf( ContactsContract.Intents.Insert.EMAIL, ContactsContract.Intents.Insert.EMAIL_TYPE, @@ -69,9 +69,9 @@ fun AddressBookParsedResult.createIntent() = Intent( ContactsContract.Intents.Insert.TERTIARY_EMAIL_TYPE, ), ).withIndex()) { - emails.getOrNull(i)?.let { phone -> + emails.getOrNull(key)?.let { phone -> putExtra(keys.first(), phone) - emailTypes.getOrNull(i)?.let { + emailTypes.getOrNull(key)?.let { putExtra(keys.last(), it) } } @@ -89,15 +89,15 @@ fun AddressBookParsedResult.createIntent() = Intent( addresses?.let { emails -> val addressTypes = addressTypes ?: arrayOf() - for ((i, keys) in listOf( + for ((key, keys) in listOf( listOf( ContactsContract.Intents.Insert.POSTAL, ContactsContract.Intents.Insert.POSTAL_TYPE, ), ).withIndex()) { - emails.getOrNull(i)?.let { phone -> + emails.getOrNull(key)?.let { phone -> putExtra(keys.first(), phone) - addressTypes.getOrNull(i)?.let { + addressTypes.getOrNull(key)?.let { putExtra(keys.last(), it) } } -- GitLab