From 23f4bed96969579f6f79fdc2e2d522e77a890526 Mon Sep 17 00:00:00 2001 From: s0llvan <178677095+s0llvan@users.noreply.github.com> Date: Fri, 27 Sep 2024 18:34:48 +0200 Subject: [PATCH 01/14] fixed: background video recording --- .../com/fadcam/ExampleInstrumentedTest.java | 6 +- app/src/main/AndroidManifest.xml | 2 - app/src/main/java/com/fadcam/Constantes.java | 13 - app/src/main/java/com/fadcam/Constants.java | 33 + .../main/java/com/fadcam/MainActivity.java | 27 +- .../java/com/fadcam/RecordingService.java | 654 +++++++++- .../java/com/fadcam/ui/AboutFragment.java | 22 - .../main/java/com/fadcam/ui/HomeFragment.java | 1076 +++++------------ .../java/com/fadcam/ui/LocationHelper.java | 2 +- .../java/com/fadcam/ui/RecordsFragment.java | 10 +- .../java/com/fadcam/ui/SettingsFragment.java | 122 +- .../com/fadcam/ui/VideoPlayerActivity.java | 3 +- .../java/com/fadcam/ui/WebViewActivity.java | 1 + app/src/main/res/layout/dialog_rename.xml | 1 - app/src/main/res/layout/fragment_home.xml | 5 +- app/src/main/res/values-fr/strings.xml | 2 + app/src/main/res/values-night/themes.xml | 2 +- app/src/main/res/values/strings.xml | 2 + app/src/main/res/values/themes.xml | 2 +- app/src/main/res/xml/file_paths.xml | 2 +- .../test/java/com/fadcam/ExampleUnitTest.java | 4 +- .../android/en-US/images/monochrome-icon.svg | 3 +- 22 files changed, 1055 insertions(+), 939 deletions(-) delete mode 100644 app/src/main/java/com/fadcam/Constantes.java create mode 100644 app/src/main/java/com/fadcam/Constants.java diff --git a/app/src/androidTest/java/com/fadcam/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/fadcam/ExampleInstrumentedTest.java index 3372f2c..f270757 100644 --- a/app/src/androidTest/java/com/fadcam/ExampleInstrumentedTest.java +++ b/app/src/androidTest/java/com/fadcam/ExampleInstrumentedTest.java @@ -1,15 +1,15 @@ package com.fadcam; +import static org.junit.Assert.assertEquals; + import android.content.Context; -import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Test; import org.junit.runner.RunWith; -import static org.junit.Assert.*; - /** * Instrumented test, which will execute on an Android device. * diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fc8d9c4..73daeb8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -91,8 +91,6 @@ android:exported="false" android:foregroundServiceType="camera|microphone" /> - - \ No newline at end of file diff --git a/app/src/main/java/com/fadcam/Constantes.java b/app/src/main/java/com/fadcam/Constantes.java deleted file mode 100644 index 51db814..0000000 --- a/app/src/main/java/com/fadcam/Constantes.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.fadcam; - -public abstract class Constantes { - - public static final int DEFAULT_VIDEO_FRAMERATE = 30; - - public static final String PREF_VIDEO_QUALITY = "video_quality"; - public static final String PREF_VIDEO_FRAMERATE = "video_framerate"; - public static final String PREF_CAMERA_SELECTION = "camera_selection"; - - public static final String CAMERA_FRONT = "front"; - public static final String CAMERA_BACK = "back"; -} diff --git a/app/src/main/java/com/fadcam/Constants.java b/app/src/main/java/com/fadcam/Constants.java new file mode 100644 index 0000000..affe6d7 --- /dev/null +++ b/app/src/main/java/com/fadcam/Constants.java @@ -0,0 +1,33 @@ +package com.fadcam; + +public abstract class Constants { + + public static final int DEFAULT_VIDEO_FRAME_RATE = 30; + + public static final String PREFS_NAME = "app_prefs"; + public static final String LANGUAGE_KEY = "language"; + + public static final String PREF_VIDEO_QUALITY = "video_quality"; + public static final String PREF_VIDEO_FRAME_RATE = "video_framerate"; + public static final String PREF_CAMERA_SELECTION = "camera_selection"; + public static final String PREF_IS_PREVIEW_ENABLED = "isPreviewEnabled"; + + public static final String CAMERA_FRONT = "front"; + public static final String CAMERA_BACK = "back"; + + public static final String QUALITY_SD = "SD"; + public static final String QUALITY_HD = "HD"; + public static final String QUALITY_FHD = "FHD"; + + public static final String BROADCAST_ON_RECORDING_STARTED = "com.fadcam.ON_RECORDING_STARTED"; + public static final String BROADCAST_ON_RECORDING_RESUMED = "com.fadcam.ON_RECORDING_RESUMED"; + public static final String BROADCAST_ON_RECORDING_PAUSED = "com.fadcam.ON_RECORDING_PAUSED"; + public static final String BROADCAST_ON_RECORDING_STOPPED = "com.fadcam.ON_RECORDING_STOPPED"; + public static final String BROADCAST_ON_APPLICATION_STOPPED = "com.fadcam.ON_APPLICATION_STOPPED"; + + public static final String INTENT_ACTION_STOP_RECORDING = "ACTION_STOP_RECORDING"; + public static final String INTENT_ACTION_CHANGE_SURFACE = "ACTION_CHANGE_SURFACE"; + public static final String INTENT_ACTION_RESUME_RECORDING = "ACTION_RESUME_RECORDING"; + public static final String INTENT_ACTION_PAUSE_RECORDING = "ACTION_PAUSE_RECORDING"; + public static final String INTENT_ACTION_START_RECORDING = "ACTION_START_RECORDING"; +} diff --git a/app/src/main/java/com/fadcam/MainActivity.java b/app/src/main/java/com/fadcam/MainActivity.java index 98495e8..9dba9a7 100644 --- a/app/src/main/java/com/fadcam/MainActivity.java +++ b/app/src/main/java/com/fadcam/MainActivity.java @@ -19,16 +19,13 @@ public class MainActivity extends AppCompatActivity { private ViewPager2 viewPager; private BottomNavigationView bottomNavigationView; - private static final String PREFS_NAME = "app_prefs"; - private static final String LANGUAGE_KEY = "language"; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Load and apply the saved language preference before anything else - SharedPreferences prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); - String savedLanguageCode = prefs.getString(LANGUAGE_KEY, Locale.getDefault().getLanguage()); + SharedPreferences prefs = getSharedPreferences(Constants.PREFS_NAME, Context.MODE_PRIVATE); + String savedLanguageCode = prefs.getString(Constants.LANGUAGE_KEY, Locale.getDefault().getLanguage()); applyLanguage(savedLanguageCode); // Apply the language preference @@ -83,8 +80,6 @@ public void onPageSelected(int position) { File osmdroidTileCache = new File(osmdroidBasePath, "tiles"); org.osmdroid.config.Configuration.getInstance().setOsmdroidBasePath(osmdroidBasePath); org.osmdroid.config.Configuration.getInstance().setOsmdroidTileCache(osmdroidTileCache); - - } public void applyLanguage(String languageCode) { @@ -98,12 +93,8 @@ public void applyLanguage(String languageCode) { Locale.setDefault(locale); android.content.res.Configuration config = new android.content.res.Configuration(); - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { - config.setLocale(locale); - getApplicationContext().createConfigurationContext(config); - } else { - config.locale = locale; - } + config.setLocale(locale); + getApplicationContext().createConfigurationContext(config); getResources().updateConfiguration(config, getResources().getDisplayMetrics()); @@ -113,4 +104,14 @@ public void applyLanguage(String languageCode) { Log.d("MainActivity", "Language is already set to " + languageCode + "; no need to change."); } } + + @Override + protected void onStart() { + super.onStart(); + } + + @Override + protected void onStop() { + super.onStop(); + } } \ No newline at end of file diff --git a/app/src/main/java/com/fadcam/RecordingService.java b/app/src/main/java/com/fadcam/RecordingService.java index a5799cd..bbbb068 100644 --- a/app/src/main/java/com/fadcam/RecordingService.java +++ b/app/src/main/java/com/fadcam/RecordingService.java @@ -1,11 +1,17 @@ package com.fadcam; +import static android.hardware.camera2.CameraDevice.*; + import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; +import android.app.PendingIntent; import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; import android.content.Intent; -import android.graphics.SurfaceTexture; +import android.content.IntentFilter; +import android.content.SharedPreferences; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; @@ -13,44 +19,117 @@ import android.hardware.camera2.CaptureRequest; import android.media.MediaRecorder; import android.os.Build; +import android.os.Environment; import android.os.IBinder; +import android.os.SystemClock; import android.util.Log; +import android.util.Range; import android.view.Surface; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; +import com.arthenica.ffmpegkit.FFmpegKit; +import com.fadcam.ui.LocationHelper; +import com.fadcam.ui.RecordsAdapter; + import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Arrays; import java.util.Date; +import java.util.List; import java.util.Locale; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; public class RecordingService extends Service { + private static final int NOTIFICATION_ID = 1; private static final String CHANNEL_ID = "RecordingServiceChannel"; - private static final int NOTIFICATION_ID = 1; // Use a constant for the notification ID private static final String TAG = "RecordingService"; + + private RecordsAdapter adapter; + + private MediaRecorder mediaRecorder; + private CameraDevice cameraDevice; + private CameraCaptureSession captureSession; + + private SharedPreferences sharedPreferences; + + private CaptureRequest.Builder captureRequestBuilder; + + private Surface previewSurface; + + private static final String PREF_LOCATION_DATA = "location_data"; + + private LocationHelper locationHelper; + + private File tempFileBeingProcessed; + private boolean isRecording = false; + private long recordingStartTime; + + private BroadcastReceiver broadcastOnApplicationStopped; + @Override public void onCreate() { super.onCreate(); + + sharedPreferences = getApplicationContext().getSharedPreferences(Constants.PREFS_NAME, Context.MODE_PRIVATE); + + locationHelper = new LocationHelper(getApplicationContext()); + + registerBroadcastOnApplicationExited(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // Android 11 and above + registerReceiver(broadcastOnApplicationStopped, new IntentFilter(Constants.BROADCAST_ON_APPLICATION_STOPPED), Context.RECEIVER_EXPORTED); + } else { + // Below Android 11 + registerReceiver(broadcastOnApplicationStopped, new IntentFilter(Constants.BROADCAST_ON_APPLICATION_STOPPED)); + } + createNotificationChannel(); Log.d(TAG, "Service created"); } + private void registerBroadcastOnApplicationExited() { + broadcastOnApplicationStopped = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent i) + { + previewSurface = null; + createCameraPreviewSession(); + } + }; + } + @Override public int onStartCommand(Intent intent, int flags, int startId) { String action = intent.getAction(); + if (action != null) { switch (action) { - case "ACTION_START_RECORDING": + case Constants.INTENT_ACTION_START_RECORDING: + setupSurfaceTexture(intent); startRecording(); break; - case "ACTION_STOP_RECORDING": + case Constants.INTENT_ACTION_PAUSE_RECORDING: + pauseRecording(); + break; + case Constants.INTENT_ACTION_RESUME_RECORDING: + resumeRecording(); + break; + case Constants.INTENT_ACTION_CHANGE_SURFACE: + setupSurfaceTexture(intent); + createCameraPreviewSession(); + break; + case Constants.INTENT_ACTION_STOP_RECORDING: stopRecording(); break; } @@ -58,86 +137,551 @@ public int onStartCommand(Intent intent, int flags, int startId) { return START_STICKY; } - @Nullable - @Override - public IBinder onBind(Intent intent) { - return null; + private void setupSurfaceTexture(Intent intent) + { + previewSurface = intent.getParcelableExtra("SURFACE"); } @Override public void onDestroy() { super.onDestroy(); stopRecording(); + + unregisterReceiver(broadcastOnApplicationStopped); + Log.d(TAG, "Service destroyed"); } + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + + private void setupMediaRecorder() { + /* + * This method sets up the MediaRecorder for video recording. + * It creates a directory for saving videos if it doesn't exist, + * generates a timestamp-based filename, and configures the + * MediaRecorder with the appropriate settings based on the + * selected video quality (SD, HD, FHD). It reduces bitrates + * by 50% using the HEVC (H.265) encoder for efficient compression + * without significantly affecting video quality. + * + * - SD: 640x480 @ 0.5 Mbps + * - HD: 1280x720 @ 2.5 Mbps + * - FHD: 1920x1080 @ 5 Mbps + * + * It also adjusts the frame rate, sets audio settings, and configures + * the orientation based on the camera selection (front or rear). + */ + + try { + // Create directory for saving videos if it doesn't exist + File videoDir = new File(getExternalFilesDir(null), "FadCam"); + if (!videoDir.exists()) { + videoDir.mkdirs(); + } + + // Generate a timestamp-based filename for the video + String timestamp = new SimpleDateFormat("yyyyMMdd_hh_mm_ssa", Locale.getDefault()).format(new Date()); + File videoFile = new File(videoDir, "temp_" + timestamp + ".mp4"); + + // Initialize MediaRecorder + mediaRecorder = new MediaRecorder(); + mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); + mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); + mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); + mediaRecorder.setOutputFile(videoFile.getAbsolutePath()); + + // Select video quality and adjust size and bitrate + switch (getCameraQuality()) { + case Constants.QUALITY_SD: + // SD: 640x480 resolution, 0.5 Mbps (50% of original 1 Mbps) + mediaRecorder.setVideoSize(640, 480); + mediaRecorder.setVideoEncodingBitRate(500000); + break; + case Constants.QUALITY_HD: + // HD: 1280x720 resolution, 2.5 Mbps (50% of original 5 Mbps) + mediaRecorder.setVideoSize(1280, 720); + mediaRecorder.setVideoEncodingBitRate(2500000); + break; + case Constants.QUALITY_FHD: + // FHD: 1920x1080 resolution, 5 Mbps (50% of original 10 Mbps) + mediaRecorder.setVideoSize(1920, 1080); + mediaRecorder.setVideoEncodingBitRate(5000000); + break; + default: + // Default to HD settings + mediaRecorder.setVideoSize(1280, 720); + mediaRecorder.setVideoEncodingBitRate(2500000); + break; + } + + // Set frame rate and capture rate + mediaRecorder.setVideoFrameRate(getCameraFrameRate()); + mediaRecorder.setCaptureRate(getCameraFrameRate()); + + // Audio settings: high-quality audio + mediaRecorder.setAudioEncodingBitRate(384000); + mediaRecorder.setAudioSamplingRate(48000); + mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); + + // Set video encoder to HEVC (H.265) for better compression + mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.HEVC); + + // Set orientation based on camera selection + if (getCameraSelection().equals(Constants.CAMERA_FRONT)) { + mediaRecorder.setOrientationHint(270); + } else { + mediaRecorder.setOrientationHint(90); + } + + // Prepare MediaRecorder + mediaRecorder.prepare(); + + } catch (IOException e) { + Log.e(TAG, "setupMediaRecorder: Error setting up media recorder", e); + e.printStackTrace(); + } + } + + private void createCameraPreviewSession() { + + if (captureSession != null) { + captureSession.close(); + captureSession = null; + } + + try { + captureRequestBuilder = cameraDevice.createCaptureRequest(TEMPLATE_RECORD); + + List surfaces = new ArrayList<>(); + Surface recorderSurface = mediaRecorder.getSurface(); + surfaces.add(recorderSurface); + + if(previewSurface != null) { + captureRequestBuilder.addTarget(previewSurface); + surfaces.add(previewSurface); + } + + captureRequestBuilder.addTarget(recorderSurface); + + Range fpsRange = Range.create(getCameraFrameRate(), getCameraFrameRate()); + captureRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange); + + cameraDevice.createCaptureSession(surfaces, + new CameraCaptureSession.StateCallback() { + @Override + public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { + captureSession = cameraCaptureSession; + + try { + cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); + } catch (CameraAccessException e) { + Log.e(TAG, "onConfigured: Error setting repeating request", e); + e.printStackTrace(); + } + + if(!isRecording) { + recordingStartTime = SystemClock.elapsedRealtime(); + mediaRecorder.start(); + isRecording = true; + } + + broadcastOnRecordingStarted(); + + startForeground(NOTIFICATION_ID, getNotification()); + } + + @Override + public void onConfigureFailed(@NonNull CameraCaptureSession session) { + Log.e(TAG, "Échec de la configuration de la session de capture"); + } + }, null); + } catch (CameraAccessException e) { + Log.e(TAG, "Erreur lors de la création de la session de capture", e); + } + } + + private void broadcastOnRecordingStarted() { + Intent broadcastIntent = new Intent(Constants.BROADCAST_ON_RECORDING_STARTED); + broadcastIntent.putExtra("recordingStartTime", recordingStartTime); + getApplicationContext().sendBroadcast(broadcastIntent); + } + + private void broadcastOnRecordingResumed() { + Intent broadcastIntent = new Intent(Constants.BROADCAST_ON_RECORDING_RESUMED); + getApplicationContext().sendBroadcast(broadcastIntent); + } + + private void broadcastOnRecordingPaused() { + Intent broadcastIntent = new Intent(Constants.BROADCAST_ON_RECORDING_PAUSED); + getApplicationContext().sendBroadcast(broadcastIntent); + } + + private void broadcastOnRecordingStopped() { + Intent broadcastIntent = new Intent(Constants.BROADCAST_ON_RECORDING_STOPPED); + getApplicationContext().sendBroadcast(broadcastIntent); + } + + private void openCamera() { + Log.d(TAG, "openCamera: Opening camera"); + CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); + try { + String[] cameraIdList = manager.getCameraIdList(); + String cameraId = getCameraSelection().equals(Constants.CAMERA_FRONT) ? cameraIdList[1] : cameraIdList[0]; + manager.openCamera(cameraId, new StateCallback() { + @Override + public void onOpened(@NonNull CameraDevice camera) { + Log.d(TAG, "onOpened: Camera opened successfully"); + cameraDevice = camera; + createCameraPreviewSession(); + } + + @Override + public void onDisconnected(@NonNull CameraDevice camera) { + Log.w(TAG, "onDisconnected: Camera disconnected"); + cameraDevice.close(); + } + + @Override + public void onError(@NonNull CameraDevice camera, int error) { + Log.e(TAG, "onError: Camera error: " + error); + + cameraDevice.close(); + cameraDevice = null; + } + }, null); + } catch (CameraAccessException | SecurityException e) { + Log.e(TAG, "openCamera: Error opening camera", e); + e.printStackTrace(); + } + } + private void startRecording() { - Log.d(TAG, "startRecording: Initiating video recording from recording service."); - if (!isRecording) { - isRecording = true; - Log.d(TAG, "Recording started from recording service."); - - // Start foreground service - Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID) - .setContentTitle("Recording in progress") - .setContentText("Recording video") - .setSmallIcon(R.drawable.unknown_icon3) - .setPriority(NotificationCompat.PRIORITY_LOW) - .build(); - startForeground(NOTIFICATION_ID, notification); - Log.d(TAG, "Foreground service started"); - - Intent intent = new Intent("RECORDING_STATE_CHANGED"); - intent.putExtra("isRecording", true); - sendBroadcast(intent); - } else { - Log.d(TAG, "Recording already in progress"); + + setupMediaRecorder(); + + if (mediaRecorder == null) { + Log.e(TAG, "MediaRecorder is not initialized"); + return; + } + + if(cameraDevice == null) { + openCamera(); + } + + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + Log.e(TAG, "startRecordingVideo: External storage not available, cannot start recording."); } } + private void resumeRecording() + { + mediaRecorder.resume(); + + broadcastOnRecordingResumed(); + } + + private void pauseRecording() + { + mediaRecorder.pause(); + + broadcastOnRecordingPaused(); + } + private void stopRecording() { Log.d(TAG, "stopRecording: Attempting to stop recording from recording service."); - if (isRecording) { - isRecording = false; - Log.d(TAG, "Recording stopped from recording service"); - - Intent intent = new Intent("RECORDING_STATE_CHANGED"); - intent.putExtra("isRecording", false); - sendBroadcast(intent); - - stopForeground(STOP_FOREGROUND_REMOVE); // Remove notification - Log.d(TAG, "Foreground service stopped, notification should be removed"); - - // Cancel the notification explicitly - NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - if (notificationManager != null) { - notificationManager.cancel(NOTIFICATION_ID); - Log.d(TAG, "Notification with ID " + NOTIFICATION_ID + " canceled"); - } else { - Log.e(TAG, "NotificationManager is null, unable to cancel notification"); + + if (mediaRecorder != null) { + try { + mediaRecorder.stop(); + mediaRecorder.reset(); + } catch (IllegalStateException e) { + Log.e(TAG, "Erreur lors de l'arrêt de l'enregistrement", e); + } finally { + mediaRecorder.release(); + mediaRecorder = null; + Log.d(TAG, "Enregistrement arrêté"); + stopForeground(true); // Supprimez la notification } + } + + if (captureSession != null) { + captureSession.close(); + captureSession = null; + } + + if (cameraDevice != null) { + cameraDevice.close(); + cameraDevice = null; + } + + processLatestVideoFileWithWatermark(); + + isRecording = false; + + broadcastOnRecordingStopped(); + } + + private void processLatestVideoFileWithWatermark() { + File latestVideoFile = getLatestVideoFile(); + if (latestVideoFile != null) { + String inputFilePath = latestVideoFile.getAbsolutePath(); + String originalFileName = latestVideoFile.getName().replace("temp_", ""); + String outputFilePath = latestVideoFile.getParent() + "/FADCAM_" + originalFileName; + Log.d(TAG, "Watermarking: Input file path: " + inputFilePath); + Log.d(TAG, "Watermarking: Output file path: " + outputFilePath); - stopSelf(); // Stop the service - Log.d(TAG, "Service stopped"); + tempFileBeingProcessed = latestVideoFile; + addTextWatermarkToVideo(inputFilePath, outputFilePath); } else { - Log.d(TAG, "No recording to stop from recording service"); + Log.e(TAG, "No video file found."); + } + } + + private void addTextWatermarkToVideo(String inputFilePath, String outputFilePath) { + String fontPath = getFilesDir().getAbsolutePath() + "/ubuntu_regular.ttf"; + String watermarkText; + String watermarkOption = getWatermarkOption(); + + boolean isLocationEnabled = sharedPreferences.getBoolean(PREF_LOCATION_DATA, false); + String locationText = isLocationEnabled ? getLocationData() : ""; + + switch (watermarkOption) { + case "timestamp": + watermarkText = getCurrentTimestamp() + (isLocationEnabled ? locationText : ""); + break; + case "no_watermark": + String ffmpegCommandNoWatermark = String.format("-i %s -codec copy %s", inputFilePath, outputFilePath); + executeFFmpegCommand(ffmpegCommandNoWatermark); + return; + default: + watermarkText = "Captured by FadCam - " + getCurrentTimestamp() + (isLocationEnabled ? locationText : ""); + break; } + + // Convert the watermark text to English numerals + watermarkText = convertArabicNumeralsToEnglish(watermarkText); + + // Get and convert the font size to English numerals + int fontSize = getFontSizeBasedOnBitrate(); + String fontSizeStr = convertArabicNumeralsToEnglish(String.valueOf(fontSize)); + + Log.d(TAG, "Watermark Text: " + watermarkText); + Log.d(TAG, "Font Path: " + fontPath); + Log.d(TAG, "Font Size: " + fontSizeStr); + + // Construct the FFmpeg command + String ffmpegCommand = String.format( + "-i %s -vf \"drawtext=text='%s':x=10:y=10:fontsize=%s:fontcolor=white:fontfile=%s\" -q:v 0 -codec:a copy %s", + inputFilePath, watermarkText, fontSizeStr, fontPath, outputFilePath + ); + + executeFFmpegCommand(ffmpegCommand); + } + + private int getFontSizeBasedOnBitrate() { + int fontSize; + int videoBitrate = getVideoBitrate(); // Ensure this method retrieves the correct bitrate based on the selected quality + + if (videoBitrate <= 1000000) { + fontSize = 12; //SD quality + } else if (videoBitrate == 10000000) { + fontSize = 24; // FHD quality + } else { + fontSize = 16; // HD or higher quality + } + + Log.d(TAG, "Determined Font Size: " + fontSize); + return fontSize; + } + + private int getVideoBitrate() { + String selectedQuality = sharedPreferences.getString(Constants.PREF_VIDEO_QUALITY, Constants.QUALITY_HD); + int bitrate; + switch (selectedQuality) { + case Constants.QUALITY_SD: + bitrate = 1000000; // 1 Mbps + break; + case Constants.QUALITY_HD: + bitrate = 5000000; // 5 Mbps + break; + case Constants.QUALITY_FHD: + bitrate = 10000000; // 10 Mbps + break; + default: + bitrate = 5000000; // Default to HD + break; + } + Log.d(TAG, "Selected Video Bitrate: " + bitrate + " bps"); + return bitrate; + } + + private void executeFFmpegCommand(String ffmpegCommand) { + Log.d(TAG, "FFmpeg Command: " + ffmpegCommand); + FFmpegKit.executeAsync(ffmpegCommand, session -> { + if (session.getReturnCode().isSuccess()) { + Log.d(TAG, "Watermark added successfully."); + // Start monitoring temp files + startMonitoring(); + + // Notify the adapter to update the thumbnail + File latestVideo = getLatestVideoFile(); + if (latestVideo != null) { + String videoFilePath = latestVideo.getAbsolutePath(); + updateThumbnailInAdapter(videoFilePath); + } + + stopSelf(); + + } else { + Log.e(TAG, "Failed to add watermark: " + session.getFailStackTrace()); + } + }); + } + + private void startMonitoring() { + final long CHECK_INTERVAL_MS = 1000; // 1 second + + Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> { + checkAndDeleteSpecificTempFile(); + }, 0, CHECK_INTERVAL_MS, TimeUnit.MILLISECONDS); + } + + private void checkAndDeleteSpecificTempFile() { + if (tempFileBeingProcessed != null) { + String tempTimestamp = extractTimestamp(tempFileBeingProcessed.getName()); + + // Construct FADCAM_ filename with the same timestamp + String outputFilePath = tempFileBeingProcessed.getParent() + "/FADCAM_" + tempFileBeingProcessed.getName().replace("temp_", ""); + File outputFile = new File(outputFilePath); + + // Check if the FADCAM_ file exists + if (outputFile.exists()) { + // Delete temp file + if (tempFileBeingProcessed.delete()) { + Log.d(TAG, "Temp file deleted successfully."); + } else { + Log.e(TAG, "Failed to delete temp file."); + } + // Reset tempFileBeingProcessed to null after deletion + tempFileBeingProcessed = null; + } else { + // FADCAM_ file does not exist yet + Log.d(TAG, "Matching FADCAM_ file not found. Temp file remains."); + } + } + } + + private String extractTimestamp(String filename) { + // Assuming filename format is "prefix_TIMESTAMP.mp4" + // Example: "temp_20240730_01_39_26PM.mp4" + // Extracting timestamp part: "20240730_01_39_26PM" + int startIndex = filename.indexOf('_') + 1; + int endIndex = filename.lastIndexOf('.'); + return filename.substring(startIndex, endIndex); + } + + private void updateThumbnailInAdapter(String videoFilePath) { + if (adapter != null) { + adapter.notifyDataSetChanged(); // Notify adapter that data has changed + } + } + + private String getCurrentTimestamp() { + SimpleDateFormat sdf = new SimpleDateFormat("dd/MMM/yyyy hh-mm a", Locale.ENGLISH); + return convertArabicNumeralsToEnglish(sdf.format(new Date())); + } + + private String convertArabicNumeralsToEnglish(String text) { + if (text == null) return null; + return text.replaceAll("٠", "0") + .replaceAll("١", "1") + .replaceAll("٢", "2") + .replaceAll("٣", "3") + .replaceAll("٤", "4") + .replaceAll("٥", "5") + .replaceAll("٦", "6") + .replaceAll("٧", "7") + .replaceAll("٨", "8") + .replaceAll("٩", "9"); + } + + private String getWatermarkOption() { + return sharedPreferences.getString("watermark_option", "timestamp_fadcam"); + } + + private File getLatestVideoFile() { + File videoDir = new File(getExternalFilesDir(null), "FadCam"); + File[] files = videoDir.listFiles(); + if (files == null || files.length == 0) { + return null; + } + + // Sort files by last modified date + Arrays.sort(files, (f1, f2) -> Long.compare(f2.lastModified(), f1.lastModified())); + return files[0]; // Return the most recently modified file + } + + private PendingIntent createOpenAppIntent() { + Intent openAppIntent = new Intent(this, MainActivity.class); + openAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + return PendingIntent.getActivity(this, 0, openAppIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + } + + private PendingIntent createStopRecordingIntent() { + Intent stopIntent = new Intent(this, RecordingService.class); + stopIntent.setAction(Constants.INTENT_ACTION_STOP_RECORDING); + return PendingIntent.getService(this, 0, stopIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + } + + private Notification getNotification() { + NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle(getString(R.string.notification_video_recording)) + .setContentText(getString(R.string.notification_video_recording_description)) + .setSmallIcon(R.drawable.unknown_icon3) + .setContentIntent(createOpenAppIntent()) + .setAutoCancel(false) + .addAction(new NotificationCompat.Action( + R.drawable.ic_stop, + getString(R.string.button_stop), + createStopRecordingIntent())) + .setPriority(NotificationCompat.PRIORITY_LOW); + + return builder.build(); } private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationChannel serviceChannel = new NotificationChannel( + NotificationChannel channel = new NotificationChannel( CHANNEL_ID, "Recording Service Channel", - NotificationManager.IMPORTANCE_LOW - ); - NotificationManager manager = getSystemService(NotificationManager.class); - if (manager != null) { - manager.createNotificationChannel(serviceChannel); + NotificationManager.IMPORTANCE_LOW); + NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + if(manager != null) { + manager.createNotificationChannel(channel); Log.d(TAG, "Notification channel created"); } else { Log.e(TAG, "NotificationManager is null, unable to create notification channel"); } } } + + private String getLocationData() { + return locationHelper.getLocationData(); + } + + private String getCameraSelection() { + return sharedPreferences.getString(Constants.PREF_CAMERA_SELECTION, Constants.CAMERA_BACK); + } + + private String getCameraQuality() { + return sharedPreferences.getString(Constants.PREF_VIDEO_QUALITY, Constants.QUALITY_HD); + } + + private int getCameraFrameRate() { + return sharedPreferences.getInt(Constants.PREF_VIDEO_FRAME_RATE, Constants.DEFAULT_VIDEO_FRAME_RATE); + } } diff --git a/app/src/main/java/com/fadcam/ui/AboutFragment.java b/app/src/main/java/com/fadcam/ui/AboutFragment.java index 12988f0..c1a07c8 100644 --- a/app/src/main/java/com/fadcam/ui/AboutFragment.java +++ b/app/src/main/java/com/fadcam/ui/AboutFragment.java @@ -4,18 +4,11 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; -import android.app.Dialog; -import android.app.DownloadManager; -import android.content.BroadcastReceiver; -import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; -import android.os.Build; import android.os.Bundle; -import android.os.Environment; import android.text.Html; import android.text.Spanned; import android.view.LayoutInflater; @@ -25,25 +18,11 @@ import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; -import android.widget.Toast; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.core.text.HtmlCompat; import androidx.fragment.app.Fragment; -import com.fadcam.R; - -import androidx.annotation.NonNull; -import androidx.core.content.FileProvider; -import androidx.fragment.app.Fragment; - import com.fadcam.R; import com.google.android.material.button.MaterialButton; import com.google.android.material.card.MaterialCardView; @@ -54,7 +33,6 @@ import org.json.JSONObject; import java.io.BufferedReader; -import java.io.File; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; diff --git a/app/src/main/java/com/fadcam/ui/HomeFragment.java b/app/src/main/java/com/fadcam/ui/HomeFragment.java index 6d1939c..36537d2 100644 --- a/app/src/main/java/com/fadcam/ui/HomeFragment.java +++ b/app/src/main/java/com/fadcam/ui/HomeFragment.java @@ -1,27 +1,12 @@ package com.fadcam.ui; -import android.Manifest; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import android.os.PowerManager; -import android.provider.Settings; -import android.util.Log; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; -import java.util.ArrayList; -import java.util.List; - - - import android.Manifest; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -31,13 +16,7 @@ import android.content.res.AssetManager; import android.graphics.Color; import android.graphics.SurfaceTexture; -import android.hardware.camera2.CameraAccessException; -import android.hardware.camera2.CameraCaptureSession; -import android.hardware.camera2.CameraDevice; -import android.hardware.camera2.CameraManager; -import android.hardware.camera2.CaptureRequest; import android.media.CamcorderProfile; -import android.media.MediaRecorder; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -54,7 +33,6 @@ import android.text.Spanned; import android.text.format.Formatter; import android.util.Log; -import android.util.Range; import android.view.LayoutInflater; import android.view.Surface; import android.view.TextureView; @@ -71,10 +49,7 @@ import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; -import com.arthenica.ffmpegkit.ExecuteCallback; -import com.arthenica.ffmpegkit.FFmpegKit; -import com.arthenica.ffmpegkit.Session; -import com.fadcam.Constantes; +import com.fadcam.Constants; import com.fadcam.R; import com.fadcam.RecordingService; import com.google.android.material.dialog.MaterialAlertDialogBuilder; @@ -95,35 +70,22 @@ import java.util.List; import java.util.Locale; import java.util.Random; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; public class HomeFragment extends Fragment { private static final String TAG = "HomeFragment"; private static final int LOCATION_PERMISSION_REQUEST_CODE = 100; - private LocationHelper locationHelper; private long recordingStartTime; private long videoBitrate; - private RecordsAdapter adapter; - - static final String PREF_LOCATION_DATA = "location_data"; - private double latitude; private double longitude; - private File tempFileBeingProcessed; - - private Handler handler = new Handler(); + private Handler handlerClock = new Handler(); private Runnable updateInfoRunnable; private Runnable updateClockRunnable; // Declare here - private CameraDevice cameraDevice; - private CameraCaptureSession cameraCaptureSession; - private CaptureRequest.Builder captureRequestBuilder; - private MediaRecorder mediaRecorder; private boolean isRecording = false; private TextureView textureView; private SharedPreferences sharedPreferences; @@ -133,10 +95,6 @@ public class HomeFragment extends Fragment { private boolean isTypingIn = true; private String currentTip = ""; - private static final String QUALITY_SD = "SD"; - private static final String QUALITY_HD = "HD"; - private static final String QUALITY_FHD = "FHD"; - private TextView tvStorageInfo; private TextView tvPreviewPlaceholder; private Button buttonStartStop; @@ -150,47 +108,55 @@ public class HomeFragment extends Fragment { private CardView cardClock; private TextView tvClock, tvDateEnglish, tvDateArabic; - private CameraManager cameraManager; - private String cameraId; //FragmentActivity tipres = requireActivity(); private TextView tvTip; private String[] tips; - private int currentTipIndex = 0; private TextView tvStats; - private List messageQueue; private List recentlyShownMessages; private Random random = new Random(); private static final int RECENT_MESSAGE_LIMIT = 3; // Adjust as needed private static final int REQUEST_PERMISSIONS = 1; - private android.os.PowerManager.WakeLock wakeLock; // Full path for clarity + private android.os.PowerManager.WakeLock wakeLock; // private static final String PREF_FIRST_LAUNCH = "first_launch"; + private BroadcastReceiver broadcastOnRecordingStarted; + private BroadcastReceiver broadcastOnRecordingResumed; + private BroadcastReceiver broadcastOnRecordingPaused; + private BroadcastReceiver broadcastOnRecordingStopped; + + private boolean isRecordingServiceRunning = false; + // important private void requestEssentialPermissions() { Log.d(TAG, "requestEssentialPermissions: Requesting essential permissions"); - String[] permissions; + List permissions; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // Android 11 and above - permissions = new String[]{ + permissions = new ArrayList<>(Arrays.asList( Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO, - Manifest.permission.READ_MEDIA_VIDEO, Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.POST_NOTIFICATIONS - }; + Manifest.permission.WRITE_EXTERNAL_STORAGE + )); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + permissions.add(Manifest.permission.READ_MEDIA_VIDEO); + permissions.add(Manifest.permission.POST_NOTIFICATIONS); + } } else { // Below Android 11 - permissions = new String[]{ + permissions = new ArrayList<>(Arrays.asList( Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO, - Manifest.permission.WRITE_EXTERNAL_STORAGE - }; + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_EXTERNAL_STORAGE + )); } List permissionsToRequest = new ArrayList<>(); @@ -207,25 +173,21 @@ private void requestEssentialPermissions() { // Request to disable battery optimization requestBatteryOptimizationPermission(); - - // Acquire wake lock when recording starts (no need to request runtime permission) } - // Method to request disabling battery optimization private void requestBatteryOptimizationPermission() { android.os.PowerManager powerManager = (android.os.PowerManager) requireActivity().getSystemService(Context.POWER_SERVICE); // Full path and context adjusted String packageName = requireActivity().getPackageName(); // Correct package retrieval - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !powerManager.isIgnoringBatteryOptimizations(packageName)) { - Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); - intent.setData(Uri.parse("package:" + packageName)); - startActivity(intent); + if (!powerManager.isIgnoringBatteryOptimizations(packageName)) { + if (!powerManager.isIgnoringBatteryOptimizations(packageName)) { + Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + intent.setData(Uri.parse("package:" + packageName)); + startActivity(intent); + } } } - // Call this method when the recording starts to acquire wake lock -// private WakeLock wakeLock; - // Call this method when the recording starts to acquire wake lock private void acquireWakeLock() { android.os.PowerManager powerManager = (android.os.PowerManager) requireActivity().getSystemService(Context.POWER_SERVICE); // Full path and context adjusted @@ -245,9 +207,6 @@ private void releaseWakeLock() { } } - - - private void initializeMessages() { messageQueue = new ArrayList<>(Arrays.asList(getResources().getStringArray(R.array.easter_eggs_array))); recentlyShownMessages = new ArrayList<>(); @@ -281,7 +240,6 @@ private void showRandomMessage() { } } - private void setupLongPressListener() { cardPreview.setOnLongClickListener(v -> { if (isRecording) { @@ -348,7 +306,6 @@ private void setupLongPressListener() { }); } - private void updatePreviewVisibility() { if (isRecording) { if (isPreviewEnabled) { @@ -364,31 +321,6 @@ private void updatePreviewVisibility() { tvPreviewPlaceholder.setVisibility(View.VISIBLE); tvPreviewPlaceholder.setText(getString(R.string.ui_preview_area)); } - - updateCameraPreview(); - } - - private void updateCameraPreview() { - if (cameraCaptureSession != null && captureRequestBuilder != null && textureView.isAvailable()) { - try { - SurfaceTexture texture = textureView.getSurfaceTexture(); - if (texture == null) { - Log.e(TAG, "updateCameraPreview: SurfaceTexture is null"); - return; - } - - Surface previewSurface = new Surface(texture); - - captureRequestBuilder.removeTarget(previewSurface); - if (isPreviewEnabled && isRecording) { - captureRequestBuilder.addTarget(previewSurface); - } - - cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); - } catch (CameraAccessException e) { - Log.e(TAG, "Error updating camera preview", e); - } - } } private void resetTimers() { @@ -399,14 +331,17 @@ private void resetTimers() { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - sharedPreferences = requireActivity().getPreferences(Context.MODE_PRIVATE); - locationHelper = new LocationHelper(requireContext()); - cameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE); + + sharedPreferences = requireActivity().getSharedPreferences(Constants.PREFS_NAME, Context.MODE_PRIVATE); + + //cameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE); Log.d(TAG, "HomeFragment created."); // Request essential permissions on every launch requestEssentialPermissions(); + isRecordingServiceRunning = isMyServiceRunning(RecordingService.class); + // Check if it's the first launch // boolean isFirstLaunch = sharedPreferences.getBoolean(PREF_FIRST_LAUNCH, true); // if (isFirstLaunch) { @@ -416,45 +351,160 @@ public void onCreate(Bundle savedInstanceState) { // // Set first launch to false // sharedPreferences.edit().putBoolean(PREF_FIRST_LAUNCH, false).apply(); // } - } @Override public void onStart() { super.onStart(); + + textureView.setVisibility(View.VISIBLE); + + registerBroadcastOnRecordingStarted(); + registerBroadcastOnRecordingResumed(); + registerBroadcastOnRecordingPaused(); + registerBrodcastOnRecordingStopped(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // Android 11 and above + requireActivity().registerReceiver(broadcastOnRecordingStarted, new IntentFilter(Constants.BROADCAST_ON_RECORDING_STARTED), Context.RECEIVER_EXPORTED); + requireActivity().registerReceiver(broadcastOnRecordingResumed, new IntentFilter(Constants.BROADCAST_ON_RECORDING_RESUMED), Context.RECEIVER_EXPORTED); + requireActivity().registerReceiver(broadcastOnRecordingPaused, new IntentFilter(Constants.BROADCAST_ON_RECORDING_PAUSED), Context.RECEIVER_EXPORTED); + requireActivity().registerReceiver(broadcastOnRecordingStopped, new IntentFilter(Constants.BROADCAST_ON_RECORDING_STOPPED), Context.RECEIVER_EXPORTED); + } else { + // Below Android 11 + requireActivity().registerReceiver(broadcastOnRecordingStarted, new IntentFilter(Constants.BROADCAST_ON_RECORDING_STARTED)); + requireActivity().registerReceiver(broadcastOnRecordingResumed, new IntentFilter(Constants.BROADCAST_ON_RECORDING_RESUMED)); + requireActivity().registerReceiver(broadcastOnRecordingPaused, new IntentFilter(Constants.BROADCAST_ON_RECORDING_PAUSED)); + requireActivity().registerReceiver(broadcastOnRecordingStopped, new IntentFilter(Constants.BROADCAST_ON_RECORDING_STOPPED)); + } + //fetch Camera status - String currentCameraSelection = sharedPreferences.getString(Constantes.PREF_CAMERA_SELECTION, Constantes.CAMERA_BACK); + String currentCameraSelection = sharedPreferences.getString(Constants.PREF_CAMERA_SELECTION, Constants.CAMERA_BACK); Toast.makeText(getContext(), this.getString(R.string.current_camera) + ": " + currentCameraSelection, Toast.LENGTH_SHORT).show(); } + private void registerBroadcastOnRecordingStarted() { + broadcastOnRecordingStarted = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent i) + { + recordingStartTime = i.getLongExtra("recordingStartTime", 0); + + onRecordingStarted(); + + vibrateTouch(); + + Toast.makeText(getContext(), R.string.video_recording_started, Toast.LENGTH_SHORT).show(); + } + }; + } + + private void registerBroadcastOnRecordingResumed() { + broadcastOnRecordingResumed = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent i) + { + onRecordingResumed(); + } + }; + } + + private void registerBroadcastOnRecordingPaused() { + broadcastOnRecordingPaused = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent i) + { + onRecordingPaused(); + } + }; + } + + private void registerBrodcastOnRecordingStopped() { + broadcastOnRecordingStopped = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent i) + { + onRecordingStopped(); + + Toast.makeText(getContext(), R.string.video_recording_stopped, Toast.LENGTH_SHORT).show(); + } + }; + } + + private void onRecordingStarted() { + isRecording = true; + + acquireWakeLock(); + + updateStorageInfo(); + setVideoBitrate(); + + buttonStartStop.setText(getString(R.string.button_stop)); + buttonStartStop.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_stop, 0, 0, 0); + buttonStartStop.setEnabled(true); + buttonPauseResume.setEnabled(true); + buttonCamSwitch.setEnabled(false); + + startUpdatingInfo(); + } + + private void onRecordingResumed() + { + buttonCamSwitch.setEnabled(false); + } + + private void onRecordingPaused() + { + buttonCamSwitch.setEnabled(false); + } + + private void onRecordingStopped() { + isRecording = false; + + releaseWakeLock(); + + buttonStartStop.setText(getString(R.string.button_start)); + buttonStartStop.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_play, 0, 0, 0); + buttonStartStop.setEnabled(true); + buttonPauseResume.setEnabled(false); + buttonCamSwitch.setEnabled(true); + + updatePreviewVisibility(); + + stopUpdatingInfo(); + updateStorageInfo(); + } + + @Override + public void onStop() { + super.onStop(); + + requireActivity().unregisterReceiver(broadcastOnRecordingStarted); + requireActivity().unregisterReceiver(broadcastOnRecordingResumed); + requireActivity().unregisterReceiver(broadcastOnRecordingPaused); + requireActivity().unregisterReceiver(broadcastOnRecordingStopped); + } + @Override public void onResume() { super.onResume(); - locationHelper.startLocationUpdates(); Log.d(TAG, "HomeFragment resumed."); - IntentFilter filter = new IntentFilter("RECORDING_STATE_CHANGED"); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - getActivity().registerReceiver(recordingStateReceiver, filter, Context.RECEIVER_NOT_EXPORTED); + if(!isRecordingServiceRunning) { + setupStartStopButton(); } - setupStartStopButton(); - updateStats(); } @Override public void onPause() { super.onPause(); - locationHelper.stopLocationUpdates(); + //locationHelper.stopLocationUpdates(); Log.d(TAG, "HomeFragment paused."); - getActivity().unregisterReceiver(recordingStateReceiver); - } - - private String getLocationData() { - return locationHelper.getLocationData(); + //getActivity().unregisterReceiver(recordingStateReceiver); } // @Override @@ -531,12 +581,15 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat super.onViewCreated(view, savedInstanceState); Log.d(TAG, "onViewCreated: Setting up UI components"); - textureView = view.findViewById(R.id.textureView); + setupTextureView(view); + tvStorageInfo = view.findViewById(R.id.tvStorageInfo); tvPreviewPlaceholder = view.findViewById(R.id.tvPreviewPlaceholder); + tvPreviewPlaceholder.setVisibility(View.VISIBLE); buttonStartStop = view.findViewById(R.id.buttonStartStop); buttonPauseResume = view.findViewById(R.id.buttonPauseResume); buttonCamSwitch = view.findViewById(R.id.buttonCamSwitch); + buttonCamSwitch.setEnabled(!isRecordingServiceRunning); tvTip = view.findViewById(R.id.tvTip); tvStats = view.findViewById(R.id.tvStats); @@ -557,8 +610,8 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat cardPreview = view.findViewById(R.id.cardPreview); vibrator = (Vibrator) requireContext().getSystemService(Context.VIBRATOR_SERVICE); - sharedPreferences = requireActivity().getPreferences(Context.MODE_PRIVATE); - isPreviewEnabled = sharedPreferences.getBoolean("isPreviewEnabled", true); + sharedPreferences = requireActivity().getSharedPreferences(Constants.PREFS_NAME, Context.MODE_PRIVATE); + isPreviewEnabled = sharedPreferences.getBoolean(Constants.PREF_IS_PREVIEW_ENABLED, true); resetTimers(); copyFontToInternalStorage(); @@ -577,14 +630,47 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat updatePreviewVisibility(); } + private void setupTextureView(@NonNull View view) { + textureView = view.findViewById(R.id.textureView); + textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { + @Override + public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int width, int height) { + surfaceTexture.setDefaultBufferSize(720, 1080); + textureView.setVisibility(View.INVISIBLE); + + if(isRecordingServiceRunning) + { + startRecording(); + } + } + + @Override + public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {} + + @Override + public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) { + return false; + } + + @Override + public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {} + }); + } + private boolean areEssentialPermissionsGranted() { boolean cameraGranted = ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED; boolean recordAudioGranted = ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED; boolean storageGranted; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // Android 11 and above - storageGranted = ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_MEDIA_VIDEO) == PackageManager.PERMISSION_GRANTED || - ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // Android 13 and above + storageGranted = ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_MEDIA_VIDEO) == PackageManager.PERMISSION_GRANTED || + ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + } else { + // Below Android 13 + storageGranted = ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + } } else { // Below Android 11 storageGranted = ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; } @@ -609,7 +695,6 @@ private void debugPermissionsStatus() { (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED ? "Granted" : "Denied")); } - private void setupButtonListeners() { buttonStartStop.setOnClickListener(v -> { debugPermissionsStatus(); @@ -645,21 +730,44 @@ private void setupButtonListeners() { }); } + private void startRecording() { + SurfaceTexture surfaceTexture = textureView.getSurfaceTexture(); + + tvPreviewPlaceholder.setVisibility(View.GONE); + textureView.setVisibility(View.VISIBLE); + + buttonStartStop.setEnabled(false); + buttonCamSwitch.setEnabled(false); + + Intent startIntent = new Intent(getActivity(), RecordingService.class); + if(isMyServiceRunning(RecordingService.class)) { + startIntent.setAction(Constants.INTENT_ACTION_CHANGE_SURFACE); + } else { + startIntent.setAction(Constants.INTENT_ACTION_START_RECORDING); + } + + if(surfaceTexture != null) { + startIntent.putExtra("SURFACE", new Surface(surfaceTexture)); + } + + getActivity().startService(startIntent); + } + private void startUpdatingClock() { updateClockRunnable = new Runnable() { @Override public void run() { updateClock(); - handler.postDelayed(this, 1000); // Update every second + handlerClock.postDelayed(this, 1000); // Update every second } }; - handler.post(updateClockRunnable); + handlerClock.post(updateClockRunnable); } // Method to stop updating the clock private void stopUpdatingClock() { if (updateClockRunnable != null) { - handler.removeCallbacks(updateClockRunnable); + handlerClock.removeCallbacks(updateClockRunnable); updateClockRunnable = null; } } @@ -672,7 +780,6 @@ private void setupClockLongPressListener() { }); } - private void addWobbleAnimation() { // Define the scale down and scale up values float scaleDown = 0.9f; @@ -722,7 +829,6 @@ public void onAnimationEnd(Animator animation) { scaleDownSet.start(); } - private void showDisplayOptionsDialog() { new MaterialAlertDialogBuilder(getContext()) .setTitle(getString(R.string.dialog_clock_title)) @@ -740,24 +846,20 @@ private void showDisplayOptionsDialog() { } private int getCurrentDisplayOption() { - SharedPreferences prefs = getActivity().getSharedPreferences("AppPreferences", Context.MODE_PRIVATE); - return prefs.getInt("display_option", 2); // Default to "Everything" + return sharedPreferences.getInt("display_option", 2); // Default to "Everything" } private void saveDisplayOption(int option) { - SharedPreferences prefs = getActivity().getSharedPreferences("AppPreferences", Context.MODE_PRIVATE); - SharedPreferences.Editor editor = prefs.edit(); + SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putInt("display_option", option); editor.apply(); } - private void showOptionsAndAnimate() { addWobbleAnimation(); showDisplayOptionsDialog(); } - // Method to update the clock and dates private void updateClock() { SharedPreferences prefs = getActivity().getSharedPreferences("AppPreferences", Context.MODE_PRIVATE); @@ -787,7 +889,6 @@ private void updateClock() { tvDateArabic.setText(displayOption == 2 ? currentDateArabic : ""); } - private void updateStorageInfo() { Log.d(TAG, "updateStorageInfo: Updating storage information"); StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath()); @@ -804,7 +905,7 @@ private void updateStorageInfo() { bytesAvailable -= estimatedBytesUsed; gbAvailable = Math.max(0, bytesAvailable / (1024.0 * 1024.0 * 1024.0)); -// Calculate remaining recording time based on available space and bitrate + // Calculate remaining recording time based on available space and bitrate long remainingTime = (videoBitrate > 0) ? (bytesAvailable * 8) / videoBitrate * 2 : 0; // Double the remaining time // Calculate days, hours, minutes, and seconds for remaining time long days = remainingTime / (24 * 3600); long hours = (remainingTime % (24 * 3600)) / 3600; @@ -864,17 +965,17 @@ public void run() { if (isRecording) { updateStorageInfo(); updateStats(); - handler.postDelayed(this, 1000); // Update every 3 seconds + handlerClock.postDelayed(this, 1000); // Update every 3 seconds } } }; - handler.post(updateInfoRunnable); + handlerClock.post(updateInfoRunnable); } private void stopUpdatingInfo() { Log.d(TAG, "stopUpdatingInfo: Stopping real-time updates"); if (updateInfoRunnable != null) { - handler.removeCallbacks(updateInfoRunnable); + handlerClock.removeCallbacks(updateInfoRunnable); updateInfoRunnable = null; } } @@ -889,7 +990,7 @@ private void updateTip() { currentTip = tips[currentTipIndex]; typingIndex = 0; isTypingIn = true; -// animateTip(); this line is giving errors so i commented it + // animateTip(); this line is giving errors so i commented it } private void animateTip(String fullText, TextView textView, int delay) { @@ -913,7 +1014,6 @@ public void run() { handler.post(runnable); } - private void updateStats() { Log.d(TAG, "updateStats: Updating video statistics"); File recordsDir = new File(getContext().getExternalFilesDir(null), "FadCam"); @@ -941,123 +1041,42 @@ private void updateStats() { private void pauseRecording() { Log.d(TAG, "pauseRecording: Pausing video recording"); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - mediaRecorder.pause(); - isPaused = true; - buttonPauseResume.setText(R.string.button_resume); - buttonPauseResume.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_play, 0, 0, 0); - } - } - private void resumeRecording() { - Log.d(TAG, "resumeRecording: Resuming video recording"); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - mediaRecorder.resume(); - isPaused = false; - buttonPauseResume.setText(getString(R.string.button_pause)); - buttonPauseResume.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_pause, 0, 0, 0); - } - } + isPaused = true; + buttonPauseResume.setText(R.string.button_resume); + buttonPauseResume.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_play, 0, 0, 0); + buttonCamSwitch.setEnabled(false); - private BroadcastReceiver recordingStateReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if ("RECORDING_STATE_CHANGED".equals(intent.getAction())) { - boolean isRecording = intent.getBooleanExtra("isRecording", false); - if (isRecording) { - buttonStartStop.setText(getString(R.string.button_stop)); - buttonStartStop.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_stop, 0, 0, 0); - buttonPauseResume.setEnabled(true); - tvPreviewPlaceholder.setVisibility(View.GONE); - textureView.setVisibility(View.VISIBLE); - } else { - buttonStartStop.setText(getString(R.string.button_start)); - buttonStartStop.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_play, 0, 0, 0); - buttonPauseResume.setEnabled(false); - tvPreviewPlaceholder.setVisibility(View.VISIBLE); - textureView.setVisibility(View.GONE); - } - } - } - }; + Intent stopIntent = new Intent(getActivity(), RecordingService.class); + stopIntent.setAction(Constants.INTENT_ACTION_PAUSE_RECORDING); + getActivity().startService(stopIntent); + } + private void resumeRecording() { + Log.d(TAG, "resumeRecording: Resuming video recording"); - private void startRecording() { - Log.d(TAG, "startRecording: Initiating video recording from home fragment"); + isPaused = false; + buttonPauseResume.setText(getString(R.string.button_pause)); + buttonPauseResume.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_pause, 0, 0, 0); - // Acquire wake lock to prevent the device from sleeping - acquireWakeLock(); + buttonCamSwitch.setEnabled(false); - // Set up the camera and MediaRecorder here - if (!isRecording) { - resetTimers(); - if (cameraDevice == null) { - openCamera(); - } else { - startRecordingVideo(); - } - recordingStartTime = SystemClock.elapsedRealtime(); - setVideoBitrate(); - - buttonStartStop.setText(getString(R.string.button_stop)); - buttonStartStop.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_stop, 0, 0, 0); - buttonPauseResume.setEnabled(true); - tvPreviewPlaceholder.setVisibility(View.GONE); - textureView.setVisibility(View.VISIBLE); - - startUpdatingInfo(); - isRecording = true; - updatePreviewVisibility(); - - // Start the recording service - Intent startIntent = new Intent(getActivity(), RecordingService.class); - startIntent.setAction("ACTION_START_RECORDING"); - getActivity().startService(startIntent); - } + Intent stopIntent = new Intent(getActivity(), RecordingService.class); + stopIntent.setAction(Constants.INTENT_ACTION_RESUME_RECORDING); + getActivity().startService(stopIntent); } - -//recording service section -// private void startRecording() { -// Log.d(TAG, "startRecording: Initiating video recording from home fragment"); -// -// Intent startIntent = new Intent(getActivity(), RecordingService.class); -// startIntent.setAction("ACTION_START_RECORDING"); -// getActivity().startService(startIntent); -// -// if (!isRecording) { -// resetTimers(); -// if (cameraDevice == null) { -// openCamera(); -// } else { -// startRecordingVideo(); -// } -// recordingStartTime = SystemClock.elapsedRealtime(); -// setVideoBitrate(); -// -// buttonStartStop.setText("Stop"); -// buttonStartStop.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_stop, 0, 0, 0); -// buttonPauseResume.setEnabled(true); -// tvPreviewPlaceholder.setVisibility(View.GONE); -// textureView.setVisibility(View.VISIBLE); -// -// startUpdatingInfo(); -// isRecording = true; -// updatePreviewVisibility(); -// } -// } - private void setVideoBitrate() { - String selectedQuality = sharedPreferences.getString(Constantes.PREF_VIDEO_QUALITY, QUALITY_HD); + String selectedQuality = sharedPreferences.getString(Constants.PREF_VIDEO_QUALITY, Constants.QUALITY_HD); switch (selectedQuality) { - case QUALITY_SD: + case Constants.QUALITY_SD: videoBitrate = 1000000; // 1 Mbps break; - case QUALITY_HD: + case Constants.QUALITY_HD: videoBitrate = 5000000; // 5 Mbps break; - case QUALITY_FHD: + case Constants.QUALITY_FHD: videoBitrate = 10000000; // 10 Mbps break; default: @@ -1068,549 +1087,31 @@ private void setVideoBitrate() { } private String getCameraSelection() { - return sharedPreferences.getString(Constantes.PREF_CAMERA_SELECTION, Constantes.CAMERA_BACK); + return sharedPreferences.getString(Constants.PREF_CAMERA_SELECTION, Constants.CAMERA_BACK); } private String getCameraQuality() { - return sharedPreferences.getString(Constantes.PREF_VIDEO_QUALITY, QUALITY_HD); - } - - private void closeCamera() { - if (cameraDevice != null) { - cameraDevice.close(); - cameraDevice = null; - } - } - - private void openCamera() { - Log.d(TAG, "openCamera: Opening camera"); - CameraManager manager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE); - try { - String[] cameraIdList = manager.getCameraIdList(); - cameraId = getCameraSelection().equals(Constantes.CAMERA_FRONT) ? cameraIdList[1] : cameraIdList[0]; - manager.openCamera(cameraId, new CameraDevice.StateCallback() { - @Override - public void onOpened(@NonNull CameraDevice camera) { - Log.d(TAG, "onOpened: Camera opened successfully"); - cameraDevice = camera; - startRecordingVideo(); - } - - @Override - public void onDisconnected(@NonNull CameraDevice camera) { - Log.w(TAG, "onDisconnected: Camera disconnected"); - cameraDevice.close(); - } - - @Override - public void onError(@NonNull CameraDevice camera, int error) { - Log.e(TAG, "onError: Camera error: " + error); - cameraDevice.close(); - cameraDevice = null; - } - }, null); - } catch (CameraAccessException | SecurityException e) { - Log.e(TAG, "openCamera: Error opening camera", e); - e.printStackTrace(); - } + return sharedPreferences.getString(Constants.PREF_VIDEO_QUALITY, Constants.QUALITY_HD); } - private void startRecordingVideo() { - Log.d(TAG, "startRecordingVideo: Setting up video recording preview area"); - buttonCamSwitch.setEnabled(false); - - // Check if TextureView is available before starting recording - if (!textureView.isAvailable()) { - tvPreviewPlaceholder.setVisibility(View.VISIBLE); - textureView.setVisibility(View.VISIBLE); - openCamera(); - Log.e(TAG, "startRecordingVideo: TextureView is now available 550"); - } - - if (null == cameraDevice || !textureView.isAvailable() || !Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - Log.e(TAG, "startRecordingVideo: Unable to start recording due to missing prerequisites"); - return; - } - try { - Log.e(TAG, "startRecordingVideo: TextureView found, success 556+"); - setupMediaRecorder(); - SurfaceTexture texture = textureView.getSurfaceTexture(); - assert texture != null; - texture.setDefaultBufferSize(720, 1080); - Surface previewSurface = new Surface(texture); - Surface recorderSurface = mediaRecorder.getSurface(); - captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); -// below line of code is to show the preview screen. -// captureRequestBuilder.addTarget(previewSurface); - if (isPreviewEnabled) { - captureRequestBuilder.addTarget(previewSurface); - } - captureRequestBuilder.addTarget(recorderSurface); - - int selectedFramerate = sharedPreferences.getInt(Constantes.PREF_VIDEO_FRAMERATE, Constantes.DEFAULT_VIDEO_FRAMERATE); - - // Define framerate - Range fpsRange = Range.create(selectedFramerate, selectedFramerate); - captureRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange); - - cameraDevice.createCaptureSession(Arrays.asList(previewSurface, recorderSurface), - new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { - Log.d(TAG, "onConfigured: Camera capture session configured"); - HomeFragment.this.cameraCaptureSession = cameraCaptureSession; - try { - cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); - } catch (CameraAccessException e) { - Log.e(TAG, "onConfigured: Error setting repeating request", e); - e.printStackTrace(); - } - mediaRecorder.start(); - getActivity().runOnUiThread(() -> { - // Haptic Feedback - vibrateTouch(); - isRecording = true; - Toast.makeText(getContext(), R.string.video_recording_started, Toast.LENGTH_SHORT).show(); - }); - } - - @Override - public void onConfigureFailed(@NonNull CameraCaptureSession session) { - Log.e(TAG, "onConfigureFailed: Failed to configure camera capture session"); - Toast.makeText(getContext(), "Failed to start recording", Toast.LENGTH_SHORT).show(); - } - }, null); - } catch (CameraAccessException e) { - Log.e(TAG, "startRecordingVideo: Camera access exception", e); - e.printStackTrace(); - } - } - - private void setupMediaRecorder() { - /* - * This method sets up the MediaRecorder for video recording. - * It creates a directory for saving videos if it doesn't exist, - * generates a timestamp-based filename, and configures the - * MediaRecorder with the appropriate settings based on the - * selected video quality (SD, HD, FHD). It reduces bitrates - * by 50% using the HEVC (H.265) encoder for efficient compression - * without significantly affecting video quality. - * - * - SD: 640x480 @ 0.5 Mbps - * - HD: 1280x720 @ 2.5 Mbps - * - FHD: 1920x1080 @ 5 Mbps - * - * It also adjusts the frame rate, sets audio settings, and configures - * the orientation based on the camera selection (front or rear). - */ - - try { - // Create directory for saving videos if it doesn't exist - File videoDir = new File(requireActivity().getExternalFilesDir(null), "FadCam"); - if (!videoDir.exists()) { - videoDir.mkdirs(); - } - - // Generate a timestamp-based filename for the video - String timestamp = new SimpleDateFormat("yyyyMMdd_hh_mm_ssa", Locale.getDefault()).format(new Date()); - File videoFile = new File(videoDir, "temp_" + timestamp + ".mp4"); - - // Initialize MediaRecorder - mediaRecorder = new MediaRecorder(); - mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); - mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); - mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); - mediaRecorder.setOutputFile(videoFile.getAbsolutePath()); - - // Select video quality and adjust size and bitrate - String selectedQuality = sharedPreferences.getString(Constantes.PREF_VIDEO_QUALITY, QUALITY_HD); - switch (selectedQuality) { - case QUALITY_SD: - // SD: 640x480 resolution, 0.5 Mbps (50% of original 1 Mbps) - mediaRecorder.setVideoSize(640, 480); - mediaRecorder.setVideoEncodingBitRate(500000); - break; - case QUALITY_HD: - // HD: 1280x720 resolution, 2.5 Mbps (50% of original 5 Mbps) - mediaRecorder.setVideoSize(1280, 720); - mediaRecorder.setVideoEncodingBitRate(2500000); - break; - case QUALITY_FHD: - // FHD: 1920x1080 resolution, 5 Mbps (50% of original 10 Mbps) - mediaRecorder.setVideoSize(1920, 1080); - mediaRecorder.setVideoEncodingBitRate(5000000); - break; - default: - // Default to HD settings - mediaRecorder.setVideoSize(1280, 720); - mediaRecorder.setVideoEncodingBitRate(2500000); - break; - } - - // Set frame rate and capture rate - int selectedFramerate = sharedPreferences.getInt(Constantes.PREF_VIDEO_FRAMERATE, Constantes.DEFAULT_VIDEO_FRAMERATE); - mediaRecorder.setVideoFrameRate(selectedFramerate); - mediaRecorder.setCaptureRate(selectedFramerate); - - // Audio settings: high-quality audio - mediaRecorder.setAudioEncodingBitRate(384000); - mediaRecorder.setAudioSamplingRate(48000); - mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); - - // Set video encoder to HEVC (H.265) for better compression - mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.HEVC); - - // Set orientation based on camera selection - if (getCameraSelection().equals(Constantes.CAMERA_FRONT)) { - mediaRecorder.setOrientationHint(270); - } else { - mediaRecorder.setOrientationHint(90); - } - - // Prepare MediaRecorder - mediaRecorder.prepare(); - - } catch (IOException e) { - Log.e(TAG, "setupMediaRecorder: Error setting up media recorder", e); - e.printStackTrace(); - } - } - - - private String extractTimestamp(String filename) { - // Assuming filename format is "prefix_TIMESTAMP.mp4" - // Example: "temp_20240730_01_39_26PM.mp4" - // Extracting timestamp part: "20240730_01_39_26PM" - int startIndex = filename.indexOf('_') + 1; - int endIndex = filename.lastIndexOf('.'); - return filename.substring(startIndex, endIndex); - } - - - private void checkAndDeleteSpecificTempFile() { - if (tempFileBeingProcessed != null) { - String tempTimestamp = extractTimestamp(tempFileBeingProcessed.getName()); - - // Construct FADCAM_ filename with the same timestamp - String outputFilePath = tempFileBeingProcessed.getParent() + "/FADCAM_" + tempFileBeingProcessed.getName().replace("temp_", ""); - File outputFile = new File(outputFilePath); - - // Check if the FADCAM_ file exists - if (outputFile.exists()) { - // Delete temp file - if (tempFileBeingProcessed.delete()) { - Log.d(TAG, "Temp file deleted successfully."); - } else { - Log.e(TAG, "Failed to delete temp file."); - } - // Reset tempFileBeingProcessed to null after deletion - tempFileBeingProcessed = null; - } else { - // FADCAM_ file does not exist yet - Log.d(TAG, "Matching FADCAM_ file not found. Temp file remains."); - } - } - } - - - private void startMonitoring() { - final long CHECK_INTERVAL_MS = 1000; // 1 second - - Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> { - checkAndDeleteSpecificTempFile(); - }, 0, CHECK_INTERVAL_MS, TimeUnit.MILLISECONDS); - } - - private void stopRecording() { Log.d(TAG, "stopRecording: Stopping video recording"); // Release wake lock when recording stops releaseWakeLock(); + buttonPauseResume.setEnabled(false); + buttonStartStop.setEnabled(false); + buttonCamSwitch.setEnabled(false); + // Stop the recording service Intent stopIntent = new Intent(getActivity(), RecordingService.class); - stopIntent.setAction("ACTION_STOP_RECORDING"); + stopIntent.setAction(Constants.INTENT_ACTION_STOP_RECORDING); getActivity().startService(stopIntent); - if (isRecording) { - try { - cameraCaptureSession.stopRepeating(); - cameraCaptureSession.abortCaptures(); - releaseCamera(); - vibrateTouch(); - Toast.makeText(getContext(), R.string.video_recording_stopped, Toast.LENGTH_SHORT).show(); - - // Add watermarking here if necessary - // Get the latest video file - File latestVideoFile = getLatestVideoFile(); - if (latestVideoFile != null) { - String inputFilePath = latestVideoFile.getAbsolutePath(); - String originalFileName = latestVideoFile.getName().replace("temp_", ""); - String outputFilePath = latestVideoFile.getParent() + "/FADCAM_" + originalFileName; - Log.d(TAG, "Watermarking: Input file path: " + inputFilePath); - Log.d(TAG, "Watermarking: Output file path: " + outputFilePath); - - tempFileBeingProcessed = latestVideoFile; - addTextWatermarkToVideo(inputFilePath, outputFilePath); - } else { - Log.e(TAG, "No video file found."); - } - - isRecording = false; - buttonStartStop.setText(getString(R.string.button_start)); - buttonStartStop.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_play, 0, 0, 0); - buttonPauseResume.setEnabled(false); - tvPreviewPlaceholder.setVisibility(View.VISIBLE); - textureView.setVisibility(View.INVISIBLE); - stopUpdatingInfo(); - updateStorageInfo(); - } catch (CameraAccessException | IllegalStateException e) { - Log.e(TAG, "stopRecording: Error stopping recording", e); - e.printStackTrace(); - } - isRecording = false; - updatePreviewVisibility(); - } - buttonCamSwitch.setEnabled(true); + vibrateTouch(); } - -//recording service section -// private void stopRecording() { -// Log.d(TAG, "stopRecording: Stopping video recording"); -// -// Intent stopIntent = new Intent(getActivity(), RecordingService.class); -// stopIntent.setAction("ACTION_STOP_RECORDING"); -// getActivity().startService(stopIntent); -// -// if (isRecording) { -// try { -// cameraCaptureSession.stopRepeating(); -// cameraCaptureSession.abortCaptures(); -// mediaRecorder.stop(); -// mediaRecorder.reset(); -// // Haptic Feedback -// vibrateTouch(); -// Toast.makeText(getContext(), "Recording stopped", Toast.LENGTH_SHORT).show(); -// } catch (CameraAccessException | IllegalStateException e) { -// Log.e(TAG, "stopRecording: Error stopping recording", e); -// e.printStackTrace(); -// } -//// below lines are new -// // Add watermarking here -// // Get the latest video file -// File latestVideoFile = getLatestVideoFile(); -// if (latestVideoFile != null) { -// // Prepare file paths -// String inputFilePath = latestVideoFile.getAbsolutePath(); -// -// // Remove 'temp_' prefix from the file name to get the original name -// String originalFileName = latestVideoFile.getName().replace("temp_", ""); -// -// // Create output file path with 'FADCAM_' prefix -// String outputFilePath = latestVideoFile.getParent() + "/FADCAM_" + originalFileName; -// Log.d(TAG, "Watermarking: Input file path: " + inputFilePath); -// Log.d(TAG, "Watermarking: Output file path: " + outputFilePath); -// -// // Track the temp file being processed -// tempFileBeingProcessed = latestVideoFile; -// -// // Add text watermark to the recorded video -// addTextWatermarkToVideo(inputFilePath, outputFilePath); -// } else { -// Log.e(TAG, "No video file found."); -// } -// -// -// releaseCamera(); -// isRecording = false; -// buttonStartStop.setText("Start"); -// buttonStartStop.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_play, 0, 0, 0); -// buttonPauseResume.setEnabled(false); -// tvPreviewPlaceholder.setVisibility(View.VISIBLE); -// textureView.setVisibility(View.INVISIBLE); -// stopUpdatingInfo(); -// updateStorageInfo(); // Final update with actual values -// -// // Start monitoring temp files -//// startMonitoring(); -// } -// isRecording = false; -// updatePreviewVisibility(); -// } - - private void releaseCamera() { - Log.d(TAG, "releaseCamera: Releasing camera resources"); - if (cameraDevice != null) { - cameraDevice.close(); - cameraDevice = null; - } - if (mediaRecorder != null) { - mediaRecorder.release(); - mediaRecorder = null; - } - cameraCaptureSession = null; - captureRequestBuilder = null; - } - -// below methods are new - - private File getLatestVideoFile() { - File videoDir = new File(requireActivity().getExternalFilesDir(null), "FadCam"); - File[] files = videoDir.listFiles(); - if (files == null || files.length == 0) { - return null; - } - - // Sort files by last modified date - Arrays.sort(files, (f1, f2) -> Long.compare(f2.lastModified(), f1.lastModified())); - return files[0]; // Return the most recently modified file - } - - - private void addTextWatermarkToVideo(String inputFilePath, String outputFilePath) { - String fontPath = getContext().getFilesDir().getAbsolutePath() + "/ubuntu_regular.ttf"; - String watermarkText; - String watermarkOption = getWatermarkOption(); - - boolean isLocationEnabled = sharedPreferences.getBoolean(PREF_LOCATION_DATA, false); - String locationText = isLocationEnabled ? locationHelper.getLocationData() : ""; - - switch (watermarkOption) { - case "timestamp_fadcam": - watermarkText = "Captured by FadCam - " + getCurrentTimestamp() + (isLocationEnabled ? "" + locationText : ""); - break; - case "timestamp": - watermarkText = getCurrentTimestamp() + (isLocationEnabled ? "" + locationText : ""); - break; - case "no_watermark": - String ffmpegCommandNoWatermark = String.format("-i %s -codec copy %s", inputFilePath, outputFilePath); - executeFFmpegCommand(ffmpegCommandNoWatermark); - return; - default: - watermarkText = "Captured by FadCam - " + getCurrentTimestamp() + (isLocationEnabled ? "" + locationText : ""); - break; - } - - // Convert the watermark text to English numerals - watermarkText = convertArabicNumeralsToEnglish(watermarkText); - - // Get and convert the font size to English numerals - int fontSize = getFontSizeBasedOnBitrate(); - String fontSizeStr = convertArabicNumeralsToEnglish(String.valueOf(fontSize)); - - Log.d(TAG, "Watermark Text: " + watermarkText); - Log.d(TAG, "Font Path: " + fontPath); - Log.d(TAG, "Font Size: " + fontSizeStr); - - // Construct the FFmpeg command - String ffmpegCommand = String.format( - "-i %s -vf \"drawtext=text='%s':x=10:y=10:fontsize=%s:fontcolor=white:fontfile=%s\" -q:v 0 -codec:a copy %s", - inputFilePath, watermarkText, fontSizeStr, fontPath, outputFilePath - ); - - executeFFmpegCommand(ffmpegCommand); - } - - - - private int getFontSizeBasedOnBitrate() { - int fontSize; - int videoBitrate = getVideoBitrate(); // Ensure this method retrieves the correct bitrate based on the selected quality - - if (videoBitrate <= 1000000) { - fontSize = 12; //SD quality - } else if (videoBitrate == 10000000) { - fontSize = 24; // FHD quality - } else { - fontSize = 16; // HD or higher quality - } - - Log.d(TAG, "Determined Font Size: " + fontSize); - return fontSize; - } - - private int getVideoBitrate() { - String selectedQuality = sharedPreferences.getString(Constantes.PREF_VIDEO_QUALITY, QUALITY_HD); - int bitrate; - switch (selectedQuality) { - case QUALITY_SD: - bitrate = 1000000; // 1 Mbps - break; - case QUALITY_HD: - bitrate = 5000000; // 5 Mbps - break; - case QUALITY_FHD: - bitrate = 10000000; // 10 Mbps - break; - default: - bitrate = 5000000; // Default to HD - break; - } - Log.d(TAG, "Selected Video Bitrate: " + bitrate + " bps"); - return bitrate; - } - - private void executeFFmpegCommand(String ffmpegCommand) { - Log.d(TAG, "FFmpeg Command: " + ffmpegCommand); - FFmpegKit.executeAsync(ffmpegCommand, new ExecuteCallback() { - @Override - public void apply(Session session) { - if (session.getReturnCode().isSuccess()) { - Log.d(TAG, "Watermark added successfully."); - // Start monitoring temp files - startMonitoring(); - - // Notify the adapter to update the thumbnail - File latestVideo = getLatestVideoFile(); - if (latestVideo != null) { - String videoFilePath = latestVideo.getAbsolutePath(); - updateThumbnailInAdapter(videoFilePath); - } - - } else { - Log.e(TAG, "Failed to add watermark: " + session.getFailStackTrace()); - } - } - }); - } - - private void updateThumbnailInAdapter(String videoFilePath) { - if (adapter != null) { - adapter.notifyDataSetChanged(); // Notify adapter that data has changed - } - } - - - private String getCurrentTimestamp() { - SimpleDateFormat sdf = new SimpleDateFormat("dd/MMM/yyyy hh-mm a", Locale.ENGLISH); - return convertArabicNumeralsToEnglish(sdf.format(new Date())); - } - - - private String convertArabicNumeralsToEnglish(String text) { - if (text == null) return null; - return text.replaceAll("٠", "0") - .replaceAll("١", "1") - .replaceAll("٢", "2") - .replaceAll("٣", "3") - .replaceAll("٤", "4") - .replaceAll("٥", "5") - .replaceAll("٦", "6") - .replaceAll("٧", "7") - .replaceAll("٨", "8") - .replaceAll("٩", "9"); - } - - - - private String getWatermarkOption() { - SharedPreferences sharedPreferences = requireActivity().getPreferences(Context.MODE_PRIVATE); - return sharedPreferences.getString("watermark_option", "timestamp_fadcam"); - } - - private void copyFontToInternalStorage() { AssetManager assetManager = getContext().getAssets(); InputStream in = null; @@ -1641,15 +1142,14 @@ private void copyFontToInternalStorage() { } } - public void switchCamera() { - String currentCameraSelection = sharedPreferences.getString(Constantes.PREF_CAMERA_SELECTION, Constantes.CAMERA_BACK); - if (currentCameraSelection.equals(Constantes.CAMERA_BACK)) { - sharedPreferences.edit().putString(Constantes.PREF_CAMERA_SELECTION, Constantes.CAMERA_FRONT).apply(); + String currentCameraSelection = sharedPreferences.getString(Constants.PREF_CAMERA_SELECTION, Constants.CAMERA_BACK); + if (currentCameraSelection.equals(Constants.CAMERA_BACK)) { + sharedPreferences.edit().putString(Constants.PREF_CAMERA_SELECTION, Constants.CAMERA_FRONT).apply(); Log.d(TAG, "Camera set to front"); Toast.makeText(getContext(), R.string.switched_front_camera, Toast.LENGTH_SHORT).show(); } else { - sharedPreferences.edit().putString(Constantes.PREF_CAMERA_SELECTION, Constantes.CAMERA_BACK).apply(); + sharedPreferences.edit().putString(Constants.PREF_CAMERA_SELECTION, Constants.CAMERA_BACK).apply(); Log.d(TAG, "Camera set to rear"); Toast.makeText(getContext(), R.string.switched_rear_camera, Toast.LENGTH_SHORT).show(); } @@ -1680,7 +1180,6 @@ public void switchCamera() { // } // } - private void copyFile(InputStream in, OutputStream out) throws IOException { byte[] buffer = new byte[1024]; int read; @@ -1691,22 +1190,22 @@ private void copyFile(InputStream in, OutputStream out) throws IOException { private void setupStartStopButton() { - if (!CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_1080P) && getCameraSelection().equals(Constantes.CAMERA_FRONT) && getCameraQuality().equals(QUALITY_FHD)) { + if (!CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_1080P) && getCameraSelection().equals(Constants.CAMERA_FRONT) && getCameraQuality().equals(Constants.QUALITY_FHD)) { buttonStartStop.setEnabled(false); } - else if (!CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_720P) && getCameraSelection().equals(Constantes.CAMERA_FRONT) && getCameraQuality().equals(QUALITY_HD)) { + else if (!CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_720P) && getCameraSelection().equals(Constants.CAMERA_FRONT) && getCameraQuality().equals(Constants.QUALITY_HD)) { buttonStartStop.setEnabled(false); } - else if (!CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_VGA) && !CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_480P) && getCameraSelection().equals(Constantes.CAMERA_FRONT) && getCameraQuality().equals(QUALITY_SD)) { + else if (!CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_VGA) && !CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_480P) && getCameraSelection().equals(Constants.CAMERA_FRONT) && getCameraQuality().equals(Constants.QUALITY_SD)) { buttonStartStop.setEnabled(false); } - else if (!CamcorderProfile.hasProfile(0, CamcorderProfile.QUALITY_1080P) && getCameraSelection().equals(Constantes.CAMERA_BACK) && getCameraQuality().equals(QUALITY_FHD)) { + else if (!CamcorderProfile.hasProfile(0, CamcorderProfile.QUALITY_1080P) && getCameraSelection().equals(Constants.CAMERA_BACK) && getCameraQuality().equals(Constants.QUALITY_FHD)) { buttonStartStop.setEnabled(false); } - else if (!CamcorderProfile.hasProfile(0, CamcorderProfile.QUALITY_720P) && getCameraSelection().equals(Constantes.CAMERA_BACK) && getCameraQuality().equals(QUALITY_HD)) { + else if (!CamcorderProfile.hasProfile(0, CamcorderProfile.QUALITY_720P) && getCameraSelection().equals(Constants.CAMERA_BACK) && getCameraQuality().equals(Constants.QUALITY_HD)) { buttonStartStop.setEnabled(false); } - else if (!CamcorderProfile.hasProfile(0, CamcorderProfile.QUALITY_VGA) && !CamcorderProfile.hasProfile(0, CamcorderProfile.QUALITY_480P) && getCameraSelection().equals(Constantes.CAMERA_BACK) && getCameraQuality().equals(QUALITY_SD)) { + else if (!CamcorderProfile.hasProfile(0, CamcorderProfile.QUALITY_VGA) && !CamcorderProfile.hasProfile(0, CamcorderProfile.QUALITY_480P) && getCameraSelection().equals(Constants.CAMERA_BACK) && getCameraQuality().equals(Constants.QUALITY_SD)) { buttonStartStop.setEnabled(false); } else { @@ -1714,13 +1213,30 @@ else if (!CamcorderProfile.hasProfile(0, CamcorderProfile.QUALITY_VGA) && !Camco } } + private void broadcastOnApplicationStopped() { + Intent broadcastIntent = new Intent(Constants.BROADCAST_ON_APPLICATION_STOPPED); + getContext().sendBroadcast(broadcastIntent); + } @Override public void onDestroyView() { super.onDestroyView(); + Log.d(TAG, "onDestroyView: Cleaning up resources"); + stopUpdatingInfo(); stopUpdatingClock(); - releaseCamera(); + + broadcastOnApplicationStopped(); + } + + public boolean isMyServiceRunning(Class serviceClass) { + ActivityManager manager = (ActivityManager) getActivity().getSystemService(Context.ACTIVITY_SERVICE); + for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { + if (serviceClass.getName().equals(service.service.getClassName())) { + return true; + } + } + return false; } } \ No newline at end of file diff --git a/app/src/main/java/com/fadcam/ui/LocationHelper.java b/app/src/main/java/com/fadcam/ui/LocationHelper.java index 91a3c9a..6cf04f2 100644 --- a/app/src/main/java/com/fadcam/ui/LocationHelper.java +++ b/app/src/main/java/com/fadcam/ui/LocationHelper.java @@ -7,9 +7,9 @@ import android.util.Log; import org.osmdroid.util.GeoPoint; +import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider; import org.osmdroid.views.overlay.mylocation.IMyLocationConsumer; import org.osmdroid.views.overlay.mylocation.IMyLocationProvider; -import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider; public class LocationHelper { diff --git a/app/src/main/java/com/fadcam/ui/RecordsFragment.java b/app/src/main/java/com/fadcam/ui/RecordsFragment.java index 6362ac8..8f67b83 100644 --- a/app/src/main/java/com/fadcam/ui/RecordsFragment.java +++ b/app/src/main/java/com/fadcam/ui/RecordsFragment.java @@ -1,33 +1,35 @@ package com.fadcam.ui; import android.annotation.SuppressLint; +import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; +import android.os.VibrationEffect; +import android.os.Vibrator; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import androidx.appcompat.app.AlertDialog; + import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; + import com.fadcam.R; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.floatingactionbutton.FloatingActionButton; + import java.io.File; import java.util.ArrayList; import java.util.List; -import android.os.VibrationEffect; -import android.os.Vibrator; -import android.content.Context; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; diff --git a/app/src/main/java/com/fadcam/ui/SettingsFragment.java b/app/src/main/java/com/fadcam/ui/SettingsFragment.java index fd800fe..afec5db 100644 --- a/app/src/main/java/com/fadcam/ui/SettingsFragment.java +++ b/app/src/main/java/com/fadcam/ui/SettingsFragment.java @@ -1,25 +1,20 @@ package com.fadcam.ui; import android.Manifest; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Configuration; -import android.hardware.camera2.CameraAccessException; -import android.hardware.camera2.CameraCharacteristics; -import android.hardware.camera2.CameraManager; -import android.hardware.camera2.params.StreamConfigurationMap; import android.media.CamcorderProfile; -import android.media.MediaRecorder; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.VibrationEffect; import android.os.Vibrator; import android.util.Log; -import android.util.Range; -import android.util.Size; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -33,7 +28,7 @@ import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; -import com.fadcam.Constantes; +import com.fadcam.Constants; import com.fadcam.MainActivity; import com.fadcam.R; import com.google.android.material.appbar.MaterialToolbar; @@ -58,17 +53,20 @@ public class SettingsFragment extends Fragment { private static final String QUALITY_FHD = "FHD"; static final String PREF_LOCATION_DATA = "location_data"; - private static final String PREFS_NAME = "app_prefs"; - private static final String LANGUAGE_KEY = "language"; - private static final int REQUEST_PERMISSIONS = 1; private static final String PREF_FIRST_LAUNCH = "first_launch"; + private Spinner qualitySpinner; private Spinner frameRateSpinner; + private Spinner watermarkSpinner; MaterialButtonToggleGroup cameraSelectionToggle; + View view; + private BroadcastReceiver broadcastOnRecordingStarted; + private BroadcastReceiver broadcastOnRecordingStopped; + private void vibrateTouch() { // Haptic Feedback Vibrator vibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); @@ -92,10 +90,63 @@ private void updateButtonAppearance(MaterialButton button, boolean isSelected) { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - sharedPreferences = requireActivity().getPreferences(Context.MODE_PRIVATE); + sharedPreferences = requireActivity().getSharedPreferences(Constants.PREFS_NAME, Context.MODE_PRIVATE); locationHelper = new LocationHelper(requireContext()); } + @Override + public void onStart() + { + super.onStart(); + + registerBroadcastOnRecordingStarted(); + registerBrodcastOnRecordingStopped(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // Android 11 and above + requireActivity().registerReceiver(broadcastOnRecordingStarted, new IntentFilter(Constants.BROADCAST_ON_RECORDING_STARTED), Context.RECEIVER_EXPORTED); + requireActivity().registerReceiver(broadcastOnRecordingStopped, new IntentFilter(Constants.BROADCAST_ON_RECORDING_STOPPED), Context.RECEIVER_EXPORTED); + } else { + // Below Android 11 + requireActivity().registerReceiver(broadcastOnRecordingStarted, new IntentFilter(Constants.BROADCAST_ON_RECORDING_STARTED)); + requireActivity().registerReceiver(broadcastOnRecordingStopped, new IntentFilter(Constants.BROADCAST_ON_RECORDING_STOPPED)); + } + } + + private void registerBroadcastOnRecordingStarted() { + broadcastOnRecordingStarted = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent i) + { + cameraSelectionToggle.setEnabled(false); + qualitySpinner.setEnabled(false); + frameRateSpinner.setEnabled(false); + watermarkSpinner.setEnabled(false); + } + }; + } + + private void registerBrodcastOnRecordingStopped() { + broadcastOnRecordingStopped = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent i) + { + cameraSelectionToggle.setEnabled(true); + qualitySpinner.setEnabled(true); + frameRateSpinner.setEnabled(true); + watermarkSpinner.setEnabled(true); + } + }; + } + + @Override + public void onStop() { + super.onStop(); + + requireActivity().unregisterReceiver(broadcastOnRecordingStarted); + requireActivity().unregisterReceiver(broadcastOnRecordingStopped); + } + @Override public void onResume() { super.onResume(); @@ -107,7 +158,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa view = inflater.inflate(R.layout.fragment_settings, container, false); // Initialize shared preferences - sharedPreferences = requireActivity().getPreferences(Context.MODE_PRIVATE); + sharedPreferences = requireActivity().getSharedPreferences(Constants.PREFS_NAME, Context.MODE_PRIVATE); // Setup language selection spinner items with array resource Spinner languageSpinner = view.findViewById(R.id.language_spinner); @@ -116,7 +167,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa languageAdapter .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); languageSpinner.setAdapter(languageAdapter ); - sharedPreferences = requireActivity().getPreferences(Context.MODE_PRIVATE); + sharedPreferences = requireActivity().getSharedPreferences(Constants.PREFS_NAME, Context.MODE_PRIVATE); MaterialToolbar toolbar = view.findViewById(R.id.topAppBar); @@ -125,7 +176,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa cameraSelectionToggle = view.findViewById(R.id.camera_selection_toggle); // Setup spinner items with array resource - Spinner qualitySpinner = view.findViewById(R.id.quality_spinner); + qualitySpinner = view.findViewById(R.id.quality_spinner); ArrayAdapter adapter = ArrayAdapter.createFromResource( getContext(), R.array.video_quality_options, android.R.layout.simple_spinner_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); @@ -143,7 +194,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa setupFrameRateSpinner(); // Setup watermark option spinner - Spinner watermarkSpinner = view.findViewById(R.id.watermark_spinner); + watermarkSpinner = view.findViewById(R.id.watermark_spinner); setupWatermarkSpinner(view, watermarkSpinner); // Set up spinner based on saved language preference @@ -176,7 +227,7 @@ private void openInAppBrowser(String url) { private void setupFramerateNoteText() { TextView frameworkNoteTextView = view.findViewById(R.id.framerate_note_textview); - frameworkNoteTextView.setText(getString(R.string.note_framerate, Constantes.DEFAULT_VIDEO_FRAMERATE)); + frameworkNoteTextView.setText(getString(R.string.note_framerate, Constants.DEFAULT_VIDEO_FRAME_RATE)); } private void setupLocationSwitch(MaterialSwitch locationSwitch) { @@ -267,8 +318,9 @@ private void syncCameraSwitch(View view, MaterialButtonToggleGroup cameraSelecti MaterialButton backCameraButton = view.findViewById(R.id.button_back_camera); MaterialButton frontCameraButton = view.findViewById(R.id.button_front_camera); - String currentCameraSelection = sharedPreferences.getString(Constantes.PREF_CAMERA_SELECTION, Constantes.CAMERA_BACK); - if (currentCameraSelection.equals(Constantes.CAMERA_FRONT)) { + String currentCameraSelection = sharedPreferences.getString(Constants.PREF_CAMERA_SELECTION, Constants.CAMERA_BACK); + + if (currentCameraSelection.equals(Constants.CAMERA_FRONT)) { cameraSelectionToggle.check(R.id.button_front_camera); updateButtonAppearance(frontCameraButton, true); updateButtonAppearance(backCameraButton, false); @@ -279,8 +331,8 @@ private void syncCameraSwitch(View view, MaterialButtonToggleGroup cameraSelecti } cameraSelectionToggle.addOnButtonCheckedListener((group, checkedId, isChecked) -> { if (isChecked) { - String selectedCamera = (checkedId == R.id.button_front_camera) ? Constantes.CAMERA_FRONT : Constantes.CAMERA_BACK; - sharedPreferences.edit().putString(Constantes.PREF_CAMERA_SELECTION, selectedCamera).apply(); + String selectedCamera = (checkedId == R.id.button_front_camera) ? Constants.CAMERA_FRONT : Constants.CAMERA_BACK; + sharedPreferences.edit().putString(Constants.PREF_CAMERA_SELECTION, selectedCamera).apply(); updateButtonAppearance(backCameraButton, checkedId == R.id.button_back_camera); updateButtonAppearance(frontCameraButton, checkedId == R.id.button_front_camera); } @@ -293,9 +345,9 @@ private void setupCameraSelectionToggle(View view, MaterialButtonToggleGroup cam MaterialButton backCameraButton = view.findViewById(R.id.button_back_camera); MaterialButton frontCameraButton = view.findViewById(R.id.button_front_camera); - String currentCameraSelection = sharedPreferences.getString(Constantes.PREF_CAMERA_SELECTION, Constantes.CAMERA_BACK); + String currentCameraSelection = sharedPreferences.getString(Constants.PREF_CAMERA_SELECTION, Constants.CAMERA_BACK); - if (currentCameraSelection.equals(Constantes.CAMERA_FRONT)) { + if (currentCameraSelection.equals(Constants.CAMERA_FRONT)) { cameraSelectionToggle.check(R.id.button_front_camera); updateButtonAppearance(frontCameraButton, true); updateButtonAppearance(backCameraButton, false); @@ -307,8 +359,8 @@ private void setupCameraSelectionToggle(View view, MaterialButtonToggleGroup cam cameraSelectionToggle.addOnButtonCheckedListener((group, checkedId, isChecked) -> { if (isChecked) { - String selectedCamera = (checkedId == R.id.button_front_camera) ? Constantes.CAMERA_FRONT : Constantes.CAMERA_BACK; - sharedPreferences.edit().putString(Constantes.PREF_CAMERA_SELECTION, selectedCamera).apply(); + String selectedCamera = (checkedId == R.id.button_front_camera) ? Constants.CAMERA_FRONT : Constants.CAMERA_BACK; + sharedPreferences.edit().putString(Constants.PREF_CAMERA_SELECTION, selectedCamera).apply(); updateButtonAppearance(backCameraButton, checkedId == R.id.button_back_camera); updateButtonAppearance(frontCameraButton, checkedId == R.id.button_front_camera); } @@ -338,7 +390,7 @@ public void onItemSelected(AdapterView parent, View view, int position, long selectedQuality = QUALITY_SD; break; } - sharedPreferences.edit().putString(Constantes.PREF_VIDEO_QUALITY, selectedQuality).apply(); + sharedPreferences.edit().putString(Constants.PREF_VIDEO_QUALITY, selectedQuality).apply(); updateFrameRateSpinner(); } @@ -360,7 +412,7 @@ private void setupFrameRateSpinner() { public void onItemSelected(AdapterView parent, View view, int position, long id) { List videoFrameratesCompatibles = getCompatiblesVideoFrameRates(getSharedPreferencesCameraSelection()); if(!videoFrameratesCompatibles.isEmpty()) { - sharedPreferences.edit().putInt(Constantes.PREF_VIDEO_FRAMERATE, videoFrameratesCompatibles.get(position)).apply(); + sharedPreferences.edit().putInt(Constants.PREF_VIDEO_FRAME_RATE, videoFrameratesCompatibles.get(position)).apply(); } } @@ -385,7 +437,7 @@ private void updateFrameRateSpinner() frameRateSpinner.setAdapter(adapter); // Set the selected item based on the saved preference - int selectedFramerate = sharedPreferences.getInt(Constantes.PREF_VIDEO_FRAMERATE, Constantes.DEFAULT_VIDEO_FRAMERATE); + int selectedFramerate = sharedPreferences.getInt(Constants.PREF_VIDEO_FRAME_RATE, Constants.DEFAULT_VIDEO_FRAME_RATE); if(!videoFrameratesCompatibles.isEmpty()) { frameRateSpinner.setSelection(videoFrameratesCompatibles.size() == 1 ? 0 : getVideoFrameRateIndex(selectedFramerate)); @@ -460,15 +512,15 @@ private void openUrl(String url) { } private void saveLanguagePreference(String languageCode) { - SharedPreferences prefs = requireActivity().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); + SharedPreferences prefs = requireActivity().getSharedPreferences(Constants.PREFS_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); - editor.putString(LANGUAGE_KEY, languageCode); + editor.putString(Constants.LANGUAGE_KEY, languageCode); editor.apply(); Log.d("SettingsFragment", "Language preference saved: " + languageCode); } private void setupLanguageSpinner(Spinner languageSpinner) { - String savedLanguageCode = sharedPreferences.getString(LANGUAGE_KEY, Locale.getDefault().getLanguage()); + String savedLanguageCode = sharedPreferences.getString(Constants.LANGUAGE_KEY, Locale.getDefault().getLanguage()); int selectedIndex = getLanguageIndex(savedLanguageCode); languageSpinner.setSelection(selectedIndex); @@ -560,7 +612,7 @@ private int getVideoFrameRateIndex(int frameRate) */ private int getVideoQualityIndex() { - String selectedQuality = sharedPreferences.getString(Constantes.PREF_VIDEO_QUALITY, QUALITY_HD); + String selectedQuality = sharedPreferences.getString(Constants.PREF_VIDEO_QUALITY, QUALITY_HD); int selectedIndex = 1; // Default to HD switch (selectedQuality) { @@ -582,7 +634,7 @@ private int getVideoQualityIndex() public List getCompatiblesVideoFrameRates(String camera) { // Get the selected quality from preferences - String selectedQuality = sharedPreferences.getString(Constantes.PREF_VIDEO_QUALITY, QUALITY_HD); + String selectedQuality = sharedPreferences.getString(Constants.PREF_VIDEO_QUALITY, QUALITY_HD); // Get the available frame rates int[] videoFrameRates = getResources().getIntArray(R.array.video_framerate_array); @@ -592,7 +644,7 @@ public List getCompatiblesVideoFrameRates(String camera) // Determine the frame rate limits based on the camera and quality int maxFrameRate = 0; - int cameraId = camera.equals(Constantes.CAMERA_FRONT) ? 1 : 0; + int cameraId = camera.equals(Constants.CAMERA_FRONT) ? 1 : 0; switch (selectedQuality) { case QUALITY_FHD: @@ -632,6 +684,6 @@ else if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) { private String getSharedPreferencesCameraSelection() { - return sharedPreferences.getString(Constantes.PREF_CAMERA_SELECTION, Constantes.CAMERA_BACK); + return sharedPreferences.getString(Constants.PREF_CAMERA_SELECTION, Constants.CAMERA_BACK); } } \ No newline at end of file diff --git a/app/src/main/java/com/fadcam/ui/VideoPlayerActivity.java b/app/src/main/java/com/fadcam/ui/VideoPlayerActivity.java index 9bfe5ab..dab9feb 100644 --- a/app/src/main/java/com/fadcam/ui/VideoPlayerActivity.java +++ b/app/src/main/java/com/fadcam/ui/VideoPlayerActivity.java @@ -2,15 +2,14 @@ import android.net.Uri; import android.os.Bundle; -import android.view.View; import android.widget.ImageButton; import androidx.appcompat.app.AppCompatActivity; +import com.fadcam.R; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.ui.StyledPlayerView; -import com.fadcam.R; public class VideoPlayerActivity extends AppCompatActivity { diff --git a/app/src/main/java/com/fadcam/ui/WebViewActivity.java b/app/src/main/java/com/fadcam/ui/WebViewActivity.java index fa49c3f..72d3c0a 100644 --- a/app/src/main/java/com/fadcam/ui/WebViewActivity.java +++ b/app/src/main/java/com/fadcam/ui/WebViewActivity.java @@ -5,6 +5,7 @@ import android.os.Bundle; import android.webkit.WebView; import android.webkit.WebViewClient; + import androidx.appcompat.app.AppCompatActivity; import com.fadcam.R; diff --git a/app/src/main/res/layout/dialog_rename.xml b/app/src/main/res/layout/dialog_rename.xml index 15f081c..6d17694 100644 --- a/app/src/main/res/layout/dialog_rename.xml +++ b/app/src/main/res/layout/dialog_rename.xml @@ -1,6 +1,5 @@