refactor(android,ios): rename package com.timesafari to org.timesafari.dailynotification
- Android: move plugin source to org/timesafari/dailynotification, update namespace, manifest package, and all package/imports; change intent actions to org.timesafari.daily.NOTIFICATION and DISMISS - iOS: update bundle IDs, BGTask identifiers, subsystem labels, and queue names in Plugin and Xcode projects - Capacitor: update plugin class registration and appIds in configs - Test apps (android-test-app, daily-notification-test, ios-test-app): applicationId/bundleId, manifests, ProGuard, scripts, and docs - Docs: bulk update references; add CONSUMING_APP_MIGRATION_COM_TO_ORG.md for consuming app migration BREAKING CHANGE: Consuming apps must update plugin class to org.timesafari.dailynotification.DailyNotificationPlugin, manifest receivers/actions, and iOS BGTask identifiers per migration doc.
This commit is contained in:
@@ -1239,10 +1239,10 @@ dependencies {
|
||||
-keep @androidx.room.Dao class *
|
||||
|
||||
# Plugin classes
|
||||
-keep class com.timesafari.dailynotification.** { *; }
|
||||
-keep class org.timesafari.dailynotification.** { *; }
|
||||
|
||||
# Capacitor plugin
|
||||
-keep class com.timesafari.dailynotification.DailyNotificationPlugin { *; }
|
||||
-keep class org.timesafari.dailynotification.DailyNotificationPlugin { *; }
|
||||
|
||||
# Encryption
|
||||
-keep class javax.crypto.** { *; }
|
||||
|
||||
@@ -653,7 +653,7 @@ public class MainActivity extends BridgeActivity {
|
||||
{
|
||||
"plugins": {
|
||||
"DailyNotification": {
|
||||
"class": "com.timesafari.dailynotification.DailyNotificationPlugin"
|
||||
"class": "org.timesafari.dailynotification.DailyNotificationPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -728,7 +728,7 @@ The Vue 3 test app uses a **project reference approach** for plugin integration:
|
||||
{
|
||||
"id": "DailyNotification",
|
||||
"name": "DailyNotification",
|
||||
"class": "com.timesafari.dailynotification.DailyNotificationPlugin"
|
||||
"class": "org.timesafari.dailynotification.DailyNotificationPlugin"
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -1128,7 +1128,7 @@ npx cap sync android
|
||||
#### AAR Duplicate Class Issues
|
||||
```bash
|
||||
# Problem: Duplicate class errors when integrating plugin AAR
|
||||
# Error: "Duplicate class com.timesafari.dailynotification.BootReceiver found in modules"
|
||||
# Error: "Duplicate class org.timesafari.dailynotification.BootReceiver found in modules"
|
||||
# Root Cause: Plugin being included both as project reference and as AAR file
|
||||
|
||||
# Solution 1: Use Project Reference Approach (Recommended)
|
||||
|
||||
@@ -564,12 +564,12 @@ await DailyNotification.updateDailyReminder('morning_checkin', {
|
||||
|
||||
<!-- NotifyReceiver for AlarmManager-based notifications -->
|
||||
<!-- REQUIRED: Without this, alarms fire but notifications won't display -->
|
||||
<receiver android:name="com.timesafari.dailynotification.NotifyReceiver"
|
||||
<receiver android:name="org.timesafari.dailynotification.NotifyReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
</receiver>
|
||||
|
||||
<receiver android:name="com.timesafari.dailynotification.BootReceiver"
|
||||
<receiver android:name="org.timesafari.dailynotification.BootReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
@@ -604,8 +604,8 @@ dependencies {
|
||||
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>com.timesafari.dailynotification.content-fetch</string>
|
||||
<string>com.timesafari.dailynotification.notification-delivery</string>
|
||||
<string>org.timesafari.dailynotification.content-fetch</string>
|
||||
<string>org.timesafari.dailynotification.notification-delivery</string>
|
||||
</array>
|
||||
```
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
android {
|
||||
namespace "com.timesafari.dailynotification.plugin"
|
||||
namespace "org.timesafari.dailynotification.plugin"
|
||||
compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35
|
||||
|
||||
defaultConfig {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# These rules are applied to consuming apps when they use this plugin
|
||||
|
||||
# Keep plugin classes
|
||||
-keep class com.timesafari.dailynotification.** { *; }
|
||||
-keep class org.timesafari.dailynotification.** { *; }
|
||||
|
||||
# Keep Capacitor plugin interface
|
||||
-keep class com.getcapacitor.Plugin { *; }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.timesafari.dailynotification.plugin">
|
||||
package="org.timesafari.dailynotification.plugin">
|
||||
|
||||
<!-- Plugin receivers are declared in consuming app's manifest -->
|
||||
<!-- This manifest is optional and mainly for library metadata -->
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
"pkg": "@timesafari/daily-notification-plugin",
|
||||
"name": "DailyNotification",
|
||||
"classpath": "com.timesafari.dailynotification.DailyNotificationPlugin"
|
||||
"classpath": "org.timesafari.dailynotification.DailyNotificationPlugin"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.timesafari.dailynotification
|
||||
package org.timesafari.dailynotification
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
@@ -43,7 +43,7 @@ public class ChannelManager {
|
||||
Log.d(TAG, "Ensuring notification channel exists");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = notificationManager.getNotificationChannel(com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
|
||||
NotificationChannel channel = notificationManager.getNotificationChannel(org.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
|
||||
|
||||
if (channel == null) {
|
||||
Log.d(TAG, "Creating notification channel");
|
||||
@@ -72,7 +72,7 @@ public class ChannelManager {
|
||||
public boolean isChannelEnabled() {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = notificationManager.getNotificationChannel(com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
|
||||
NotificationChannel channel = notificationManager.getNotificationChannel(org.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
|
||||
if (channel == null) {
|
||||
Log.w(TAG, "Channel does not exist");
|
||||
return false;
|
||||
@@ -99,7 +99,7 @@ public class ChannelManager {
|
||||
public int getChannelImportance() {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = notificationManager.getNotificationChannel(com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
|
||||
NotificationChannel channel = notificationManager.getNotificationChannel(org.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
|
||||
if (channel != null) {
|
||||
return channel.getImportance();
|
||||
}
|
||||
@@ -117,7 +117,7 @@ public class ChannelManager {
|
||||
* @return true if settings intent was launched, false otherwise
|
||||
*/
|
||||
public boolean openChannelSettings() {
|
||||
return openChannelSettings(com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
|
||||
return openChannelSettings(org.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,7 +142,7 @@ public class ChannelManager {
|
||||
try {
|
||||
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
|
||||
.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName())
|
||||
.putExtra(Settings.EXTRA_CHANNEL_ID, channelId != null ? channelId : com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID)
|
||||
.putExtra(Settings.EXTRA_CHANNEL_ID, channelId != null ? channelId : org.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
context.startActivity(intent);
|
||||
@@ -180,11 +180,11 @@ public class ChannelManager {
|
||||
private void createDefaultChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID,
|
||||
com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_NAME,
|
||||
org.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID,
|
||||
org.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_NAME,
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
);
|
||||
channel.setDescription(com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_DESCRIPTION);
|
||||
channel.setDescription(org.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_DESCRIPTION);
|
||||
channel.enableLights(true);
|
||||
channel.enableVibration(true);
|
||||
channel.setShowBadge(true);
|
||||
@@ -200,7 +200,7 @@ public class ChannelManager {
|
||||
* @return the default channel ID
|
||||
*/
|
||||
public String getDefaultChannelId() {
|
||||
return com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID;
|
||||
return org.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,7 +209,7 @@ public class ChannelManager {
|
||||
public void logChannelStatus() {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = notificationManager.getNotificationChannel(com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
|
||||
NotificationChannel channel = notificationManager.getNotificationChannel(org.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
|
||||
if (channel != null) {
|
||||
Log.i(TAG, "Channel Status - ID: " + channel.getId() +
|
||||
", Importance: " + channel.getImportance() +
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification
|
||||
package org.timesafari.dailynotification
|
||||
|
||||
/**
|
||||
* Centralized constants for Daily Notification Plugin
|
||||
@@ -56,7 +56,7 @@ object DailyNotificationConstants {
|
||||
* Action string for notification broadcast intents
|
||||
* Used by AlarmManager PendingIntents
|
||||
*/
|
||||
const val ACTION_NOTIFICATION = "com.timesafari.daily.NOTIFICATION"
|
||||
const val ACTION_NOTIFICATION = "org.timesafari.daily.NOTIFICATION"
|
||||
|
||||
// ============================================================
|
||||
// Intent Extras Keys
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
@@ -42,7 +42,7 @@ public class DailyNotificationFetcher {
|
||||
|
||||
private final Context context;
|
||||
private final DailyNotificationStorage storage; // Deprecated path (kept for transitional read paths)
|
||||
private final com.timesafari.dailynotification.storage.DailyNotificationStorageRoom roomStorage; // Preferred path
|
||||
private final org.timesafari.dailynotification.storage.DailyNotificationStorageRoom roomStorage; // Preferred path
|
||||
private final WorkManager workManager;
|
||||
|
||||
// ETag manager for efficient fetching
|
||||
@@ -60,7 +60,7 @@ public class DailyNotificationFetcher {
|
||||
|
||||
public DailyNotificationFetcher(Context context,
|
||||
DailyNotificationStorage storage,
|
||||
com.timesafari.dailynotification.storage.DailyNotificationStorageRoom roomStorage) {
|
||||
org.timesafari.dailynotification.storage.DailyNotificationStorageRoom roomStorage) {
|
||||
this.context = context;
|
||||
this.storage = storage;
|
||||
this.roomStorage = roomStorage;
|
||||
@@ -220,8 +220,8 @@ public class DailyNotificationFetcher {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
com.timesafari.dailynotification.entities.NotificationContentEntity entity =
|
||||
new com.timesafari.dailynotification.entities.NotificationContentEntity(
|
||||
org.timesafari.dailynotification.entities.NotificationContentEntity entity =
|
||||
new org.timesafari.dailynotification.entities.NotificationContentEntity(
|
||||
content.getId() != null ? content.getId() : java.util.UUID.randomUUID().toString(),
|
||||
"1.0.0",
|
||||
null,
|
||||
@@ -9,7 +9,7 @@
|
||||
* @created 2025-10-03 06:53:30 UTC
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.util.Log;
|
||||
import android.content.Context;
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Debug;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.timesafari.dailynotification
|
||||
package org.timesafari.dailynotification
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
@@ -18,7 +18,7 @@ import androidx.work.WorkManager
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.Data
|
||||
import java.util.concurrent.TimeUnit
|
||||
import com.timesafari.dailynotification.DailyNotificationFetchWorker
|
||||
import org.timesafari.dailynotification.DailyNotificationFetchWorker
|
||||
import com.getcapacitor.JSObject
|
||||
import com.getcapacitor.Plugin
|
||||
import com.getcapacitor.PluginCall
|
||||
@@ -585,7 +585,7 @@ open class DailyNotificationPlugin : Plugin() {
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
val config = com.timesafari.dailynotification.entities.NotificationConfigEntity(
|
||||
val config = org.timesafari.dailynotification.entities.NotificationConfigEntity(
|
||||
configId, null, "native_fetcher", "config", configValue, "json"
|
||||
)
|
||||
getDatabase().notificationConfigDao().insertConfig(config)
|
||||
@@ -2153,7 +2153,7 @@ open class DailyNotificationPlugin : Plugin() {
|
||||
?: return@launch call.reject("Config value is required")
|
||||
val configDataType = configJson.getString("configDataType", "string")
|
||||
|
||||
val entity = com.timesafari.dailynotification.entities.NotificationConfigEntity(
|
||||
val entity = org.timesafari.dailynotification.entities.NotificationConfigEntity(
|
||||
id, timesafariDid, configType, configKey, configValue, configDataType
|
||||
)
|
||||
|
||||
@@ -2285,7 +2285,7 @@ open class DailyNotificationPlugin : Plugin() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun configToJson(config: com.timesafari.dailynotification.entities.NotificationConfigEntity): JSObject {
|
||||
private fun configToJson(config: org.timesafari.dailynotification.entities.NotificationConfigEntity): JSObject {
|
||||
return JSObject().apply {
|
||||
put("id", config.id)
|
||||
put("timesafariDid", config.timesafariDid)
|
||||
@@ -2474,7 +2474,7 @@ object TestDataHelper {
|
||||
suspend fun injectInvalidNotificationData(database: DailyNotificationDatabase): Boolean {
|
||||
return try {
|
||||
val invalidNotification =
|
||||
com.timesafari.dailynotification.entities.NotificationContentEntity()
|
||||
org.timesafari.dailynotification.entities.NotificationContentEntity()
|
||||
invalidNotification.id = "" // Empty ID - should be skipped by recovery
|
||||
invalidNotification.title = "Test Invalid Notification"
|
||||
invalidNotification.body = "This has an empty ID"
|
||||
@@ -2683,7 +2683,7 @@ object ScheduleHelper {
|
||||
// Persist title/body for this scheduleId so rollover and post-reboot resolve user content
|
||||
// (see plugin-feedback-android-rollover-double-fire-and-user-content)
|
||||
try {
|
||||
val entity = com.timesafari.dailynotification.entities.NotificationContentEntity(
|
||||
val entity = org.timesafari.dailynotification.entities.NotificationContentEntity(
|
||||
scheduleId,
|
||||
"1.3.1",
|
||||
null,
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
@@ -59,7 +59,7 @@ public class DailyNotificationReceiver extends BroadcastReceiver {
|
||||
return;
|
||||
}
|
||||
|
||||
if ("com.timesafari.daily.NOTIFICATION".equals(action)) {
|
||||
if ("org.timesafari.daily.NOTIFICATION".equals(action)) {
|
||||
// Parse intent and enqueue work - keep receiver ultra-light
|
||||
String notificationId = intent.getStringExtra(EXTRA_NOTIFICATION_ID);
|
||||
if (notificationId == null) {
|
||||
@@ -72,7 +72,7 @@ public class DailyNotificationReceiver extends BroadcastReceiver {
|
||||
enqueueNotificationWork(context, notificationId, intent);
|
||||
Log.d(TAG, "DN|RECEIVE_OK enqueued=" + notificationId);
|
||||
|
||||
} else if ("com.timesafari.daily.DISMISS".equals(action)) {
|
||||
} else if ("org.timesafari.daily.DISMISS".equals(action)) {
|
||||
// Handle dismissal - also lightweight
|
||||
String notificationId = intent.getStringExtra(EXTRA_NOTIFICATION_ID);
|
||||
if (notificationId != null) {
|
||||
@@ -362,7 +362,7 @@ public class DailyNotificationReceiver extends BroadcastReceiver {
|
||||
|
||||
// Add dismiss action
|
||||
Intent dismissIntent = new Intent(context, DailyNotificationReceiver.class);
|
||||
dismissIntent.setAction("com.timesafari.daily.DISMISS");
|
||||
dismissIntent.setAction("org.timesafari.daily.DISMISS");
|
||||
dismissIntent.putExtra(EXTRA_NOTIFICATION_ID, content.getId());
|
||||
|
||||
PendingIntent dismissPendingIntent = PendingIntent.getBroadcast(
|
||||
@@ -432,8 +432,8 @@ public class DailyNotificationReceiver extends BroadcastReceiver {
|
||||
}
|
||||
|
||||
// Create config for next notification
|
||||
com.timesafari.dailynotification.UserNotificationConfig config =
|
||||
new com.timesafari.dailynotification.UserNotificationConfig(
|
||||
org.timesafari.dailynotification.UserNotificationConfig config =
|
||||
new org.timesafari.dailynotification.UserNotificationConfig(
|
||||
true, // enabled
|
||||
cronExpression,
|
||||
content.getTitle() != null ? content.getTitle() : "Daily Notification",
|
||||
@@ -444,14 +444,14 @@ public class DailyNotificationReceiver extends BroadcastReceiver {
|
||||
);
|
||||
|
||||
// Use centralized scheduling function with ROLLOVER_ON_FIRE source
|
||||
com.timesafari.dailynotification.NotifyReceiver.scheduleExactNotification(
|
||||
org.timesafari.dailynotification.NotifyReceiver.scheduleExactNotification(
|
||||
context,
|
||||
nextScheduledTime,
|
||||
config,
|
||||
false, // isStaticReminder
|
||||
null, // reminderId
|
||||
scheduleId,
|
||||
com.timesafari.dailynotification.ScheduleSource.ROLLOVER_ON_FIRE,
|
||||
org.timesafari.dailynotification.ScheduleSource.ROLLOVER_ON_FIRE,
|
||||
false // skipPendingIntentIdempotence – rollover path does not skip
|
||||
);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
@@ -157,8 +157,8 @@ public class DailyNotificationScheduler {
|
||||
// Create intent for the notification; setPackage ensures AlarmManager delivery on all OEMs
|
||||
Intent intent = new Intent(context, DailyNotificationReceiver.class);
|
||||
intent.setPackage(context.getPackageName());
|
||||
intent.setAction(com.timesafari.dailynotification.DailyNotificationConstants.ACTION_NOTIFICATION);
|
||||
intent.putExtra(com.timesafari.dailynotification.DailyNotificationConstants.EXTRA_NOTIFICATION_ID, content.getId());
|
||||
intent.setAction(org.timesafari.dailynotification.DailyNotificationConstants.ACTION_NOTIFICATION);
|
||||
intent.putExtra(org.timesafari.dailynotification.DailyNotificationConstants.EXTRA_NOTIFICATION_ID, content.getId());
|
||||
|
||||
// Check if this is a static reminder
|
||||
if (content.getId().startsWith("reminder_") || content.getId().contains("_reminder")) {
|
||||
@@ -481,7 +481,7 @@ public class DailyNotificationScheduler {
|
||||
try {
|
||||
Log.d(TAG, "Scheduling test alarm in " + secondsFromNow + " seconds");
|
||||
// Delegate to NotifyReceiver.testAlarm()
|
||||
com.timesafari.dailynotification.NotifyReceiver.Companion.testAlarm(context, secondsFromNow);
|
||||
org.timesafari.dailynotification.NotifyReceiver.Companion.testAlarm(context, secondsFromNow);
|
||||
Log.i(TAG, "Test alarm scheduled successfully");
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error scheduling test alarm", e);
|
||||
@@ -591,7 +591,7 @@ public class DailyNotificationScheduler {
|
||||
// Note: NotifyReceiver.isAlarmScheduled is a Kotlin companion object function with default parameters
|
||||
// From Java, we need to use Companion and provide explicit values (null is acceptable for optional params)
|
||||
// Kotlin Long? maps to java.lang.Long in Java
|
||||
return com.timesafari.dailynotification.NotifyReceiver.Companion.isAlarmScheduled(
|
||||
return org.timesafari.dailynotification.NotifyReceiver.Companion.isAlarmScheduled(
|
||||
context,
|
||||
scheduleId,
|
||||
triggerAtMillis
|
||||
@@ -624,7 +624,7 @@ public class DailyNotificationScheduler {
|
||||
// Delegate to NotifyReceiver which checks actual AlarmManager state
|
||||
// Note: NotifyReceiver.getNextAlarmTime is a Kotlin companion object function
|
||||
// Kotlin Long? maps to java.lang.Long in Java
|
||||
return com.timesafari.dailynotification.NotifyReceiver.Companion.getNextAlarmTime(context);
|
||||
return org.timesafari.dailynotification.NotifyReceiver.Companion.getNextAlarmTime(context);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error getting next alarm time", e);
|
||||
return null;
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
@@ -30,9 +30,9 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.timesafari.dailynotification.storage.DailyNotificationStorageRoom;
|
||||
import com.timesafari.dailynotification.entities.NotificationContentEntity;
|
||||
import com.timesafari.dailynotification.DailyNotificationFetcher;
|
||||
import org.timesafari.dailynotification.storage.DailyNotificationStorageRoom;
|
||||
import org.timesafari.dailynotification.entities.NotificationContentEntity;
|
||||
import org.timesafari.dailynotification.DailyNotificationFetcher;
|
||||
|
||||
/**
|
||||
* WorkManager worker for processing daily notifications
|
||||
@@ -382,7 +382,7 @@ public class DailyNotificationWorker extends Worker {
|
||||
|
||||
// Create one-time work request
|
||||
androidx.work.OneTimeWorkRequest softRefetchWork = new androidx.work.OneTimeWorkRequest.Builder(
|
||||
com.timesafari.dailynotification.SoftRefetchWorker.class)
|
||||
org.timesafari.dailynotification.SoftRefetchWorker.class)
|
||||
.setConstraints(constraints)
|
||||
.setInputData(inputData)
|
||||
.setInitialDelay(softRefetchTime - System.currentTimeMillis(), java.util.concurrent.TimeUnit.MILLISECONDS)
|
||||
@@ -469,7 +469,7 @@ public class DailyNotificationWorker extends Worker {
|
||||
// Add action buttons
|
||||
// 1. Dismiss action
|
||||
Intent dismissIntent = new Intent(getApplicationContext(), DailyNotificationReceiver.class);
|
||||
dismissIntent.setAction("com.timesafari.daily.DISMISS");
|
||||
dismissIntent.setAction("org.timesafari.daily.DISMISS");
|
||||
dismissIntent.putExtra("notification_id", content.getId());
|
||||
|
||||
PendingIntent dismissPendingIntent = PendingIntent.getBroadcast(
|
||||
@@ -550,14 +550,14 @@ public class DailyNotificationWorker extends Worker {
|
||||
// When firing run used daily_rollover_* id, resolve canonical schedule so we still apply rolloverIntervalMinutes
|
||||
String logicalScheduleIdForRollover = scheduleIdForRollover;
|
||||
if (scheduleIdForRollover != null && scheduleIdForRollover.startsWith("daily_rollover_")) {
|
||||
com.timesafari.dailynotification.Schedule canonical = com.timesafari.dailynotification.ScheduleHelper.getCanonicalRolloverScheduleBlocking(getApplicationContext());
|
||||
org.timesafari.dailynotification.Schedule canonical = org.timesafari.dailynotification.ScheduleHelper.getCanonicalRolloverScheduleBlocking(getApplicationContext());
|
||||
if (canonical != null) {
|
||||
logicalScheduleIdForRollover = canonical.getId();
|
||||
}
|
||||
}
|
||||
Integer rolloverMinutes = null;
|
||||
if (logicalScheduleIdForRollover != null && !logicalScheduleIdForRollover.isEmpty()) {
|
||||
com.timesafari.dailynotification.Schedule s = com.timesafari.dailynotification.ScheduleHelper.getScheduleBlocking(getApplicationContext(), logicalScheduleIdForRollover);
|
||||
org.timesafari.dailynotification.Schedule s = org.timesafari.dailynotification.ScheduleHelper.getScheduleBlocking(getApplicationContext(), logicalScheduleIdForRollover);
|
||||
if (s != null && s.getRolloverIntervalMinutes() != null && s.getRolloverIntervalMinutes() > 0) {
|
||||
rolloverMinutes = s.getRolloverIntervalMinutes();
|
||||
Log.d(TAG, "DN|ROLLOVER_INTERVAL scheduleId=" + logicalScheduleIdForRollover + " minutes=" + rolloverMinutes);
|
||||
@@ -621,8 +621,8 @@ public class DailyNotificationWorker extends Worker {
|
||||
}
|
||||
|
||||
// Create config for next notification
|
||||
com.timesafari.dailynotification.UserNotificationConfig config =
|
||||
new com.timesafari.dailynotification.UserNotificationConfig(
|
||||
org.timesafari.dailynotification.UserNotificationConfig config =
|
||||
new org.timesafari.dailynotification.UserNotificationConfig(
|
||||
true, // enabled
|
||||
cronExpression,
|
||||
content.getTitle() != null ? content.getTitle() : "Daily Notification",
|
||||
@@ -634,18 +634,18 @@ public class DailyNotificationWorker extends Worker {
|
||||
|
||||
// Use centralized scheduling function with ROLLOVER_ON_FIRE source
|
||||
Log.d(TAG, "DN|ROLLOVER next=" + nextScheduledTime + " scheduleId=" + scheduleId + " static=" + preserveStaticReminder);
|
||||
com.timesafari.dailynotification.NotifyReceiver.scheduleExactNotification(
|
||||
org.timesafari.dailynotification.NotifyReceiver.scheduleExactNotification(
|
||||
getApplicationContext(),
|
||||
nextScheduledTime,
|
||||
config,
|
||||
preserveStaticReminder, // isStaticReminder – preserve so next run keeps title/body
|
||||
preserveStaticReminder ? scheduleId : null, // reminderId
|
||||
scheduleId,
|
||||
com.timesafari.dailynotification.ScheduleSource.ROLLOVER_ON_FIRE,
|
||||
org.timesafari.dailynotification.ScheduleSource.ROLLOVER_ON_FIRE,
|
||||
false // skipPendingIntentIdempotence – rollover path does not skip
|
||||
);
|
||||
if (scheduleId != null && !scheduleId.startsWith("daily_rollover_")) {
|
||||
com.timesafari.dailynotification.ScheduleHelper.updateScheduleNextRunTimeBlocking(
|
||||
org.timesafari.dailynotification.ScheduleHelper.updateScheduleNextRunTimeBlocking(
|
||||
getApplicationContext(), scheduleId, content.getScheduledTime(), nextScheduledTime);
|
||||
}
|
||||
// Log next scheduled time in readable format
|
||||
@@ -693,8 +693,8 @@ public class DailyNotificationWorker extends Worker {
|
||||
private NotificationContent getContentByScheduleId(String scheduleId) {
|
||||
if (scheduleId == null || scheduleId.isEmpty()) return null;
|
||||
try {
|
||||
com.timesafari.dailynotification.DailyNotificationDatabase db =
|
||||
com.timesafari.dailynotification.DailyNotificationDatabase.getInstance(getApplicationContext());
|
||||
org.timesafari.dailynotification.DailyNotificationDatabase db =
|
||||
org.timesafari.dailynotification.DailyNotificationDatabase.getInstance(getApplicationContext());
|
||||
NotificationContentEntity entity = db.notificationContentDao().getNotificationById(scheduleId);
|
||||
if (entity == null) {
|
||||
entity = db.notificationContentDao().getNotificationById("daily_" + scheduleId);
|
||||
@@ -716,8 +716,8 @@ public class DailyNotificationWorker extends Worker {
|
||||
try {
|
||||
DailyNotificationStorageRoom room = new DailyNotificationStorageRoom(getApplicationContext());
|
||||
// Use unified database (Kotlin schema with Java entities)
|
||||
com.timesafari.dailynotification.DailyNotificationDatabase db =
|
||||
com.timesafari.dailynotification.DailyNotificationDatabase.getInstance(getApplicationContext());
|
||||
org.timesafari.dailynotification.DailyNotificationDatabase db =
|
||||
org.timesafari.dailynotification.DailyNotificationDatabase.getInstance(getApplicationContext());
|
||||
NotificationContentEntity entity = db.notificationContentDao().getNotificationById(notificationId);
|
||||
if (entity != null) {
|
||||
return mapEntityToContent(entity);
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
/**
|
||||
* Information about a scheduled daily reminder
|
||||
@@ -9,7 +9,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
@@ -1,15 +1,15 @@
|
||||
package com.timesafari.dailynotification
|
||||
package org.timesafari.dailynotification
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.*
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import com.timesafari.dailynotification.entities.NotificationContentEntity
|
||||
import com.timesafari.dailynotification.entities.NotificationDeliveryEntity
|
||||
import com.timesafari.dailynotification.entities.NotificationConfigEntity
|
||||
import com.timesafari.dailynotification.dao.NotificationContentDao
|
||||
import com.timesafari.dailynotification.dao.NotificationDeliveryDao
|
||||
import com.timesafari.dailynotification.dao.NotificationConfigDao
|
||||
import org.timesafari.dailynotification.entities.NotificationContentEntity
|
||||
import org.timesafari.dailynotification.entities.NotificationDeliveryEntity
|
||||
import org.timesafari.dailynotification.entities.NotificationConfigEntity
|
||||
import org.timesafari.dailynotification.dao.NotificationContentDao
|
||||
import org.timesafari.dailynotification.dao.NotificationDeliveryDao
|
||||
import org.timesafari.dailynotification.dao.NotificationConfigDao
|
||||
|
||||
/**
|
||||
* Unified SQLite schema for Daily Notification Plugin
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.content.Context;
|
||||
@@ -9,7 +9,7 @@
|
||||
* @created 2025-10-03 06:53:30 UTC
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
@@ -11,7 +11,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.timesafari.dailynotification
|
||||
package org.timesafari.dailynotification
|
||||
|
||||
import android.content.Context
|
||||
import android.os.SystemClock
|
||||
@@ -203,7 +203,7 @@ class FetchWorker(
|
||||
val notificationId = "notify_$notificationTime"
|
||||
val (title, body) = parsePayload(payload)
|
||||
|
||||
val entity = com.timesafari.dailynotification.entities.NotificationContentEntity(
|
||||
val entity = org.timesafari.dailynotification.entities.NotificationContentEntity(
|
||||
notificationId,
|
||||
"1.3.3", // Plugin version
|
||||
null, // timesafariDid - can be set if available
|
||||
@@ -15,7 +15,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import java.util.List;
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.util.Log;
|
||||
import java.util.UUID;
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
@@ -518,13 +518,13 @@ public class NotificationStatusChecker {
|
||||
* @param database Database instance for querying schedules and history
|
||||
* @return JSObject containing notification status (schedules, last notification time, etc.)
|
||||
*/
|
||||
public JSObject getNotificationStatus(com.timesafari.dailynotification.DailyNotificationDatabase database) {
|
||||
public JSObject getNotificationStatus(org.timesafari.dailynotification.DailyNotificationDatabase database) {
|
||||
try {
|
||||
Log.d(TAG, "DN|NOTIFICATION_STATUS_START");
|
||||
|
||||
// Delegate to Kotlin helper function (uses runBlocking internally)
|
||||
// This is safe because status checks are quick operations
|
||||
return com.timesafari.dailynotification.NotificationStatusHelper.getNotificationStatusBlocking(database);
|
||||
return org.timesafari.dailynotification.NotificationStatusHelper.getNotificationStatusBlocking(database);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "DN|NOTIFICATION_STATUS_ERR err=" + e.getMessage(), e);
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.timesafari.dailynotification
|
||||
package org.timesafari.dailynotification
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.app.AlarmManager.AlarmClockInfo
|
||||
@@ -148,9 +148,9 @@ class NotifyReceiver : BroadcastReceiver() {
|
||||
val notificationId = reminderId ?: "notify_${triggerAtMillis}"
|
||||
|
||||
val requestCode = getRequestCode(stableScheduleId)
|
||||
val checkIntent = Intent(context, com.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
|
||||
val checkIntent = Intent(context, org.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
|
||||
setPackage(context.packageName)
|
||||
action = "com.timesafari.daily.NOTIFICATION"
|
||||
action = "org.timesafari.daily.NOTIFICATION"
|
||||
}
|
||||
|
||||
// IDEMPOTENCE CHECK: Verify no existing alarm for this trigger time before scheduling.
|
||||
@@ -254,8 +254,8 @@ class NotifyReceiver : BroadcastReceiver() {
|
||||
|
||||
// Always create a notification content entity for recovery tracking
|
||||
// Phase 1: Recovery needs NotificationContentEntity to detect missed notifications
|
||||
val roomStorage = com.timesafari.dailynotification.storage.DailyNotificationStorageRoom(context)
|
||||
val entity = com.timesafari.dailynotification.entities.NotificationContentEntity(
|
||||
val roomStorage = org.timesafari.dailynotification.storage.DailyNotificationStorageRoom(context)
|
||||
val entity = org.timesafari.dailynotification.entities.NotificationContentEntity(
|
||||
notificationId,
|
||||
"1.3.3", // Plugin version
|
||||
null, // timesafariDid - can be set if available
|
||||
@@ -288,9 +288,9 @@ class NotifyReceiver : BroadcastReceiver() {
|
||||
// FIX: Use DailyNotificationReceiver (registered in manifest) instead of NotifyReceiver
|
||||
// FIX: Set action to match manifest registration; setPackage() ensures AlarmManager
|
||||
// delivery reaches this app on all OEMs (see daily-notification-plugin-android-receiver-issue)
|
||||
val intent = Intent(context, com.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
|
||||
val intent = Intent(context, org.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
|
||||
setPackage(context.packageName)
|
||||
action = "com.timesafari.daily.NOTIFICATION" // Must match manifest intent-filter action
|
||||
action = "org.timesafari.daily.NOTIFICATION" // Must match manifest intent-filter action
|
||||
putExtra("notification_id", notificationId) // DailyNotificationReceiver expects this extra
|
||||
putExtra("schedule_id", stableScheduleId) // Add stable scheduleId for tracking
|
||||
// Also preserve original extras for backward compatibility if needed
|
||||
@@ -484,9 +484,9 @@ class NotifyReceiver : BroadcastReceiver() {
|
||||
fun cancelNotification(context: Context, scheduleId: String? = null, triggerAtMillis: Long? = null) {
|
||||
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||
// FIX: Use DailyNotificationReceiver to match what was scheduled
|
||||
val intent = Intent(context, com.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
|
||||
val intent = Intent(context, org.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
|
||||
setPackage(context.packageName)
|
||||
action = "com.timesafari.daily.NOTIFICATION"
|
||||
action = "org.timesafari.daily.NOTIFICATION"
|
||||
}
|
||||
val requestCode = when {
|
||||
scheduleId != null -> getRequestCode(scheduleId)
|
||||
@@ -540,9 +540,9 @@ class NotifyReceiver : BroadcastReceiver() {
|
||||
*/
|
||||
fun isAlarmScheduled(context: Context, scheduleId: String? = null, triggerAtMillis: Long? = null): Boolean {
|
||||
// FIX: Use DailyNotificationReceiver to match what was scheduled
|
||||
val intent = Intent(context, com.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
|
||||
val intent = Intent(context, org.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
|
||||
setPackage(context.packageName)
|
||||
action = "com.timesafari.daily.NOTIFICATION"
|
||||
action = "org.timesafari.daily.NOTIFICATION"
|
||||
}
|
||||
val requestCode = when {
|
||||
scheduleId != null -> getRequestCode(scheduleId)
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 2.0.0 - Modular Architecture
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
@@ -87,7 +87,7 @@ public class PermissionManager {
|
||||
androidx.core.app.ActivityCompat.requestPermissions(
|
||||
activity,
|
||||
new String[]{android.Manifest.permission.POST_NOTIFICATIONS},
|
||||
com.timesafari.dailynotification.DailyNotificationConstants.PERMISSION_REQUEST_CODE // Centralized constant
|
||||
org.timesafari.dailynotification.DailyNotificationConstants.PERMISSION_REQUEST_CODE // Centralized constant
|
||||
);
|
||||
|
||||
Log.d(TAG, "Permission dialog shown, waiting for user response");
|
||||
@@ -125,7 +125,7 @@ public class PermissionManager {
|
||||
*
|
||||
* @return PermissionStatus with all permission states
|
||||
*/
|
||||
public com.timesafari.dailynotification.PermissionStatus getPermissionStatus() {
|
||||
public org.timesafari.dailynotification.PermissionStatus getPermissionStatus() {
|
||||
boolean postNotificationsGranted = false;
|
||||
boolean exactAlarmsGranted = false;
|
||||
boolean notificationsEnabledAtOsLevel = false;
|
||||
@@ -168,7 +168,7 @@ public class PermissionManager {
|
||||
batteryOptimizationsIgnored = true; // Pre-Android 6, no battery optimization restrictions
|
||||
}
|
||||
|
||||
return new com.timesafari.dailynotification.PermissionStatus(
|
||||
return new org.timesafari.dailynotification.PermissionStatus(
|
||||
postNotificationsGranted,
|
||||
exactAlarmsGranted,
|
||||
batteryOptimizationsIgnored,
|
||||
@@ -187,7 +187,7 @@ public class PermissionManager {
|
||||
try {
|
||||
Log.d(TAG, "Checking permission status");
|
||||
|
||||
com.timesafari.dailynotification.PermissionStatus status = getPermissionStatus();
|
||||
org.timesafari.dailynotification.PermissionStatus status = getPermissionStatus();
|
||||
|
||||
JSObject result = status.toJSObject();
|
||||
result.put("success", true);
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification
|
||||
package org.timesafari.dailynotification
|
||||
|
||||
/**
|
||||
* Comprehensive permission status model
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.timesafari.dailynotification
|
||||
package org.timesafari.dailynotification
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
@@ -280,7 +280,7 @@ class ReactivationManager(private val context: Context) {
|
||||
db.notificationContentDao().updateNotification(existing)
|
||||
} else {
|
||||
// Create new notification content entry for missed alarm
|
||||
val notification = com.timesafari.dailynotification.entities.NotificationContentEntity(
|
||||
val notification = org.timesafari.dailynotification.entities.NotificationContentEntity(
|
||||
notificationId,
|
||||
"1.3.3", // Plugin version
|
||||
null, // timesafariDid
|
||||
@@ -479,9 +479,9 @@ class ReactivationManager(private val context: Context) {
|
||||
private fun alarmsExist(): Boolean {
|
||||
return try {
|
||||
// Check if any PendingIntent for our receiver exists (must match NotifyReceiver schedule path)
|
||||
val intent = Intent(context, com.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
|
||||
val intent = Intent(context, org.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
|
||||
setPackage(context.packageName)
|
||||
action = "com.timesafari.daily.NOTIFICATION"
|
||||
action = "org.timesafari.daily.NOTIFICATION"
|
||||
}
|
||||
val pendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
@@ -1050,7 +1050,7 @@ class ReactivationManager(private val context: Context) {
|
||||
db.notificationContentDao().updateNotification(existing)
|
||||
} else {
|
||||
// Create new notification content entry for missed alarm
|
||||
val notification = com.timesafari.dailynotification.entities.NotificationContentEntity(
|
||||
val notification = org.timesafari.dailynotification.entities.NotificationContentEntity(
|
||||
notificationId,
|
||||
"1.3.3", // Plugin version
|
||||
null, // timesafariDid
|
||||
@@ -11,7 +11,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -8,7 +8,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Trace;
|
||||
@@ -24,7 +24,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
@@ -9,10 +9,10 @@
|
||||
* @since 2025-10-20
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification.dao;
|
||||
package org.timesafari.dailynotification.dao;
|
||||
|
||||
import androidx.room.*;
|
||||
import com.timesafari.dailynotification.entities.NotificationConfigEntity;
|
||||
import org.timesafari.dailynotification.entities.NotificationConfigEntity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
* @since 2025-10-20
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification.dao;
|
||||
package org.timesafari.dailynotification.dao;
|
||||
|
||||
import androidx.room.*;
|
||||
import com.timesafari.dailynotification.entities.NotificationContentEntity;
|
||||
import org.timesafari.dailynotification.entities.NotificationContentEntity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
* @since 2025-10-20
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification.dao;
|
||||
package org.timesafari.dailynotification.dao;
|
||||
|
||||
import androidx.room.*;
|
||||
import com.timesafari.dailynotification.entities.NotificationDeliveryEntity;
|
||||
import org.timesafari.dailynotification.entities.NotificationDeliveryEntity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* @since 2025-10-20
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification.entities;
|
||||
package org.timesafari.dailynotification.entities;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.ColumnInfo;
|
||||
@@ -9,7 +9,7 @@
|
||||
* @since 2025-10-20
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification.entities;
|
||||
package org.timesafari.dailynotification.entities;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.ColumnInfo;
|
||||
@@ -9,7 +9,7 @@
|
||||
* @since 2025-10-20
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification.entities;
|
||||
package org.timesafari.dailynotification.entities;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.ColumnInfo;
|
||||
@@ -9,18 +9,18 @@
|
||||
* @since 2025-10-20
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification.storage;
|
||||
package org.timesafari.dailynotification.storage;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.timesafari.dailynotification.DailyNotificationDatabase;
|
||||
import com.timesafari.dailynotification.dao.NotificationContentDao;
|
||||
import com.timesafari.dailynotification.dao.NotificationDeliveryDao;
|
||||
import com.timesafari.dailynotification.dao.NotificationConfigDao;
|
||||
import com.timesafari.dailynotification.entities.NotificationContentEntity;
|
||||
import com.timesafari.dailynotification.entities.NotificationDeliveryEntity;
|
||||
import com.timesafari.dailynotification.entities.NotificationConfigEntity;
|
||||
import org.timesafari.dailynotification.DailyNotificationDatabase;
|
||||
import org.timesafari.dailynotification.dao.NotificationContentDao;
|
||||
import org.timesafari.dailynotification.dao.NotificationDeliveryDao;
|
||||
import org.timesafari.dailynotification.dao.NotificationConfigDao;
|
||||
import org.timesafari.dailynotification.entities.NotificationContentEntity;
|
||||
import org.timesafari.dailynotification.entities.NotificationDeliveryEntity;
|
||||
import org.timesafari.dailynotification.entities.NotificationConfigEntity;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -9,7 +9,7 @@
|
||||
* @since 2025-12-22
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification
|
||||
package org.timesafari.dailynotification
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
@@ -12,7 +12,7 @@
|
||||
* @since 2025-12-22
|
||||
*/
|
||||
|
||||
package com.timesafari.dailynotification
|
||||
package org.timesafari.dailynotification
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CapacitorConfig } from '@capacitor/cli';
|
||||
|
||||
const config: CapacitorConfig = {
|
||||
appId: 'com.timesafari.dailynotification',
|
||||
appId: 'org.timesafari.dailynotification',
|
||||
appName: 'DailyNotification Test App',
|
||||
webDir: 'www',
|
||||
server: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"name": "DailyNotification",
|
||||
"class": "com.timesafari.dailynotification.DailyNotificationPlugin"
|
||||
"class": "org.timesafari.dailynotification.DailyNotificationPlugin"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -38,7 +38,7 @@ pnpm add @timesafari/daily-notification-plugin
|
||||
```xml
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>com.timesafari.dailynotification.fetch</string>
|
||||
<string>org.timesafari.dailynotification.fetch</string>
|
||||
</array>
|
||||
```
|
||||
|
||||
@@ -49,7 +49,7 @@ import BackgroundTasks
|
||||
|
||||
func application(_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.timesafari.dailynotification.fetch",
|
||||
BGTaskScheduler.shared.register(forTaskWithIdentifier: "org.timesafari.dailynotification.fetch",
|
||||
using: nil) { task in
|
||||
// Handle background fetch task
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ fi
|
||||
xcrun simctl install "$SIMULATOR_ID" "$APP_PATH"
|
||||
|
||||
# Launch app
|
||||
xcrun simctl launch "$SIMULATOR_ID" com.timesafari.dailynotification.test
|
||||
xcrun simctl launch "$SIMULATOR_ID" org.timesafari.dailynotification.test
|
||||
```
|
||||
|
||||
**Result:** ✅ Simulator now boots and app launches automatically
|
||||
|
||||
@@ -94,7 +94,7 @@ po UNUserNotificationCenter.current().pendingNotificationRequests()
|
||||
po await UNUserNotificationCenter.current().notificationSettings()
|
||||
|
||||
// Manually trigger BGTask (Simulator only)
|
||||
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]
|
||||
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"org.timesafari.dailynotification.fetch"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -196,7 +196,7 @@ DailyNotificationScheduler: Scheduling notification: [id]
|
||||
|
||||
**Solution:** Use simulator-only LLDB command:
|
||||
```swift
|
||||
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]
|
||||
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"org.timesafari.dailynotification.fetch"]
|
||||
```
|
||||
|
||||
### Notifications Not Delivering
|
||||
|
||||
@@ -169,7 +169,7 @@ The plugin class did NOT conform to `CAPBridgedPlugin` protocol, which is requir
|
||||
**Solution Implemented (2025-11-13):**
|
||||
|
||||
1. **Added `CAPBridgedPlugin` conformance** via `@objc` extension:
|
||||
- Implemented `identifier` property (returns `"com.timesafari.dailynotification"`)
|
||||
- Implemented `identifier` property (returns `"org.timesafari.dailynotification"`)
|
||||
- Implemented `jsName` property (returns `"DailyNotification"`)
|
||||
- Implemented `pluginMethods` property (returns array of all `@objc` methods)
|
||||
|
||||
@@ -220,7 +220,7 @@ The plugin class did NOT conform to `CAPBridgedPlugin` protocol, which is requir
|
||||
- Added diagnostic check to verify class is in `objc_getClassList()`
|
||||
|
||||
2. **Add CAPBridgedPlugin conformance** via `@objc` extension:
|
||||
- Implemented `identifier` property (returns `"com.timesafari.dailynotification"`)
|
||||
- Implemented `identifier` property (returns `"org.timesafari.dailynotification"`)
|
||||
- Implemented `jsName` property (returns `"DailyNotification"`)
|
||||
- Implemented `pluginMethods` property (returns array of all `@objc` methods)
|
||||
|
||||
@@ -750,7 +750,7 @@ A "successful run" is defined as: BGTask handler invoked, content fetch complete
|
||||
|
||||
3. **Serial Queue Pattern (Alternative):**
|
||||
```swift
|
||||
private let stateQueue = DispatchQueue(label: "com.timesafari.dailynotification.state", attributes: .serial)
|
||||
private let stateQueue = DispatchQueue(label: "org.timesafari.dailynotification.state", attributes: .serial)
|
||||
```
|
||||
|
||||
4. **Enforcement:**
|
||||
@@ -1365,8 +1365,8 @@ scripts/
|
||||
- **Lesson:** Verify actual state, not just command success
|
||||
|
||||
5. **Bundle Identifier Mismatch:**
|
||||
- **Issue:** Script was using `com.timesafari.dailynotification.test` but actual bundle ID is `com.timesafari.dailynotification`
|
||||
- **Fix:** Updated all launch commands to use correct bundle ID `com.timesafari.dailynotification`
|
||||
- **Issue:** Script was using `org.timesafari.dailynotification.test` but actual bundle ID is `org.timesafari.dailynotification`
|
||||
- **Fix:** Updated all launch commands to use correct bundle ID `org.timesafari.dailynotification`
|
||||
- **Root Cause:** Project file has `.test` suffix but Info.plist resolves to base bundle ID
|
||||
- **Files Affected:** `scripts/build-ios-test-app.sh`
|
||||
- **Lesson:** Always verify actual bundle ID from installed app, not just project settings; bundle ID resolution can differ from project settings
|
||||
@@ -1424,7 +1424,7 @@ scripts/
|
||||
6. **Permission Reset for Testing:**
|
||||
- **Issue:** Simulator permissions persist across app launches; need to reset for testing
|
||||
- **Fix:** Use `xcrun simctl privacy booted reset all <bundle-id>` to reset permissions
|
||||
- **Command:** `xcrun simctl privacy booted reset all com.timesafari.dailynotification`
|
||||
- **Command:** `xcrun simctl privacy booted reset all org.timesafari.dailynotification`
|
||||
- **Lesson:** Simulator permissions don't reset automatically; must manually reset for testing different permission states
|
||||
|
||||
7. **JavaScript Method Existence Check:**
|
||||
@@ -1555,7 +1555,7 @@ scripts/
|
||||
1. **BGTaskScheduler Not Running:**
|
||||
- Check Info.plist has `BGTaskSchedulerPermittedIdentifiers`
|
||||
- Verify task registered in AppDelegate before app finishes launching
|
||||
- **Simulator-only debugging trick:** Use LLDB command to manually trigger: `e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]`
|
||||
- **Simulator-only debugging trick:** Use LLDB command to manually trigger: `e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"org.timesafari.dailynotification.fetch"]`
|
||||
- Note: This is for simulator testing only, not available in production
|
||||
- **Testing Guide:** See `doc/test-app-ios/IOS_PREFETCH_TESTING.md` for comprehensive testing procedures
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ npx cap sync ios
|
||||
cat android/app/src/main/assets/capacitor.plugins.json | grep DailyNotification
|
||||
|
||||
# Expected output should include:
|
||||
# "DailyNotification": { "class": "com.timesafari.dailynotification.DailyNotificationPlugin" }
|
||||
# "DailyNotification": { "class": "org.timesafari.dailynotification.DailyNotificationPlugin" }
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
@@ -151,14 +151,14 @@ cat android/app/src/main/assets/capacitor.plugins.json | grep DailyNotification
|
||||
<!-- Daily Notification Plugin Receivers -->
|
||||
<!-- CRITICAL: NotifyReceiver is REQUIRED for notifications to work -->
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.NotifyReceiver"
|
||||
android:name="org.timesafari.dailynotification.NotifyReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
</receiver>
|
||||
|
||||
<!-- BootReceiver for reboot recovery (optional but recommended) -->
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.BootReceiver"
|
||||
android:name="org.timesafari.dailynotification.BootReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
@@ -176,7 +176,7 @@ grep -A 3 "NotifyReceiver" android/app/src/main/AndroidManifest.xml
|
||||
|
||||
# Expected output:
|
||||
# <receiver
|
||||
# android:name="com.timesafari.dailynotification.NotifyReceiver"
|
||||
# android:name="org.timesafari.dailynotification.NotifyReceiver"
|
||||
# android:enabled="true"
|
||||
```
|
||||
|
||||
@@ -223,8 +223,8 @@ grep -A 3 "NotifyReceiver" android/app/src/main/AndroidManifest.xml
|
||||
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>com.timesafari.dailynotification.content-fetch</string>
|
||||
<string>com.timesafari.dailynotification.notification-delivery</string>
|
||||
<string>org.timesafari.dailynotification.content-fetch</string>
|
||||
<string>org.timesafari.dailynotification.notification-delivery</string>
|
||||
</array>
|
||||
</dict>
|
||||
```
|
||||
@@ -491,7 +491,7 @@ files:
|
||||
- type: "uses-permission"
|
||||
name: "android.permission.POST_NOTIFICATIONS"
|
||||
- type: "receiver"
|
||||
name: "com.timesafari.dailynotification.NotifyReceiver"
|
||||
name: "org.timesafari.dailynotification.NotifyReceiver"
|
||||
attributes:
|
||||
android:enabled: "true"
|
||||
android:exported: "false"
|
||||
|
||||
@@ -132,7 +132,7 @@ android/plugin/src/main/java/com/timesafari/dailynotification/
|
||||
### **BootReceiver Registration**
|
||||
```xml
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.BootReceiver"
|
||||
android:name="org.timesafari.dailynotification.BootReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:directBootAware="true">
|
||||
|
||||
@@ -469,7 +469,7 @@ public class DailyNotificationScheduler {
|
||||
|
||||
<!-- Boot Receiver -->
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.BootReceiver"
|
||||
android:name="org.timesafari.dailynotification.BootReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:directBootAware="true">
|
||||
@@ -482,7 +482,7 @@ public class DailyNotificationScheduler {
|
||||
|
||||
<!-- Notification Receiver -->
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.DailyNotificationReceiver"
|
||||
android:name="org.timesafari.dailynotification.DailyNotificationReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
</receiver>
|
||||
|
||||
@@ -720,7 +720,7 @@ The plugin **MUST NOT** support or guarantee the following behaviors:
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.BootReceiver"
|
||||
android:name="org.timesafari.dailynotification.BootReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
@@ -730,11 +730,11 @@ The plugin **MUST NOT** support or guarantee the following behaviors:
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.DailyNotificationReceiver"
|
||||
android:name="org.timesafari.dailynotification.DailyNotificationReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.timesafari.daily.NOTIFICATION" />
|
||||
<action android:name="org.timesafari.daily.NOTIFICATION" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
```
|
||||
@@ -789,15 +789,15 @@ The plugin **MUST NOT** support or guarantee the following behaviors:
|
||||
#### 10.2.2 Background Tasks
|
||||
|
||||
**Required Background Task Identifiers**:
|
||||
* `com.timesafari.dailynotification.fetch` - Background fetch
|
||||
* `com.timesafari.dailynotification.notify` - Notification task (if used)
|
||||
* `org.timesafari.dailynotification.fetch` - Background fetch
|
||||
* `org.timesafari.dailynotification.notify` - Notification task (if used)
|
||||
|
||||
**Background Task Registration**:
|
||||
* Register in `Info.plist`:
|
||||
```xml
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>com.timesafari.dailynotification.fetch</string>
|
||||
<string>org.timesafari.dailynotification.fetch</string>
|
||||
</array>
|
||||
```
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ This guide provides step-by-step instructions for testing Phase 1 (Cold Start Re
|
||||
**Environment**
|
||||
|
||||
- Device: Android Emulator – Pixel 8 API 34
|
||||
- App ID: `com.timesafari.dailynotification`
|
||||
- App ID: `org.timesafari.dailynotification`
|
||||
- Build: Debug APK from `test-apps/android-test-app`
|
||||
- Script: `./test-phase1.sh`
|
||||
- Date: 27 November 2025
|
||||
@@ -109,7 +109,7 @@ adb install -r app/build/outputs/apk/debug/app-debug.apk
|
||||
|
||||
# Verify installation
|
||||
adb shell pm list packages | grep timesafari
|
||||
# Should show: package:com.timesafari.dailynotification
|
||||
# Should show: package:org.timesafari.dailynotification
|
||||
```
|
||||
|
||||
### Option 2: Vue Test App (More Features)
|
||||
@@ -162,7 +162,7 @@ adb logcat -s DNP-REACTIVATION > recovery_test.log
|
||||
|
||||
```bash
|
||||
# Launch app to initialize database
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
|
||||
# Wait a few seconds for initialization
|
||||
sleep 3
|
||||
@@ -181,7 +181,7 @@ sleep 3
|
||||
adb logcat -c
|
||||
|
||||
# 2. Launch app
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
|
||||
# 3. Schedule notification for 2 minutes in future
|
||||
# (Use app UI or API - see "Scheduling Notifications" below)
|
||||
@@ -195,7 +195,7 @@ adb shell dumpsys alarm | grep -i timesafari
|
||||
# Should show scheduled alarm
|
||||
|
||||
# 6. Kill app process (simulates OS kill, NOT force stop)
|
||||
adb shell am kill com.timesafari.dailynotification
|
||||
adb shell am kill org.timesafari.dailynotification
|
||||
|
||||
# 7. Verify app is killed
|
||||
adb shell ps | grep timesafari
|
||||
@@ -206,7 +206,7 @@ adb shell ps | grep timesafari
|
||||
# Or: Set system time forward (see "Time Manipulation" below)
|
||||
|
||||
# 9. Launch app (cold start)
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
|
||||
# 10. Check recovery logs immediately
|
||||
adb logcat -d | grep DNP-REACTIVATION
|
||||
@@ -226,11 +226,11 @@ DNP-REACTIVATION: App launch recovery completed: missed=1, rescheduled=0, verifi
|
||||
|
||||
```bash
|
||||
# Check database (requires root or debug build)
|
||||
adb shell run-as com.timesafari.dailynotification sqlite3 databases/daily_notification_plugin.db \
|
||||
adb shell run-as org.timesafari.dailynotification sqlite3 databases/daily_notification_plugin.db \
|
||||
"SELECT id, delivery_status, scheduled_time FROM notification_content WHERE delivery_status = 'missed';"
|
||||
|
||||
# Or check history table
|
||||
adb shell run-as com.timesafari.dailynotification sqlite3 databases/daily_notification_plugin.db \
|
||||
adb shell run-as org.timesafari.dailynotification sqlite3 databases/daily_notification_plugin.db \
|
||||
"SELECT * FROM history WHERE kind = 'recovery' ORDER BY occurredAt DESC LIMIT 1;"
|
||||
```
|
||||
|
||||
@@ -254,7 +254,7 @@ adb shell run-as com.timesafari.dailynotification sqlite3 databases/daily_notifi
|
||||
adb logcat -c
|
||||
|
||||
# 2. Launch app
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
|
||||
# 3. Schedule notification for 10 minutes in future
|
||||
# (Use app UI or API)
|
||||
@@ -276,7 +276,7 @@ adb shell dumpsys alarm | grep -i timesafari
|
||||
# Should show no alarms (or fewer alarms)
|
||||
|
||||
# 7. Launch app (triggers recovery)
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
|
||||
# 8. Check recovery logs
|
||||
adb logcat -d | grep DNP-REACTIVATION
|
||||
@@ -318,7 +318,7 @@ adb logcat -c
|
||||
# See "Database Manipulation" section below
|
||||
|
||||
# 3. Launch app
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
|
||||
# 4. Check logs immediately
|
||||
adb logcat -d | grep DNP-REACTIVATION
|
||||
@@ -354,7 +354,7 @@ adb logcat -c
|
||||
# See "Database Manipulation" section below
|
||||
|
||||
# 3. Launch app
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
|
||||
# 4. Check logs
|
||||
adb logcat -d | grep DNP-REACTIVATION
|
||||
@@ -426,10 +426,10 @@ adb shell date -s "2025-11-15 14:30:00"
|
||||
|
||||
```bash
|
||||
# Check if app is debuggable
|
||||
adb shell dumpsys package com.timesafari.dailynotification | grep debuggable
|
||||
adb shell dumpsys package org.timesafari.dailynotification | grep debuggable
|
||||
|
||||
# Access database
|
||||
adb shell run-as com.timesafari.dailynotification sqlite3 databases/daily_notification_plugin.db
|
||||
adb shell run-as org.timesafari.dailynotification sqlite3 databases/daily_notification_plugin.db
|
||||
|
||||
# Example: Insert test notification
|
||||
sqlite> INSERT INTO notification_content (
|
||||
@@ -492,7 +492,7 @@ adb logcat -d > phase1_test_$(date +%Y%m%d_%H%M%S).log
|
||||
#!/bin/bash
|
||||
# Phase 1 Complete Test Sequence
|
||||
|
||||
PACKAGE="com.timesafari.dailynotification"
|
||||
PACKAGE="org.timesafari.dailynotification"
|
||||
ACTIVITY="${PACKAGE}/.MainActivity"
|
||||
|
||||
echo "=== Phase 1 Testing on Emulator ==="
|
||||
@@ -601,7 +601,7 @@ adb devices
|
||||
**Permission denied for database access**:
|
||||
```bash
|
||||
# Check if app is debuggable
|
||||
adb shell dumpsys package com.timesafari.dailynotification | grep debuggable
|
||||
adb shell dumpsys package org.timesafari.dailynotification | grep debuggable
|
||||
|
||||
# If not debuggable, rebuild with debug signing
|
||||
cd test-apps/android-test-app
|
||||
@@ -617,7 +617,7 @@ adb install -r app/build/outputs/apk/debug/app-debug.apk
|
||||
adb shell pm list packages | grep timesafari
|
||||
|
||||
# Uninstall and reinstall
|
||||
adb uninstall com.timesafari.dailynotification
|
||||
adb uninstall org.timesafari.dailynotification
|
||||
adb install -r app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
@@ -658,10 +658,10 @@ cd test-apps/android-test-app
|
||||
adb install -r app/build/outputs/apk/debug/app-debug.apk
|
||||
|
||||
# Launch app
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
|
||||
# Kill app
|
||||
adb shell am kill com.timesafari.dailynotification
|
||||
adb shell am kill org.timesafari.dailynotification
|
||||
|
||||
# Monitor logs
|
||||
adb logcat -s DNP-REACTIVATION
|
||||
|
||||
@@ -90,19 +90,19 @@ Verify that when a force stop clears alarms, the plugin:
|
||||
3. **Verify alarms are scheduled**
|
||||
* Script runs:
|
||||
```bash
|
||||
adb shell dumpsys alarm | grep com.timesafari.dailynotification
|
||||
adb shell dumpsys alarm | grep org.timesafari.dailynotification
|
||||
```
|
||||
* Confirm at least one `RTC_WAKEUP` alarm for `com.timesafari.dailynotification`.
|
||||
* Confirm at least one `RTC_WAKEUP` alarm for `org.timesafari.dailynotification`.
|
||||
|
||||
4. **Force stop the app**
|
||||
* Script executes:
|
||||
```bash
|
||||
adb shell am force-stop com.timesafari.dailynotification
|
||||
adb shell am force-stop org.timesafari.dailynotification
|
||||
```
|
||||
|
||||
5. **Confirm alarms after force stop**
|
||||
* Script re-runs `dumpsys alarm`.
|
||||
* Ideal test case: **0** alarms for `com.timesafari.dailynotification` (alarms cleared).
|
||||
* Ideal test case: **0** alarms for `org.timesafari.dailynotification` (alarms cleared).
|
||||
|
||||
6. **Trigger recovery**
|
||||
* Script clears logcat and launches the app.
|
||||
@@ -165,12 +165,12 @@ Ensure we **do not run heavy force-stop recovery** when alarms are still intact.
|
||||
* Click **Test Notification** again to create a second schedule.
|
||||
|
||||
3. **Verify alarms are scheduled**
|
||||
* Confirm multiple alarms for `com.timesafari.dailynotification` via `dumpsys alarm`.
|
||||
* Confirm multiple alarms for `org.timesafari.dailynotification` via `dumpsys alarm`.
|
||||
|
||||
4. **Simulate a "soft stop"**
|
||||
* Script runs:
|
||||
```bash
|
||||
adb shell am kill com.timesafari.dailynotification
|
||||
adb shell am kill org.timesafari.dailynotification
|
||||
```
|
||||
* Intent: stop the process but **not** clear alarms (actual behavior may vary by OS).
|
||||
|
||||
@@ -217,7 +217,7 @@ Ensure **force-stop recovery is not mis-triggered** when the app is freshly inst
|
||||
1. **Clear state**
|
||||
* Script uninstalls the app to clear DB/state:
|
||||
```bash
|
||||
adb uninstall com.timesafari.dailynotification
|
||||
adb uninstall org.timesafari.dailynotification
|
||||
```
|
||||
|
||||
2. **Reinstall APK**
|
||||
@@ -273,7 +273,7 @@ Fill this in after your first successful emulator run.
|
||||
**Environment**
|
||||
|
||||
- Device: Pixel 8 API 34 (Android 14)
|
||||
- App ID: `com.timesafari.dailynotification`
|
||||
- App ID: `org.timesafari.dailynotification`
|
||||
- Build: Debug APK (`app-debug.apk`) from commit `<GIT_HASH>`
|
||||
- Script: `./test-phase2.sh`
|
||||
- Date: 2025-11-XX
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Phase 2 – Force Stop Recovery Verification
|
||||
|
||||
**Plugin:** Daily Notification Plugin
|
||||
**Scope:** Force stop detection & recovery (App ID: `com.timesafari.dailynotification`)
|
||||
**Scope:** Force stop detection & recovery (App ID: `org.timesafari.dailynotification`)
|
||||
**Related Docs:**
|
||||
|
||||
- `android-implementation-directive-phase2.md`
|
||||
@@ -142,7 +142,7 @@ or:
|
||||
**Environment**
|
||||
|
||||
* Device: Pixel 8 API 34 (Android 14)
|
||||
* App ID: `com.timesafari.dailynotification`
|
||||
* App ID: `org.timesafari.dailynotification`
|
||||
* Build: Debug `app-debug.apk` from commit `<GIT_HASH>`
|
||||
* Script: `./test-phase2.sh`
|
||||
* Date: 2025-11-XX
|
||||
|
||||
@@ -91,7 +91,7 @@ Verify alarms are recreated on boot when schedules have **future run times**.
|
||||
|
||||
3. **Verify alarms are scheduled (pre-boot)**
|
||||
* Script calls `show_alarms` and `count_alarms`.
|
||||
* You should see at least one `RTC_WAKEUP` entry for `com.timesafari.dailynotification`.
|
||||
* You should see at least one `RTC_WAKEUP` entry for `org.timesafari.dailynotification`.
|
||||
|
||||
4. **Reboot emulator**
|
||||
* Script calls `reboot_emulator`:
|
||||
@@ -193,7 +193,7 @@ Verify boot recovery handles an **empty DB / no schedules** safely and does **no
|
||||
1. **Uninstall app to clear DB/state**
|
||||
* Script calls:
|
||||
```bash
|
||||
adb uninstall com.timesafari.dailynotification
|
||||
adb uninstall org.timesafari.dailynotification
|
||||
```
|
||||
|
||||
2. **Reinstall APK**
|
||||
|
||||
@@ -146,7 +146,7 @@ Script passes if:
|
||||
**Environment**
|
||||
|
||||
* Device: Pixel 8 API 34 (Android 14)
|
||||
* App ID: `com.timesafari.dailynotification`
|
||||
* App ID: `org.timesafari.dailynotification`
|
||||
* Build: Debug `app-debug.apk` from commit `<GIT_HASH>`
|
||||
* Script: `./test-phase3.sh`
|
||||
* Date: 2025-11-XX
|
||||
|
||||
@@ -223,8 +223,8 @@ public class MyNativeFetcher implements NativeNotificationContentFetcher {
|
||||
package com.example.app;
|
||||
|
||||
import android.app.Application;
|
||||
import com.timesafari.dailynotification.DailyNotificationPlugin;
|
||||
import com.timesafari.dailynotification.NativeNotificationContentFetcher;
|
||||
import org.timesafari.dailynotification.DailyNotificationPlugin;
|
||||
import org.timesafari.dailynotification.NativeNotificationContentFetcher;
|
||||
|
||||
public class MyApplication extends Application {
|
||||
@Override
|
||||
@@ -285,7 +285,7 @@ export async function setupDailyNotifications() {
|
||||
package com.example.app;
|
||||
|
||||
import android.util.Log;
|
||||
import com.timesafari.dailynotification.*;
|
||||
import org.timesafari.dailynotification.*;
|
||||
import java.util.*;
|
||||
|
||||
public class MyNativeFetcher implements NativeNotificationContentFetcher {
|
||||
|
||||
@@ -18,8 +18,8 @@ This document provides comprehensive guidance for legal and store compliance req
|
||||
const iosBackgroundTaskConfig = {
|
||||
// Required: Register background task identifiers
|
||||
backgroundTaskIdentifiers: [
|
||||
'com.timesafari.dailynotification.fetch',
|
||||
'com.timesafari.dailynotification.maintenance'
|
||||
'org.timesafari.dailynotification.fetch',
|
||||
'org.timesafari.dailynotification.maintenance'
|
||||
],
|
||||
|
||||
// Required: Background modes in Info.plist
|
||||
|
||||
@@ -794,7 +794,7 @@ class SecureJWTStorage(private val context: Context) {
|
||||
**iOS (iOS Keychain)**:
|
||||
```swift
|
||||
class SecureJWTStorage {
|
||||
private let keychain = Keychain(service: "com.timesafari.dailynotification")
|
||||
private let keychain = Keychain(service: "org.timesafari.dailynotification")
|
||||
|
||||
func storeJWTSecret(_ secret: String) throws {
|
||||
let data = secret.data(using: .utf8)!
|
||||
@@ -1006,7 +1006,7 @@ fun scheduleImmediateCatchUp() {
|
||||
**iOS (BGTaskScheduler)**:
|
||||
```swift
|
||||
// BGTaskScheduler Configuration
|
||||
let taskIdentifier = "com.timesafari.dailynotification.starred-projects-polling"
|
||||
let taskIdentifier = "org.timesafari.dailynotification.starred-projects-polling"
|
||||
|
||||
// Register background task
|
||||
BGTaskScheduler.shared.register(
|
||||
@@ -1025,7 +1025,7 @@ try BGTaskScheduler.shared.submit(request)
|
||||
/*
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>com.timesafari.dailynotification.starred-projects-polling</string>
|
||||
<string>org.timesafari.dailynotification.starred-projects-polling</string>
|
||||
</array>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
@@ -2472,7 +2472,7 @@ interface PollingScheduleConfig<TRequest, TResponse> {
|
||||
**File**: `src/android/GenericPollingManager.java`
|
||||
|
||||
```java
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -19,7 +19,7 @@ The initial approach using `BootReceiver` to restore notifications after device
|
||||
|
||||
```bash
|
||||
# Boot receiver was registered but not triggered
|
||||
adb shell "dumpsys package com.timesafari.dailynotification | grep -A5 -B5 BootReceiver"
|
||||
adb shell "dumpsys package org.timesafari.dailynotification | grep -A5 -B5 BootReceiver"
|
||||
# Output: BootReceiver registered but not in enabledComponents list
|
||||
|
||||
# After reboot, no recovery logs appeared
|
||||
@@ -167,7 +167,7 @@ adb shell "dumpsys alarm | grep timesafari"
|
||||
|
||||
```bash
|
||||
# 1. Schedule notification
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
# Tap "Test Notification" (5 minutes from now)
|
||||
|
||||
# 2. Verify initial scheduling
|
||||
@@ -179,7 +179,7 @@ adb reboot
|
||||
# Wait 2-3 minutes for boot completion
|
||||
|
||||
# 4. Launch app (triggers recovery)
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
|
||||
# 5. Check recovery logs
|
||||
adb logcat -d | grep -i "recovery" | tail -5
|
||||
|
||||
@@ -621,7 +621,7 @@ Gaps uncovered:
|
||||
adb shell dumpsys alarm | grep -i timesafari
|
||||
|
||||
# Force kill (not force-stop) - adjust package name based on test app
|
||||
adb shell am kill com.timesafari.dailynotification
|
||||
adb shell am kill org.timesafari.dailynotification
|
||||
# Or for test apps:
|
||||
# adb shell am kill com.timesafari.androidtestapp
|
||||
# adb shell am kill <package-name-from-test-app-manifest>
|
||||
|
||||
@@ -305,7 +305,7 @@ The plugin **must**:
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.BootReceiver"
|
||||
android:name="org.timesafari.dailynotification.BootReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
@@ -315,11 +315,11 @@ The plugin **must**:
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.DailyNotificationReceiver"
|
||||
android:name="org.timesafari.dailynotification.DailyNotificationReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.timesafari.daily.NOTIFICATION" />
|
||||
<action android:name="org.timesafari.daily.NOTIFICATION" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
```
|
||||
@@ -372,15 +372,15 @@ The plugin **must**:
|
||||
#### 5.2.2 Background Tasks
|
||||
|
||||
**Required Background Task Identifiers**:
|
||||
* `com.timesafari.dailynotification.fetch` - Background fetch
|
||||
* `com.timesafari.dailynotification.notify` - Notification task (if used)
|
||||
* `org.timesafari.dailynotification.fetch` - Background fetch
|
||||
* `org.timesafari.dailynotification.notify` - Notification task (if used)
|
||||
|
||||
**Background Task Registration**:
|
||||
* Register in `Info.plist`:
|
||||
```xml
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>com.timesafari.dailynotification.fetch</string>
|
||||
<string>org.timesafari.dailynotification.fetch</string>
|
||||
</array>
|
||||
```
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ Add to `Info.plist`:
|
||||
```xml
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>com.timesafari.dailynotification.fetch</string>
|
||||
<string>org.timesafari.dailynotification.fetch</string>
|
||||
</array>
|
||||
```
|
||||
|
||||
|
||||
129
docs/integration/CONSUMING_APP_MIGRATION_COM_TO_ORG.md
Normal file
129
docs/integration/CONSUMING_APP_MIGRATION_COM_TO_ORG.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# Consuming App Migration: com.timesafari → org.timesafari
|
||||
|
||||
Use this document in your **consuming app** repo (e.g. with Cursor or as a checklist) to migrate from `com.timesafari.dailynotification` to `org.timesafari.dailynotification` after the daily-notification-plugin has been updated.
|
||||
|
||||
## Summary of plugin changes
|
||||
|
||||
The plugin’s package/namespace and public identifiers were renamed:
|
||||
|
||||
- **Package/namespace**: `com.timesafari.dailynotification` → `org.timesafari.dailynotification`
|
||||
- **Intent action (Android)**: `com.timesafari.daily.NOTIFICATION` → `org.timesafari.daily.NOTIFICATION`
|
||||
- **Dismiss action (Android)**: `com.timesafari.daily.DISMISS` → `org.timesafari.daily.DISMISS`
|
||||
- **iOS BGTask identifiers**: `com.timesafari.dailynotification.fetch` / `.notify` / `.prefetch` → `org.timesafari.dailynotification.*`
|
||||
- **Capacitor plugin class**: `com.timesafari.dailynotification.DailyNotificationPlugin` → `org.timesafari.dailynotification.DailyNotificationPlugin`
|
||||
|
||||
Your app must align with these so the plugin loads and receivers/intents work.
|
||||
|
||||
---
|
||||
|
||||
## 1. Update plugin dependency
|
||||
|
||||
- Ensure the consuming app depends on a **version of the plugin that already uses `org.timesafari`** (e.g. after the plugin repo’s com→org migration is merged/released).
|
||||
- Reinstall/sync: `npm install` (or equivalent) so the updated plugin is used.
|
||||
|
||||
---
|
||||
|
||||
## 2. Capacitor plugin registration (Android & iOS)
|
||||
|
||||
The plugin is registered by **class name**. Update every place that references the plugin class.
|
||||
|
||||
**Typical locations:**
|
||||
|
||||
- `android/app/src/main/assets/capacitor.plugins.json` (or `public/plugins` if you use that)
|
||||
- Any script or config that writes the plugin class string
|
||||
|
||||
**Change:**
|
||||
|
||||
- From: `"class": "com.timesafari.dailynotification.DailyNotificationPlugin"` or `"classpath": "com.timesafari.dailynotification.DailyNotificationPlugin"`
|
||||
- To: `"class": "org.timesafari.dailynotification.DailyNotificationPlugin"` or `"classpath": "org.timesafari.dailynotification.DailyNotificationPlugin"`
|
||||
|
||||
If you use a script (e.g. `fix-capacitor-plugins.js`) that injects this class name, update the script to use `org.timesafari.dailynotification.DailyNotificationPlugin`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Android: `AndroidManifest.xml`
|
||||
|
||||
**Receiver class names** (must match the plugin’s new package):
|
||||
|
||||
- `com.timesafari.dailynotification.DailyNotificationReceiver` → `org.timesafari.dailynotification.DailyNotificationReceiver`
|
||||
- `com.timesafari.dailynotification.NotifyReceiver` → `org.timesafari.dailynotification.NotifyReceiver`
|
||||
- `com.timesafari.dailynotification.BootReceiver` → `org.timesafari.dailynotification.BootReceiver`
|
||||
- Any other `com.timesafari.dailynotification.*` receiver → `org.timesafari.dailynotification.*`
|
||||
|
||||
**Intent action** (must match plugin’s `DailyNotificationConstants.ACTION_NOTIFICATION`):
|
||||
|
||||
- `<action android:name="com.timesafari.daily.NOTIFICATION" />` → `<action android:name="org.timesafari.daily.NOTIFICATION" />`
|
||||
|
||||
Search the manifest for `com.timesafari` and replace with `org.timesafari` for these plugin-related entries.
|
||||
|
||||
---
|
||||
|
||||
## 4. Android: ProGuard / R8 (if you keep plugin classes)
|
||||
|
||||
If you have custom keep rules that reference the plugin package:
|
||||
|
||||
- `com.timesafari.dailynotification` → `org.timesafari.dailynotification`
|
||||
|
||||
(e.g. `-keep class com.timesafari.dailynotification.**` → `-keep class org.timesafari.dailynotification.**`).
|
||||
|
||||
---
|
||||
|
||||
## 5. iOS: `Info.plist` (BGTask identifiers)
|
||||
|
||||
The plugin uses these background task identifiers. Your app’s `Info.plist` must list the **same** identifiers.
|
||||
|
||||
**In `BGTaskSchedulerPermittedIdentifiers` (or equivalent):**
|
||||
|
||||
- `com.timesafari.dailynotification.fetch` → `org.timesafari.dailynotification.fetch`
|
||||
- `com.timesafari.dailynotification.notify` → `org.timesafari.dailynotification.notify`
|
||||
|
||||
If you use the prefetch identifier:
|
||||
|
||||
- `com.timesafari.dailynotification.prefetch` → `org.timesafari.dailynotification.prefetch`
|
||||
|
||||
Update any other `com.timesafari.dailynotification.*` task IDs to `org.timesafari.dailynotification.*`.
|
||||
|
||||
---
|
||||
|
||||
## 6. App ID / Bundle ID (optional; breaking for installs)
|
||||
|
||||
- **Plugin package/namespace** change does **not** require you to change your app’s **applicationId** (Android) or **Bundle ID** (iOS).
|
||||
- If you **do** change your app’s id from `com.timesafari.*` to `org.timesafari.*`, the store and OS will treat it as a **new app** (new install, no in-place update). Only change this if you intend that.
|
||||
|
||||
---
|
||||
|
||||
## 7. Custom intent actions / deep links
|
||||
|
||||
If your app or backend uses custom actions that included the old prefix (e.g. `com.timesafari.dailynotification.REFRESH_DATA` or `OPEN_SETTINGS`), update them to `org.timesafari.dailynotification.*` so they still match what the plugin or your code expects.
|
||||
|
||||
---
|
||||
|
||||
## 8. Scripts and docs in the consuming app
|
||||
|
||||
- Any script that uses the **plugin package** or **intent action** (e.g. `adb shell am start`, `dumpsys package`, or broadcast actions) should use `org.timesafari.dailynotification` and `org.timesafari.daily.NOTIFICATION` where applicable.
|
||||
- Update internal docs or runbooks that reference the old package or action strings.
|
||||
|
||||
---
|
||||
|
||||
## 9. Verification
|
||||
|
||||
- **Android**: Build the app, install, and confirm notifications still schedule and fire. Check `adb shell dumpsys package <your.package>` for receivers and that alarms use `org.timesafari.daily.NOTIFICATION` if you inspect with `dumpsys alarm`.
|
||||
- **iOS**: Build and run; confirm BGTask registration and notification behavior. Ensure `Info.plist` identifiers match the plugin’s Swift constants.
|
||||
- **Capacitor**: Confirm the plugin is loaded (e.g. no “class not found” or missing plugin in the bridge).
|
||||
|
||||
---
|
||||
|
||||
## Quick find-and-replace (consuming app only)
|
||||
|
||||
Use with care; prefer updating specific files as above. Suggested patterns:
|
||||
|
||||
- **Plugin class**: `com.timesafari.dailynotification.DailyNotificationPlugin` → `org.timesafari.dailynotification.DailyNotificationPlugin`
|
||||
- **Receiver/package references**: `com.timesafari.dailynotification.` → `org.timesafari.dailynotification.`
|
||||
- **Notification intent action**: `com.timesafari.daily.NOTIFICATION` → `org.timesafari.daily.NOTIFICATION`
|
||||
- **BGTask identifiers**: `com.timesafari.dailynotification.fetch` / `.notify` / `.prefetch` → `org.timesafari.dailynotification.fetch` / `.notify` / `.prefetch`
|
||||
|
||||
Do **not** blindly replace `com.timesafari` in your **app’s own** applicationId/Bundle ID unless you intend to ship as a new app.
|
||||
|
||||
---
|
||||
|
||||
**Reference:** This migration is aligned with the changes in the `daily-notification-plugin` repo (package rename com → org). For plugin-side details, see that repo’s history and docs.
|
||||
@@ -508,10 +508,10 @@ Add required permissions to `android/app/src/main/AndroidManifest.xml`:
|
||||
<!-- Existing application configuration -->
|
||||
|
||||
<!-- Daily Notification Plugin receivers and services -->
|
||||
<receiver android:name="com.timesafari.dailynotification.NotifyReceiver"
|
||||
<receiver android:name="org.timesafari.dailynotification.NotifyReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
<receiver android:name="com.timesafari.dailynotification.BootReceiver"
|
||||
<receiver android:name="org.timesafari.dailynotification.BootReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
@@ -599,8 +599,8 @@ Add required permissions to `ios/App/App/Info.plist`:
|
||||
<!-- BGTaskScheduler identifiers -->
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>com.timesafari.dailynotification.content-fetch</string>
|
||||
<string>com.timesafari.dailynotification.notification-delivery</string>
|
||||
<string>org.timesafari.dailynotification.content-fetch</string>
|
||||
<string>org.timesafari.dailynotification.notification-delivery</string>
|
||||
</array>
|
||||
|
||||
<!-- Notification usage description -->
|
||||
|
||||
@@ -59,14 +59,14 @@ Add to `android/app/src/main/AndroidManifest.xml`:
|
||||
<!-- Daily Notification Plugin Receivers -->
|
||||
<!-- REQUIRED: NotifyReceiver for AlarmManager-based notifications -->
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.NotifyReceiver"
|
||||
android:name="org.timesafari.dailynotification.NotifyReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
</receiver>
|
||||
|
||||
<!-- BootReceiver for reboot recovery (optional but recommended) -->
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.BootReceiver"
|
||||
android:name="org.timesafari.dailynotification.BootReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
@@ -108,8 +108,8 @@ Add to `ios/App/App/Info.plist`:
|
||||
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>com.timesafari.dailynotification.content-fetch</string>
|
||||
<string>com.timesafari.dailynotification.notification-delivery</string>
|
||||
<string>org.timesafari.dailynotification.content-fetch</string>
|
||||
<string>org.timesafari.dailynotification.notification-delivery</string>
|
||||
</array>
|
||||
```
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ DailyNotificationPlugin.load()
|
||||
**File to Create**: `NativeNotificationContentFetcher.java`
|
||||
|
||||
```java
|
||||
package com.timesafari.dailynotification;
|
||||
package org.timesafari.dailynotification;
|
||||
|
||||
public interface NativeNotificationContentFetcher {
|
||||
java.util.concurrent.CompletableFuture<java.util.List<NotificationContent>>
|
||||
|
||||
@@ -24,7 +24,7 @@ This document provides comprehensive troubleshooting guidance for integrating th
|
||||
|
||||
**Error Message:**
|
||||
```
|
||||
Duplicate class com.timesafari.dailynotification.BootReceiver found in modules:
|
||||
Duplicate class org.timesafari.dailynotification.BootReceiver found in modules:
|
||||
- plugin-debug.aar -> plugin-debug-runtime (:plugin-debug:)
|
||||
- plugin-debug.aar -> plugin-debug-runtime (plugin-debug.aar)
|
||||
```
|
||||
@@ -82,7 +82,7 @@ implementation(name: 'plugin-debug', ext: 'aar')
|
||||
{
|
||||
"id": "DailyNotification",
|
||||
"name": "DailyNotification",
|
||||
"class": "com.timesafari.dailynotification.DailyNotificationPlugin"
|
||||
"class": "org.timesafari.dailynotification.DailyNotificationPlugin"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -166,7 +166,7 @@ dependencies {
|
||||
{
|
||||
"id": "DailyNotification",
|
||||
"name": "DailyNotification",
|
||||
"class": "com.timesafari.dailynotification.DailyNotificationPlugin"
|
||||
"class": "org.timesafari.dailynotification.DailyNotificationPlugin"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
@@ -180,8 +180,8 @@ await DailyNotification.configure({
|
||||
iosConfig: {
|
||||
// Background task configuration
|
||||
backgroundTasks: {
|
||||
'com.timesafari.daily-notification-fetch': {
|
||||
identifier: 'com.timesafari.daily-notification-fetch',
|
||||
'org.timesafari.daily-notification-fetch': {
|
||||
identifier: 'org.timesafari.daily-notification-fetch',
|
||||
requiresNetworkConnectivity: true,
|
||||
requiresExternalPower: false,
|
||||
requiresDeviceIdle: false
|
||||
|
||||
@@ -118,7 +118,7 @@ public class MainActivity extends BridgeActivity {
|
||||
|
||||
```xml
|
||||
<!-- App Configuration -->
|
||||
<application android:name="com.timesafari.dailynotification">
|
||||
<application android:name="org.timesafari.dailynotification">
|
||||
<activity android:name=".MainActivity" android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
@@ -142,16 +142,16 @@ public class MainActivity extends BridgeActivity {
|
||||
<!-- Plugin Components -->
|
||||
<!-- Internal receiver: keep non-exported unless intentionally public -->
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.DailyNotificationReceiver"
|
||||
android:name="org.timesafari.dailynotification.DailyNotificationReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.timesafari.daily.NOTIFICATION" />
|
||||
<action android:name="org.timesafari.daily.NOTIFICATION" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.BootReceiver"
|
||||
android:name="org.timesafari.dailynotification.BootReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter android:priority="1000">
|
||||
@@ -164,7 +164,7 @@ public class MainActivity extends BridgeActivity {
|
||||
**Minimal example (recommended):**
|
||||
```xml
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.BootReceiver"
|
||||
android:name="org.timesafari.dailynotification.BootReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
@@ -203,7 +203,7 @@ public class MainActivity extends BridgeActivity {
|
||||
|
||||
```json
|
||||
{
|
||||
"appId": "com.timesafari.dailynotification",
|
||||
"appId": "org.timesafari.dailynotification",
|
||||
"appName": "DailyNotification Test App",
|
||||
"webDir": "www",
|
||||
"server": {
|
||||
@@ -229,7 +229,7 @@ public class MainActivity extends BridgeActivity {
|
||||
[
|
||||
{
|
||||
"name": "DailyNotification",
|
||||
"classpath": "com.timesafari.dailynotification.DailyNotificationPlugin"
|
||||
"classpath": "org.timesafari.dailynotification.DailyNotificationPlugin"
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -429,11 +429,11 @@ dependencies {
|
||||
|
||||
```gradle
|
||||
android {
|
||||
namespace "com.timesafari.dailynotification"
|
||||
namespace "org.timesafari.dailynotification"
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.timesafari.dailynotification"
|
||||
applicationId "org.timesafari.dailynotification"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
|
||||
@@ -110,7 +110,7 @@ www/
|
||||
#### Implementation Plan
|
||||
```java
|
||||
// New organization
|
||||
com.timesafari.dailynotification/
|
||||
org.timesafari.dailynotification/
|
||||
├── plugin/
|
||||
│ └── DailyNotificationPlugin.java (thin facade)
|
||||
├── usecases/
|
||||
@@ -587,16 +587,16 @@ public class SecureNetworkClient {
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.DailyNotificationReceiver"
|
||||
android:name="org.timesafari.dailynotification.DailyNotificationReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.timesafari.daily.NOTIFICATION" />
|
||||
<action android:name="org.timesafari.daily.NOTIFICATION" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.BootReceiver"
|
||||
android:name="org.timesafari.dailynotification.BootReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter android:priority="1000">
|
||||
@@ -605,7 +605,7 @@ public class SecureNetworkClient {
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.TimeChangeReceiver"
|
||||
android:name="org.timesafari.dailynotification.TimeChangeReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
|
||||
@@ -96,7 +96,7 @@ This directive provides **descriptive overview and integration guidance** for An
|
||||
**⚠️ Illustrative only** – See Phase 1 for canonical implementation.
|
||||
|
||||
```kotlin
|
||||
package com.timesafari.dailynotification
|
||||
package org.timesafari.dailynotification
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.content.Context
|
||||
@@ -763,7 +763,7 @@ private suspend fun handleMissedAlarmOnBoot(
|
||||
|
||||
### 5.1 Test Setup
|
||||
|
||||
**Package Name**: `com.timesafari.dailynotification`
|
||||
**Package Name**: `org.timesafari.dailynotification`
|
||||
|
||||
**Test App Location**: `test-apps/android-test-app/`
|
||||
|
||||
@@ -806,7 +806,7 @@ adb shell dumpsys jobscheduler | grep -i timesafari
|
||||
#### Step 1: Schedule Alarm
|
||||
|
||||
**Via Test App UI**:
|
||||
1. Launch test app: `adb shell am start -n com.timesafari.dailynotification/.MainActivity`
|
||||
1. Launch test app: `adb shell am start -n org.timesafari.dailynotification/.MainActivity`
|
||||
2. Click "Test Notification" button
|
||||
3. Schedule alarm for 4 minutes in future (test app default)
|
||||
4. Note the scheduled time
|
||||
@@ -814,7 +814,7 @@ adb shell dumpsys jobscheduler | grep -i timesafari
|
||||
**Via ADB (Alternative)**:
|
||||
```bash
|
||||
# Launch app
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
|
||||
# Wait for app to load, then use UI to schedule
|
||||
# Or use monkey to click button (if button ID known)
|
||||
@@ -835,7 +835,7 @@ adb logcat -d | grep "DN|SCHEDULE\|DN|ALARM"
|
||||
|
||||
```bash
|
||||
# Kill app process (simulates OS kill)
|
||||
adb shell am kill com.timesafari.dailynotification
|
||||
adb shell am kill org.timesafari.dailynotification
|
||||
|
||||
# Verify app is killed
|
||||
adb shell ps | grep timesafari
|
||||
@@ -866,7 +866,7 @@ adb shell date
|
||||
|
||||
```bash
|
||||
# Launch app (triggers cold start)
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
|
||||
# Monitor logs for recovery
|
||||
adb logcat -c # Clear logs first
|
||||
@@ -915,7 +915,7 @@ adb logcat -d | grep -E "DNP-REACTIVATION|COLD_START|missed"
|
||||
#### Step 1: Schedule Multiple Alarms
|
||||
|
||||
**Via Test App UI**:
|
||||
1. Launch app: `adb shell am start -n com.timesafari.dailynotification/.MainActivity`
|
||||
1. Launch app: `adb shell am start -n org.timesafari.dailynotification/.MainActivity`
|
||||
2. Schedule alarm #1 for 2 minutes in future
|
||||
3. Schedule alarm #2 for 5 minutes in future
|
||||
4. Schedule alarm #3 for 10 minutes in future
|
||||
@@ -936,7 +936,7 @@ adb logcat -d | grep "DN|SCHEDULE"
|
||||
|
||||
```bash
|
||||
# Force stop app (hard kill)
|
||||
adb shell am force-stop com.timesafari.dailynotification
|
||||
adb shell am force-stop org.timesafari.dailynotification
|
||||
|
||||
# Verify app is force-stopped
|
||||
adb shell ps | grep timesafari
|
||||
@@ -963,7 +963,7 @@ adb shell date
|
||||
|
||||
```bash
|
||||
# Launch app (triggers force stop recovery)
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
|
||||
# Monitor logs immediately
|
||||
adb logcat -c
|
||||
@@ -1014,7 +1014,7 @@ adb shell dumpsys alarm | grep -A 10 timesafari
|
||||
#### Step 1: Schedule Alarm Before Reboot
|
||||
|
||||
**Via Test App UI**:
|
||||
1. Launch app: `adb shell am start -n com.timesafari.dailynotification/.MainActivity`
|
||||
1. Launch app: `adb shell am start -n org.timesafari.dailynotification/.MainActivity`
|
||||
2. Schedule alarm for 5 minutes in future
|
||||
3. Note scheduled time
|
||||
|
||||
@@ -1075,7 +1075,7 @@ adb logcat -d | grep -E "DNP-BOOT|BOOT_COMPLETED|reschedule"
|
||||
|
||||
```bash
|
||||
# Launch app to verify state
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
|
||||
# Check if missed alarms were handled
|
||||
adb logcat -d | grep -E "missed|boot_recovery"
|
||||
@@ -1103,7 +1103,7 @@ adb logcat -d | grep -E "missed|boot_recovery"
|
||||
#### Step 1: Schedule Alarm
|
||||
|
||||
**Via Test App UI**:
|
||||
1. Launch app: `adb shell am start -n com.timesafari.dailynotification/.MainActivity`
|
||||
1. Launch app: `adb shell am start -n org.timesafari.dailynotification/.MainActivity`
|
||||
2. Schedule alarm for 10 minutes in future
|
||||
3. Note scheduled time
|
||||
|
||||
@@ -1142,7 +1142,7 @@ adb shell ps | grep timesafari
|
||||
|
||||
```bash
|
||||
# Bring app to foreground
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
|
||||
# Monitor logs for warm start recovery
|
||||
adb logcat -c
|
||||
@@ -1191,7 +1191,7 @@ adb shell dumpsys alarm | grep -A 5 timesafari
|
||||
|
||||
```bash
|
||||
# Launch app and schedule alarm for 4 minutes
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
# Use UI to schedule alarm
|
||||
```
|
||||
|
||||
@@ -1269,7 +1269,7 @@ adb logcat -d | grep -E "DN|RECEIVE_START|DN|WORK_START|DN|DISPLAY"
|
||||
**Android 12+ (API 31+)**:
|
||||
```bash
|
||||
# Check current permission status
|
||||
adb shell dumpsys package com.timesafari.dailynotification | grep -i "schedule_exact_alarm"
|
||||
adb shell dumpsys package org.timesafari.dailynotification | grep -i "schedule_exact_alarm"
|
||||
|
||||
# Revoke permission (requires root or system app)
|
||||
# Or use Settings UI:
|
||||
@@ -1299,7 +1299,7 @@ adb logcat -d | grep -E "EXACT_ALARM|permission|SCHEDULE_EXACT"
|
||||
adb shell am start -a android.settings.REQUEST_SCHEDULE_EXACT_ALARM
|
||||
|
||||
# Or navigate manually:
|
||||
adb shell am start -a android.settings.APPLICATION_DETAILS_SETTINGS -d package:com.timesafari.dailynotification
|
||||
adb shell am start -a android.settings.APPLICATION_DETAILS_SETTINGS -d package:org.timesafari.dailynotification
|
||||
```
|
||||
|
||||
#### Step 4: Verify Alarm Scheduling
|
||||
@@ -1412,7 +1412,7 @@ adb shell date +%s
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
PACKAGE="com.timesafari.dailynotification"
|
||||
PACKAGE="org.timesafari.dailynotification"
|
||||
ACTIVITY="${PACKAGE}/.MainActivity"
|
||||
|
||||
echo "=== Test 1: Cold Start Recovery ==="
|
||||
|
||||
@@ -84,7 +84,7 @@ Phase 1 implements **minimal viable app launch recovery** for cold start scenari
|
||||
### 2.2 Class Structure
|
||||
|
||||
```kotlin
|
||||
package com.timesafari.dailynotification
|
||||
package org.timesafari.dailynotification
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
@@ -561,9 +561,9 @@ override fun load() {
|
||||
|
||||
**Steps**:
|
||||
1. Schedule notification for 2 minutes in future
|
||||
2. Kill app process: `adb shell am kill com.timesafari.dailynotification`
|
||||
2. Kill app process: `adb shell am kill org.timesafari.dailynotification`
|
||||
3. Wait 5 minutes (past scheduled time)
|
||||
4. Launch app: `adb shell am start -n com.timesafari.dailynotification/.MainActivity`
|
||||
4. Launch app: `adb shell am start -n org.timesafari.dailynotification/.MainActivity`
|
||||
5. Check logs: `adb logcat -d | grep DNP-REACTIVATION`
|
||||
|
||||
**Expected**:
|
||||
|
||||
@@ -163,7 +163,7 @@ private fun alarmsExist(): Boolean {
|
||||
// Check if any PendingIntent for our receiver exists
|
||||
// This is more reliable than nextAlarmClock
|
||||
val intent = Intent(context, DailyNotificationReceiver::class.java).apply {
|
||||
action = "com.timesafari.daily.NOTIFICATION"
|
||||
action = "org.timesafari.daily.NOTIFICATION"
|
||||
}
|
||||
val pendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
@@ -629,9 +629,9 @@ private fun calculateNextOccurrence(schedule: Schedule, fromTime: Long): Long {
|
||||
**Steps**:
|
||||
1. Schedule 3 notifications (2 minutes, 5 minutes, 10 minutes in future)
|
||||
2. Verify alarms scheduled: `adb shell dumpsys alarm | grep timesafari`
|
||||
3. Force stop app: `adb shell am force-stop com.timesafari.dailynotification`
|
||||
3. Force stop app: `adb shell am force-stop org.timesafari.dailynotification`
|
||||
4. Verify alarms cancelled: `adb shell dumpsys alarm | grep timesafari` (should be empty)
|
||||
5. Launch app: `adb shell am start -n com.timesafari.dailynotification/.MainActivity`
|
||||
5. Launch app: `adb shell am start -n org.timesafari.dailynotification/.MainActivity`
|
||||
6. Check logs: `adb logcat -d | grep DNP-REACTIVATION`
|
||||
|
||||
**Expected**:
|
||||
|
||||
@@ -29,7 +29,7 @@ public class TestApplication extends Application {
|
||||
|
||||
Context context = getApplicationContext();
|
||||
NativeNotificationContentFetcher testFetcher =
|
||||
new com.timesafari.dailynotification.test.TestNativeFetcher(context);
|
||||
new org.timesafari.dailynotification.test.TestNativeFetcher(context);
|
||||
DailyNotificationPlugin.setNativeFetcher(testFetcher);
|
||||
}
|
||||
}
|
||||
@@ -175,7 +175,7 @@ implementation 'com.google.code.gson:gson:2.10.1'
|
||||
**Test App (Working):**
|
||||
```xml
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.DailyNotificationReceiver"
|
||||
android:name="org.timesafari.dailynotification.DailyNotificationReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false"> <!-- Note: false -->
|
||||
```
|
||||
@@ -183,7 +183,7 @@ implementation 'com.google.code.gson:gson:2.10.1'
|
||||
**TimeSafari (Broken):**
|
||||
```xml
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.DailyNotificationReceiver"
|
||||
android:name="org.timesafari.dailynotification.DailyNotificationReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true"> <!-- Note: true - potential security issue -->
|
||||
```
|
||||
@@ -240,8 +240,8 @@ package app.timesafari;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import com.timesafari.dailynotification.DailyNotificationPlugin;
|
||||
import com.timesafari.dailynotification.NativeNotificationContentFetcher;
|
||||
import org.timesafari.dailynotification.DailyNotificationPlugin;
|
||||
import org.timesafari.dailynotification.NativeNotificationContentFetcher;
|
||||
|
||||
public class TimeSafariApplication extends Application {
|
||||
|
||||
@@ -272,8 +272,8 @@ Create file: `android/app/src/main/java/app/timesafari/TimeSafariNativeFetcher.j
|
||||
package app.timesafari;
|
||||
|
||||
import android.content.Context;
|
||||
import com.timesafari.dailynotification.NativeNotificationContentFetcher;
|
||||
import com.timesafari.dailynotification.NotificationContent;
|
||||
import org.timesafari.dailynotification.NativeNotificationContentFetcher;
|
||||
import org.timesafari.dailynotification.NotificationContent;
|
||||
|
||||
public class TimeSafariNativeFetcher implements NativeNotificationContentFetcher {
|
||||
|
||||
@@ -318,11 +318,11 @@ public class TimeSafariNativeFetcher implements NativeNotificationContentFetcher
|
||||
|
||||
<!-- Fix: Change exported to false -->
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.DailyNotificationReceiver"
|
||||
android:name="org.timesafari.dailynotification.DailyNotificationReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.timesafari.daily.NOTIFICATION" />
|
||||
<action android:name="org.timesafari.daily.NOTIFICATION" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
@@ -431,9 +431,9 @@ After implementing fixes, verify:
|
||||
|
||||
3. **Test receiver manually:**
|
||||
```bash
|
||||
adb shell am broadcast -a com.timesafari.daily.NOTIFICATION \
|
||||
adb shell am broadcast -a org.timesafari.daily.NOTIFICATION \
|
||||
--es id "test_notification" \
|
||||
-n app.timesafari.app/com.timesafari.dailynotification.DailyNotificationReceiver
|
||||
-n app.timesafari.app/org.timesafari.dailynotification.DailyNotificationReceiver
|
||||
```
|
||||
|
||||
4. **Check notification permissions:**
|
||||
|
||||
@@ -173,10 +173,10 @@ console.log('Performance:', status.performance);
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<!-- Register new receivers -->
|
||||
<receiver android:name="com.timesafari.dailynotification.NotifyReceiver"
|
||||
<receiver android:name="org.timesafari.dailynotification.NotifyReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
<receiver android:name="com.timesafari.dailynotification.BootReceiver"
|
||||
<receiver android:name="org.timesafari.dailynotification.BootReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
@@ -209,8 +209,8 @@ dependencies {
|
||||
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>com.timesafari.dailynotification.content-fetch</string>
|
||||
<string>com.timesafari.dailynotification.notification-delivery</string>
|
||||
<string>org.timesafari.dailynotification.content-fetch</string>
|
||||
<string>org.timesafari.dailynotification.notification-delivery</string>
|
||||
</array>
|
||||
```
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ This guide provides solutions to common iOS-specific issues when using the Daily
|
||||
1. **Check BGTaskScheduler Registration:**
|
||||
```swift
|
||||
// Verify registration in AppDelegate
|
||||
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.timesafari.dailynotification.fetch", using: nil) { task in
|
||||
BGTaskScheduler.shared.register(forTaskWithIdentifier: "org.timesafari.dailynotification.fetch", using: nil) { task in
|
||||
// Handler should be registered
|
||||
}
|
||||
```
|
||||
@@ -97,7 +97,7 @@ This guide provides solutions to common iOS-specific issues when using the Daily
|
||||
```xml
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>com.timesafari.dailynotification.fetch</string>
|
||||
<string>org.timesafari.dailynotification.fetch</string>
|
||||
</array>
|
||||
```
|
||||
|
||||
@@ -364,7 +364,7 @@ print("Registered tasks: \(registered)")
|
||||
|
||||
**LLDB Command in Xcode:**
|
||||
```lldb
|
||||
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]
|
||||
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"org.timesafari.dailynotification.fetch"]
|
||||
```
|
||||
|
||||
**Note:** This only works in simulator, not on physical devices.
|
||||
|
||||
@@ -75,10 +75,10 @@
|
||||
**How to Run:**
|
||||
```bash
|
||||
# Run all combined edge case tests
|
||||
cd android && ./gradlew test --tests "com.timesafari.dailynotification.DailyNotificationRecoveryTests"
|
||||
cd android && ./gradlew test --tests "org.timesafari.dailynotification.DailyNotificationRecoveryTests"
|
||||
|
||||
# Or run specific test
|
||||
cd android && ./gradlew test --tests "com.timesafari.dailynotification.DailyNotificationRecoveryTests.test_combined_dst_boundary_duplicate_delivery_cold_start"
|
||||
cd android && ./gradlew test --tests "org.timesafari.dailynotification.DailyNotificationRecoveryTests.test_combined_dst_boundary_duplicate_delivery_cold_start"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -97,7 +97,7 @@ Before starting, verify:
|
||||
|
||||
**Example placeholder test:**
|
||||
```kotlin
|
||||
package com.timesafari.dailynotification
|
||||
package org.timesafari.dailynotification
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.Assert.*
|
||||
@@ -148,7 +148,7 @@ class DailyNotificationRecoveryTests {
|
||||
|
||||
**Example structure:**
|
||||
```kotlin
|
||||
package com.timesafari.dailynotification
|
||||
package org.timesafari.dailynotification
|
||||
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
|
||||
@@ -793,7 +793,7 @@ Add to `Info.plist`:
|
||||
\`\`\`xml
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>com.timesafari.dailynotification.fetch</string>
|
||||
<string>org.timesafari.dailynotification.fetch</string>
|
||||
</array>
|
||||
\`\`\`
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ This guide provides comprehensive testing procedures for the **fixed BootReceive
|
||||
### **1. AndroidManifest.xml Updates**
|
||||
```xml
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.BootReceiver"
|
||||
android:name="org.timesafari.dailynotification.BootReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:directBootAware="true">
|
||||
@@ -80,13 +80,13 @@ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
**Steps**:
|
||||
```bash
|
||||
# Check BootReceiver registration
|
||||
adb shell "dumpsys package com.timesafari.dailynotification | grep -A10 -B5 BootReceiver"
|
||||
adb shell "dumpsys package org.timesafari.dailynotification | grep -A10 -B5 BootReceiver"
|
||||
```
|
||||
|
||||
**Expected Output**:
|
||||
```
|
||||
android.intent.action.LOCKED_BOOT_COMPLETED:
|
||||
a440fcf com.timesafari.dailynotification/.BootReceiver filter 4e5fd5c
|
||||
a440fcf org.timesafari.dailynotification/.BootReceiver filter 4e5fd5c
|
||||
Action: "android.intent.action.LOCKED_BOOT_COMPLETED"
|
||||
Action: "android.intent.action.BOOT_COMPLETED"
|
||||
Action: "android.intent.action.MY_PACKAGE_REPLACED"
|
||||
@@ -106,7 +106,7 @@ android.intent.action.LOCKED_BOOT_COMPLETED:
|
||||
**Steps**:
|
||||
```bash
|
||||
# 1. Schedule notification
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
# Tap "Test Notification" (5 minutes from now)
|
||||
|
||||
# 2. Verify initial scheduling
|
||||
@@ -147,7 +147,7 @@ BootReceiver: Notification recovery completed: X/X recovered
|
||||
**Steps**:
|
||||
```bash
|
||||
# 1. Schedule notification
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
# Tap "Test Notification" (5 minutes from now)
|
||||
|
||||
# 2. Verify initial scheduling
|
||||
@@ -182,7 +182,7 @@ BootReceiver: Notification recovery completed: X/X recovered
|
||||
**Steps**:
|
||||
```bash
|
||||
# 1. Schedule notification
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
# Tap "Test Notification" (5 minutes from now)
|
||||
|
||||
# 2. Reboot device
|
||||
@@ -211,7 +211,7 @@ BootReceiver: Locked boot completed - ready for full recovery on unlock
|
||||
**Steps**:
|
||||
```bash
|
||||
# 1. Launch app
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
|
||||
# 2. Tap "Exact Alarm Settings" button
|
||||
# Should open exact alarm settings if needed
|
||||
@@ -230,14 +230,14 @@ adb shell "dumpsys alarm | grep SCHEDULE_EXACT_ALARM"
|
||||
### **Check BootReceiver Status**
|
||||
```bash
|
||||
# Verify registration
|
||||
adb shell "dumpsys package com.timesafari.dailynotification | grep -A10 -B5 BootReceiver"
|
||||
adb shell "dumpsys package org.timesafari.dailynotification | grep -A10 -B5 BootReceiver"
|
||||
|
||||
# Check if enabled
|
||||
adb shell "pm list packages -d | grep timesafari"
|
||||
# Should return nothing (app not disabled)
|
||||
|
||||
# Check permissions
|
||||
adb shell "dumpsys package com.timesafari.dailynotification | grep -A5 -B5 permission"
|
||||
adb shell "dumpsys package org.timesafari.dailynotification | grep -A5 -B5 permission"
|
||||
```
|
||||
|
||||
### **Monitor Boot Events**
|
||||
@@ -273,13 +273,13 @@ adb shell "dumpsys alarm | grep -A5 -B5 timesafari"
|
||||
**Solutions**:
|
||||
```bash
|
||||
# Check if receiver is registered
|
||||
adb shell "dumpsys package com.timesafari.dailynotification | grep BootReceiver"
|
||||
adb shell "dumpsys package org.timesafari.dailynotification | grep BootReceiver"
|
||||
|
||||
# Check if app is disabled
|
||||
adb shell "pm list packages -d | grep timesafari"
|
||||
|
||||
# Check if permissions are granted
|
||||
adb shell "dumpsys package com.timesafari.dailynotification | grep RECEIVE_BOOT_COMPLETED"
|
||||
adb shell "dumpsys package org.timesafari.dailynotification | grep RECEIVE_BOOT_COMPLETED"
|
||||
```
|
||||
|
||||
### **Issue 2: Direct Boot Errors**
|
||||
@@ -288,10 +288,10 @@ adb shell "dumpsys package com.timesafari.dailynotification | grep RECEIVE_BOOT_
|
||||
**Solutions**:
|
||||
```bash
|
||||
# Check Direct Boot compatibility
|
||||
adb shell "dumpsys package com.timesafari.dailynotification | grep directBootAware"
|
||||
adb shell "dumpsys package org.timesafari.dailynotification | grep directBootAware"
|
||||
|
||||
# Check device protected storage
|
||||
adb shell "ls -la /data/user_de/0/com.timesafari.dailynotification/"
|
||||
adb shell "ls -la /data/user_de/0/org.timesafari.dailynotification/"
|
||||
```
|
||||
|
||||
### **Issue 3: Exact Alarm Permission Denied**
|
||||
@@ -303,7 +303,7 @@ adb shell "ls -la /data/user_de/0/com.timesafari.dailynotification/"
|
||||
adb shell "dumpsys alarm | grep SCHEDULE_EXACT_ALARM"
|
||||
|
||||
# Open exact alarm settings
|
||||
adb shell am start -a android.settings.REQUEST_SCHEDULE_EXACT_ALARM --es android.provider.extra.APP_PACKAGE com.timesafari.dailynotification
|
||||
adb shell am start -a android.settings.REQUEST_SCHEDULE_EXACT_ALARM --es android.provider.extra.APP_PACKAGE org.timesafari.dailynotification
|
||||
```
|
||||
|
||||
## 📊 **Success Metrics**
|
||||
|
||||
@@ -31,7 +31,7 @@ This document provides comprehensive testing procedures for the DailyNotificatio
|
||||
**Steps**:
|
||||
```bash
|
||||
# 1. Launch app and check channel status
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
|
||||
# 2. In app UI, tap "Check Channel Status"
|
||||
# 3. Verify channel exists and is enabled
|
||||
@@ -59,7 +59,7 @@ adb shell "dumpsys notification | grep -A5 'daily_default'"
|
||||
**Steps**:
|
||||
```bash
|
||||
# 1. Block the notification channel manually
|
||||
adb shell "am start -a android.settings.CHANNEL_NOTIFICATION_SETTINGS -e android.provider.extra.APP_PACKAGE com.timesafari.dailynotification -e android.provider.extra.CHANNEL_ID daily_default"
|
||||
adb shell "am start -a android.settings.CHANNEL_NOTIFICATION_SETTINGS -e android.provider.extra.APP_PACKAGE org.timesafari.dailynotification -e android.provider.extra.CHANNEL_ID daily_default"
|
||||
|
||||
# 2. In system settings, disable the channel
|
||||
# 3. Return to app and tap "Check Channel Status"
|
||||
@@ -304,7 +304,7 @@ adb logcat -d | grep -i "recovery.*count"
|
||||
**Steps**:
|
||||
```bash
|
||||
# 1. Schedule notification
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
# Tap "Test Notification" in UI
|
||||
|
||||
# 2. Verify initial scheduling
|
||||
@@ -383,7 +383,7 @@ adb logcat -d | grep -i "recovery.*performed.*recently.*skipping"
|
||||
|
||||
set -e
|
||||
|
||||
APP_PACKAGE="com.timesafari.dailynotification"
|
||||
APP_PACKAGE="org.timesafari.dailynotification"
|
||||
APP_ACTIVITY=".MainActivity"
|
||||
TEST_TIMEOUT=300 # 5 minutes
|
||||
|
||||
@@ -645,7 +645,7 @@ import json
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
class DailyNotificationTesterV2:
|
||||
def __init__(self, package: str = "com.timesafari.dailynotification"):
|
||||
def __init__(self, package: str = "org.timesafari.dailynotification"):
|
||||
self.package = package
|
||||
self.activity = f"{package}/.MainActivity"
|
||||
self.test_results: Dict[str, bool] = {}
|
||||
@@ -905,7 +905,7 @@ adb shell "svc data disable"
|
||||
**Steps**:
|
||||
```bash
|
||||
# 1. Enable battery optimization for app
|
||||
adb shell "dumpsys deviceidle whitelist -com.timesafari.dailynotification"
|
||||
adb shell "dumpsys deviceidle whitelist -org.timesafari.dailynotification"
|
||||
|
||||
# 2. Schedule notification
|
||||
# 3. Wait for notification
|
||||
@@ -945,7 +945,7 @@ adb shell "dumpsys deviceidle whitelist -com.timesafari.dailynotification"
|
||||
**Steps**:
|
||||
```bash
|
||||
# 1. Check initial memory usage
|
||||
adb shell "dumpsys meminfo com.timesafari.dailynotification"
|
||||
adb shell "dumpsys meminfo org.timesafari.dailynotification"
|
||||
|
||||
# 2. Schedule multiple notifications
|
||||
# 3. Check memory usage again
|
||||
@@ -1005,7 +1005,7 @@ adb logcat -d | grep -i "channelmanager"
|
||||
adb shell "dumpsys alarm | grep SCHEDULE_EXACT_ALARM"
|
||||
|
||||
# Open exact alarm settings
|
||||
adb shell "am start -a android.settings.REQUEST_SCHEDULE_EXACT_ALARM -d package:com.timesafari.dailynotification"
|
||||
adb shell "am start -a android.settings.REQUEST_SCHEDULE_EXACT_ALARM -d package:org.timesafari.dailynotification"
|
||||
```
|
||||
|
||||
#### Issue 3: JIT Refresh Not Working
|
||||
@@ -1040,10 +1040,10 @@ adb logcat -d | grep -i "recovery.*performed.*recently"
|
||||
# Comprehensive status check
|
||||
adb shell "dumpsys notification | grep -A10 daily_default"
|
||||
adb shell "dumpsys alarm | grep -A5 timesafari"
|
||||
adb shell "dumpsys package com.timesafari.dailynotification | grep -A5 receiver"
|
||||
adb shell "dumpsys package org.timesafari.dailynotification | grep -A5 receiver"
|
||||
|
||||
# Recovery state check
|
||||
adb shell "run-as com.timesafari.dailynotification ls -la /data/data/com.timesafari.dailynotification/shared_prefs/"
|
||||
adb shell "run-as org.timesafari.dailynotification ls -la /data/data/org.timesafari.dailynotification/shared_prefs/"
|
||||
|
||||
# Channel status check
|
||||
adb shell "cmd notification list | grep daily_default"
|
||||
|
||||
@@ -188,10 +188,10 @@ adb install -r app/build/outputs/apk/debug/app-debug.apk
|
||||
|
||||
```bash
|
||||
# Launch the app
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
|
||||
# Alternative: Launch with specific intent
|
||||
adb shell am start -a android.intent.action.MAIN -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -a android.intent.action.MAIN -n org.timesafari.dailynotification/.MainActivity
|
||||
```
|
||||
|
||||
### 8. Monitor App Logs
|
||||
@@ -231,7 +231,7 @@ cd android && ./gradlew :app:assembleDebug
|
||||
adb install app/build/outputs/apk/debug/app-debug.apk
|
||||
|
||||
# 6. Launch app
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
|
||||
# 7. Monitor logs
|
||||
adb logcat -s "Capacitor" "DailyNotification" "Console"
|
||||
@@ -259,7 +259,7 @@ npx cap run android
|
||||
cd android
|
||||
./gradlew :app:assembleDebug
|
||||
adb install app/build/outputs/apk/debug/app-debug.apk
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
```
|
||||
|
||||
### Method 3: Using Monkey (Alternative Launch)
|
||||
@@ -267,7 +267,7 @@ adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
```bash
|
||||
# Install and launch with Monkey
|
||||
adb install app/build/outputs/apk/debug/app-debug.apk
|
||||
adb shell monkey -p com.timesafari.dailynotification -c android.intent.category.LAUNCHER 1
|
||||
adb shell monkey -p org.timesafari.dailynotification -c android.intent.category.LAUNCHER 1
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
@@ -318,7 +318,7 @@ adb version
|
||||
adb shell pm list packages | grep timesafari
|
||||
|
||||
# Uninstall existing app
|
||||
adb uninstall com.timesafari.dailynotification
|
||||
adb uninstall org.timesafari.dailynotification
|
||||
|
||||
# Install with force
|
||||
adb install -r -t app/build/outputs/apk/debug/app-debug.apk
|
||||
@@ -369,7 +369,7 @@ When the app launches successfully, you should see:
|
||||
|
||||
```bash
|
||||
# ADB output
|
||||
Starting: Intent { cmp=com.timesafari.dailynotification/.MainActivity }
|
||||
Starting: Intent { cmp=org.timesafari.dailynotification/.MainActivity }
|
||||
|
||||
# Logcat output
|
||||
D Capacitor: Starting BridgeActivity
|
||||
@@ -415,7 +415,7 @@ The app should display:
|
||||
./scripts/build-native.sh --platform android
|
||||
cd android && ./gradlew :app:assembleDebug
|
||||
adb install -r app/build/outputs/apk/debug/app-debug.apk
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
```
|
||||
|
||||
### Automated Testing
|
||||
@@ -426,7 +426,7 @@ adb wait-for-device
|
||||
./scripts/build-native.sh --platform android
|
||||
cd android && ./gradlew :app:assembleDebug
|
||||
adb install app/build/outputs/apk/debug/app-debug.apk
|
||||
adb shell am start -n com.timesafari.dailynotification/.MainActivity
|
||||
adb shell am start -n org.timesafari.dailynotification/.MainActivity
|
||||
# Run tests...
|
||||
```
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ po UNUserNotificationCenter.current().pendingNotificationRequests()
|
||||
po await UNUserNotificationCenter.current().notificationSettings()
|
||||
|
||||
// Manually trigger BGTask (simulator only)
|
||||
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]
|
||||
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"org.timesafari.dailynotification.fetch"]
|
||||
```
|
||||
|
||||
---
|
||||
@@ -78,7 +78,7 @@ e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWith
|
||||
|
||||
3. **Filter logs:**
|
||||
- Click search box (top right)
|
||||
- Type: `DNP-` or `com.timesafari.dailynotification`
|
||||
- Type: `DNP-` or `org.timesafari.dailynotification`
|
||||
- Press Enter
|
||||
|
||||
### Filter by Subsystem (Structured Logging):
|
||||
@@ -86,14 +86,14 @@ e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWith
|
||||
The plugin uses structured logging with subsystems:
|
||||
|
||||
```
|
||||
com.timesafari.dailynotification.plugin # Plugin operations
|
||||
com.timesafari.dailynotification.fetch # Fetch operations
|
||||
com.timesafari.dailynotification.scheduler # Scheduling operations
|
||||
com.timesafari.dailynotification.storage # Storage operations
|
||||
org.timesafari.dailynotification.plugin # Plugin operations
|
||||
org.timesafari.dailynotification.fetch # Fetch operations
|
||||
org.timesafari.dailynotification.scheduler # Scheduling operations
|
||||
org.timesafari.dailynotification.storage # Storage operations
|
||||
```
|
||||
|
||||
**To filter by subsystem:**
|
||||
- In Console.app search: `subsystem:com.timesafari.dailynotification`
|
||||
- In Console.app search: `subsystem:org.timesafari.dailynotification`
|
||||
|
||||
---
|
||||
|
||||
@@ -108,7 +108,7 @@ com.timesafari.dailynotification.storage # Storage operations
|
||||
xcrun simctl spawn booted log stream
|
||||
|
||||
# Stream only plugin logs (filtered)
|
||||
xcrun simctl spawn booted log stream --predicate 'subsystem == "com.timesafari.dailynotification"'
|
||||
xcrun simctl spawn booted log stream --predicate 'subsystem == "org.timesafari.dailynotification"'
|
||||
|
||||
# Stream with DNP- prefix filter
|
||||
xcrun simctl spawn booted log stream | grep "DNP-"
|
||||
@@ -121,7 +121,7 @@ xcrun simctl spawn booted log stream | grep "DNP-"
|
||||
xcrun simctl spawn booted log stream > device.log 2>&1
|
||||
|
||||
# Save filtered logs
|
||||
xcrun simctl spawn booted log stream --predicate 'subsystem == "com.timesafari.dailynotification"' > plugin.log 2>&1
|
||||
xcrun simctl spawn booted log stream --predicate 'subsystem == "org.timesafari.dailynotification"' > plugin.log 2>&1
|
||||
|
||||
# Then analyze with grep
|
||||
grep -E "\[DNP-(FETCH|SCHEDULER|PLUGIN)\]" device.log
|
||||
@@ -144,7 +144,7 @@ xcrun simctl spawn booted log show --start "2025-11-15 10:00:00" --end "2025-11-
|
||||
xcrun devicectl list devices
|
||||
|
||||
# Stream logs from physical device (requires device UDID)
|
||||
xcrun devicectl device process launch --device <UDID> com.timesafari.dailynotification.test
|
||||
xcrun devicectl device process launch --device <UDID> org.timesafari.dailynotification.test
|
||||
|
||||
# Or use Console.app for physical devices (easier)
|
||||
```
|
||||
@@ -162,7 +162,7 @@ xcrun devicectl device process launch --device <UDID> com.timesafari.dailynotifi
|
||||
./scripts/validate-ios-logs.sh device.log
|
||||
|
||||
# From live stream
|
||||
xcrun simctl spawn booted log stream --predicate 'subsystem == "com.timesafari.dailynotification"' | ./scripts/validate-ios-logs.sh
|
||||
xcrun simctl spawn booted log stream --predicate 'subsystem == "org.timesafari.dailynotification"' | ./scripts/validate-ios-logs.sh
|
||||
|
||||
# From filtered grep
|
||||
grep -E "\[DNP-(FETCH|SCHEDULER|PLUGIN)\]" device.log | ./scripts/validate-ios-logs.sh
|
||||
@@ -207,7 +207,7 @@ grep -E "\[DNP-(FETCH|SCHEDULER|PLUGIN)\]" device.log | ./scripts/validate-ios-l
|
||||
|
||||
**Solutions:**
|
||||
1. **Use specific filters:** `DNP-` instead of `DailyNotification`
|
||||
2. **Filter by subsystem:** `subsystem:com.timesafari.dailynotification`
|
||||
2. **Filter by subsystem:** `subsystem:org.timesafari.dailynotification`
|
||||
3. **Use time range:** Only show logs from last 5 minutes
|
||||
4. **Use validation script:** Automatically filters for important events
|
||||
|
||||
@@ -235,7 +235,7 @@ grep -E "\[DNP-(FETCH|SCHEDULER|PLUGIN)\]" device.log | ./scripts/validate-ios-l
|
||||
|
||||
```bash
|
||||
# Stream plugin logs (simulator)
|
||||
xcrun simctl spawn booted log stream --predicate 'subsystem == "com.timesafari.dailynotification"'
|
||||
xcrun simctl spawn booted log stream --predicate 'subsystem == "org.timesafari.dailynotification"'
|
||||
|
||||
# Save logs to file
|
||||
xcrun simctl spawn booted log stream > device.log 2>&1
|
||||
|
||||
@@ -150,7 +150,7 @@ po UNUserNotificationCenter.current().pendingNotificationRequests()
|
||||
|
||||
# Check BGTask scheduling (simulator only)
|
||||
# Use LLDB command in Xcode debugger:
|
||||
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]
|
||||
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"org.timesafari.dailynotification.fetch"]
|
||||
```
|
||||
|
||||
---
|
||||
@@ -316,7 +316,7 @@ DNP-FETCH: BGTask rescheduled for [date]
|
||||
**Manual Trigger (Simulator Only):**
|
||||
```bash
|
||||
# In Xcode debugger (LLDB)
|
||||
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]
|
||||
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"org.timesafari.dailynotification.fetch"]
|
||||
```
|
||||
|
||||
---
|
||||
@@ -422,8 +422,8 @@ open DailyNotificationPlugin.xcodeproj
|
||||
```xml
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>com.timesafari.dailynotification.fetch</string>
|
||||
<string>com.timesafari.dailynotification.notify</string>
|
||||
<string>org.timesafari.dailynotification.fetch</string>
|
||||
<string>org.timesafari.dailynotification.notify</string>
|
||||
</array>
|
||||
```
|
||||
|
||||
@@ -464,7 +464,7 @@ po await UNUserNotificationCenter.current().notificationSettings()
|
||||
|
||||
**Check BGTask Status (Simulator Only):**
|
||||
```swift
|
||||
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]
|
||||
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"org.timesafari.dailynotification.fetch"]
|
||||
```
|
||||
|
||||
**Check Storage:**
|
||||
|
||||
@@ -137,7 +137,7 @@ Quick validation checklist (each step links to log verification):
|
||||
- Background fetch
|
||||
- Background processing (if using `BGProcessingTask`)
|
||||
- Info.plist has:
|
||||
- `BGTaskSchedulerPermittedIdentifiers` array with task identifier: `com.timesafari.dailynotification.fetch`
|
||||
- `BGTaskSchedulerPermittedIdentifiers` array with task identifier: `org.timesafari.dailynotification.fetch`
|
||||
- Plugin exposes:
|
||||
- `scheduleDailyNotification()` method that schedules both prefetch and notification
|
||||
|
||||
@@ -186,7 +186,7 @@ Add structured logs at key points:
|
||||
|
||||
**On app startup:**
|
||||
```
|
||||
[DNP-FETCH] Registering BGTaskScheduler task (id=com.timesafari.dailynotification.fetch)
|
||||
[DNP-FETCH] Registering BGTaskScheduler task (id=org.timesafari.dailynotification.fetch)
|
||||
```
|
||||
|
||||
**When scheduling:**
|
||||
@@ -197,7 +197,7 @@ Add structured logs at key points:
|
||||
|
||||
**When BGTask handler fires:**
|
||||
```
|
||||
[DNP-FETCH] BGTask handler invoked (task.identifier=com.timesafari.dailynotification.fetch)
|
||||
[DNP-FETCH] BGTask handler invoked (task.identifier=org.timesafari.dailynotification.fetch)
|
||||
```
|
||||
|
||||
**Inside prefetch logic:**
|
||||
@@ -239,7 +239,7 @@ Add structured logs at key points:
|
||||
2. **Open Xcode Debug Console** (View → Debug Area → Activate Console, or press Cmd+Shift+Y)
|
||||
3. **In LLDB console, paste and execute:**
|
||||
```bash
|
||||
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]
|
||||
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"org.timesafari.dailynotification.fetch"]
|
||||
```
|
||||
4. Press Enter
|
||||
|
||||
@@ -270,7 +270,7 @@ Add structured logs at key points:
|
||||
**Troubleshooting:**
|
||||
- If LLDB command doesn't work, ensure the app is backgrounded first
|
||||
- If you see "Simulate Background Fetch" in menu when app is running, it may be a different Xcode version - try the LLDB method anyway
|
||||
- Verify BGTask identifier matches exactly: `com.timesafari.dailynotification.fetch`
|
||||
- Verify BGTask identifier matches exactly: `org.timesafari.dailynotification.fetch`
|
||||
|
||||
### 5. Trigger or Wait for Notification
|
||||
|
||||
@@ -544,7 +544,7 @@ When everything is wired correctly, one full cycle should produce:
|
||||
|
||||
**Expected logs:**
|
||||
```
|
||||
[DNP-FETCH] Registering BGTaskScheduler task (id=com.timesafari.dailynotification.fetch)
|
||||
[DNP-FETCH] Registering BGTaskScheduler task (id=org.timesafari.dailynotification.fetch)
|
||||
[DNP-PLUGIN] Startup complete (hasPendingSchedules=true|false)
|
||||
```
|
||||
|
||||
@@ -585,7 +585,7 @@ When everything is wired correctly, one full cycle should produce:
|
||||
|
||||
**Expected logs:**
|
||||
```
|
||||
[DNP-FETCH] BGTask handler invoked (id=com.timesafari.dailynotification.fetch)
|
||||
[DNP-FETCH] BGTask handler invoked (id=org.timesafari.dailynotification.fetch)
|
||||
[DNP-FETCH] Resolved next notification needing content (time=..., scheduleId=...)
|
||||
[DNP-FETCH] Starting fetch from <URL> (notificationTime=..., jwtPresent=true)
|
||||
[DNP-FETCH] Fetch success (status=200, bytes=1234, ttl=86400)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user