diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index d6a0581e8..16ad43dd5 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -21,8 +21,8 @@ android {
applicationId = "com.sameerasw.essentials"
minSdk = 26
targetSdk = 36
- versionCode = 41
- versionName = "13.2"
+ versionCode = 42
+ versionName = "13.3"
val whatsNewCounter = 1
buildConfigField("int", "WHATS_NEW_COUNTER", whatsNewCounter.toString())
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a4d5c189f..53e9eb8be 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -296,6 +296,15 @@
android:exported="false"
android:foregroundServiceType="specialUse" />
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/sameerasw/essentials/EssentialsApp.kt b/app/src/main/java/com/sameerasw/essentials/EssentialsApp.kt
index 29dc53abc..ad5982dd7 100644
--- a/app/src/main/java/com/sameerasw/essentials/EssentialsApp.kt
+++ b/app/src/main/java/com/sameerasw/essentials/EssentialsApp.kt
@@ -41,6 +41,7 @@ class EssentialsApp : Application() {
com.sameerasw.essentials.domain.diy.DIYRepository.init(this)
com.sameerasw.essentials.services.automation.AutomationManager.init(this)
com.sameerasw.essentials.services.CalendarSyncManager.init(this)
+ com.sameerasw.essentials.services.DeviceInfoSyncManager.init(this)
com.sameerasw.essentials.utils.ServiceUtils.startRequiredServices(this)
initSentry()
diff --git a/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt b/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt
index a498aaabd..6cab6273f 100644
--- a/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt
+++ b/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt
@@ -359,6 +359,10 @@ class FeatureSettingsActivity : AppCompatActivity() {
}
if (featureId == "Watch") {
+ val context = androidx.compose.ui.platform.LocalContext.current
+ LaunchedEffect(Unit) {
+ watchViewModel.check(context)
+ }
WatchSettingsUI(
viewModel = watchViewModel,
modifier = Modifier.padding(top = 16.dp)
@@ -610,6 +614,15 @@ class FeatureSettingsActivity : AppCompatActivity() {
)
}
+ "Lock from Watch" -> {
+ com.sameerasw.essentials.ui.composables.configs.RemoteLockSettingsUI(
+ mainViewModel = viewModel,
+ watchViewModel = watchViewModel,
+ modifier = Modifier.padding(top = 16.dp),
+ highlightSetting = highlightSetting
+ )
+ }
+
"Maps power saving mode" -> {
MapsPowerSavingSettingsUI(
viewModel = viewModel,
diff --git a/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt b/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt
index 27bc2a7a6..75bec9ff7 100644
--- a/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt
+++ b/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt
@@ -176,6 +176,7 @@ class SettingsRepository(private val context: Context) {
const val KEY_CALENDAR_SYNC_ENABLED = "calendar_sync_enabled"
const val KEY_CALENDAR_SYNC_SELECTED_CALENDARS = "calendar_sync_selected_calendars"
const val KEY_CALENDAR_SYNC_PERIODIC_ENABLED = "calendar_sync_periodic_enabled"
+ const val KEY_REMOTE_LOCK_MODE = "remote_lock_mode" // 0: Screen off, 1: Lock
const val KEY_GITHUB_ACCESS_TOKEN = "github_access_token"
const val KEY_GITHUB_USER_PROFILE = "github_user_profile"
diff --git a/app/src/main/java/com/sameerasw/essentials/domain/registry/FeatureRegistry.kt b/app/src/main/java/com/sameerasw/essentials/domain/registry/FeatureRegistry.kt
index e36dc303d..a8f66c95b 100644
--- a/app/src/main/java/com/sameerasw/essentials/domain/registry/FeatureRegistry.kt
+++ b/app/src/main/java/com/sameerasw/essentials/domain/registry/FeatureRegistry.kt
@@ -964,6 +964,21 @@ object FeatureRegistry {
override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) =
viewModel.setCalendarSyncEnabled(enabled, context)
},
+
+ object : Feature(
+ id = "Lock from Watch",
+ title = R.string.feat_lock_from_watch_title,
+ iconRes = R.drawable.rounded_lock_24,
+ category = R.string.cat_tools,
+ description = R.string.feat_lock_from_watch_desc,
+ aboutDescription = R.string.feat_lock_from_watch_desc,
+ parentFeatureId = "Watch",
+ hasMoreSettings = true,
+ showToggle = false
+ ) {
+ override fun isEnabled(viewModel: MainViewModel) = true
+ override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {}
+ },
object : Feature(
diff --git a/app/src/main/java/com/sameerasw/essentials/services/DeviceInfoSyncManager.kt b/app/src/main/java/com/sameerasw/essentials/services/DeviceInfoSyncManager.kt
new file mode 100644
index 000000000..c08591e8c
--- /dev/null
+++ b/app/src/main/java/com/sameerasw/essentials/services/DeviceInfoSyncManager.kt
@@ -0,0 +1,156 @@
+package com.sameerasw.essentials.services
+
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.BatteryManager
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import android.hardware.camera2.CameraManager
+import com.sameerasw.essentials.utils.FlashlightUtil
+import com.google.android.gms.wearable.PutDataMapRequest
+import com.google.android.gms.wearable.Wearable
+
+object DeviceInfoSyncManager {
+ private const val TAG = "DeviceInfoSyncManager"
+ private const val SYNC_PATH = "/device_info"
+ private val handler = Handler(Looper.getMainLooper())
+ private var isInitialized = false
+
+ private var isTorchOn = false
+ private var torchLevel = 1
+ private var maxTorchLevel = 1
+ private var isIntensitySupported = false
+
+ private val torchCallback = object : CameraManager.TorchCallback() {
+ override fun onTorchModeChanged(cameraId: String, enabled: Boolean) {
+ val context = currentContext ?: return
+ val primaryId = FlashlightUtil.getCameraId(context)
+ if (cameraId == primaryId) {
+ isTorchOn = enabled
+
+ var level = FlashlightUtil.getCurrentLevel(context, cameraId)
+ // Fallback to last known intensity if system returns default level 1
+ if (enabled && level <= 1) {
+ val prefs = context.getSharedPreferences("essentials_prefs", Context.MODE_PRIVATE)
+ level = prefs.getInt("flashlight_last_intensity", level)
+ }
+
+ torchLevel = level
+ maxTorchLevel = FlashlightUtil.getMaxLevel(context, cameraId)
+ isIntensitySupported = FlashlightUtil.isIntensitySupported(context, cameraId)
+ syncDeviceInfo(context)
+ }
+ }
+
+ override fun onTorchStrengthLevelChanged(cameraId: String, newStrengthLevel: Int) {
+ val context = currentContext ?: return
+ val primaryId = FlashlightUtil.getCameraId(context)
+ if (cameraId == primaryId) {
+ torchLevel = newStrengthLevel
+ syncDeviceInfo(context)
+ }
+ }
+ }
+
+ private val syncRunnable = object : Runnable {
+ override fun run() {
+ syncDeviceInfo(currentContext ?: return)
+ handler.postDelayed(this, 5 * 60 * 1000) // Sync every 5 minutes
+ }
+ }
+
+ private var currentContext: Context? = null
+
+ fun init(context: Context) {
+ if (isInitialized) return
+ currentContext = context.applicationContext
+ isInitialized = true
+
+ // Initial sync
+ syncDeviceInfo(context)
+
+ // Start periodic sync
+ handler.postDelayed(syncRunnable, 5 * 60 * 1000)
+
+ // Sync on battery change
+ context.registerReceiver(object : android.content.BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ syncDeviceInfo(context)
+ }
+ }, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
+
+ // Sync on ringer mode change
+ context.registerReceiver(object : android.content.BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ syncDeviceInfo(context)
+ }
+ }, IntentFilter(android.media.AudioManager.RINGER_MODE_CHANGED_ACTION))
+
+ // Sync on flashlight change
+ val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
+ cameraManager.registerTorchCallback(torchCallback, handler)
+
+ // Get initial flashlight state
+ val id = FlashlightUtil.getCameraId(context)
+ if (id != null) {
+ isIntensitySupported = FlashlightUtil.isIntensitySupported(context, id)
+ maxTorchLevel = FlashlightUtil.getMaxLevel(context, id)
+ torchLevel = FlashlightUtil.getCurrentLevel(context, id)
+ }
+ }
+
+ private val syncDebouncer = Runnable {
+ currentContext?.let { performSync(it) }
+ }
+
+ private fun syncDeviceInfo(context: Context) {
+ handler.removeCallbacks(syncDebouncer)
+ handler.postDelayed(syncDebouncer, 250L)
+ }
+
+ fun forceSync(context: Context) {
+ handler.removeCallbacks(syncDebouncer)
+ performSync(context)
+ }
+
+ private fun performSync(context: Context) {
+ val batteryStatus: Intent? = IntentFilter(Intent.ACTION_BATTERY_CHANGED).let { ifilter ->
+ context.registerReceiver(null, ifilter)
+ }
+
+ val level: Int = batteryStatus?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
+ val scale: Int = batteryStatus?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1
+ val batteryPct = if (level != -1 && scale != -1) (level / scale.toFloat() * 100).toInt() else -1
+
+ val status: Int = batteryStatus?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1
+ val plugged: Int = batteryStatus?.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) ?: -1
+ val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
+ status == BatteryManager.BATTERY_STATUS_FULL ||
+ plugged == BatteryManager.BATTERY_PLUGGED_AC ||
+ plugged == BatteryManager.BATTERY_PLUGGED_USB ||
+ plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS
+
+ val putDataMapReq = PutDataMapRequest.create(SYNC_PATH)
+ val ringerMode = (context.getSystemService(Context.AUDIO_SERVICE) as? android.media.AudioManager)?.ringerMode ?: 2
+ val deviceName = android.provider.Settings.Global.getString(context.contentResolver, android.provider.Settings.Global.DEVICE_NAME)
+ ?: android.os.Build.MODEL
+
+ val dataMap = putDataMapReq.dataMap
+ dataMap.putInt("battery_level", batteryPct)
+ dataMap.putBoolean("is_charging", isCharging)
+ dataMap.putBoolean("flashlight_on", isTorchOn)
+ dataMap.putInt("flashlight_level", torchLevel)
+ dataMap.putInt("flashlight_max_level", maxTorchLevel)
+ dataMap.putBoolean("flashlight_intensity_supported", isIntensitySupported)
+ dataMap.putInt("ringer_mode", ringerMode)
+ dataMap.putString("device_name", deviceName)
+ dataMap.putLong("timestamp", System.currentTimeMillis())
+
+ val putDataReq = putDataMapReq.asPutDataRequest()
+ putDataReq.setUrgent()
+
+ Wearable.getDataClient(context).putDataItem(putDataReq)
+ }
+}
diff --git a/app/src/main/java/com/sameerasw/essentials/services/EssentialsWearableListenerService.kt b/app/src/main/java/com/sameerasw/essentials/services/EssentialsWearableListenerService.kt
new file mode 100644
index 000000000..65d13dace
--- /dev/null
+++ b/app/src/main/java/com/sameerasw/essentials/services/EssentialsWearableListenerService.kt
@@ -0,0 +1,62 @@
+package com.sameerasw.essentials.services
+
+import android.util.Log
+import com.google.android.gms.wearable.MessageEvent
+import com.google.android.gms.wearable.WearableListenerService
+
+class EssentialsWearableListenerService : WearableListenerService() {
+ companion object {
+ private const val TAG = "EssentialsWearableListener"
+ private const val PATH_REQUEST_SYNC = "/request_device_info_sync"
+ }
+
+ override fun onMessageReceived(messageEvent: MessageEvent) {
+ super.onMessageReceived(messageEvent)
+
+ when (messageEvent.path) {
+ PATH_REQUEST_SYNC -> {
+ DeviceInfoSyncManager.forceSync(this)
+ }
+ "/toggle_flashlight" -> {
+ val intent = android.content.Intent(this, com.sameerasw.essentials.services.receivers.FlashlightActionReceiver::class.java).apply {
+ action = com.sameerasw.essentials.services.receivers.FlashlightActionReceiver.ACTION_TOGGLE
+ }
+ sendBroadcast(intent)
+ }
+ "/set_flashlight_intensity" -> {
+ val intensity = try {
+ String(messageEvent.data).toInt()
+ } catch (e: Exception) {
+ 1
+ }
+ val intent = android.content.Intent(this, com.sameerasw.essentials.services.receivers.FlashlightActionReceiver::class.java).apply {
+ action = com.sameerasw.essentials.services.receivers.FlashlightActionReceiver.ACTION_SET_INTENSITY
+ putExtra(com.sameerasw.essentials.services.receivers.FlashlightActionReceiver.EXTRA_INTENSITY, intensity)
+ }
+ sendBroadcast(intent)
+ }
+ "/toggle_sound_mode" -> {
+ com.sameerasw.essentials.services.handlers.SoundModeHandler(this).cycleNextMode()
+ }
+ "/lock_device" -> {
+ val repository = com.sameerasw.essentials.data.repository.SettingsRepository(this)
+ val mode = repository.getInt(com.sameerasw.essentials.data.repository.SettingsRepository.KEY_REMOTE_LOCK_MODE, 0)
+
+ if (mode == 1) {
+ // Device Admin Lock
+ val dpm = getSystemService(android.content.Context.DEVICE_POLICY_SERVICE) as android.app.admin.DevicePolicyManager
+ val adminComponent = android.content.ComponentName(this, com.sameerasw.essentials.services.receivers.SecurityDeviceAdminReceiver::class.java)
+ if (dpm.isAdminActive(adminComponent)) {
+ dpm.lockNow()
+ }
+ } else {
+ // Accessibility Lock
+ val intent = android.content.Intent(this, com.sameerasw.essentials.services.tiles.ScreenOffAccessibilityService::class.java).apply {
+ action = "LOCK_SCREEN"
+ }
+ startService(intent)
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenOffAccessibilityService.kt b/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenOffAccessibilityService.kt
index 13c57487d..3e699069b 100644
--- a/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenOffAccessibilityService.kt
+++ b/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenOffAccessibilityService.kt
@@ -110,6 +110,14 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene
"FORCE_TURN_OFF_AOD" -> {
aodForceTurnOffHandler.forceTurnOff()
}
+
+ FlashlightActionReceiver.ACTION_TOGGLE,
+ FlashlightActionReceiver.ACTION_OFF,
+ FlashlightActionReceiver.ACTION_SET_INTENSITY,
+ FlashlightActionReceiver.ACTION_INCREASE,
+ FlashlightActionReceiver.ACTION_DECREASE -> {
+ flashlightHandler.handleIntent(intent)
+ }
}
}
}
@@ -120,6 +128,11 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene
addAction(InputEventListenerService.ACTION_VOLUME_LONG_PRESSED)
addAction("SHOW_AMBIENT_GLANCE")
addAction("FORCE_TURN_OFF_AOD")
+ addAction(FlashlightActionReceiver.ACTION_TOGGLE)
+ addAction(FlashlightActionReceiver.ACTION_OFF)
+ addAction(FlashlightActionReceiver.ACTION_SET_INTENSITY)
+ addAction(FlashlightActionReceiver.ACTION_INCREASE)
+ addAction(FlashlightActionReceiver.ACTION_DECREASE)
}
registerReceiver(screenReceiver, filter, RECEIVER_EXPORTED)
diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/RemoteLockSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/RemoteLockSettingsUI.kt
new file mode 100644
index 000000000..88fc0866c
--- /dev/null
+++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/RemoteLockSettingsUI.kt
@@ -0,0 +1,113 @@
+package com.sameerasw.essentials.ui.composables.configs
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.sameerasw.essentials.R
+import com.sameerasw.essentials.data.repository.SettingsRepository
+import com.sameerasw.essentials.ui.components.cards.ConfigPickerItem
+import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer
+import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenuItem
+import com.sameerasw.essentials.ui.modifiers.highlight
+import com.sameerasw.essentials.utils.PermissionUtils
+import com.sameerasw.essentials.viewmodels.MainViewModel
+import com.sameerasw.essentials.viewmodels.WatchViewModel
+
+@Composable
+fun RemoteLockSettingsUI(
+ mainViewModel: MainViewModel,
+ watchViewModel: WatchViewModel,
+ modifier: Modifier = Modifier,
+ highlightSetting: String? = null
+) {
+ val context = LocalContext.current
+ val settingsRepository = remember { SettingsRepository(context) }
+ var showPermissionSheet by remember { mutableStateOf(false) }
+
+ val isAccessibilityEnabled by mainViewModel.isAccessibilityEnabled
+ val isDeviceAdminEnabled by mainViewModel.isDeviceAdminEnabled
+ val remoteLockMode by watchViewModel.remoteLockMode
+
+ LaunchedEffect(Unit) {
+ watchViewModel.load(settingsRepository)
+ }
+
+ if (showPermissionSheet) {
+ com.sameerasw.essentials.ui.components.sheets.PermissionsBottomSheet(
+ onDismissRequest = { showPermissionSheet = false },
+ featureTitle = R.string.feat_lock_from_watch_title,
+ permissions = listOf(
+ com.sameerasw.essentials.ui.components.sheets.PermissionItem(
+ iconRes = R.drawable.rounded_settings_accessibility_24,
+ title = R.string.perm_accessibility_title,
+ description = R.string.perm_accessibility_desc_common,
+ dependentFeatures = listOf(R.string.feat_lock_from_watch_title),
+ actionLabel = R.string.perm_action_enable,
+ action = { PermissionUtils.openAccessibilitySettings(context) },
+ isGranted = isAccessibilityEnabled
+ ),
+ com.sameerasw.essentials.ui.components.sheets.PermissionItem(
+ iconRes = R.drawable.rounded_security_24,
+ title = R.string.perm_device_admin_title,
+ description = R.string.perm_device_admin_desc,
+ dependentFeatures = listOf(R.string.feat_lock_from_watch_title),
+ actionLabel = R.string.perm_action_grant,
+ action = { mainViewModel.requestDeviceAdmin(context) },
+ isGranted = isDeviceAdminEnabled
+ )
+ )
+ )
+ }
+
+ Column(
+ modifier = modifier
+ .fillMaxSize()
+ .padding(16.dp)
+ ) {
+ Text(
+ text = stringResource(R.string.remote_lock_mode_title),
+ style = MaterialTheme.typography.titleMedium,
+ modifier = Modifier.padding(start = 16.dp, top = 16.dp, bottom = 8.dp),
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+
+ RoundedCardContainer {
+ com.sameerasw.essentials.ui.components.pickers.SegmentedPicker(
+ items = listOf(0, 1),
+ selectedItem = remoteLockMode,
+ onItemSelected = { mode ->
+ val hasPermission = when (mode) {
+ 0 -> isAccessibilityEnabled
+ 1 -> isDeviceAdminEnabled
+ else -> false
+ }
+ if (!hasPermission) {
+ showPermissionSheet = true
+ } else {
+ watchViewModel.setRemoteLockMode(mode, settingsRepository)
+ }
+ },
+ labelProvider = { mode ->
+ when (mode) {
+ 0 -> context.getString(R.string.remote_lock_mode_screen_off)
+ 1 -> context.getString(R.string.remote_lock_mode_lock)
+ else -> ""
+ }
+ },
+ modifier = Modifier.highlight(highlightSetting == "remote_lock_mode")
+ )
+ }
+
+ Text(
+ text = stringResource(R.string.remote_lock_mode_admin_note),
+ style = MaterialTheme.typography.labelMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = Modifier.padding(top = 12.dp, start = 16.dp, end = 16.dp)
+ )
+ }
+}
diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/WatchSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/WatchSettingsUI.kt
index 8a04174e5..3f8204972 100644
--- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/WatchSettingsUI.kt
+++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/WatchSettingsUI.kt
@@ -1,18 +1,16 @@
package com.sameerasw.essentials.ui.composables.configs
import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.Button
-import androidx.compose.material3.ButtonDefaults
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
+import androidx.compose.material3.*
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.sameerasw.essentials.R
@@ -24,16 +22,64 @@ fun WatchSettingsUI(
viewModel: WatchViewModel,
modifier: Modifier = Modifier
) {
- LocalContext.current
val uriHandler = LocalUriHandler.current
val isWatchDetected = viewModel.isWatchDetected.value
+ val connectedWatchName = viewModel.connectedWatchName.value
Column(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
- if (!isWatchDetected) {
+ if (isWatchDetected) {
+ RoundedCardContainer(
+ modifier = Modifier
+ .padding(bottom = 18.dp)
+ .fillMaxWidth(),
+ cornerRadius = 24.dp
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(280.dp)
+ .background(MaterialTheme.colorScheme.surfaceBright)
+ .padding(24.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ Box(
+ modifier = Modifier
+ .size(100.dp)
+ .background(
+ color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f),
+ shape = CircleShape
+ ),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.rounded_watch_24),
+ contentDescription = null,
+ modifier = Modifier.size(56.dp),
+ tint = MaterialTheme.colorScheme.primary
+ )
+ }
+
+ Spacer(modifier = Modifier.height(20.dp))
+
+ Text(
+ text = connectedWatchName ?: stringResource(R.string.watch_unknown_name),
+ style = MaterialTheme.typography.headlineSmall,
+ color = MaterialTheme.colorScheme.onSurface
+ )
+
+ Text(
+ text = stringResource(R.string.watch_connected_status),
+ style = MaterialTheme.typography.labelLarge,
+ color = MaterialTheme.colorScheme.primary
+ )
+ }
+ }
+ } else {
RoundedCardContainer(
modifier = Modifier
.padding(bottom = 18.dp)
diff --git a/app/src/main/java/com/sameerasw/essentials/utils/FlashlightUtil.kt b/app/src/main/java/com/sameerasw/essentials/utils/FlashlightUtil.kt
index 747676c61..30c63df5e 100644
--- a/app/src/main/java/com/sameerasw/essentials/utils/FlashlightUtil.kt
+++ b/app/src/main/java/com/sameerasw/essentials/utils/FlashlightUtil.kt
@@ -170,4 +170,33 @@ object FlashlightUtil {
return fadeFlashlight(context, cameraId, currentLevel, targetLevel, durationMs, steps)
}
+ fun getCameraId(context: Context): String? {
+ val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
+ try {
+ var targetCameraId: String? = null
+ for (id in cameraManager.cameraIdList) {
+ val chars = cameraManager.getCameraCharacteristics(id)
+ val flashAvailable = chars.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) ?: false
+ val lensFacing = chars.get(CameraCharacteristics.LENS_FACING)
+ if (flashAvailable && lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
+ targetCameraId = id
+ break
+ }
+ }
+ if (targetCameraId == null) {
+ for (id in cameraManager.cameraIdList) {
+ val chars = cameraManager.getCameraCharacteristics(id)
+ if (chars.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) == true) {
+ targetCameraId = id
+ break
+ }
+ }
+ }
+ return targetCameraId
+ } catch (e: Exception) {
+ Log.e(TAG, "Error getting camera ID", e)
+ }
+ return null
+ }
+
}
diff --git a/app/src/main/java/com/sameerasw/essentials/utils/PermissionUtils.kt b/app/src/main/java/com/sameerasw/essentials/utils/PermissionUtils.kt
index 3ede76411..235ed8434 100644
--- a/app/src/main/java/com/sameerasw/essentials/utils/PermissionUtils.kt
+++ b/app/src/main/java/com/sameerasw/essentials/utils/PermissionUtils.kt
@@ -258,4 +258,15 @@ object PermissionUtils {
} catch (_: Exception) {
}
}
+
+ fun openDeviceAdminSettings(context: Context) {
+ try {
+ val intent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN)
+ val adminComponent = ComponentName(context, SecurityDeviceAdminReceiver::class.java)
+ intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, adminComponent)
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ context.startActivity(intent)
+ } catch (e: Exception) {
+ }
+ }
}
diff --git a/app/src/main/java/com/sameerasw/essentials/viewmodels/WatchViewModel.kt b/app/src/main/java/com/sameerasw/essentials/viewmodels/WatchViewModel.kt
index 9b80b0d2d..1878d448e 100644
--- a/app/src/main/java/com/sameerasw/essentials/viewmodels/WatchViewModel.kt
+++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/WatchViewModel.kt
@@ -5,15 +5,30 @@ import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import com.google.android.gms.wearable.Wearable
+import com.sameerasw.essentials.data.repository.SettingsRepository
+
class WatchViewModel : ViewModel() {
val isWatchDetected = mutableStateOf(false)
+ val connectedWatchName = mutableStateOf(null)
+ val remoteLockMode = mutableStateOf(0) // 0: Screen off, 1: Lock
+
+ fun load(repository: SettingsRepository) {
+ remoteLockMode.value = repository.getInt(SettingsRepository.KEY_REMOTE_LOCK_MODE, 0)
+ }
+
+ fun setRemoteLockMode(mode: Int, repository: SettingsRepository) {
+ remoteLockMode.value = mode
+ repository.putInt(SettingsRepository.KEY_REMOTE_LOCK_MODE, mode)
+ }
fun check(context: Context) {
val nodeClient = Wearable.getNodeClient(context)
nodeClient.connectedNodes.addOnSuccessListener { nodes ->
isWatchDetected.value = nodes.isNotEmpty()
+ connectedWatchName.value = nodes.firstOrNull()?.displayName
}.addOnFailureListener {
isWatchDetected.value = false
+ connectedWatchName.value = null
}
}
}
diff --git a/app/src/main/res/drawable/rounded_lock_24.xml b/app/src/main/res/drawable/rounded_lock_24.xml
new file mode 100644
index 000000000..427742d4f
--- /dev/null
+++ b/app/src/main/res/drawable/rounded_lock_24.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 173b6874d..ac2619e61 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -447,7 +447,15 @@
Always on Display
Show time and info while screen off
Calendar Sync
- Sync events to your watch
+ Sync your phone calendar to your watch
+ Lock from Watch
+ Lock your phone remotely
+ Lock Mode
+ Screen off
+ Lock
+ Using device admin lock will disable biometrics
+ Connected
+ Unknown Watch
Overlay
Frame
Device Brand