diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e55504e44..ab215e52d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -36,6 +36,19 @@ + + + + @@ -909,6 +922,21 @@ android:value="android.service.quicksettings.CATEGORY_DISPLAY" /> + + + + + + + + diff --git a/app/src/main/java/com/sameerasw/essentials/external/ExternalActionReceiver.kt b/app/src/main/java/com/sameerasw/essentials/external/ExternalActionReceiver.kt new file mode 100644 index 000000000..173d20929 --- /dev/null +++ b/app/src/main/java/com/sameerasw/essentials/external/ExternalActionReceiver.kt @@ -0,0 +1,31 @@ +package com.sameerasw.essentials.external + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.util.Log + +class ExternalActionReceiver : BroadcastReceiver() { + companion object { + const val ACTION_EXTERNAL_CONTROL = "com.sameerasw.essentials.action.EXTERNAL_CONTROL" + const val EXTRA_PATH = "path" + const val EXTRA_ACTION = "action" + const val EXTRA_VALUE = "value" + } + + override fun onReceive(context: Context, intent: Intent) { + if (intent.action != ACTION_EXTERNAL_CONTROL) return + + val path = intent.getStringExtra(EXTRA_PATH) ?: return + val action = intent.getStringExtra(EXTRA_ACTION) + val value = intent.getStringExtra(EXTRA_VALUE) + + Log.d("ExternalActionReceiver", "Received external control request: path=$path, action=$action, value=$value") + + if (action == "update") { + ExternalRouter.update(context, path, value, intent.extras) + } else if (action != null) { + ExternalRouter.action(context, path, action, intent.extras) + } + } +} diff --git a/app/src/main/java/com/sameerasw/essentials/external/ExternalControlProvider.kt b/app/src/main/java/com/sameerasw/essentials/external/ExternalControlProvider.kt new file mode 100644 index 000000000..8d0669e12 --- /dev/null +++ b/app/src/main/java/com/sameerasw/essentials/external/ExternalControlProvider.kt @@ -0,0 +1,62 @@ +package com.sameerasw.essentials.external + +import android.content.ContentProvider +import android.content.ContentValues +import android.database.Cursor +import android.database.MatrixCursor +import android.net.Uri +import android.os.Bundle + +class ExternalControlProvider : ContentProvider() { + + override fun onCreate(): Boolean { + return true + } + + override fun query( + uri: Uri, + projection: Array?, + selection: String?, + selectionArgs: Array?, + sortOrder: String? + ): Cursor? { + val context = context ?: return null + val path = uri.path ?: return null + return ExternalRouter.query(context, path, null) + } + + override fun getType(uri: Uri): String? { + return "vnd.android.cursor.item/vnd.com.sameerasw.essentials.external" + } + + override fun insert(uri: Uri, values: ContentValues?): Uri? { + return null + } + + override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int { + return 0 + } + + override fun update( + uri: Uri, + values: ContentValues?, + selection: String?, + selectionArgs: Array? + ): Int { + val context = context ?: return 0 + val path = uri.path ?: return 0 + val value = values?.getAsString("value") ?: return 0 + val success = ExternalRouter.update(context, path, value, null) + return if (success) 1 else 0 + } + + override fun call(method: String, arg: String?, extras: Bundle?): Bundle? { + val context = context ?: return null + if (method == "action") { + val path = arg ?: return null + val action = extras?.getString("action") + return ExternalRouter.action(context, path, action, extras) + } + return null + } +} diff --git a/app/src/main/java/com/sameerasw/essentials/external/ExternalHandler.kt b/app/src/main/java/com/sameerasw/essentials/external/ExternalHandler.kt new file mode 100644 index 000000000..0b15fdd49 --- /dev/null +++ b/app/src/main/java/com/sameerasw/essentials/external/ExternalHandler.kt @@ -0,0 +1,13 @@ +package com.sameerasw.essentials.external + +import android.content.Context +import android.database.Cursor +import android.os.Bundle + +interface ExternalHandler { + val path: String + + fun onQuery(context: Context, remainingPath: String, extras: Bundle?): Cursor? + fun onUpdate(context: Context, remainingPath: String, value: String?, extras: Bundle?): Boolean + fun onAction(context: Context, remainingPath: String, action: String?, extras: Bundle?): Bundle? +} diff --git a/app/src/main/java/com/sameerasw/essentials/external/ExternalRouter.kt b/app/src/main/java/com/sameerasw/essentials/external/ExternalRouter.kt new file mode 100644 index 000000000..6ca92b900 --- /dev/null +++ b/app/src/main/java/com/sameerasw/essentials/external/ExternalRouter.kt @@ -0,0 +1,46 @@ +package com.sameerasw.essentials.external + +import android.content.Context +import android.database.Cursor +import android.os.Bundle + +object ExternalRouter { + private val handlers = mutableMapOf() + + init { + registerHandler(SettingsExternalHandler()) + registerHandler(LocationAlarmExternalHandler()) + } + + fun registerHandler(handler: ExternalHandler) { + handlers[handler.path] = handler + } + + private fun getHandlerAndRemainingPath(fullPath: String): Pair? { + val cleanPath = fullPath.trim('/') + for ((registeredPath, handler) in handlers) { + if (cleanPath == registeredPath) { + return Pair(handler, "") + } else if (cleanPath.startsWith("$registeredPath/")) { + val remaining = cleanPath.substring(registeredPath.length + 1) + return Pair(handler, remaining) + } + } + return null + } + + fun query(context: Context, path: String, extras: Bundle?): Cursor? { + val (handler, remaining) = getHandlerAndRemainingPath(path) ?: return null + return handler.onQuery(context, remaining, extras) + } + + fun update(context: Context, path: String, value: String?, extras: Bundle?): Boolean { + val (handler, remaining) = getHandlerAndRemainingPath(path) ?: return false + return handler.onUpdate(context, remaining, value, extras) + } + + fun action(context: Context, path: String, action: String?, extras: Bundle?): Bundle? { + val (handler, remaining) = getHandlerAndRemainingPath(path) ?: return null + return handler.onAction(context, remaining, action, extras) + } +} diff --git a/app/src/main/java/com/sameerasw/essentials/external/LocationAlarmExternalHandler.kt b/app/src/main/java/com/sameerasw/essentials/external/LocationAlarmExternalHandler.kt new file mode 100644 index 000000000..ed572d1d2 --- /dev/null +++ b/app/src/main/java/com/sameerasw/essentials/external/LocationAlarmExternalHandler.kt @@ -0,0 +1,77 @@ +package com.sameerasw.essentials.external + +import android.content.Context +import android.content.Intent +import android.database.Cursor +import android.database.MatrixCursor +import android.os.Bundle +import com.sameerasw.essentials.data.repository.LocationReachedRepository +import com.sameerasw.essentials.services.LocationReachedService + +class LocationAlarmExternalHandler : ExternalHandler { + override val path: String = "location_alarm" + + override fun onQuery(context: Context, remainingPath: String, extras: Bundle?): Cursor? { + val repository = LocationReachedRepository(context) + if (remainingPath == "list") { + val alarms = repository.getAlarms() + val activeId = repository.getActiveAlarmId() + + val cursor = MatrixCursor(arrayOf( + "id", + "name", + "latitude", + "longitude", + "radius", + "isEnabled", + "isPaused", + "lastTravelled", + "isActive", + "iconResName" + )) + + for (alarm in alarms) { + cursor.addRow(arrayOf( + alarm.id, + alarm.name, + alarm.latitude, + alarm.longitude, + alarm.radius, + if (alarm.isEnabled) 1 else 0, + if (alarm.isPaused) 1 else 0, + alarm.lastTravelled ?: 0L, + if (alarm.id == activeId) 1 else 0, + alarm.iconResName + )) + } + return cursor + } + return null + } + + override fun onUpdate(context: Context, remainingPath: String, value: String?, extras: Bundle?): Boolean { + return false + } + + override fun onAction(context: Context, remainingPath: String, action: String?, extras: Bundle?): Bundle? { + val repository = LocationReachedRepository(context) + val targetAction = action ?: remainingPath + when (targetAction) { + "start" -> { + val alarmId = extras?.getString("id") ?: return null + repository.saveActiveAlarmId(alarmId) + LocationReachedService.start(context) + return Bundle().apply { putBoolean("success", true) } + } + "stop" -> { + repository.saveActiveAlarmId(null) + val intent = Intent(context, LocationReachedService::class.java).apply { + this.action = LocationReachedService.ACTION_STOP + } + context.startService(intent) + return Bundle().apply { putBoolean("success", true) } + } + } + return null + } +} diff --git a/app/src/main/java/com/sameerasw/essentials/external/SettingsExternalHandler.kt b/app/src/main/java/com/sameerasw/essentials/external/SettingsExternalHandler.kt new file mode 100644 index 000000000..c158d9c36 --- /dev/null +++ b/app/src/main/java/com/sameerasw/essentials/external/SettingsExternalHandler.kt @@ -0,0 +1,60 @@ +package com.sameerasw.essentials.external + +import android.content.Context +import android.database.Cursor +import android.database.MatrixCursor +import android.os.Bundle +import com.sameerasw.essentials.data.repository.SettingsRepository + +class SettingsExternalHandler : ExternalHandler { + override val path: String = "settings" + + override fun onQuery(context: Context, remainingPath: String, extras: Bundle?): Cursor? { + val key = remainingPath + val prefs = context.getSharedPreferences(SettingsRepository.PREFS_NAME, Context.MODE_PRIVATE) + if (!prefs.contains(key)) return null + + val value = prefs.all[key] ?: return null + val cursor = MatrixCursor(arrayOf("key", "value", "type")) + cursor.addRow(arrayOf(key, value, value.javaClass.simpleName)) + return cursor + } + + override fun onUpdate(context: Context, remainingPath: String, value: String?, extras: Bundle?): Boolean { + val key = remainingPath + val prefs = context.getSharedPreferences(SettingsRepository.PREFS_NAME, Context.MODE_PRIVATE) + if (!prefs.contains(key)) return false + + val currentValue = prefs.all[key] ?: return false + val repository = SettingsRepository(context) + + return try { + when (currentValue) { + is Boolean -> repository.putBoolean(key, value?.toBoolean() ?: false) + is String -> repository.putString(key, value) + is Int -> repository.putInt(key, value?.toInt() ?: 0) + is Float -> repository.putFloat(key, value?.toFloat() ?: 0f) + is Long -> repository.putLong(key, value?.toLong() ?: 0L) + else -> false + } + true + } catch (e: Exception) { + false + } + } + + override fun onAction(context: Context, remainingPath: String, action: String?, extras: Bundle?): Bundle? { + if (action == "toggle") { + val key = remainingPath + val prefs = context.getSharedPreferences(SettingsRepository.PREFS_NAME, Context.MODE_PRIVATE) + val currentValue = prefs.all[key] + if (currentValue is Boolean) { + val repository = SettingsRepository(context) + val newValue = !currentValue + repository.putBoolean(key, newValue) + return Bundle().apply { putBoolean("value", newValue) } + } + } + return null + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7fe5b43d0..0e1a042f5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1556,4 +1556,7 @@ Favorite Features View and launch your favorite features at a glance. No favorite features pinned yet. Open the app to pin some! + + control Essentials features + read, update and control Essentials features and settings