Files
daily-notification-plugin/docs/platform/android/TIMESAFARI_ANDROID_COMPARISON.md

12 KiB

Android Notification Implementation Comparison

Test App (Working) vs TimeSafari (Not Working)

This document identifies the critical differences between the test app where notifications work correctly and the TimeSafari app where notifications don't work at all. Use this as a checklist to fix TimeSafari.


Critical Issues (Must Fix)

1. Missing Custom Application Class

This is likely the primary cause of failure.

Test App (Working):

<!-- AndroidManifest.xml -->
<application
    android:name=".TestApplication"
    ...>
// TestApplication.java
public class TestApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        
        Context context = getApplicationContext();
        NativeNotificationContentFetcher testFetcher = 
            new com.timesafari.dailynotification.test.TestNativeFetcher(context);
        DailyNotificationPlugin.setNativeFetcher(testFetcher);
    }
}

TimeSafari (Broken):

<!-- AndroidManifest.xml - NO android:name attribute -->
<application
    android:allowBackup="true"
    ...>
  • No custom Application class exists
  • No native fetcher is registered
  • Plugin cannot fetch notification content

Fix Required:

  1. Create TimeSafariApplication.java in android/app/src/main/java/app/timesafari/
  2. Implement NativeNotificationContentFetcher specific to TimeSafari
  3. Add android:name=".TimeSafariApplication" to AndroidManifest.xml

2. Missing Capacitor Plugin Configuration

Test App (Working):

// capacitor.config.ts
plugins: {
  DailyNotification: {
    debugMode: true,
    enableNotifications: true,
    timesafariConfig: {
      activeDid: "did:ethr:0x...",
      endpoints: {
        projectsLastUpdated: "http://..."
      },
      starredProjectsConfig: {
        enabled: true,
        starredPlanHandleIds: [...],
        fetchInterval: '0 8 * * *'
      },
      credentialConfig: {
        jwtSecret: '...',
        tokenExpirationMinutes: 1
      }
    },
    networkConfig: {
      timeout: 30000,
      retryAttempts: 3,
      retryDelay: 1000
    },
    contentFetch: {
      enabled: true,
      schedule: '0 00 * * *',
      fetchLeadTimeMinutes: 5
    }
  }
}

TimeSafari (Broken):

// capacitor.config.ts - NO DailyNotification configuration at all
plugins: {
  App: { ... },
  SplashScreen: { ... },
  CapSQLite: { ... }
  // DailyNotification is MISSING
}

Fix Required: Add DailyNotification configuration to capacitor.config.ts with appropriate values for TimeSafari.


3. Missing Permissions in AndroidManifest.xml

Test App has these permissions that TimeSafari is missing:

<!-- Add to TimeSafari's AndroidManifest.xml -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />

Current TimeSafari permissions (incomplete):

  • INTERNET
  • POST_NOTIFICATIONS
  • SCHEDULE_EXACT_ALARM
  • RECEIVE_BOOT_COMPLETED
  • WAKE_LOCK
  • ACCESS_NETWORK_STATE - MISSING
  • FOREGROUND_SERVICE - MISSING
  • SYSTEM_ALERT_WINDOW - MISSING
  • REQUEST_IGNORE_BATTERY_OPTIMIZATIONS - MISSING

4. Missing Gradle Dependencies

Test App (Working):

// android/app/build.gradle
dependencies {
    // Capacitor annotation processor for automatic plugin discovery
    annotationProcessor project(':capacitor-android')
    
    // Required dependencies for the plugin
    implementation 'androidx.work:work-runtime:2.9.0'
    implementation 'androidx.lifecycle:lifecycle-service:2.7.0'
    implementation 'com.google.code.gson:gson:2.10.1'
}

TimeSafari (Broken):

dependencies {
    // Missing: annotationProcessor project(':capacitor-android')
    implementation "androidx.work:work-runtime-ktx:2.9.0"  // Using Kotlin version
    // Missing: androidx.lifecycle:lifecycle-service
    // Missing: com.google.code.gson:gson
}

Fix Required: Add to TimeSafari's android/app/build.gradle:

annotationProcessor project(':capacitor-android')
implementation 'androidx.lifecycle:lifecycle-service:2.7.0'
implementation 'com.google.code.gson:gson:2.10.1'

Secondary Issues (Should Fix)

5. DailyNotificationReceiver Export Status

Test App (Working):

<receiver
    android:name="com.timesafari.dailynotification.DailyNotificationReceiver"
    android:enabled="true"
    android:exported="false">  <!-- Note: false -->

TimeSafari (Broken):

<receiver
    android:name="com.timesafari.dailynotification.DailyNotificationReceiver"
    android:enabled="true"
    android:exported="true">  <!-- Note: true - potential security issue -->

The test app uses exported="false" because the plugin creates PendingIntents with explicit component targeting. Using exported="true" is unnecessary and a potential security concern.


6. Missing Network Security Config

Test App (Working):

<application
    android:networkSecurityConfig="@xml/network_security_config"
    ...>

TimeSafari (Broken):

<application>
    <!-- No networkSecurityConfig -->

This may affect HTTP (non-HTTPS) requests during development.


7. Missing Java Compile Options

Test App (Working):

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_17
        targetCompatibility JavaVersion.VERSION_17
    }
}

TimeSafari (Broken): No explicit compile options set.


Complete Fix Checklist

Step 1: Create Custom Application Class

Create file: android/app/src/main/java/app/timesafari/TimeSafariApplication.java

package app.timesafari;

import android.app.Application;
import android.content.Context;
import android.util.Log;
import com.timesafari.dailynotification.DailyNotificationPlugin;
import com.timesafari.dailynotification.NativeNotificationContentFetcher;

public class TimeSafariApplication extends Application {
    
    private static final String TAG = "TimeSafariApplication";
    
    @Override
    public void onCreate() {
        super.onCreate();
        
        Log.i(TAG, "Initializing TimeSafari notifications");
        
        // Register native fetcher with application context
        Context context = getApplicationContext();
        NativeNotificationContentFetcher fetcher = 
            new TimeSafariNativeFetcher(context);
        DailyNotificationPlugin.setNativeFetcher(fetcher);
        
        Log.i(TAG, "Native fetcher registered");
    }
}

Step 2: Create Native Fetcher Implementation

Create file: android/app/src/main/java/app/timesafari/TimeSafariNativeFetcher.java

package app.timesafari;

import android.content.Context;
import com.timesafari.dailynotification.NativeNotificationContentFetcher;
import com.timesafari.dailynotification.NotificationContent;

public class TimeSafariNativeFetcher implements NativeNotificationContentFetcher {
    
    private final Context context;
    
    public TimeSafariNativeFetcher(Context context) {
        this.context = context;
    }
    
    @Override
    public NotificationContent fetchContent(String scheduleId) {
        // TODO: Implement actual content fetching for TimeSafari
        // This should query the TimeSafari API for notification content
        return new NotificationContent(
            "timesafari_" + System.currentTimeMillis(),
            "TimeSafari Update",
            "Check your starred projects for updates!",
            System.currentTimeMillis(),
            null,
            System.currentTimeMillis()
        );
    }
}

Step 3: Update AndroidManifest.xml

<?xml version="1.0" encoding="utf-8" ?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application
        android:name=".TimeSafariApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:networkSecurityConfig="@xml/network_security_config"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        
        <!-- ... existing content ... -->
        
        <!-- Fix: Change exported to false -->
        <receiver
            android:name="com.timesafari.dailynotification.DailyNotificationReceiver"
            android:enabled="true"
            android:exported="false">
            <intent-filter>
                <action android:name="com.timesafari.daily.NOTIFICATION" />
            </intent-filter>
        </receiver>
        
        <!-- ... rest of receivers ... -->
        
    </application>

    <!-- Existing permissions -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    
    <!-- ADD these missing permissions -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
</manifest>

Step 4: Update build.gradle

Add to android/app/build.gradle:

android {
    // ... existing config ...
    
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_17
        targetCompatibility JavaVersion.VERSION_17
    }
}

dependencies {
    // ... existing dependencies ...
    
    // ADD these for notification plugin
    annotationProcessor project(':capacitor-android')
    implementation 'androidx.lifecycle:lifecycle-service:2.7.0'
    implementation 'com.google.code.gson:gson:2.10.1'
}

Step 5: Update capacitor.config.ts

Add DailyNotification configuration:

plugins: {
    // ... existing plugins ...
    
    DailyNotification: {
      debugMode: true,
      enableNotifications: true,
      timesafariConfig: {
        activeDid: '', // Will be set dynamically from user's DID
        endpoints: {
          projectsLastUpdated: 'https://api.endorser.ch/api/v2/report/plansLastUpdatedBetween'
        },
        starredProjectsConfig: {
          enabled: true,
          starredPlanHandleIds: [],
          fetchInterval: '0 8 * * *'
        }
      },
      networkConfig: {
        timeout: 30000,
        retryAttempts: 3,
        retryDelay: 1000
      },
      contentFetch: {
        enabled: true,
        schedule: '0 8 * * *',
        fetchLeadTimeMinutes: 5
      }
    }
}

Step 6: Rebuild

npx cap sync android
cd android && ./gradlew clean
cd .. && npx cap build android

Verification

After implementing fixes, verify:

  1. Check logs for Application initialization:

    adb logcat | grep -E "TimeSafariApplication|Native fetcher"
    
  2. Check alarm scheduling:

    adb shell dumpsys alarm | grep -i timesafari
    
  3. Test receiver manually:

    adb shell am broadcast -a com.timesafari.daily.NOTIFICATION \
      --es id "test_notification" \
      -n app.timesafari.app/com.timesafari.dailynotification.DailyNotificationReceiver
    
  4. Check notification permissions:

    adb shell dumpsys package app.timesafari.app | grep -A 5 "granted=true"
    

Summary of Critical Differences

Component Test App (Working) TimeSafari (Broken)
Custom Application class TestApplication.java None
Native fetcher registration In Application.onCreate() Not registered
DailyNotification config Full config in capacitor.config.ts Not configured
ACCESS_NETWORK_STATE Present Missing
FOREGROUND_SERVICE Present Missing
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS Present Missing
Gson dependency Present Missing
lifecycle-service dependency Present Missing
Capacitor annotation processor Present Missing

The most critical missing piece is the custom Application class with native fetcher registration. Without this, the plugin has no way to fetch notification content when the alarm fires.