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..f36e6a9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -73,6 +73,8 @@ @@ -91,8 +93,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..9842024 --- /dev/null +++ b/app/src/main/java/com/fadcam/Constants.java @@ -0,0 +1,40 @@ +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_RECORDING_STATE_REQUEST = "com.fadcam.ON_RECORDING_STATE_REQUEST"; + public static final String BROADCAST_ON_RECORDING_STATE_CALLBACK = "com.fadcam.ON_RECORDING_STATE_CALLBACK"; + + public static final String BROADCAST_EXTRA_RECORDING_STATE = "RECORDING_STATE"; + public static final String BROADCAST_EXTRA_RECORDING_START_TIME = "RECORDING_START_TIME"; + + 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"; + + public static final String RECORDING_DIRECTORY = "FadCam"; + public static final String RECORDING_FILE_EXTENSION = "mp4"; +} diff --git a/app/src/main/java/com/fadcam/MainActivity.java b/app/src/main/java/com/fadcam/MainActivity.java index 245ca23..3c42f43 100644 --- a/app/src/main/java/com/fadcam/MainActivity.java +++ b/app/src/main/java/com/fadcam/MainActivity.java @@ -20,26 +20,23 @@ 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 - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); setContentView(R.layout.activity_main); //force dark theme even on light themed devices + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); //force dark theme even on light themed devices // Check if current locale is Pashto if (getResources().getConfiguration().locale.getLanguage().equals("ps")) { getWindow().getDecorView().setLayoutDirection(View.LAYOUT_DIRECTION_LTR); } - + setContentView(R.layout.activity_main); viewPager = findViewById(R.id.view_pager); @@ -90,8 +87,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) { @@ -105,12 +100,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()); @@ -120,4 +111,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..2dd345e 100644 --- a/app/src/main/java/com/fadcam/RecordingService.java +++ b/app/src/main/java/com/fadcam/RecordingService.java @@ -1,11 +1,16 @@ package com.fadcam; -import android.app.Notification; +import static android.hardware.camera2.CameraDevice.StateCallback; +import static android.hardware.camera2.CameraDevice.TEMPLATE_RECORD; + import android.app.NotificationChannel; import android.app.NotificationManager; +import android.app.PendingIntent; import android.app.Service; +import android.content.Context; import android.content.Intent; -import android.graphics.SurfaceTexture; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; @@ -13,131 +18,788 @@ 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 android.widget.Toast; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; + +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 boolean isRecording = false; + + 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 RecordingState recordingState = RecordingState.NONE; + + public boolean isRecording() { + return recordingState.equals(RecordingState.IN_PROGRESS); + } + + public boolean isPaused() { + return recordingState.equals(RecordingState.PAUSED); + } + + public boolean isWorkingInProgress() { return !recordingState.equals(RecordingState.NONE) || isProcessingWatermark; } + + private boolean isProcessingWatermark = false; + + private long recordingStartTime; @Override public void onCreate() { super.onCreate(); + + sharedPreferences = getApplicationContext().getSharedPreferences(Constants.PREFS_NAME, Context.MODE_PRIVATE); + + locationHelper = new LocationHelper(getApplicationContext()); + createNotificationChannel(); + Log.d(TAG, "Service created"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { - String action = intent.getAction(); - if (action != null) { - switch (action) { - case "ACTION_START_RECORDING": - startRecording(); - break; - case "ACTION_STOP_RECORDING": - stopRecording(); - break; + // Checks if the intent is null + if (intent != null) { + String action = intent.getAction(); + if (action != null) { + switch (action) { + case Constants.INTENT_ACTION_START_RECORDING: + setupSurfaceTexture(intent); + startRecording(); + break; + case Constants.INTENT_ACTION_PAUSE_RECORDING: + pauseRecording(); + break; + case Constants.INTENT_ACTION_RESUME_RECORDING: + setupSurfaceTexture(intent); + resumeRecording(); + break; + case Constants.INTENT_ACTION_CHANGE_SURFACE: + setupSurfaceTexture(intent); + createCameraPreviewSession(); + break; + case Constants.INTENT_ACTION_STOP_RECORDING: + stopRecording(); + break; + case Constants.BROADCAST_ON_RECORDING_STATE_REQUEST: + broadcastOnRecordingStateCallback(); + + if (!isWorkingInProgress()) { + stopSelf(); + } + break; + } } } 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(); + Log.d(TAG, "Service destroyed"); + + cancelNotification(); + stopRecording(); + } + + @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), Constants.RECORDING_DIRECTORY); + if (!videoDir.exists()) { + if (videoDir.mkdirs()) { + Log.d(TAG, "setupMediaRecorder: Directory created successfully"); + } else { + Log.e(TAG, "setupMediaRecorder: Failed to create directory"); + } + } + + // 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 + "." + Constants.RECORDING_FILE_EXTENSION); + + // 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; + } + + if(cameraDevice == null) { + openCamera(); + return; + } + + try { + captureRequestBuilder = cameraDevice.createCaptureRequest(TEMPLATE_RECORD); + + List surfaces = new ArrayList<>(); + Surface recorderSurface = mediaRecorder.getSurface(); + surfaces.add(recorderSurface); + + if(previewSurface != null && previewSurface.isValid()) { + 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 CaptureSessionCallback(), null); + } catch (CameraAccessException e) { + Log.e(TAG, "createCameraPreviewSession: Error while creating capture session", e); + } + } + + public class CaptureSessionCallback extends CameraCaptureSession.StateCallback { + @Override + public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { + captureSession = cameraCaptureSession; + + try { + cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); + } catch (CameraAccessException | IllegalArgumentException e) { + Log.e(TAG, "onConfigured: Error setting repeating request", e); + e.printStackTrace(); + } catch (IllegalStateException e) { + Log.e(TAG, "onConfigured: Error camera session", e); + e.printStackTrace(); + } + + if (recordingState.equals(RecordingState.NONE)) { + recordingStartTime = SystemClock.elapsedRealtime(); + mediaRecorder.start(); + setupRecordingInProgressNotification(); + recordingState = RecordingState.IN_PROGRESS; + broadcastOnRecordingStarted(); + } else if (recordingState.equals(RecordingState.PAUSED)) { + mediaRecorder.resume(); + setupRecordingInProgressNotification(); + recordingState = RecordingState.IN_PROGRESS; + showRecordingResumedToast(); + broadcastOnRecordingResumed(); + } + } + + @Override + public void onConfigureFailed(@NonNull CameraCaptureSession session) { + Log.e(TAG, "onConfigureFailed: Failed to configure capture session"); + } + } + + private void broadcastOnRecordingStarted() { + Intent broadcastIntent = new Intent(Constants.BROADCAST_ON_RECORDING_STARTED); + broadcastIntent.putExtra(Constants.BROADCAST_EXTRA_RECORDING_START_TIME, recordingStartTime); + broadcastIntent.putExtra(Constants.BROADCAST_EXTRA_RECORDING_STATE, recordingState); + 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 broadcastOnRecordingStateCallback() { + Intent broadcastIntent = new Intent(Constants.BROADCAST_ON_RECORDING_STATE_CALLBACK); + broadcastIntent.putExtra(Constants.BROADCAST_EXTRA_RECORDING_STATE, recordingState); + 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(); + cameraDevice = null; + + if(isRecording()) { + Log.e(TAG, "onDisconnected: Camera paused"); + pauseRecording(); + } else { + Log.e(TAG, "onDisconnected: Camera closing"); + } + } + + @Override + public void onError(@NonNull CameraDevice camera, int error) { + Log.e(TAG, "onError: Camera error: " + error); + + cameraDevice.close(); + cameraDevice = null; + + if(isRecording()) { + Log.e(TAG, "onError: Camera paused"); + pauseRecording(); + } else { + Log.e(TAG, "onError: Camera closing"); + } + } + }, 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); + + setupMediaRecorder(); + + if (mediaRecorder == null) { + Log.e(TAG, "startRecording: MediaRecorder is not initialized"); + return; + } + + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + Log.e(TAG, "startRecording: External storage not available, cannot start recording."); + return; + } + + openCamera(); + } + + private void resumeRecording() + { + if(cameraDevice != null) { + mediaRecorder.resume(); + setupRecordingInProgressNotification(); + recordingState = RecordingState.IN_PROGRESS; + showRecordingResumedToast(); + broadcastOnRecordingResumed(); } else { - Log.d(TAG, "Recording already in progress"); + openCamera(); } } + private void pauseRecording() + { + mediaRecorder.pause(); + + recordingState = RecordingState.PAUSED; + + setupRecordingResumeNotification(); + + showRecordingInPausedToast(); + + broadcastOnRecordingPaused(); + + Toast.makeText(this, R.string.video_recording_paused, Toast.LENGTH_SHORT).show(); + } + private void stopRecording() { + + if(recordingState.equals(RecordingState.NONE)) + { + return; + } + 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"); + + if (mediaRecorder != null) { + try { + mediaRecorder.resume(); + mediaRecorder.stop(); + mediaRecorder.reset(); + } catch (IllegalStateException e) { + Log.e(TAG, "stopRecording: Error while stopping the recording", e); + } finally { + mediaRecorder.release(); + mediaRecorder = null; + Log.d(TAG, "stopRecording: Recording stopped"); + stopForeground(true); + } + } + + if (captureSession != null) { + captureSession.close(); + captureSession = null; + } + + if (cameraDevice != null) { + cameraDevice.close(); + cameraDevice = null; + } + + recordingState = RecordingState.NONE; + + cancelNotification(); + + processLatestVideoFileWithWatermark(); + + broadcastOnRecordingStopped(); + + Toast.makeText(this, R.string.video_recording_stopped, Toast.LENGTH_SHORT).show(); + + if(!isWorkingInProgress()) { + stopSelf(); + } + } + + private void processLatestVideoFileWithWatermark() { + isProcessingWatermark = true; + 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."); + } + isProcessingWatermark = false; + } + + 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().scheduleWithFixedDelay(this::checkAndDeleteSpecificTempFile, 0, CHECK_INTERVAL_MS, TimeUnit.MILLISECONDS); + } + + private void checkAndDeleteSpecificTempFile() { + if (tempFileBeingProcessed != null) { + // Construct FADCAM_ filename with the same timestamp + String outputFilePath = tempFileBeingProcessed.getParent() + "/" + Constants.RECORDING_DIRECTORY + "_" + 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 { - Log.e(TAG, "NotificationManager is null, unable to cancel notification"); + // FADCAM_ file does not exist yet + Log.d(TAG, "Matching " + Constants.RECORDING_DIRECTORY + "_ 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), Constants.RECORDING_DIRECTORY); + 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 PendingIntent createResumeRecordingIntent() { + Intent stopIntent = new Intent(this, RecordingService.class); + stopIntent.setAction(Constants.INTENT_ACTION_RESUME_RECORDING); + return PendingIntent.getService(this, 0, stopIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + } + + private void setupRecordingInProgressNotification() { + + if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + return; + } + + NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle(getString(R.string.notification_video_recording)) + .setContentText(getString(R.string.notification_video_recording_progress_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); - stopSelf(); // Stop the service - Log.d(TAG, "Service stopped"); + if(recordingState.equals(RecordingState.NONE)) { + startForeground(NOTIFICATION_ID, builder.build()); } else { - Log.d(TAG, "No recording to stop from recording service"); + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); + notificationManager.notify(NOTIFICATION_ID, builder.build()); } } + private void setupRecordingResumeNotification() { + + if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + return; + } + + NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle(getString(R.string.notification_video_recording)) + .setContentText(getString(R.string.notification_video_recording_paused_description)) + .setSmallIcon(R.drawable.unknown_icon3) + .setContentIntent(createOpenAppIntent()) + .setAutoCancel(false) + .addAction(new NotificationCompat.Action( + R.drawable.ic_play, + getString(R.string.button_resume), + createResumeRecordingIntent())) + .setPriority(NotificationCompat.PRIORITY_LOW); + + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); + notificationManager.notify(NOTIFICATION_ID, 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 void cancelNotification() { + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); + notificationManager.cancel(NOTIFICATION_ID); + } + + private void showRecordingResumedToast() { + Toast.makeText(getApplicationContext(), getText(R.string.video_recording_resumed), Toast.LENGTH_SHORT).show(); + } + + private void showRecordingInPausedToast() { + Toast.makeText(getApplicationContext(), getText(R.string.video_recording_paused), Toast.LENGTH_SHORT).show(); + } + + 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/RecordingState.java b/app/src/main/java/com/fadcam/RecordingState.java new file mode 100644 index 0000000..5d2c7fd --- /dev/null +++ b/app/src/main/java/com/fadcam/RecordingState.java @@ -0,0 +1,9 @@ +package com.fadcam; + +import java.io.Serializable; + +public enum RecordingState implements Serializable { + IN_PROGRESS, + PAUSED, + NONE +} 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..86626db 100644 --- a/app/src/main/java/com/fadcam/ui/HomeFragment.java +++ b/app/src/main/java/com/fadcam/ui/HomeFragment.java @@ -1,22 +1,6 @@ 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; @@ -31,13 +15,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 +32,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,12 +48,10 @@ 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.fadcam.RecordingState; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import java.io.File; @@ -95,36 +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,16 +94,11 @@ 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; private Button buttonPauseResume; private Button buttonCamSwitch; - private boolean isPaused = false; private boolean isPreviewEnabled = true; private View cardPreview; @@ -150,47 +106,56 @@ 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 final 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 RecordingState recordingState = RecordingState.NONE; + + private BroadcastReceiver broadcastOnRecordingStarted; + private BroadcastReceiver broadcastOnRecordingResumed; + private BroadcastReceiver broadcastOnRecordingPaused; + private BroadcastReceiver broadcastOnRecordingStopped; + private BroadcastReceiver broadcastOnRecordingStateCallback; + // 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 +172,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 +206,6 @@ private void releaseWakeLock() { } } - - - private void initializeMessages() { messageQueue = new ArrayList<>(Arrays.asList(getResources().getStringArray(R.array.easter_eggs_array))); recentlyShownMessages = new ArrayList<>(); @@ -281,10 +239,9 @@ private void showRandomMessage() { } } - private void setupLongPressListener() { cardPreview.setOnLongClickListener(v -> { - if (isRecording) { + if (isRecording()) { // Start scaling down animation cardPreview.animate() .scaleX(0.9f) @@ -348,9 +305,8 @@ private void setupLongPressListener() { }); } - private void updatePreviewVisibility() { - if (isRecording) { + if (isRecording()) { if (isPreviewEnabled) { textureView.setVisibility(View.VISIBLE); tvPreviewPlaceholder.setVisibility(View.GONE); @@ -364,31 +320,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,9 +330,9 @@ 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); + Log.d(TAG, "HomeFragment created."); // Request essential permissions on every launch @@ -416,30 +347,225 @@ public void onCreate(Bundle savedInstanceState) { // // Set first launch to false // sharedPreferences.edit().putBoolean(PREF_FIRST_LAUNCH, false).apply(); // } - } @Override public void onStart() { super.onStart(); + + if(!textureView.isAvailable()) { + textureView.setVisibility(View.VISIBLE); + } + + registerBroadcastOnRecordingStarted(); + registerBroadcastOnRecordingResumed(); + registerBroadcastOnRecordingPaused(); + registerBrodcastOnRecordingStopped(); + registerBroadcastOnRecordingStateCallback(); + + 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); + requireActivity().registerReceiver(broadcastOnRecordingStateCallback, new IntentFilter(Constants.BROADCAST_ON_RECORDING_STATE_CALLBACK), 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)); + requireActivity().registerReceiver(broadcastOnRecordingStateCallback, new IntentFilter(Constants.BROADCAST_ON_RECORDING_STATE_CALLBACK)); + } + //fetch Camera status - String currentCameraSelection = sharedPreferences.getString(Constantes.PREF_CAMERA_SELECTION, Constantes.CAMERA_BACK); + String currentCameraSelection = getCameraSelection(); Toast.makeText(getContext(), this.getString(R.string.current_camera) + ": " + currentCameraSelection, Toast.LENGTH_SHORT).show(); } + private void fetchRecordingState() + { + Intent startIntent = new Intent(getActivity(), RecordingService.class); + startIntent.setAction(Constants.BROADCAST_ON_RECORDING_STATE_REQUEST); + requireActivity().startService(startIntent); + } + + private void registerBroadcastOnRecordingStateCallback() { + broadcastOnRecordingStateCallback = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent i) + { + RecordingState recordingStateIntent = (RecordingState) i.getSerializableExtra(Constants.BROADCAST_EXTRA_RECORDING_STATE); + if (recordingStateIntent == null) { + recordingStateIntent = RecordingState.NONE; + } + + switch(recordingStateIntent) { + case NONE: + onRecordingStopped(); + break; + case IN_PROGRESS: + if(isRecording()) { + updateRecordingSurface(); + } else { + onRecordingStarted(false); + updateRecordingSurface(); + } + break; + case PAUSED: + onRecordingPaused(); + break; + } + + recordingState = recordingStateIntent; + } + }; + } + + private void registerBroadcastOnRecordingStarted() { + broadcastOnRecordingStarted = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent i) + { + recordingStartTime = i.getLongExtra(Constants.BROADCAST_EXTRA_RECORDING_START_TIME, 0); + + onRecordingStarted(true); + } + }; + } + + 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(); + } + }; + } + + private void onRecordingStarted(boolean toast) { + recordingState = RecordingState.IN_PROGRESS; + + acquireWakeLock(); + + 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(); + + if(toast) { + vibrateTouch(); + Toast.makeText(getContext(), R.string.video_recording_started, Toast.LENGTH_SHORT).show(); + } + } + + private void onRecordingResumed() + { + recordingState = RecordingState.IN_PROGRESS; + + buttonPauseResume.setText(getString(R.string.button_pause)); + buttonPauseResume.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_pause, 0, 0, 0); + buttonPauseResume.setEnabled(true); + + buttonStartStop.setText(getString(R.string.button_stop)); + buttonStartStop.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_stop, 0, 0, 0); + buttonStartStop.setEnabled(true); + + buttonCamSwitch.setEnabled(false); + + startUpdatingInfo(); + } + + private void onRecordingPaused() + { + recordingState = RecordingState.PAUSED; + + buttonPauseResume.setText(getString(R.string.button_resume)); + buttonPauseResume.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_play, 0, 0, 0); + buttonPauseResume.setEnabled(true); + + buttonCamSwitch.setEnabled(false); + + buttonStartStop.setText(getString(R.string.button_stop)); + buttonStartStop.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_stop, 0, 0, 0); + } + + private void onRecordingStopped() { + + recordingState = RecordingState.NONE; + + releaseWakeLock(); + + buttonStartStop.setText(getString(R.string.button_start)); + buttonStartStop.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_play, 0, 0, 0); + + setupStartStopButton(); + + buttonPauseResume.setText(getString(R.string.button_pause)); + buttonPauseResume.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_pause, 0, 0, 0); + buttonPauseResume.setEnabled(false); + + buttonCamSwitch.setEnabled(true); + + updatePreviewVisibility(); + + stopUpdatingInfo(); + } + + @Override + public void onStop() { + super.onStop(); + + Log.e(TAG, "HomeFragment stopped"); + + if(isRecording()) { + Intent recordingIntent = new Intent(getActivity(), RecordingService.class); + recordingIntent.setAction(Constants.INTENT_ACTION_CHANGE_SURFACE); + requireActivity().startService(recordingIntent); + } + + requireActivity().unregisterReceiver(broadcastOnRecordingStarted); + requireActivity().unregisterReceiver(broadcastOnRecordingResumed); + requireActivity().unregisterReceiver(broadcastOnRecordingPaused); + requireActivity().unregisterReceiver(broadcastOnRecordingStopped); + requireActivity().unregisterReceiver(broadcastOnRecordingStateCallback); + } + @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); - } - setupStartStopButton(); + fetchRecordingState(); updateStats(); } @@ -447,14 +573,8 @@ public void onResume() { @Override public void onPause() { super.onPause(); - locationHelper.stopLocationUpdates(); + //locationHelper.stopLocationUpdates(); Log.d(TAG, "HomeFragment paused."); - - getActivity().unregisterReceiver(recordingStateReceiver); - } - - private String getLocationData() { - return locationHelper.getLocationData(); } // @Override @@ -513,7 +633,7 @@ private void savePreviewState() { // function to use haptic feedbacks private void vibrateTouch() { // Haptic Feedback - Vibrator vibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); + Vibrator vibrator = (Vibrator) requireContext().getSystemService(Context.VIBRATOR_SERVICE); if (vibrator != null && vibrator.hasVibrator()) { VibrationEffect effect = null; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { @@ -531,12 +651,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); + tvTip = view.findViewById(R.id.tvTip); tvStats = view.findViewById(R.id.tvStats); @@ -557,8 +680,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 +700,44 @@ 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); + + fetchRecordingState(); + } + + @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 +762,6 @@ private void debugPermissionsStatus() { (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED ? "Granted" : "Denied")); } - private void setupButtonListeners() { buttonStartStop.setOnClickListener(v -> { debugPermissionsStatus(); @@ -617,7 +769,7 @@ private void setupButtonListeners() { debugPermissionsStatus(); showPermissionsInfoDialog(); } else { - if (!isRecording) { + if (recordingState.equals(RecordingState.NONE)) { startRecording(); } else { stopRecording(); @@ -627,16 +779,12 @@ private void setupButtonListeners() { }); buttonPauseResume.setOnClickListener(v -> { - if (isRecording) { - if (isPaused) { - vibrateTouch(); - Toast.makeText(getContext(), R.string.video_recording_resumed, Toast.LENGTH_SHORT).show(); - resumeRecording(); - } else { - vibrateTouch(); - Toast.makeText(getContext(), R.string.video_recording_paused, Toast.LENGTH_SHORT).show(); - pauseRecording(); - } + if (isPaused()) { + vibrateTouch(); + resumeRecording(); + } else { + vibrateTouch(); + pauseRecording(); } }); @@ -645,21 +793,57 @@ 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); + startIntent.setAction(Constants.INTENT_ACTION_START_RECORDING); + + if(surfaceTexture != null) { + startIntent.putExtra("SURFACE", new Surface(surfaceTexture)); + } + + requireActivity().startService(startIntent); + } + + private void updateRecordingSurface() + { + SurfaceTexture surfaceTexture = textureView.getSurfaceTexture(); + + tvPreviewPlaceholder.setVisibility(View.GONE); + textureView.setVisibility(View.VISIBLE); + + Intent startIntent = new Intent(getActivity(), RecordingService.class); + startIntent.setAction(Constants.INTENT_ACTION_CHANGE_SURFACE); + + if(surfaceTexture != null) { + startIntent.putExtra("SURFACE", new Surface(surfaceTexture)); + } + + requireActivity().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 +856,6 @@ private void setupClockLongPressListener() { }); } - private void addWobbleAnimation() { // Define the scale down and scale up values float scaleDown = 0.9f; @@ -722,9 +905,8 @@ public void onAnimationEnd(Animator animation) { scaleDownSet.start(); } - private void showDisplayOptionsDialog() { - new MaterialAlertDialogBuilder(getContext()) + new MaterialAlertDialogBuilder(requireContext()) .setTitle(getString(R.string.dialog_clock_title)) .setSingleChoiceItems(new String[]{ getString(R.string.dialog_clock_timeonly), @@ -740,27 +922,23 @@ private void showDisplayOptionsDialog() { } private int getCurrentDisplayOption() { - SharedPreferences prefs = getActivity().getSharedPreferences("AppPreferences", Context.MODE_PRIVATE); - return prefs.getInt("display_option", 2); // Default to "Everything" + return requireActivity().getSharedPreferences("AppPreferences", Context.MODE_PRIVATE).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 = requireActivity().getSharedPreferences("AppPreferences", Context.MODE_PRIVATE).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); + SharedPreferences prefs = requireActivity().getSharedPreferences("AppPreferences", Context.MODE_PRIVATE); int displayOption = prefs.getInt("display_option", 2); // Default to "Everything" // Update the time @@ -787,7 +965,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 +981,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; @@ -857,24 +1034,23 @@ private String getRecordingTimeEstimate(long availableBytes, long bitrate) { // update storage and stats in real time while recording is started private void startUpdatingInfo() { - Log.d(TAG, "startUpdatingInfo: Beginning real-time updates"); updateInfoRunnable = new Runnable() { @Override public void run() { - if (isRecording) { + if (isRecording() && isAdded()) { updateStorageInfo(); updateStats(); - handler.postDelayed(this, 1000); // Update every 3 seconds + handlerClock.postDelayed(this, 1000); // Update every second } } }; - 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 +1065,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,10 +1089,9 @@ public void run() { handler.post(runnable); } - private void updateStats() { Log.d(TAG, "updateStats: Updating video statistics"); - File recordsDir = new File(getContext().getExternalFilesDir(null), "FadCam"); + File recordsDir = new File(requireContext().getExternalFilesDir(null), Constants.RECORDING_DIRECTORY); int numVideos = 0; long totalSize = 0; @@ -924,7 +1099,7 @@ private void updateStats() { File[] files = recordsDir.listFiles(); if (files != null) { for (File file : files) { - if (file.isFile() && file.getName().endsWith(".mp4")) { + if (file.isFile() && file.getName().endsWith("." + Constants.RECORDING_FILE_EXTENSION)) { numVideos++; totalSize += file.length(); } @@ -941,123 +1116,46 @@ 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); - } - } + buttonPauseResume.setText(R.string.button_resume); + buttonPauseResume.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_play, 0, 0, 0); - 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); - } - } - } - }; - + buttonCamSwitch.setEnabled(false); - private void startRecording() { - Log.d(TAG, "startRecording: Initiating video recording from home fragment"); + Intent stopIntent = new Intent(getActivity(), RecordingService.class); + stopIntent.setAction(Constants.INTENT_ACTION_PAUSE_RECORDING); + requireActivity().startService(stopIntent); + } - // Acquire wake lock to prevent the device from sleeping - acquireWakeLock(); + private void resumeRecording() { + Log.d(TAG, "resumeRecording: Resuming video recording"); - // Set up the camera and MediaRecorder here - if (!isRecording) { - resetTimers(); - if (cameraDevice == null) { - openCamera(); - } else { - startRecordingVideo(); - } - recordingStartTime = SystemClock.elapsedRealtime(); - setVideoBitrate(); + buttonPauseResume.setText(getString(R.string.button_pause)); + buttonPauseResume.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_pause, 0, 0, 0); + buttonPauseResume.setEnabled(false); - 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); + buttonCamSwitch.setEnabled(false); - startUpdatingInfo(); - isRecording = true; - updatePreviewVisibility(); + SurfaceTexture surfaceTexture = textureView.getSurfaceTexture(); - // Start the recording service - Intent startIntent = new Intent(getActivity(), RecordingService.class); - startIntent.setAction("ACTION_START_RECORDING"); - getActivity().startService(startIntent); + Intent recordingServiceIntent = new Intent(getActivity(), RecordingService.class); + recordingServiceIntent.setAction(Constants.INTENT_ACTION_RESUME_RECORDING); + if(surfaceTexture != null) { + recordingServiceIntent.putExtra("SURFACE", new Surface(surfaceTexture)); } + requireActivity().startService(recordingServiceIntent); } - -//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 +1166,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(); - } - } - - 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(); - } + return sharedPreferences.getString(Constants.PREF_VIDEO_QUALITY, Constants.QUALITY_HD); } - 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"); - 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); - } - - -//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 - ); + stopIntent.setAction(Constants.INTENT_ACTION_STOP_RECORDING); + requireActivity().startService(stopIntent); - executeFFmpegCommand(ffmpegCommand); + vibrateTouch(); } - - - 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 +1221,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 +1259,6 @@ public void switchCamera() { // } // } - private void copyFile(InputStream in, OutputStream out) throws IOException { byte[] buffer = new byte[1024]; int read; @@ -1691,22 +1269,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 +1292,21 @@ else if (!CamcorderProfile.hasProfile(0, CamcorderProfile.QUALITY_VGA) && !Camco } } - @Override public void onDestroyView() { super.onDestroyView(); + Log.d(TAG, "onDestroyView: Cleaning up resources"); + stopUpdatingInfo(); stopUpdatingClock(); - releaseCamera(); + } + + public boolean isRecording() { + return recordingState.equals(RecordingState.IN_PROGRESS); + } + + public boolean isPaused() { + return recordingState.equals(RecordingState.PAUSED); } } \ 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/RecordsAdapter.java b/app/src/main/java/com/fadcam/ui/RecordsAdapter.java index 2e4cc46..f90a5b6 100644 --- a/app/src/main/java/com/fadcam/ui/RecordsAdapter.java +++ b/app/src/main/java/com/fadcam/ui/RecordsAdapter.java @@ -21,6 +21,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; +import com.fadcam.Constants; import com.fadcam.R; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.textfield.TextInputEditText; @@ -185,8 +186,8 @@ private void saveToGallery(Context context, File video) { private void saveToGalleryAndroid10Plus(Context context, File video) { ContentValues values = new ContentValues(); values.put(MediaStore.MediaColumns.DISPLAY_NAME, video.getName()); - values.put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4"); - values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + File.separator + "FadCam"); + values.put(MediaStore.MediaColumns.MIME_TYPE, "video/" + Constants.RECORDING_FILE_EXTENSION); + values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + File.separator + Constants.RECORDING_DIRECTORY); ContentResolver resolver = context.getContentResolver(); Uri collection = null; @@ -214,7 +215,7 @@ private void saveToGalleryAndroid10Plus(Context context, File video) { } private void saveToGalleryLegacy(Context context, File video) { - File destDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "FadCam"); + File destDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), Constants.RECORDING_DIRECTORY); if (!destDir.exists()) { destDir.mkdirs(); } @@ -272,7 +273,7 @@ private void renameVideo(int position, String newName) { String formattedName = newName.trim().replace(" ", "_"); File oldFile = videoFiles.get(position); - File newFile = new File(oldFile.getParent(), formattedName + ".mp4"); + File newFile = new File(oldFile.getParent(), formattedName + "." + Constants.RECORDING_FILE_EXTENSION); if (oldFile.renameTo(newFile)) { // Update the list and notify the adapter diff --git a/app/src/main/java/com/fadcam/ui/RecordsFragment.java b/app/src/main/java/com/fadcam/ui/RecordsFragment.java index 6362ac8..37e915d 100644 --- a/app/src/main/java/com/fadcam/ui/RecordsFragment.java +++ b/app/src/main/java/com/fadcam/ui/RecordsFragment.java @@ -1,33 +1,36 @@ 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.Constants; 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; @@ -85,7 +88,7 @@ private void setupFabListeners() { private void toggleViewMode() { // Haptic Feedback - Vibrator vibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); + Vibrator vibrator = (Vibrator) requireContext().getSystemService(Context.VIBRATOR_SERVICE); if (vibrator != null && vibrator.hasVibrator()) { VibrationEffect effect = null; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { @@ -114,14 +117,14 @@ private void loadRecordsList() { private List getRecordsList() { List recordsList = new ArrayList<>(); - File recordsDir = new File(getContext().getExternalFilesDir(null), "FadCam"); + File recordsDir = new File(requireContext().getExternalFilesDir(null), Constants.RECORDING_DIRECTORY); if (recordsDir.exists()) { // Introduce a delay before refreshing the list new Handler(Looper.getMainLooper()).postDelayed(this::loadRecordsList, 500); File[] files = recordsDir.listFiles(); if (files != null) { for (File file : files) { - if (file.isFile() && file.getName().endsWith(".mp4")) { + if (file.isFile() && file.getName().endsWith("." + Constants.RECORDING_FILE_EXTENSION)) { recordsList.add(file); } } @@ -152,7 +155,7 @@ public void onVideoLongClick(File video, boolean isSelected) { } updateDeleteButtonVisibility(); // Haptic Feedback - Vibrator vibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); + Vibrator vibrator = (Vibrator) requireContext().getSystemService(Context.VIBRATOR_SERVICE); if (vibrator != null && vibrator.hasVibrator()) { VibrationEffect effect = null; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { @@ -170,7 +173,7 @@ private void updateDeleteButtonVisibility() { private void confirmDeleteSelected() { // Haptic Feedback - Vibrator vibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); + Vibrator vibrator = (Vibrator) requireContext().getSystemService(Context.VIBRATOR_SERVICE); if (vibrator != null && vibrator.hasVibrator()) { VibrationEffect effect = null; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { @@ -214,7 +217,7 @@ public boolean onOptionsItemSelected(MenuItem item) { private void confirmDeleteAll() { // Haptic Feedback - Vibrator vibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); + Vibrator vibrator = (Vibrator) requireContext().getSystemService(Context.VIBRATOR_SERVICE); if (vibrator != null && vibrator.hasVibrator()) { VibrationEffect effect = null; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { @@ -233,12 +236,12 @@ private void confirmDeleteAll() { } private void deleteAllVideos() { - File recordsDir = new File(getContext().getExternalFilesDir(null), "FadCam"); + File recordsDir = new File(requireContext().getExternalFilesDir(null), Constants.RECORDING_DIRECTORY); if (recordsDir.exists()) { File[] files = recordsDir.listFiles(); if (files != null) { for (File file : files) { - if (file.isFile() && file.getName().endsWith(".mp4")) { + if (file.isFile() && file.getName().endsWith("." + Constants.RECORDING_FILE_EXTENSION)) { file.delete(); } } diff --git a/app/src/main/java/com/fadcam/ui/SettingsFragment.java b/app/src/main/java/com/fadcam/ui/SettingsFragment.java index 421dec2..fd295c7 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,20 +53,23 @@ 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); + Vibrator vibrator = (Vibrator) requireContext().getSystemService(Context.VIBRATOR_SERVICE); if (vibrator != null && vibrator.hasVibrator()) { VibrationEffect effect = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { @@ -82,20 +80,74 @@ private void vibrateTouch() { } } } + private void updateButtonAppearance(MaterialButton button, boolean isSelected) { button.setIconTintResource(isSelected ? R.color.black : android.R.color.transparent); // color for check icon button.setStrokeColorResource(isSelected ? R.color.colorPrimary : R.color.material_on_surface_stroke); // the last color is for the button that's not selected - button.setTextColor(getResources().getColor(isSelected ? R.color.black : R.color.material_on_surface_emphasis_medium)); - button.setBackgroundColor(getResources().getColor(isSelected ? R.color.colorPrimary : android.R.color.transparent)); + button.setTextColor(ContextCompat.getColor(requireContext(), isSelected ? R.color.black : R.color.material_on_surface_emphasis_medium)); + button.setBackgroundColor(ContextCompat.getColor(requireContext(), isSelected ? R.color.colorPrimary : android.R.color.transparent)); } @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,16 +159,16 @@ 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); ArrayAdapter languageAdapter = ArrayAdapter.createFromResource( - getContext(), R.array.languages_array, android.R.layout.simple_spinner_item); + requireContext(), R.array.languages_array, android.R.layout.simple_spinner_item); 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,9 +177,9 @@ 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); + requireContext(), R.array.video_quality_options, android.R.layout.simple_spinner_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); qualitySpinner.setAdapter(adapter); @@ -143,7 +195,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 +228,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) { @@ -254,7 +306,7 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { sharedPreferences.edit().putBoolean(PREF_LOCATION_DATA, true).apply(); } else { - MaterialSwitch locationSwitch = getView().findViewById(R.id.location_toggle_group); + MaterialSwitch locationSwitch = requireView().findViewById(R.id.location_toggle_group); if (locationSwitch != null) { locationSwitch.setChecked(false); } @@ -267,8 +319,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 +332,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 +346,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 +360,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 +391,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 +413,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 +438,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)); @@ -401,7 +454,6 @@ private void setupWatermarkSpinner(View view, Spinner watermarkSpinner) { adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); watermarkSpinner.setAdapter(adapter); - SharedPreferences sharedPreferences = requireActivity().getPreferences(Context.MODE_PRIVATE); String savedWatermark = sharedPreferences.getString(PREF_WATERMARK_OPTION, "timestamp_fadcam"); int watermarkIndex = getWatermarkIndex(savedWatermark); watermarkSpinner.setSelection(watermarkIndex); @@ -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); @@ -538,7 +590,7 @@ private void applyLanguage(String languageCode) { Locale.setDefault(locale); Configuration config = new Configuration(); config.setLocale(locale); - getActivity().getResources().updateConfiguration(config, getActivity().getResources().getDisplayMetrics()); + requireActivity().getResources().updateConfiguration(config, requireActivity().getResources().getDisplayMetrics()); // Restart the activity or fragment to apply changes requireActivity().recreate(); @@ -546,7 +598,7 @@ private void applyLanguage(String languageCode) { /** * Set the selected item based on the frame rate - * @param frameRate + * @param frameRate Frame rate * @return Video frameRate index in selection list */ private int getVideoFrameRateIndex(int frameRate) @@ -568,7 +620,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) { @@ -590,7 +642,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); @@ -600,7 +652,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: @@ -640,6 +692,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 @@