From 728f4e4f31dbe5d5fd2d062372dc2992e91128a4 Mon Sep 17 00:00:00 2001 From: Robin Date: Mon, 30 Dec 2024 12:44:46 +0600 Subject: [PATCH] updated new version - fix autostart - better flip detection --- .idea/.name | 1 + .idea/AndroidProjectSystem.xml | 6 + .idea/appInsightsSettings.xml | 26 ++ .idea/compiler.xml | 6 + .idea/kotlinc.xml | 2 +- .idea/misc.xml | 3 +- .idea/vcs.xml | 6 + app/build.gradle.kts | 4 +- app/src/main/AndroidManifest.xml | 8 + .../java/dev/robin/flip_2_dnd/MainActivity.kt | 192 ++++++------- .../repository/OrientationRepositoryImpl.kt | 68 ++--- .../flip_2_dnd/services/AutoStartService.kt | 18 ++ .../robin/flip_2_dnd/services/DndService.kt | 7 +- .../flip_2_dnd/services/SensorService.kt | 256 +++++++++--------- metadata/en-US/changelogs/8.txt | 0 15 files changed, 339 insertions(+), 264 deletions(-) create mode 100644 .idea/.name create mode 100644 .idea/AndroidProjectSystem.xml create mode 100644 .idea/appInsightsSettings.xml create mode 100644 .idea/compiler.xml create mode 100644 .idea/vcs.xml create mode 100644 app/src/main/java/dev/robin/flip_2_dnd/services/AutoStartService.kt create mode 100644 metadata/en-US/changelogs/8.txt diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..85ebb6a --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Flip_2_DND \ No newline at end of file diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml new file mode 100644 index 0000000..371f2e2 --- /dev/null +++ b/.idea/appInsightsSettings.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..e58d3e4 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 6d0ee1c..fdf8d99 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 74dd639..bbf9fc8 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,6 @@ - - + diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 888bd00..53ec9fc 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,8 +16,8 @@ android { applicationId = "dev.robin.flip_2_dnd" minSdk = 23 targetSdk = 34 - versionCode = 7 - versionName = "1.0.7" + versionCode = 8 + versionName = "1.0.8" vectorDrawables { useSupportLibrary = true } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index aae05f9..540377c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ + @@ -40,5 +41,12 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/java/dev/robin/flip_2_dnd/MainActivity.kt b/app/src/main/java/dev/robin/flip_2_dnd/MainActivity.kt index a43dede..b57f8be 100644 --- a/app/src/main/java/dev/robin/flip_2_dnd/MainActivity.kt +++ b/app/src/main/java/dev/robin/flip_2_dnd/MainActivity.kt @@ -12,8 +12,8 @@ import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels -import androidx.compose.runtime.* -import androidx.compose.ui.platform.LocalContext +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController @@ -26,98 +26,98 @@ import dev.robin.flip_2_dnd.ui.theme.Flip_2_DNDTheme @AndroidEntryPoint class MainActivity : ComponentActivity() { - private val mainViewModel: MainViewModel by viewModels() - - private val dndPermissionLauncher = registerForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) { - checkAndStartService() - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - enableEdgeToEdge() - checkAndStartService() - - setContent { - Flip_2_DNDTheme { - val navController = rememberNavController() - - NavHost( - navController = navController, - startDestination = "main" - ) { - composable("main") { - val state by mainViewModel.state.collectAsState() - MainScreen( - state = state, - onSettingsClick = { - navController.navigate("settings") - } - ) - } - composable("settings") { - SettingsScreen( - navController = navController - ) - } - } - } - } - } - - private fun checkAndStartService() { - val notificationPolicyGranted = isNotificationPolicyAccessGranted() - val batteryOptimizationDisabled = isBatteryOptimizationDisabled() - - // Always start the service - startFlipDetectorService() - - // If permissions are not granted, show a warning - if (!notificationPolicyGranted || !batteryOptimizationDisabled) { - // Optional: Add a toast or dialog to inform user about missing permissions - Toast.makeText( - this, - "Please grant all permissions for full functionality", - Toast.LENGTH_LONG - ).show() - - if (!notificationPolicyGranted) { - requestNotificationPolicyAccess() - } - - if (!batteryOptimizationDisabled) { - requestDisableBatteryOptimization() - } - } - } - - private fun isNotificationPolicyAccessGranted(): Boolean { - val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager - return notificationManager.isNotificationPolicyAccessGranted - } - - private fun requestNotificationPolicyAccess() { - val intent = Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS) - dndPermissionLauncher.launch(intent) - } - - private fun isBatteryOptimizationDisabled(): Boolean { - val powerManager = getSystemService(POWER_SERVICE) as PowerManager - return powerManager.isIgnoringBatteryOptimizations(packageName) - } - - private fun requestDisableBatteryOptimization() { - val intent = Intent().apply { - action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS - data = Uri.parse("package:$packageName") - } - startActivity(intent) - } - - private fun startFlipDetectorService() { - Intent(this, FlipDetectorService::class.java).also { intent -> - startForegroundService(intent) - } - } + private val mainViewModel: MainViewModel by viewModels() + + private val dndPermissionLauncher = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { + checkAndStartService() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + checkAndStartService() + + setContent { + Flip_2_DNDTheme { + val navController = rememberNavController() + + NavHost( + navController = navController, + startDestination = "main" + ) { + composable("main") { + val state by mainViewModel.state.collectAsState() + MainScreen( + state = state, + onSettingsClick = { + navController.navigate("settings") + } + ) + } + composable("settings") { + SettingsScreen( + navController = navController + ) + } + } + } + } + } + + private fun checkAndStartService() { + val notificationPolicyGranted = isNotificationPolicyAccessGranted() + val batteryOptimizationDisabled = isBatteryOptimizationDisabled() + + // Always start the service + startFlipDetectorService() + + // If permissions are not granted, show a warning + if (!notificationPolicyGranted || !batteryOptimizationDisabled) { + // Optional: Add a toast or dialog to inform user about missing permissions + Toast.makeText( + this, + "Please grant all permissions for full functionality", + Toast.LENGTH_LONG + ).show() + + if (!notificationPolicyGranted) { + requestNotificationPolicyAccess() + } + + if (!batteryOptimizationDisabled) { + requestDisableBatteryOptimization() + } + } + } + + private fun isNotificationPolicyAccessGranted(): Boolean { + val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager + return notificationManager.isNotificationPolicyAccessGranted + } + + private fun requestNotificationPolicyAccess() { + val intent = Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS) + dndPermissionLauncher.launch(intent) + } + + private fun isBatteryOptimizationDisabled(): Boolean { + val powerManager = getSystemService(POWER_SERVICE) as PowerManager + return powerManager.isIgnoringBatteryOptimizations(packageName) + } + + private fun requestDisableBatteryOptimization() { + val intent = Intent().apply { + action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS + data = Uri.parse("package:$packageName") + } + startActivity(intent) + } + + private fun startFlipDetectorService() { + Intent(this, FlipDetectorService::class.java).also { intent -> + startForegroundService(intent) + } + } } \ No newline at end of file diff --git a/app/src/main/java/dev/robin/flip_2_dnd/data/repository/OrientationRepositoryImpl.kt b/app/src/main/java/dev/robin/flip_2_dnd/data/repository/OrientationRepositoryImpl.kt index 88c0398..32c07e0 100644 --- a/app/src/main/java/dev/robin/flip_2_dnd/data/repository/OrientationRepositoryImpl.kt +++ b/app/src/main/java/dev/robin/flip_2_dnd/data/repository/OrientationRepositoryImpl.kt @@ -5,9 +5,9 @@ import android.hardware.Sensor import android.hardware.SensorEvent import android.hardware.SensorEventListener import android.hardware.SensorManager +import dagger.hilt.android.qualifiers.ApplicationContext import dev.robin.flip_2_dnd.domain.model.PhoneOrientation import dev.robin.flip_2_dnd.domain.repository.OrientationRepository -import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import javax.inject.Inject @@ -16,39 +16,39 @@ import kotlin.math.abs @Singleton class OrientationRepositoryImpl @Inject constructor( - @ApplicationContext private val context: Context + @ApplicationContext private val context: Context ) : OrientationRepository, SensorEventListener { - private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager - private val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) - private val _orientation = MutableStateFlow(PhoneOrientation.UNKNOWN) - - override fun getOrientation(): Flow = _orientation - - override suspend fun startMonitoring() { - sensorManager.registerListener( - this, - accelerometer, - SensorManager.SENSOR_DELAY_NORMAL - ) - } - - override suspend fun stopMonitoring() { - sensorManager.unregisterListener(this) - } - - override fun onSensorChanged(event: SensorEvent?) { - if (event?.sensor?.type == Sensor.TYPE_ACCELEROMETER) { - val z = event.values[2] - _orientation.value = when { - abs(z) > 8.0f && z < 0 -> PhoneOrientation.FACE_DOWN - // abs(z) > 9.0f && z > 0 -> PhoneOrientation.FACE_UP - else -> PhoneOrientation.FACE_UP - } - } - } - - override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { - // Not needed for this implementation - } + private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager + private val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) + private val _orientation = MutableStateFlow(PhoneOrientation.UNKNOWN) + + override fun getOrientation(): Flow = _orientation + + override suspend fun startMonitoring() { + sensorManager.registerListener( + this, + accelerometer, + SensorManager.SENSOR_DELAY_NORMAL + ) + } + + override suspend fun stopMonitoring() { + sensorManager.unregisterListener(this) + } + + override fun onSensorChanged(event: SensorEvent?) { + if (event?.sensor?.type == Sensor.TYPE_ACCELEROMETER) { + val z = event.values[2] + _orientation.value = when { + abs(z) > 9.5f && z < 0 -> PhoneOrientation.FACE_DOWN + // abs(z) > 9.0f && z > 0 -> PhoneOrientation.FACE_UP + else -> PhoneOrientation.FACE_UP + } + } + } + + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { + // Not needed for this implementation + } } diff --git a/app/src/main/java/dev/robin/flip_2_dnd/services/AutoStartService.kt b/app/src/main/java/dev/robin/flip_2_dnd/services/AutoStartService.kt new file mode 100644 index 0000000..7d51a10 --- /dev/null +++ b/app/src/main/java/dev/robin/flip_2_dnd/services/AutoStartService.kt @@ -0,0 +1,18 @@ +package dev.robin.flip_2_dnd.services + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import androidx.core.content.ContextCompat + +class AutoStartService : BroadcastReceiver() { + + override fun onReceive(context: Context?, intent: Intent?) { + if (intent?.action == Intent.ACTION_BOOT_COMPLETED) { + context?.let { + val serviceIntent = Intent(it, FlipDetectorService::class.java) + ContextCompat.startForegroundService(it, serviceIntent) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/robin/flip_2_dnd/services/DndService.kt b/app/src/main/java/dev/robin/flip_2_dnd/services/DndService.kt index 6e9a956..3dd60cc 100644 --- a/app/src/main/java/dev/robin/flip_2_dnd/services/DndService.kt +++ b/app/src/main/java/dev/robin/flip_2_dnd/services/DndService.kt @@ -53,10 +53,9 @@ class DndService(private val context: Context) { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val timings = pattern val amplitudes = IntArray(pattern.size) { VibrationEffect.DEFAULT_AMPLITUDE } Log.d(TAG, "Creating waveform vibration for Android O+") - vibrator.vibrate(VibrationEffect.createWaveform(timings, amplitudes, -1)) + vibrator.vibrate(VibrationEffect.createWaveform(pattern, amplitudes, -1)) } else { Log.d(TAG, "Using deprecated vibration method for older Android versions") @Suppress("DEPRECATION") @@ -86,13 +85,13 @@ class DndService(private val context: Context) { } } - fun checkDndPermission(): Boolean { + private fun checkDndPermission(): Boolean { val hasPermission = notificationManager.isNotificationPolicyAccessGranted Log.d(TAG, "DND Permission check: $hasPermission") return hasPermission } - fun openDndSettings() { + private fun openDndSettings() { Log.d(TAG, "Opening DND settings") val intent = Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) diff --git a/app/src/main/java/dev/robin/flip_2_dnd/services/SensorService.kt b/app/src/main/java/dev/robin/flip_2_dnd/services/SensorService.kt index 5edb049..dbf3995 100644 --- a/app/src/main/java/dev/robin/flip_2_dnd/services/SensorService.kt +++ b/app/src/main/java/dev/robin/flip_2_dnd/services/SensorService.kt @@ -13,129 +13,135 @@ import kotlin.math.abs private const val TAG = "SensorService" class SensorService(context: Context) { - private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager - private val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) - private val gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) - - private val _orientation = MutableStateFlow("Face up") - val orientation: StateFlow = _orientation - - private val _accelerometerData = MutableStateFlow(FloatArray(3) { 0f }) - val accelerometerData: StateFlow = _accelerometerData - - private val _gyroscopeData = MutableStateFlow(FloatArray(3) { 0f }) - val gyroscopeData: StateFlow = _gyroscopeData - - private var lastAccelReading = FloatArray(3) - private var lastGyroReading = FloatArray(3) - private var isProcessing = false - private var isRegistered = false - - init { - if (accelerometer == null) { - Log.e(TAG, "No accelerometer sensor found!") - } - if (gyroscope == null) { - Log.e(TAG, "No gyroscope sensor found!") - } - } - - private val sensorListener = object : SensorEventListener { - override fun onSensorChanged(event: SensorEvent) { - when (event.sensor.type) { - Sensor.TYPE_ACCELEROMETER -> { - lastAccelReading = event.values.clone() - _accelerometerData.value = event.values.clone() - Log.d(TAG, "Accelerometer data: ${lastAccelReading.contentToString()}") - processOrientation() - } - Sensor.TYPE_GYROSCOPE -> { - lastGyroReading = event.values.clone() - _gyroscopeData.value = event.values.clone() - Log.d(TAG, "Gyroscope data: ${lastGyroReading.contentToString()}") - } - } - } - - override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { - Log.d(TAG, "Sensor accuracy changed: ${sensor?.name}, accuracy: $accuracy") - } - } - - fun startMonitoring() { - if (isRegistered) { - Log.d(TAG, "Sensors already registered") - return - } - - if (accelerometer == null || gyroscope == null) { - Log.e(TAG, "Required sensors not available - Accelerometer: ${accelerometer != null}, Gyroscope: ${gyroscope != null}") - return - } - - var success = true - - success = success && sensorManager.registerListener( - sensorListener, - accelerometer, - SensorManager.SENSOR_DELAY_UI - ) - if (!success) { - Log.e(TAG, "Failed to register accelerometer") - return - } - - success = success && sensorManager.registerListener( - sensorListener, - gyroscope, - SensorManager.SENSOR_DELAY_UI - ) - if (!success) { - Log.e(TAG, "Failed to register gyroscope") - sensorManager.unregisterListener(sensorListener) - return - } - - isRegistered = true - Log.d(TAG, "Successfully registered sensor listeners") - } - - fun stopMonitoring() { - if (!isRegistered) { - Log.d(TAG, "Sensors not registered") - return - } - sensorManager.unregisterListener(sensorListener) - isRegistered = false - Log.d(TAG, "Unregistered sensor listeners") - } - - private fun processOrientation() { - if (isProcessing) return - isProcessing = true - - val x = lastAccelReading[0] - val y = lastAccelReading[1] - val z = lastAccelReading[2] - - // Check if the phone is relatively stable (not in motion) - val isStable = abs(lastGyroReading[0]) < 0.02f && - abs(lastGyroReading[1]) < 0.02f && - abs(lastGyroReading[2]) < 0.02f - - // For face down, check stability. For other orientations, update immediately - val orientation = when { - abs(z) > 8.0f && z < 0 -> { - // Only check stability for face down - if (isStable) "Face down" else _orientation.value - } - else -> "Face up" - } - - if (orientation != _orientation.value) { - _orientation.value = orientation - } - - isProcessing = false - } + private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager + private val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) + private val gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) + + private val _orientation = MutableStateFlow("Face up") + val orientation: StateFlow = _orientation + + private val _accelerometerData = MutableStateFlow(FloatArray(3) { 0f }) + val accelerometerData: StateFlow = _accelerometerData + + private val _gyroscopeData = MutableStateFlow(FloatArray(3) { 0f }) + val gyroscopeData: StateFlow = _gyroscopeData + + private var lastAccelReading = FloatArray(3) + private var lastGyroReading = FloatArray(3) + private var isProcessing = false + private var isRegistered = false + + init { + if (accelerometer == null) { + Log.e(TAG, "No accelerometer sensor found!") + } + if (gyroscope == null) { + Log.e(TAG, "No gyroscope sensor found!") + } + } + + private val sensorListener = object : SensorEventListener { + override fun onSensorChanged(event: SensorEvent) { + when (event.sensor.type) { + Sensor.TYPE_ACCELEROMETER -> { + lastAccelReading = event.values.clone() + _accelerometerData.value = event.values.clone() + Log.d(TAG, "Accelerometer data: ${lastAccelReading.contentToString()}") + processOrientation() + } + + Sensor.TYPE_GYROSCOPE -> { + lastGyroReading = event.values.clone() + _gyroscopeData.value = event.values.clone() + Log.d(TAG, "Gyroscope data: ${lastGyroReading.contentToString()}") + } + } + } + + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { + Log.d(TAG, "Sensor accuracy changed: ${sensor?.name}, accuracy: $accuracy") + } + } + + fun startMonitoring() { + if (isRegistered) { + Log.d(TAG, "Sensors already registered") + return + } + + if (accelerometer == null || gyroscope == null) { + Log.e( + TAG, + "Required sensors not available - Accelerometer: ${accelerometer != null}, Gyroscope: ${gyroscope != null}" + ) + return + } + + var success = true + + success = success && sensorManager.registerListener( + sensorListener, + accelerometer, + SensorManager.SENSOR_DELAY_UI + ) + if (!success) { + Log.e(TAG, "Failed to register accelerometer") + return + } + + success = success && sensorManager.registerListener( + sensorListener, + gyroscope, + SensorManager.SENSOR_DELAY_UI + ) + if (!success) { + Log.e(TAG, "Failed to register gyroscope") + sensorManager.unregisterListener(sensorListener) + return + } + + isRegistered = true + Log.d(TAG, "Successfully registered sensor listeners") + } + + fun stopMonitoring() { + if (!isRegistered) { + Log.d(TAG, "Sensors not registered") + return + } + sensorManager.unregisterListener(sensorListener) + isRegistered = false + Log.d(TAG, "Unregistered sensor listeners") + } + + private fun processOrientation() { + if (isProcessing) return + isProcessing = true + + val x = lastAccelReading[0] + val y = lastAccelReading[1] + val z = lastAccelReading[2] + + // Check if the phone is relatively stable (not in motion) + val isStable = abs(lastGyroReading[0]) < 0.02f && + abs(lastGyroReading[1]) < 0.02f && + abs(lastGyroReading[2]) < 0.02f + + // For face down, check stability. For other orientations, update immediately + val orientation = when { + abs(z) >= 9.5f && z < 0 -> { + // Only check stability for face down + Log.d(TAG, "z value: " + abs(z) + " " + z) + if (isStable) "Face down" else _orientation.value + } + + else -> "Face up" + } + + if (orientation != _orientation.value) { + _orientation.value = orientation + } + + isProcessing = false + } } diff --git a/metadata/en-US/changelogs/8.txt b/metadata/en-US/changelogs/8.txt new file mode 100644 index 0000000..e69de29