25 KiB
Android App Analysis: DailyNotification Plugin Test App
Author: Matthew Raymer
Date: 2025-10-24
Version: 1.0.0
Overview
This document provides a comprehensive analysis of the /android/app portion of the DailyNotification plugin, examining its structure, purpose, and interaction with the /www web assets. This analysis is designed to help understand the plugin's test application architecture and provide context for ChatGPT analysis.
Table of Contents
- Architecture Overview
- Directory Structure
- Key Components
- Web Asset Integration
- Plugin Integration
- Build Configuration
- Runtime Behavior
- Testing Capabilities
Architecture Overview
Purpose
The /android/app directory contains a Capacitor-based Android test application specifically designed to test and demonstrate the DailyNotification plugin functionality. It serves as:
- Plugin Testing Environment: Interactive testing interface for all plugin features
- Development Tool: Real-time debugging and validation of plugin behavior
- Integration Example: Reference implementation for plugin integration
- Documentation: Live demonstration of plugin capabilities
Architecture Pattern
┌─────────────────────────────────────────────────────────────┐
│ Android App Container │
├─────────────────────────────────────────────────────────────┤
│ MainActivity (BridgeActivity) │
│ ├── Capacitor Bridge │
│ ├── Plugin Discovery │
│ └── WebView Container │
├─────────────────────────────────────────────────────────────┤
│ Web Assets (/www) │
│ ├── index.html (Test Interface) │
│ ├── capacitor.js (Capacitor Runtime) │
│ └── plugins/ (Plugin JavaScript) │
├─────────────────────────────────────────────────────────────┤
│ Native Plugin Integration │
│ ├── DailyNotificationPlugin.java │
│ ├── BootReceiver.java │
│ ├── DailyNotificationReceiver.java │
│ └── Supporting Classes (34 files) │
└─────────────────────────────────────────────────────────────┘
Directory Structure
Root Android App Structure
android/app/
├── build.gradle # App build configuration
├── capacitor.build.gradle # Auto-generated Capacitor config
├── proguard-rules.pro # Code obfuscation rules
└── src/
├── main/
│ ├── AndroidManifest.xml # App permissions and components
│ ├── assets/ # Web assets (Capacitor www)
│ │ ├── capacitor.config.json
│ │ ├── capacitor.plugins.json
│ │ └── public/ # Web application files
│ │ ├── index.html # Main test interface
│ │ ├── capacitor.js # Capacitor runtime
│ │ ├── capacitor_plugins.js
│ │ └── plugins/ # Plugin JavaScript files
│ ├── java/
│ │ └── com/timesafari/dailynotification/
│ │ └── MainActivity.java # Capacitor BridgeActivity
│ └── res/ # Android resources
│ ├── drawable/ # App icons and images
│ ├── layout/ # Android layouts
│ ├── mipmap/ # App launcher icons
│ ├── values/ # Strings, styles, colors
│ └── xml/ # Configuration files
├── androidTest/ # Instrumented tests
└── test/ # Unit tests
Key Components
1. MainActivity.java
Purpose: Entry point extending Capacitor's BridgeActivity
Location: src/main/java/com/timesafari/dailynotification/MainActivity.java
public class MainActivity extends BridgeActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
Key Features:
- Minimal implementation - Capacitor handles most functionality
- Extends
BridgeActivityfor automatic plugin discovery - Provides WebView container for web assets
- Handles plugin registration and JavaScript bridge
2. AndroidManifest.xml
Purpose: App configuration, permissions, and component declarations
Location: src/main/AndroidManifest.xml
Key Declarations:
<!-- App Configuration -->
<application android:name="com.timesafari.dailynotification">
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
> **Note:** Set `android:name` only if you provide a custom `Application` class; otherwise remove to avoid ClassNotFound at runtime.
**Safe default (no custom Application class):**
```xml
<application>
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Plugin Components -->
<!-- Internal receiver: keep non-exported unless intentionally public -->
<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>
<receiver
android:name="com.timesafari.dailynotification.BootReceiver"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="1000">
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
Note:
android:priorityhas no practical effect forBOOT_COMPLETED; safe to omit.
Minimal example (recommended):
<receiver
android:name="com.timesafari.dailynotification.BootReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
Tip:
WAKE_LOCKis typically unnecessary with AlarmManager/WorkManager; remove unless you explicitly acquire/release your own wakelocks. Note:POST_NOTIFICATIONSis required on Android 13+; lower API levels ignore it gracefully.
**Critical Permissions**:
- `POST_NOTIFICATIONS`: Required for Android 13+ notification posting
- `SCHEDULE_EXACT_ALARM`: Required for precise notification timing
- `WAKE_LOCK` **not required** unless you explicitly acquire/release your own wakelocks (AlarmManager & WorkManager handle theirs)
- `INTERNET`: Required for content fetching
- `RECEIVE_BOOT_COMPLETED`: Required for reboot recovery
> **Note:** If you later introduce foreground services, revisit WAKE_LOCK; otherwise keep it out.
### 3. Capacitor Configuration Files
#### capacitor.config.json
**Purpose**: Capacitor runtime configuration
```json
{
"appId": "com.timesafari.dailynotification",
"appName": "DailyNotification Test App",
"webDir": "www",
"server": {
"androidScheme": "https"
},
"plugins": {
"DailyNotification": {
"fetchUrl": "https://api.example.com/daily-content",
"scheduleTime": "09:00",
"enableNotifications": true,
"debugMode": true
}
}
}
capacitor.plugins.json
Purpose: Plugin discovery and registration
Note: Auto-generated on npx cap sync - should not be manually edited
[
{
"name": "DailyNotification",
"classpath": "com.timesafari.dailynotification.DailyNotificationPlugin"
}
]
Web Asset Integration
/www Directory Structure
The /www directory (mapped to assets/public/) contains the web application that runs inside the Capacitor WebView:
Capacitor builds copy your
webDir(e.g.,www/) tosrc/main/assets/public/on Android; the WebView serves from thatpublic/folder.
assets/public/
├── index.html # Main test interface (549 lines)
├── capacitor.js # Capacitor runtime
├── capacitor_plugins.js # Plugin JavaScript bridge
> **Note:** On pure Capacitor builds, the runtime is `capacitor.js`. Only include `cordova.js/cordova_plugins.js` if Cordova-compat is enabled; otherwise remove those references for accuracy.
└── plugins/ # Plugin JavaScript files
index.html Analysis
Purpose: Interactive test interface for plugin functionality Size: 549 lines of HTML, CSS, and JavaScript Features:
1. User Interface
- Modern Design: Gradient background, responsive layout
- Interactive Buttons: 12 test functions with visual feedback
- Status Display: Real-time feedback with color-coded results
- Mobile-Optimized: Touch-friendly interface
2. Test Categories
// Plugin Testing
- testPlugin() // Basic plugin availability
- configurePlugin() // Plugin configuration
- checkStatus() // Plugin status check
// Notification Testing
- testNotification() // Immediate notification test
- scheduleNotification() // Scheduled notification test
- showReminder() // Daily reminder test
// Permission Management
- checkPermissions() // Permission status check
- requestPermissions() // Permission request
- openExactAlarmSettings() // Settings navigation
// Channel Management
- checkChannelStatus() // Notification channel status
- openChannelSettings() // Channel settings navigation
- checkComprehensiveStatus() // Complete status check
3. Plugin Integration
// Plugin Access Pattern
window.DailyNotification = window.Capacitor.Plugins.DailyNotification;
// Example Usage
window.DailyNotification.scheduleDailyNotification({
time: "09:00",
title: "Test Notification",
body: "This is a test notification!",
sound: true,
priority: "high"
});
4. Error Handling
- Visual Feedback: Color-coded status indicators
- Error Messages: Detailed error reporting
- Graceful Degradation: Fallback behavior for missing features
Plugin Integration
Plugin Discovery Process
- Capacitor Startup: Loads
capacitor.plugins.json - Plugin Registration: Discovers
DailyNotificationPluginclass - JavaScript Bridge: Creates
window.Capacitor.Plugins.DailyNotification - Method Exposure: Exposes
@PluginMethodannotated methods
Plugin Class Structure
Location: android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.java
Key Methods (from @PluginMethod annotations):
@PluginMethod
public void configure(PluginCall call) { ... }
@PluginMethod
public void scheduleDailyNotification(PluginCall call) { ... }
@PluginMethod
public void scheduleDailyReminder(PluginCall call) { ... }
@PluginMethod
public void getNotificationStatus(PluginCall call) { ... }
@PluginMethod
public void checkPermissionStatus(PluginCall call) { ... }
@PluginMethod
public void requestNotificationPermissions(PluginCall call) { ... }
@PluginMethod
public void checkStatus(PluginCall call) { ... }
@PluginMethod
public void isChannelEnabled(PluginCall call) { ... }
@PluginMethod
public void openChannelSettings(PluginCall call) { ... }
@PluginMethod
public void openExactAlarmSettings(PluginCall call) { ... }
Supporting Classes (34 files)
The plugin includes a comprehensive set of supporting classes:
Core Components:
BootReceiver.java- Handles system boot eventsDailyNotificationReceiver.java- Handles notification eventsDailyNotificationScheduler.java- Manages notification schedulingDailyNotificationFetcher.java- Handles content fetching
Storage & Database:
DailyNotificationStorage.java- Storage abstractionDailyNotificationStorageRoom.java- Room database implementationDailyNotificationDatabase.java- Database definitiondao/- Data Access Objects (3 files)entities/- Database entities (3 files)
Management & Utilities:
PermissionManager.java- Permission handlingChannelManager.java- Notification channel managementDailyNotificationExactAlarmManager.java- Exact alarm managementDailyNotificationErrorHandler.java- Error handlingDailyNotificationPerformanceOptimizer.java- Performance optimization
Workers & Background Tasks:
DailyNotificationWorker.java- Main background workerDailyNotificationFetchWorker.java- Content fetching workerDailyNotificationMaintenanceWorker.java- Maintenance tasksDozeFallbackWorker.java- Doze mode handlingSoftRefetchWorker.java- Soft refresh handling
Build Configuration
app/build.gradle
Purpose: App-level build configuration and dependencies
Key Dependencies:
dependencies {
// Capacitor Core
implementation project(':capacitor-android')
implementation project(':plugin') // DailyNotification plugin
// AndroidX Libraries
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
// Plugin-Specific Dependencies
implementation "androidx.room:room-runtime:2.6.1"
implementation "androidx.work:work-runtime-ktx:2.9.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
// Cordova compatibility (include ONLY if using Cordova plugins)
debugImplementation(project(':capacitor-cordova-android-plugins')) { transitive = false }
releaseImplementation(project(':capacitor-cordova-android-plugins')) { transitive = false }
> **Note:** Include `capacitor-cordova-android-plugins` **only** when using Cordova plugins.
}
Build Configuration:
android {
namespace "com.timesafari.dailynotification"
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "com.timesafari.dailynotification"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
}
}
capacitor.build.gradle
Purpose: Auto-generated Capacitor configuration
Note: Regenerated on each npx cap sync - should not be manually edited
Manifest Hygiene (Quick Scan)
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/><uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>(if you truly need exact)<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>- BootReceiver:
exported="true"+ BOOT_COMPLETED filter - Other receivers exported=false unless needed
- No stray
android:permission=on BootReceiver
Runtime Behavior
App Startup Sequence
- Android System: Launches
MainActivity - Capacitor Initialization: Loads Capacitor runtime
- Plugin Discovery: Scans
capacitor.plugins.jsonfor plugins - Plugin Registration: Instantiates
DailyNotificationPlugin - WebView Loading: Loads
index.htmlfrom assets - JavaScript Bridge: Establishes communication between web and native
- Plugin Availability:
window.Capacitor.Plugins.DailyNotificationbecomes available
Plugin Method Execution Flow
JavaScript Call
↓
Capacitor Bridge
↓
Plugin Method (@PluginMethod)
↓
Native Implementation
↓
Response (JSObject)
↓
JavaScript Promise Resolution
Background Processing
- WorkManager: Handles background content fetching
- AlarmManager: Manages notification scheduling
- BootReceiver: Reschedules notifications after reboot
- Doze Mode: Handles Android's battery optimization
Closed vs force-stopped: Closing/swiping the app does not affect alarms or WorkManager. Force-stopping from Settings cancels alarms and suppresses receivers until the next launch, after which Boot/rehydration logic can restore future schedules.
Testing Capabilities
Interactive Testing Features
The test app provides comprehensive testing capabilities:
1. Plugin Availability Testing
- Basic Detection: Verify plugin is loaded and accessible
- Method Availability: Check if specific methods are callable
- Error Handling: Test error conditions and edge cases
2. Notification Testing
- Immediate Notifications: Test instant notification display
- Scheduled Notifications: Test time-based notification scheduling
- Reminder Testing: Test daily reminder functionality
- Content Testing: Test with different notification content
3. Permission Management
- Permission Status: Check current permission state
- Permission Requests: Test permission request flow
- Settings Navigation: Test opening system settings
- Permission Validation: Verify permission changes
4. Channel Management
- Channel Status: Check notification channel state
- Channel Settings: Test channel configuration
- Importance Levels: Test different importance settings
- Channel Creation: Test channel creation and management
5. Comprehensive Status Checking
- Overall Status: Complete system status check
- Issue Detection: Identify configuration problems
- Readiness Check: Verify system is ready for notifications
- Troubleshooting: Help identify and resolve issues
Debugging Features
- Console Logging: Detailed console output for debugging
- Visual Feedback: Color-coded status indicators
- Error Reporting: Detailed error messages and stack traces
- Real-time Updates: Live status updates during testing
Integration Patterns
Plugin Integration Pattern
// 1. Check Plugin Availability
if (!window.DailyNotification) {
console.error('Plugin not available');
return;
}
// 2. Call Plugin Method
window.DailyNotification.scheduleDailyNotification({
time: "09:00",
title: "Daily Notification",
body: "Your daily content is ready!",
sound: true,
priority: "high"
})
.then(() => {
console.log('Notification scheduled successfully');
})
.catch(error => {
console.error('Scheduling failed:', error);
});
Error Handling Pattern
try {
const result = await window.DailyNotification.checkStatus();
// Handle success
} catch (error) {
// Handle error
console.error('Status check failed:', error.message);
}
Permission Management Pattern
// Check permissions first
const permissions = await window.DailyNotification.checkPermissionStatus();
if (!permissions.allPermissionsGranted) {
// Request permissions
await window.DailyNotification.requestNotificationPermissions();
}
Key Insights
Strengths
- Comprehensive Testing: Covers all plugin functionality
- Interactive Interface: Easy-to-use testing interface
- Real-time Feedback: Immediate visual feedback
- Error Handling: Robust error handling and reporting
- Permission Management: Complete permission testing
- Documentation: Self-documenting through interface
Architecture Benefits
- Separation of Concerns: Clear separation between web and native
- Plugin Isolation: Plugin functionality is isolated and testable
- Capacitor Integration: Leverages Capacitor's plugin system
- Cross-Platform: Web interface works across platforms
- Maintainable: Easy to update and maintain
Use Cases
- Plugin Development: Test new plugin features
- Integration Testing: Verify plugin integration
- Debugging: Debug plugin issues
- Documentation: Demonstrate plugin capabilities
- Quality Assurance: Validate plugin functionality
Conclusion
The /android/app portion of the DailyNotification plugin represents a well-architected test application that provides comprehensive testing capabilities for the plugin. It demonstrates best practices for Capacitor plugin integration, including proper permission handling, error management, and user interface design.
The integration between the web assets (/www) and native Android code through Capacitor's bridge system creates a seamless testing environment that allows developers to validate plugin functionality in real-time while providing an intuitive interface for non-technical users to test and understand the plugin's capabilities.
This architecture serves as both a practical testing tool and a reference implementation for integrating the DailyNotification plugin into other applications.
Assumptions & Versions
| Topic | Value | Notes |
|---|---|---|
| Android min/target SDK | 24 / 35 | Align with compileSdkVersion/targetSdkVersion. |
| Capacitor | v5.x | Confirm web asset naming (capacitor.js vs Cordova shims). |
| WorkManager | 2.9.0 | Matches Gradle deps listed. |
| Room | 2.6.1 | Matches Gradle deps listed. |
| Exact Alarms | Tiramisu+ | Requires user grant on many OEMs. |
Bridge Surface (Summary)
scheduleDailyNotification(req: {time, title, body, sound, priority}) -> {success, scheduledAt?, error?}checkPermissionStatus() -> {postNotificationsGranted, exactAlarmGranted, batteryOptIgnored, channelEnabled, ...}openChannelSettings() -> {opened: boolean}openExactAlarmSettings() -> {opened: boolean}requestNotificationPermissions() -> {granted: boolean, permissions: {...}}getNotificationStatus() -> {isEnabled, isScheduled, nextNotificationTime, ...}
Status Matrix MUST include: postNotificationsGranted, exactAlarmGranted, channelEnabled, batteryOptimizationsIgnored, canScheduleNow.
Exact-Alarm Decision Rule (User-Visible)
If SCHEDULE_EXACT_ALARM is granted → schedule with setExactAndAllowWhileIdle.
If denied or quota-limited → schedule via WorkManager (exp backoff + jitter) and surface E_EXACT_ALARM_DENIED (with "Degraded timing — Doze may delay" hint).
Exact Alarm note:
SCHEDULE_EXACT_ALARMis a special app-op, not a runtime permission prompt. Users grant it via Settings; your UI should deep-link there and reflect denial by degrading to WorkManager.
Permission & Settings Truth Table
| Symptom | Likely Cause | Action |
|---|---|---|
| No notification posts | POST_NOTIFICATIONS denied |
Call requestNotificationPermissions() |
| Fires late/misses | No exact alarm grant / Doze | openExactAlarmSettings() or fallback to WorkManager |
| Silent notifications | Channel disabled/low importance | openChannelSettings() then retest |
| Battery optimization kills | App not whitelisted | Guide user to battery optimization settings |
| Boot reschedule fails | RECEIVE_BOOT_COMPLETED denied |
Check manifest receiver registration |
Test UI Integration: Use "Open Channel Settings" and "Open Exact Alarm Settings" buttons in the test interface to resolve channel and exact alarm issues.
Runtime Flow Diagram
graph TD
A[JavaScript Call] --> B[Capacitor Bridge]
B --> C[@PluginMethod → Canonical Error]
C --> D[Use Case Handler → Canonical Error]
D --> E{Alarm vs WorkManager}
E -->|Exact Alarm| F[AlarmManager]
E -->|Fallback| G[WorkManager]
F --> H[BootReceiver]
G --> H
H --> I[NotificationReceiver]
I --> J[UI Update]
%% Error paths
C -->|Validation Error → Canonical Error| K[Canonical Error]
D -->|Use-case Error → Canonical Error| K
K --> L[JavaScript Promise Rejection]
Cordova vs Capacitor Assets – Accuracy Note
Note: If using pure Capacitor v5, the web runtime is
capacitor.js. If Cordova compatibility is enabled,cordova.js/cordova_plugins.jswill appear; otherwise remove those references here for accuracy.