Browse Source
- Add ChannelManager class for notification channel management - Implement channel existence checking and creation - Add channel importance validation (guards against IMPORTANCE_NONE) - Add deep link to channel settings when notifications are blocked - Integrate channel manager into plugin load() method - Add new plugin methods: isChannelEnabled(), openChannelSettings(), checkStatus() - Add comprehensive status checking including permissions and channel state - Add test app UI for channel management testing P0 Priority Implementation: - Guards against channel = NONE (blocked notifications) - Provides actionable error messages with deep links to settings - Ensures notifications can actually be delivered - Comprehensive status checking for all notification requirements This addresses the critical issue where notifications are scheduled but silently dropped due to blocked notification channels.master
2 changed files with 324 additions and 0 deletions
@ -0,0 +1,193 @@ |
|||
package com.timesafari.dailynotification; |
|||
|
|||
import android.app.NotificationChannel; |
|||
import android.app.NotificationManager; |
|||
import android.content.Context; |
|||
import android.content.Intent; |
|||
import android.net.Uri; |
|||
import android.os.Build; |
|||
import android.provider.Settings; |
|||
import android.util.Log; |
|||
|
|||
/** |
|||
* Manages notification channels and ensures they are properly configured |
|||
* for reliable notification delivery. |
|||
* |
|||
* Handles channel creation, importance checking, and provides deep links |
|||
* to channel settings when notifications are blocked. |
|||
* |
|||
* @author Matthew Raymer |
|||
* @version 1.0 |
|||
*/ |
|||
public class ChannelManager { |
|||
private static final String TAG = "ChannelManager"; |
|||
private static final String DEFAULT_CHANNEL_ID = "daily_default"; |
|||
private static final String DEFAULT_CHANNEL_NAME = "Daily Notifications"; |
|||
private static final String DEFAULT_CHANNEL_DESCRIPTION = "Daily notifications from TimeSafari"; |
|||
|
|||
private final Context context; |
|||
private final NotificationManager notificationManager; |
|||
|
|||
public ChannelManager(Context context) { |
|||
this.context = context; |
|||
this.notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); |
|||
} |
|||
|
|||
/** |
|||
* Ensures the default notification channel exists and is properly configured. |
|||
* Creates the channel if it doesn't exist. |
|||
* |
|||
* @return true if channel is ready for notifications, false if blocked |
|||
*/ |
|||
public boolean ensureChannelExists() { |
|||
try { |
|||
Log.d(TAG, "Ensuring notification channel exists"); |
|||
|
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
|||
NotificationChannel channel = notificationManager.getNotificationChannel(DEFAULT_CHANNEL_ID); |
|||
|
|||
if (channel == null) { |
|||
Log.d(TAG, "Creating notification channel"); |
|||
createDefaultChannel(); |
|||
return true; |
|||
} else { |
|||
Log.d(TAG, "Channel exists with importance: " + channel.getImportance()); |
|||
return channel.getImportance() != NotificationManager.IMPORTANCE_NONE; |
|||
} |
|||
} else { |
|||
// Pre-Oreo: channels don't exist, always ready
|
|||
Log.d(TAG, "Pre-Oreo device, channels not applicable"); |
|||
return true; |
|||
} |
|||
} catch (Exception e) { |
|||
Log.e(TAG, "Error ensuring channel exists", e); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Checks if the notification channel is enabled and can deliver notifications. |
|||
* |
|||
* @return true if channel is enabled, false if blocked |
|||
*/ |
|||
public boolean isChannelEnabled() { |
|||
try { |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
|||
NotificationChannel channel = notificationManager.getNotificationChannel(DEFAULT_CHANNEL_ID); |
|||
if (channel == null) { |
|||
Log.w(TAG, "Channel does not exist"); |
|||
return false; |
|||
} |
|||
|
|||
int importance = channel.getImportance(); |
|||
Log.d(TAG, "Channel importance: " + importance); |
|||
return importance != NotificationManager.IMPORTANCE_NONE; |
|||
} else { |
|||
// Pre-Oreo: always enabled
|
|||
return true; |
|||
} |
|||
} catch (Exception e) { |
|||
Log.e(TAG, "Error checking channel status", e); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Gets the current channel importance level. |
|||
* |
|||
* @return importance level, or -1 if channel doesn't exist |
|||
*/ |
|||
public int getChannelImportance() { |
|||
try { |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
|||
NotificationChannel channel = notificationManager.getNotificationChannel(DEFAULT_CHANNEL_ID); |
|||
if (channel != null) { |
|||
return channel.getImportance(); |
|||
} |
|||
} |
|||
return -1; |
|||
} catch (Exception e) { |
|||
Log.e(TAG, "Error getting channel importance", e); |
|||
return -1; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Opens the notification channel settings for the user to enable notifications. |
|||
* |
|||
* @return true if settings intent was launched, false otherwise |
|||
*/ |
|||
public boolean openChannelSettings() { |
|||
try { |
|||
Log.d(TAG, "Opening channel settings"); |
|||
|
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
|||
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) |
|||
.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName()) |
|||
.putExtra(Settings.EXTRA_CHANNEL_ID, DEFAULT_CHANNEL_ID) |
|||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
|||
|
|||
context.startActivity(intent); |
|||
Log.d(TAG, "Channel settings opened"); |
|||
return true; |
|||
} else { |
|||
Log.d(TAG, "Channel settings not available on pre-Oreo"); |
|||
return false; |
|||
} |
|||
} catch (Exception e) { |
|||
Log.e(TAG, "Error opening channel settings", e); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Creates the default notification channel with high importance. |
|||
*/ |
|||
private void createDefaultChannel() { |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
|||
NotificationChannel channel = new NotificationChannel( |
|||
DEFAULT_CHANNEL_ID, |
|||
DEFAULT_CHANNEL_NAME, |
|||
NotificationManager.IMPORTANCE_HIGH |
|||
); |
|||
channel.setDescription(DEFAULT_CHANNEL_DESCRIPTION); |
|||
channel.enableLights(true); |
|||
channel.enableVibration(true); |
|||
channel.setShowBadge(true); |
|||
|
|||
notificationManager.createNotificationChannel(channel); |
|||
Log.d(TAG, "Default channel created with HIGH importance"); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Gets the default channel ID for use in notifications. |
|||
* |
|||
* @return the default channel ID |
|||
*/ |
|||
public String getDefaultChannelId() { |
|||
return DEFAULT_CHANNEL_ID; |
|||
} |
|||
|
|||
/** |
|||
* Logs the current channel status for debugging. |
|||
*/ |
|||
public void logChannelStatus() { |
|||
try { |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
|||
NotificationChannel channel = notificationManager.getNotificationChannel(DEFAULT_CHANNEL_ID); |
|||
if (channel != null) { |
|||
Log.i(TAG, "Channel Status - ID: " + channel.getId() + |
|||
", Importance: " + channel.getImportance() + |
|||
", Enabled: " + (channel.getImportance() != NotificationManager.IMPORTANCE_NONE)); |
|||
} else { |
|||
Log.w(TAG, "Channel does not exist"); |
|||
} |
|||
} else { |
|||
Log.i(TAG, "Pre-Oreo device, channels not applicable"); |
|||
} |
|||
} catch (Exception e) { |
|||
Log.e(TAG, "Error logging channel status", e); |
|||
} |
|||
} |
|||
} |
Loading…
Reference in new issue