docs: add comprehensive integration guides and diagnostic method documentation
Add integration guides and update API documentation with new Android diagnostic methods. Emphasize critical NotifyReceiver registration requirement that was causing notification delivery failures. Documentation Updates: - API.md: Document isAlarmScheduled(), getNextAlarmTime(), testAlarm() - README.md: Add Quick Integration section and Android diagnostic methods - notification-testing-procedures.md: Add BroadcastReceiver troubleshooting New Integration Guides: - QUICK_INTEGRATION.md: Step-by-step guide for human developers - AI_INTEGRATION_GUIDE.md: Machine-readable guide with verification steps - TODO.md: Task tracking for pending improvements Key Improvements: - Explicit NotifyReceiver registration requirement highlighted - Complete troubleshooting flow for BroadcastReceiver issues - Diagnostic method examples for debugging alarm scheduling - AI-friendly integration instructions with verification commands Fixes notification delivery issues caused by missing NotifyReceiver registration in host app AndroidManifest.xml files.
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
package com.timesafari.dailynotification;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
/**
|
||||
* Instrumentation tests for Daily Notification Plugin
|
||||
*
|
||||
* Tests critical notification scheduling and delivery paths
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class NotificationInstrumentationTest {
|
||||
|
||||
private Context appContext;
|
||||
private AlarmManager alarmManager;
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule permissionRule = GrantPermissionRule.grant(
|
||||
android.Manifest.permission.POST_NOTIFICATIONS,
|
||||
android.Manifest.permission.SCHEDULE_EXACT_ALARM
|
||||
);
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
alarmManager = (AlarmManager) appContext.getSystemService(Context.ALARM_SERVICE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyReceiverRegistration() {
|
||||
// Verify NotifyReceiver is registered in AndroidManifest
|
||||
Intent intent = new Intent(appContext, NotifyReceiver.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
||||
appContext,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE
|
||||
);
|
||||
|
||||
// If NotifyReceiver is registered, we can create a PendingIntent for it
|
||||
assertNotNull("NotifyReceiver should be registered in AndroidManifest", pendingIntent);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlarmScheduling() {
|
||||
// Test that alarms can be scheduled
|
||||
long triggerTime = System.currentTimeMillis() + 60000; // 1 minute from now
|
||||
|
||||
Intent intent = new Intent(appContext, NotifyReceiver.class);
|
||||
intent.putExtra("title", "Test Notification");
|
||||
intent.putExtra("body", "Test body");
|
||||
|
||||
int requestCode = NotifyReceiver.Companion.getRequestCode(triggerTime);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
||||
appContext,
|
||||
requestCode,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
||||
);
|
||||
|
||||
try {
|
||||
// Use setAlarmClock for Android 5.0+
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(
|
||||
triggerTime,
|
||||
null
|
||||
);
|
||||
alarmManager.setAlarmClock(alarmClockInfo, pendingIntent);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
alarmManager.setExactAndAllowWhileIdle(
|
||||
AlarmManager.RTC_WAKEUP,
|
||||
triggerTime,
|
||||
pendingIntent
|
||||
);
|
||||
} else {
|
||||
alarmManager.setExact(
|
||||
AlarmManager.RTC_WAKEUP,
|
||||
triggerTime,
|
||||
pendingIntent
|
||||
);
|
||||
}
|
||||
|
||||
// Verify alarm is scheduled
|
||||
boolean isScheduled = NotifyReceiver.Companion.isAlarmScheduled(appContext, triggerTime);
|
||||
assertTrue("Alarm should be scheduled", isScheduled);
|
||||
|
||||
// Clean up
|
||||
alarmManager.cancel(pendingIntent);
|
||||
} catch (SecurityException e) {
|
||||
fail("Should have permission to schedule exact alarms: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUniqueRequestCodes() {
|
||||
// Test that different trigger times generate different request codes
|
||||
long triggerTime1 = System.currentTimeMillis() + 60000;
|
||||
long triggerTime2 = System.currentTimeMillis() + 120000;
|
||||
|
||||
int requestCode1 = NotifyReceiver.Companion.getRequestCode(triggerTime1);
|
||||
int requestCode2 = NotifyReceiver.Companion.getRequestCode(triggerTime2);
|
||||
|
||||
// Request codes should be different for different trigger times
|
||||
assertNotEquals("Different trigger times should generate different request codes",
|
||||
requestCode1, requestCode2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlarmStatusCheck() {
|
||||
// Test isAlarmScheduled method
|
||||
long triggerTime = System.currentTimeMillis() + 60000;
|
||||
|
||||
// Initially should not be scheduled
|
||||
boolean initiallyScheduled = NotifyReceiver.Companion.isAlarmScheduled(appContext, triggerTime);
|
||||
assertFalse("Alarm should not be scheduled initially", initiallyScheduled);
|
||||
|
||||
// Schedule alarm
|
||||
Intent intent = new Intent(appContext, NotifyReceiver.class);
|
||||
intent.putExtra("title", "Test");
|
||||
intent.putExtra("body", "Test");
|
||||
|
||||
int requestCode = NotifyReceiver.Companion.getRequestCode(triggerTime);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
||||
appContext,
|
||||
requestCode,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
||||
);
|
||||
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(
|
||||
triggerTime,
|
||||
null
|
||||
);
|
||||
alarmManager.setAlarmClock(alarmClockInfo, pendingIntent);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
alarmManager.setExactAndAllowWhileIdle(
|
||||
AlarmManager.RTC_WAKEUP,
|
||||
triggerTime,
|
||||
pendingIntent
|
||||
);
|
||||
} else {
|
||||
alarmManager.setExact(
|
||||
AlarmManager.RTC_WAKEUP,
|
||||
triggerTime,
|
||||
pendingIntent
|
||||
);
|
||||
}
|
||||
|
||||
// Now should be scheduled
|
||||
boolean afterScheduling = NotifyReceiver.Companion.isAlarmScheduled(appContext, triggerTime);
|
||||
assertTrue("Alarm should be scheduled after calling setAlarmClock", afterScheduling);
|
||||
|
||||
// Clean up
|
||||
alarmManager.cancel(pendingIntent);
|
||||
} catch (SecurityException e) {
|
||||
fail("Should have permission to schedule exact alarms: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNextAlarmTime() {
|
||||
// Test getNextAlarmTime method (requires Android 5.0+)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
Long nextAlarmTime = NotifyReceiver.Companion.getNextAlarmTime(appContext);
|
||||
|
||||
// May be null if no alarm scheduled, which is valid
|
||||
if (nextAlarmTime != null) {
|
||||
assertTrue("Next alarm time should be in the future",
|
||||
nextAlarmTime > System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlarmCancellation() {
|
||||
// Test that alarms can be cancelled
|
||||
long triggerTime = System.currentTimeMillis() + 60000;
|
||||
|
||||
Intent intent = new Intent(appContext, NotifyReceiver.class);
|
||||
intent.putExtra("title", "Test");
|
||||
intent.putExtra("body", "Test");
|
||||
|
||||
int requestCode = NotifyReceiver.Companion.getRequestCode(triggerTime);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
||||
appContext,
|
||||
requestCode,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
||||
);
|
||||
|
||||
try {
|
||||
// Schedule alarm
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(
|
||||
triggerTime,
|
||||
null
|
||||
);
|
||||
alarmManager.setAlarmClock(alarmClockInfo, pendingIntent);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
alarmManager.setExactAndAllowWhileIdle(
|
||||
AlarmManager.RTC_WAKEUP,
|
||||
triggerTime,
|
||||
pendingIntent
|
||||
);
|
||||
} else {
|
||||
alarmManager.setExact(
|
||||
AlarmManager.RTC_WAKEUP,
|
||||
triggerTime,
|
||||
pendingIntent
|
||||
);
|
||||
}
|
||||
|
||||
// Verify scheduled
|
||||
assertTrue("Alarm should be scheduled",
|
||||
NotifyReceiver.Companion.isAlarmScheduled(appContext, triggerTime));
|
||||
|
||||
// Cancel alarm
|
||||
NotifyReceiver.Companion.cancelNotification(appContext, triggerTime);
|
||||
|
||||
// Verify cancelled
|
||||
assertFalse("Alarm should be cancelled",
|
||||
NotifyReceiver.Companion.isAlarmScheduled(appContext, triggerTime));
|
||||
} catch (SecurityException e) {
|
||||
fail("Should have permission to schedule exact alarms: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPendingIntentUniqueness() {
|
||||
// Test that PendingIntents with different request codes don't conflict
|
||||
long triggerTime1 = System.currentTimeMillis() + 60000;
|
||||
long triggerTime2 = System.currentTimeMillis() + 120000;
|
||||
|
||||
Intent intent1 = new Intent(appContext, NotifyReceiver.class);
|
||||
intent1.putExtra("title", "Test 1");
|
||||
intent1.putExtra("body", "Test 1");
|
||||
|
||||
Intent intent2 = new Intent(appContext, NotifyReceiver.class);
|
||||
intent2.putExtra("title", "Test 2");
|
||||
intent2.putExtra("body", "Test 2");
|
||||
|
||||
int requestCode1 = NotifyReceiver.Companion.getRequestCode(triggerTime1);
|
||||
int requestCode2 = NotifyReceiver.Companion.getRequestCode(triggerTime2);
|
||||
|
||||
PendingIntent pendingIntent1 = PendingIntent.getBroadcast(
|
||||
appContext,
|
||||
requestCode1,
|
||||
intent1,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
||||
);
|
||||
|
||||
PendingIntent pendingIntent2 = PendingIntent.getBroadcast(
|
||||
appContext,
|
||||
requestCode2,
|
||||
intent2,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
||||
);
|
||||
|
||||
// Both PendingIntents should be created successfully
|
||||
assertNotNull("First PendingIntent should be created", pendingIntent1);
|
||||
assertNotNull("Second PendingIntent should be created", pendingIntent2);
|
||||
|
||||
// They should be different objects
|
||||
assertNotSame("PendingIntents should be different objects",
|
||||
pendingIntent1, pendingIntent2);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user