fix(android): resolve MainActivity ClassNotFoundException and add exact alarm permission check
- Fix MainActivity ClassNotFoundException by using dynamic package launcher intent - Replace hardcoded MainActivity class references with getLaunchIntent() helper - Uses packageManager.getLaunchIntentForPackage() to work with any host app - Removes dependency on specific MainActivity package/class name - Fixes 3 occurrences in NotifyReceiver.kt (alarm clock, notification click, reminder click) - Add exact alarm permission check before scheduling (Android 12+) - Add canScheduleExactAlarms() helper to check SCHEDULE_EXACT_ALARM permission - Check permission before scheduling exact alarms in scheduleExactNotification() - Gracefully fall back to inexact alarms when permission not granted - Prevents SecurityException and provides clear logging - Bump version to 1.0.2 Fixes: - ClassNotFoundException when plugin tries to resolve hardcoded MainActivity path - SecurityException on Android 12+ when exact alarm permission not granted - Plugin now works with any host app regardless of MainActivity package/class All changes maintain backward compatibility and improve reliability.
This commit is contained in:
@@ -37,6 +37,43 @@ class NotifyReceiver : BroadcastReceiver() {
|
||||
return (triggerAtMillis and 0xFFFF).toInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get launch intent for the host app
|
||||
* Uses package launcher intent to avoid hardcoding MainActivity class name
|
||||
* This works across all host apps regardless of their MainActivity package/class
|
||||
*
|
||||
* @param context Application context
|
||||
* @return Intent to launch the app, or null if not available
|
||||
*/
|
||||
private fun getLaunchIntent(context: Context): Intent? {
|
||||
return try {
|
||||
// Use package launcher intent - works for any host app
|
||||
context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to get launch intent for package: ${context.packageName}", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if exact alarm permission is granted
|
||||
* On Android 12+ (API 31+), SCHEDULE_EXACT_ALARM must be granted at runtime
|
||||
*
|
||||
* @param context Application context
|
||||
* @return true if exact alarms can be scheduled, false otherwise
|
||||
*/
|
||||
private fun canScheduleExactAlarms(context: Context): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
|
||||
alarmManager?.canScheduleExactAlarms() ?: false
|
||||
} else {
|
||||
// Pre-Android 12: exact alarms are always allowed
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule an exact notification using AlarmManager
|
||||
* Uses setAlarmClock() for Android 5.0+ for better reliability
|
||||
@@ -85,21 +122,27 @@ class NotifyReceiver : BroadcastReceiver() {
|
||||
|
||||
Log.i(TAG, "Scheduling alarm: triggerTime=$triggerTimeStr, delayMs=$delayMs, requestCode=$requestCode")
|
||||
|
||||
// Check exact alarm permission before scheduling (Android 12+)
|
||||
val canScheduleExact = canScheduleExactAlarms(context)
|
||||
if (!canScheduleExact && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
Log.w(TAG, "Exact alarm permission not granted. Cannot schedule exact alarm. User must grant SCHEDULE_EXACT_ALARM permission in settings.")
|
||||
// Fall back to inexact alarm
|
||||
alarmManager.set(
|
||||
AlarmManager.RTC_WAKEUP,
|
||||
triggerAtMillis,
|
||||
pendingIntent
|
||||
)
|
||||
Log.i(TAG, "Inexact alarm scheduled (exact permission denied): triggerAt=$triggerAtMillis, requestCode=$requestCode")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Use setAlarmClock() for Android 5.0+ (API 21+) - most reliable method
|
||||
// Shows alarm icon in status bar and is exempt from doze mode
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
// Create show intent for alarm clock (opens app when alarm fires)
|
||||
val showIntent = try {
|
||||
Intent(context, Class.forName("com.timesafari.dailynotification.MainActivity")).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
}
|
||||
} catch (e: ClassNotFoundException) {
|
||||
Log.w(TAG, "MainActivity not found, using package launcher", e)
|
||||
context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
}
|
||||
}
|
||||
// Use package launcher intent to avoid hardcoding MainActivity class name
|
||||
val showIntent = getLaunchIntent(context)
|
||||
|
||||
val showPendingIntent = if (showIntent != null) {
|
||||
PendingIntent.getActivity(
|
||||
@@ -359,17 +402,8 @@ class NotifyReceiver : BroadcastReceiver() {
|
||||
}
|
||||
|
||||
// Create intent to launch app when notification is clicked
|
||||
val intent = try {
|
||||
Intent(context, Class.forName("com.timesafari.dailynotification.MainActivity")).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
}
|
||||
} catch (e: ClassNotFoundException) {
|
||||
Log.w(TAG, "MainActivity not found, using package launcher", e)
|
||||
// Fallback: launch app by package name
|
||||
context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
} ?: return
|
||||
}
|
||||
// Use package launcher intent to avoid hardcoding MainActivity class name
|
||||
val intent = getLaunchIntent(context) ?: return
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
context,
|
||||
0,
|
||||
@@ -478,17 +512,8 @@ class NotifyReceiver : BroadcastReceiver() {
|
||||
createReminderNotificationChannel(context, notificationManager)
|
||||
|
||||
// Create intent to launch app when notification is clicked
|
||||
val intent = try {
|
||||
Intent(context, Class.forName("com.timesafari.dailynotification.MainActivity")).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
}
|
||||
} catch (e: ClassNotFoundException) {
|
||||
Log.w(TAG, "MainActivity not found, using package launcher", e)
|
||||
// Fallback: launch app by package name
|
||||
context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
} ?: return
|
||||
}
|
||||
// Use package launcher intent to avoid hardcoding MainActivity class name
|
||||
val intent = getLaunchIntent(context) ?: return
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
context,
|
||||
reminderId.hashCode(),
|
||||
|
||||
Reference in New Issue
Block a user