fix(android): implement missing plugin methods and permission handling
- Add handleOnResume() fallback to resolve permission requests when Capacitor Bridge doesn't route results (requestCode 1001) - Implement checkPermissions() with override modifier for Capacitor standard PermissionStatus format - Implement getExactAlarmStatus() to return exact alarm capability info - Implement updateStarredPlans() to store plan IDs in SharedPreferences - Fix requestPermissions() override to properly delegate to requestNotificationPermissions() - Fix handleRequestPermissionsResult() return type to Unit These changes ensure permission requests resolve correctly even when Capacitor's Bridge doesn't recognize our custom request code, and implement all missing methods called by the test application.
This commit is contained in:
@@ -5,9 +5,11 @@ import android.app.Activity
|
||||
import android.app.AlarmManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
@@ -20,6 +22,7 @@ import com.getcapacitor.annotation.CapacitorPlugin
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
@@ -76,6 +79,8 @@ open class DailyNotificationPlugin : Plugin() {
|
||||
|
||||
private var db: DailyNotificationDatabase? = null
|
||||
|
||||
private val PERMISSION_REQUEST_CODE = 1001
|
||||
|
||||
override fun load() {
|
||||
super.load()
|
||||
try {
|
||||
@@ -91,6 +96,38 @@ open class DailyNotificationPlugin : Plugin() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle app resume - check for pending permission calls
|
||||
* This is a fallback since Capacitor's Bridge intercepts permission results
|
||||
* and may not route them to our plugin's handleRequestPermissionsResult
|
||||
*/
|
||||
override fun handleOnResume() {
|
||||
super.handleOnResume()
|
||||
|
||||
// Check if we have a pending permission call
|
||||
val call = savedCall
|
||||
if (call != null && context != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
// Check if this is a permission request call by checking the method name
|
||||
val methodName = call.methodName
|
||||
if (methodName == "requestPermissions" || methodName == "requestNotificationPermissions") {
|
||||
// Check current permission status
|
||||
val granted = ContextCompat.checkSelfPermission(
|
||||
context!!,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
|
||||
val result = JSObject().apply {
|
||||
put("status", if (granted) "granted" else "denied")
|
||||
put("granted", granted)
|
||||
put("notifications", if (granted) "granted" else "denied")
|
||||
}
|
||||
|
||||
Log.i(TAG, "Resolving pending permission call on resume: granted=$granted")
|
||||
call.resolve(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDatabase(): DailyNotificationDatabase {
|
||||
if (db == null) {
|
||||
if (context == null) {
|
||||
@@ -174,6 +211,175 @@ open class DailyNotificationPlugin : Plugin() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check permissions (Capacitor standard format)
|
||||
* Returns PermissionStatus with notifications field as PermissionState
|
||||
*/
|
||||
@PluginMethod
|
||||
override fun checkPermissions(call: PluginCall) {
|
||||
try {
|
||||
if (context == null) {
|
||||
return call.reject("Context not available")
|
||||
}
|
||||
|
||||
Log.i(TAG, "Checking permissions (Capacitor format)")
|
||||
|
||||
var notificationsState = "denied"
|
||||
var notificationsEnabled = false
|
||||
|
||||
// Check POST_NOTIFICATIONS permission
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val granted = context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
|
||||
notificationsState = if (granted) "granted" else "prompt"
|
||||
notificationsEnabled = granted
|
||||
} else {
|
||||
// Pre-Android 13: check if notifications are enabled
|
||||
val enabled = NotificationManagerCompat.from(context).areNotificationsEnabled()
|
||||
notificationsState = if (enabled) "granted" else "denied"
|
||||
notificationsEnabled = enabled
|
||||
}
|
||||
|
||||
val result = JSObject().apply {
|
||||
put("status", notificationsState)
|
||||
put("granted", notificationsEnabled)
|
||||
put("notifications", notificationsState)
|
||||
put("notificationsEnabled", notificationsEnabled)
|
||||
}
|
||||
|
||||
Log.i(TAG, "Permissions check: notifications=$notificationsState, enabled=$notificationsEnabled")
|
||||
call.resolve(result)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to check permissions", e)
|
||||
call.reject("Permission check failed: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get exact alarm status
|
||||
* Returns detailed information about exact alarm scheduling capability
|
||||
*/
|
||||
@PluginMethod
|
||||
fun getExactAlarmStatus(call: PluginCall) {
|
||||
try {
|
||||
if (context == null) {
|
||||
return call.reject("Context not available")
|
||||
}
|
||||
|
||||
Log.i(TAG, "Getting exact alarm status")
|
||||
|
||||
val supported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
||||
var enabled = false
|
||||
var canSchedule = false
|
||||
val fallbackWindow = "15 minutes" // Standard fallback window for inexact alarms
|
||||
|
||||
if (supported) {
|
||||
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
|
||||
enabled = alarmManager?.canScheduleExactAlarms() ?: false
|
||||
canSchedule = enabled
|
||||
} else {
|
||||
// Pre-Android 12: exact alarms are always allowed
|
||||
enabled = true
|
||||
canSchedule = true
|
||||
}
|
||||
|
||||
val result = JSObject().apply {
|
||||
put("supported", supported)
|
||||
put("enabled", enabled)
|
||||
put("canSchedule", canSchedule)
|
||||
put("fallbackWindow", fallbackWindow)
|
||||
}
|
||||
|
||||
Log.i(TAG, "Exact alarm status: supported=$supported, enabled=$enabled, canSchedule=$canSchedule")
|
||||
call.resolve(result)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to get exact alarm status", e)
|
||||
call.reject("Exact alarm status check failed: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update starred plan IDs
|
||||
* Stores plan IDs in SharedPreferences for native fetcher to use
|
||||
*/
|
||||
@PluginMethod
|
||||
fun updateStarredPlans(call: PluginCall) {
|
||||
try {
|
||||
if (context == null) {
|
||||
return call.reject("Context not available")
|
||||
}
|
||||
|
||||
val options = call.data ?: return call.reject("Options are required")
|
||||
|
||||
// Extract planIds array from options
|
||||
// Capacitor passes arrays as JSONArray in JSObject
|
||||
val planIdsValue = options.get("planIds")
|
||||
val planIds = mutableListOf<String>()
|
||||
|
||||
when (planIdsValue) {
|
||||
is JSONArray -> {
|
||||
// Direct JSONArray
|
||||
for (i in 0 until planIdsValue.length()) {
|
||||
planIds.add(planIdsValue.getString(i))
|
||||
}
|
||||
}
|
||||
is List<*> -> {
|
||||
// List from JSObject conversion
|
||||
planIds.addAll(planIdsValue.filterIsInstance<String>())
|
||||
}
|
||||
is String -> {
|
||||
// Single string (unlikely but handle it)
|
||||
planIds.add(planIdsValue)
|
||||
}
|
||||
else -> {
|
||||
// Try to get as JSObject and extract array
|
||||
val planIdsObj = options.getJSObject("planIds")
|
||||
if (planIdsObj != null) {
|
||||
val array = planIdsObj.get("planIds")
|
||||
if (array is JSONArray) {
|
||||
for (i in 0 until array.length()) {
|
||||
planIds.add(array.getString(i))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return call.reject("planIds array is required")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.i(TAG, "Updating starred plans: count=${planIds.size}")
|
||||
|
||||
// Store in SharedPreferences (matching TestNativeFetcher expectations)
|
||||
val prefsName = "daily_notification_timesafari"
|
||||
val keyStarredPlanIds = "starredPlanIds"
|
||||
|
||||
val prefs: SharedPreferences = context.getSharedPreferences(prefsName, Context.MODE_PRIVATE)
|
||||
val editor = prefs.edit()
|
||||
|
||||
// Convert planIds list to JSON array string
|
||||
val jsonArray = JSONArray()
|
||||
planIds.forEach { planId ->
|
||||
jsonArray.put(planId)
|
||||
}
|
||||
editor.putString(keyStarredPlanIds, jsonArray.toString())
|
||||
editor.apply()
|
||||
|
||||
val result = JSObject().apply {
|
||||
put("success", true)
|
||||
put("planIdsCount", planIds.size)
|
||||
put("updatedAt", System.currentTimeMillis())
|
||||
}
|
||||
|
||||
Log.i(TAG, "Starred plans updated: count=${planIds.size}")
|
||||
call.resolve(result)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to update starred plans", e)
|
||||
call.reject("Failed to update starred plans: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun requestNotificationPermissions(call: PluginCall) {
|
||||
try {
|
||||
@@ -194,20 +400,20 @@ open class DailyNotificationPlugin : Plugin() {
|
||||
}
|
||||
call.resolve(result)
|
||||
} else {
|
||||
// Request permission
|
||||
// Save the call using Capacitor's mechanism so it can be retrieved later
|
||||
saveCall(call)
|
||||
|
||||
// Request permission - result will be handled in handleRequestPermissionsResult
|
||||
// Note: Capacitor's Bridge intercepts permission results, so we also check
|
||||
// permission status when the app resumes as a fallback
|
||||
ActivityCompat.requestPermissions(
|
||||
activity,
|
||||
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
||||
1001 // Request code
|
||||
PERMISSION_REQUEST_CODE
|
||||
)
|
||||
// Note: Permission result will be handled by onRequestPermissionsResult
|
||||
// For now, resolve with pending status
|
||||
val result = JSObject().apply {
|
||||
put("status", "prompt")
|
||||
put("granted", false)
|
||||
put("notifications", "prompt")
|
||||
}
|
||||
call.resolve(result)
|
||||
|
||||
Log.i(TAG, "Permission dialog shown, waiting for user response (requestCode=$PERMISSION_REQUEST_CODE)")
|
||||
// Don't resolve here - wait for handleRequestPermissionsResult
|
||||
}
|
||||
} else {
|
||||
// For older versions, permissions are granted at install time
|
||||
@@ -224,6 +430,51 @@ open class DailyNotificationPlugin : Plugin() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request permissions (alias for requestNotificationPermissions)
|
||||
* Delegates to requestNotificationPermissions for consistency
|
||||
*/
|
||||
@PluginMethod
|
||||
override fun requestPermissions(call: PluginCall) {
|
||||
requestNotificationPermissions(call)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle permission request results
|
||||
* Called by Capacitor when user responds to permission dialog
|
||||
*/
|
||||
override fun handleRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
Log.i(TAG, "handleRequestPermissionsResult called: requestCode=$requestCode, permissions=${permissions.contentToString()}")
|
||||
|
||||
if (requestCode == PERMISSION_REQUEST_CODE) {
|
||||
// Retrieve the saved call
|
||||
val call = savedCall
|
||||
if (call != null) {
|
||||
val granted = grantResults.isNotEmpty() &&
|
||||
grantResults[0] == PackageManager.PERMISSION_GRANTED
|
||||
|
||||
val result = JSObject().apply {
|
||||
put("status", if (granted) "granted" else "denied")
|
||||
put("granted", granted)
|
||||
put("notifications", if (granted) "granted" else "denied")
|
||||
}
|
||||
|
||||
Log.i(TAG, "Permission request result: granted=$granted, resolving call")
|
||||
call.resolve(result)
|
||||
return
|
||||
} else {
|
||||
Log.w(TAG, "No saved call found for permission request code $requestCode")
|
||||
}
|
||||
}
|
||||
|
||||
// Not handled by this plugin, let parent handle it
|
||||
super.handleRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun configureNativeFetcher(call: PluginCall) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user