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