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