# DailyNotification Android Test App — Implementation Plan (v1) **Author**: Matthew Raymer **Date**: 2025-10-24 **Version**: 1.0.0 ## Overview This document provides a structured implementation plan for improving the DailyNotification Android test app based on the improvement directive. The plan focuses on architecture, code organization, testing, and maintainability improvements. ## Table of Contents - [Implementation Phases](#implementation-phases) - [Architecture Improvements](#architecture-improvements) - [Code Organization](#code-organization) - [Testing Strategy](#testing-strategy) - [Security Hardening](#security-hardening) - [Performance & Reliability](#performance--reliability) - [Documentation Updates](#documentation-updates) - [Task Breakdown](#task-breakdown) - [Acceptance Criteria](#acceptance-criteria) ## Implementation Phases ### Phase 1: Foundation **Focus**: Core architecture improvements and status matrix - [ ] Create status matrix module - [ ] Add input schema validation - [ ] Centralize exact-alarm gate - [ ] Make BootReceiver idempotent - [ ] Introduce use-case classes ### Phase 2: Testing & Reliability **Focus**: Testing infrastructure and reliability improvements - [ ] Refactor test UI into modular scenarios - [ ] Add instrumentation tests - [ ] Implement error handling improvements - [ ] Add structured logging ### Phase 3: Security & Performance **Focus**: Security hardening and performance optimization - [ ] Implement security hardening - [ ] Add performance optimizations - [ ] Create diagnostics system - [ ] Update documentation ## Architecture Improvements ### 1. Split Monolithic Test UI **Current State**: 549-line `index.html` with all functionality **Target State**: Modular, maintainable UI components #### Implementation Plan ```javascript // New structure www/ ├── index.html (minimal entry point) ├── ui/ │ ├── status-matrix.js │ ├── test-buttons.js │ └── diagnostics.js ├── lib/ │ ├── bridge.ts │ ├── schema-validation.ts │ └── error-handling.ts ├── scenarios/ │ ├── plugin-testing.js │ ├── notification-testing.js │ ├── permission-testing.js │ └── channel-testing.js └── fixtures/ ├── test-data.json └── expected-results.json ``` #### Key Changes - **Modular Components**: Split UI into focused modules - **Scenario Runners**: Named test scenarios with fixtures - **TypeScript Bridge**: Typed interface to native plugin - **Schema Validation**: Input/output validation at boundary ### 2. Native Plugin Layering **Current State**: 34 supporting classes with mixed responsibilities **Target State**: Organized by capability with use-case classes #### Implementation Plan ```java // New organization com.timesafari.dailynotification/ ├── plugin/ │ └── DailyNotificationPlugin.java (thin facade) ├── usecases/ │ ├── ScheduleDaily.java │ ├── CheckPermissions.java │ ├── OpenSettings.java │ └── CollectRuntimeStatus.java ├── scheduling/ │ ├── ExactAlarmManager.java │ ├── DozeFallbackManager.java │ └── BootRescheduleManager.java ├── permissions/ │ ├── PermissionManager.java │ └── ChannelManager.java ├── storage/ │ ├── NotificationStorage.java │ └── MigrationManager.java └── workers/ ├── FetchWorker.java ├── ScheduleWorker.java └── MaintenanceWorker.java ``` #### Key Changes - **Use-Case Classes**: Business logic separated from plugin facade - **Capability Grouping**: Related functionality grouped together - **Service Locator**: Dependency injection for testability - **Thin Plugin Facade**: @PluginMethod methods delegate to use cases ### 3. Bridge Contract (Typed) **Current State**: Unvalidated JavaScript calls to native methods **Target State**: Typed, validated interface with schema enforcement #### Implementation Plan ```typescript // lib/bridge.ts interface DailyNotificationBridge { scheduleDailyNotification(request: ScheduleRequest): Promise; checkPermissions(): Promise; collectRuntimeStatus(): Promise; // ... other methods } interface ScheduleRequest { time: string; // HH:mm format title: string; // max 100 chars body: string; // max 500 chars sound: boolean; priority: 'low' | 'default' | 'high'; } interface ScheduleResponse { success: boolean; scheduledAt?: number; error?: { code: string; message: string; hint?: string; }; } ``` #### Key Changes - **TypeScript Interface**: Typed bridge contract - **Schema Validation**: Input validation before native calls - **Error Standardization**: Canonical error model - **Documentation**: API reference with schemas ## Code Organization ### 1. Status Matrix Module **Purpose**: Single source of truth for runtime capabilities **Implementation**: Centralized status collection and display #### Implementation Plan ```javascript // ui/status-matrix.js class StatusMatrix { async collectRuntimeStatus() { return { postNotificationsGranted: await this.checkPostNotifications(), exactAlarmGranted: await this.checkExactAlarm(), channelEnabled: await this.checkChannelStatus(), batteryOptimizationsIgnored: await this.checkBatteryOptimization(), canScheduleNow: await this.canScheduleNow(), lastError: await this.getLastError(), capabilities: await this.getCapabilityMatrix() }; } renderStatusMatrix(status) { // Render actionable status matrix with buttons } } ``` #### Key Features - **Single Source of Truth**: Centralized status collection - **Actionable UI**: Buttons to fix issues - **Real-time Updates**: Live status monitoring - **Diagnostics Export**: JSON export for debugging ### 2. Use-Case Classes **Purpose**: Business logic separation from plugin facade **Implementation**: Thin @PluginMethod methods delegate to use cases #### Implementation Plan ```java // usecases/ScheduleDaily.java public class ScheduleDaily { private final ExactAlarmManager exactAlarmManager; private final DozeFallbackManager dozeFallbackManager; private final NotificationStorage storage; public ScheduleResponse execute(ScheduleRequest request) { // Validate input ValidationResult validation = validateRequest(request); if (!validation.isValid()) { return ScheduleResponse.error(validation.getError()); } // Check prerequisites if (!canScheduleNow()) { return ScheduleResponse.error("E_SCHEDULE_BLOCKED", "Cannot schedule now"); } // Execute scheduling logic return doSchedule(request); } } ``` #### Key Features - **Business Logic**: Core functionality separated from plugin - **Dependency Injection**: Testable with mock dependencies - **Error Handling**: Standardized error responses - **Validation**: Input validation before processing ### 3. Service Locator **Purpose**: Dependency injection for testability **Implementation**: Lightweight DI container #### Implementation Plan ```java // core/ServiceLocator.java public class ServiceLocator { private static final Map, Object> services = new HashMap<>(); public static void register(Class type, T instance) { services.put(type, instance); } public static T get(Class type) { return (T) services.get(type); } public static void initialize(Context context) { // Register core services register(WorkManager.class, WorkManager.getInstance(context)); register(AlarmManager.class, (AlarmManager) context.getSystemService(Context.ALARM_SERVICE)); register(NotificationStorage.class, new NotificationStorage(context)); // ... other services } } ``` #### Key Features - **Dependency Injection**: Testable service wiring - **Service Registration**: Centralized service management - **Context Integration**: Android context integration - **Testing Support**: Easy mocking for tests ## Testing Strategy ### 1. Scenario-Based Testing **Current State**: 12 test buttons with mixed functionality **Target State**: Named scenarios with expected results #### Implementation Plan ```javascript // scenarios/notification-testing.js class NotificationScenarios { async testImmediateNotification() { const scenario = { name: 'Immediate Notification Test', description: 'Test immediate notification display', expectedResult: 'SUCCESS', timeout: 5000 }; try { const result = await window.Capacitor.Plugins.DailyNotification.scheduleDailyNotification({ time: this.getTimeIn5Minutes(), title: 'Test Notification', body: 'This is a test notification', sound: true, priority: 'high' }); return this.validateResult(result, scenario); } catch (error) { return this.handleError(error, scenario); } } async testScheduledNotification() { // Similar pattern for scheduled notifications } } ``` #### Key Features - **Named Scenarios**: Clear test descriptions - **Expected Results**: Machine-readable outcomes - **Error Handling**: Standardized error responses - **Timeout Management**: Configurable timeouts ### 2. Instrumentation Tests **Purpose**: Automated testing of critical paths **Implementation**: Android instrumentation tests #### Implementation Plan ```java // androidTest/NotificationInstrumentationTest.java @RunWith(AndroidJUnit4.class) public class NotificationInstrumentationTest { @Test public void testChannelDisabledPath() { // Test channel disabled scenario // Verify error handling // Check status matrix updates } @Test public void testExactAlarmDeniedPath() { // Test exact alarm denied scenario // Verify fallback behavior // Check graceful degradation } @Test public void testBootReschedule() { // Test boot reschedule functionality // Verify idempotent behavior // Check migration handling } } ``` #### Key Features - **Critical Path Testing**: Test important user journeys - **Error Scenario Testing**: Test error handling paths - **Integration Testing**: Test full plugin integration - **Automated Validation**: CI/CD integration ### 3. Unit Tests **Purpose**: Test individual components **Implementation**: JUnit tests for use-case classes #### Implementation Plan ```java // test/ScheduleDailyTest.java @RunWith(MockitoJUnitRunner.class) public class ScheduleDailyTest { @Mock private ExactAlarmManager exactAlarmManager; @Mock private DozeFallbackManager dozeFallbackManager; @Mock private NotificationStorage storage; private ScheduleDaily scheduleDaily; @Before public void setUp() { scheduleDaily = new ScheduleDaily(exactAlarmManager, dozeFallbackManager, storage); } @Test public void testScheduleWithValidInput() { // Test successful scheduling } @Test public void testScheduleWithInvalidInput() { // Test input validation } @Test public void testScheduleWhenBlocked() { // Test blocked scheduling } } ``` #### Key Features - **Component Testing**: Test individual use cases - **Mock Dependencies**: Isolated testing - **Edge Case Testing**: Test error conditions - **Fast Execution**: Quick feedback loop ## Security Hardening ### 1. Bridge Input Validation **Purpose**: Validate all inputs before native processing **Implementation**: Schema validation at JavaScript boundary #### Implementation Plan ```typescript // lib/schema-validation.ts export class SchemaValidator { validateScheduleRequest(request: any): ValidationResult { const errors: string[] = []; // Validate time format if (!this.isValidTimeFormat(request.time)) { errors.push('Time must be in HH:mm format'); } // Validate title length if (request.title && request.title.length > 100) { errors.push('Title must be 100 characters or less'); } // Validate body length if (request.body && request.body.length > 500) { errors.push('Body must be 500 characters or less'); } // Validate boolean fields if (typeof request.sound !== 'boolean') { errors.push('Sound must be a boolean'); } // Validate priority if (!['low', 'default', 'high'].includes(request.priority)) { errors.push('Priority must be low, default, or high'); } return { isValid: errors.length === 0, errors }; } } ``` #### Key Features - **Input Validation**: Validate all inputs - **Length Limits**: Prevent oversized inputs - **Type Validation**: Ensure correct types - **Format Validation**: Validate time formats ### 2. Network Security **Purpose**: Secure network communication **Implementation**: HTTPS enforcement and timeout limits #### Implementation Plan ```java // network/SecureNetworkClient.java public class SecureNetworkClient { private static final int TIMEOUT_SECONDS = 30; private static final int MAX_RESPONSE_SIZE = 1024 * 1024; // 1MB public String fetchContent(String url) throws NetworkException { // Enforce HTTPS if (!url.startsWith("https://")) { throw new NetworkException("E_INSECURE_URL", "Only HTTPS URLs allowed"); } // Set timeouts HttpURLConnection connection = createConnection(url); connection.setConnectTimeout(TIMEOUT_SECONDS * 1000); connection.setReadTimeout(TIMEOUT_SECONDS * 1000); // Limit response size long contentLength = connection.getContentLengthLong(); if (contentLength > MAX_RESPONSE_SIZE) { throw new NetworkException("E_RESPONSE_TOO_LARGE", "Response too large"); } return readResponse(connection); } } ``` #### Key Features - **HTTPS Enforcement**: Only secure connections - **Timeout Limits**: Prevent hanging requests - **Size Limits**: Prevent memory exhaustion - **Error Handling**: Standardized network errors ### 3. Intent Filter Security **Purpose**: Secure intent handling **Implementation**: Review and secure intent filters #### Implementation Plan ```xml ``` #### Key Features - **Export Control**: Minimize exported components - **Permission Requirements**: Require permissions for exported components - **Intent Validation**: Validate incoming intents - **Security Review**: Regular security audits ## Performance & Reliability ### 1. WebView Cold-Start Optimization **Purpose**: Minimize WebView initialization time **Implementation**: Lazy loading and preloading strategies #### Implementation Plan ```javascript // lib/lazy-loader.js class LazyLoader { constructor() { this.loadedModules = new Set(); this.loadingPromises = new Map(); } async loadModule(moduleName) { if (this.loadedModules.has(moduleName)) { return; } if (this.loadingPromises.has(moduleName)) { return this.loadingPromises.get(moduleName); } const loadPromise = this.doLoadModule(moduleName); this.loadingPromises.set(moduleName, loadPromise); try { await loadPromise; this.loadedModules.add(moduleName); } finally { this.loadingPromises.delete(moduleName); } } async doLoadModule(moduleName) { // Load module dynamically const module = await import(`./modules/${moduleName}.js`); return module; } } ``` #### Key Features - **Lazy Loading**: Load modules on demand - **Preloading**: Preload critical modules - **Caching**: Cache loaded modules - **Performance Monitoring**: Track load times ### 2. Worker Backoff Strategy **Purpose**: Implement exponential backoff with jitter **Implementation**: Smart retry logic for background work #### Implementation Plan ```java // workers/BackoffStrategy.java public class BackoffStrategy { private static final long BASE_DELAY_MS = 1000; private static final long MAX_DELAY_MS = 300000; // 5 minutes private static final double BACKOFF_MULTIPLIER = 2.0; private static final double JITTER_FACTOR = 0.1; public long calculateDelay(int attemptNumber) { long delay = (long) (BASE_DELAY_MS * Math.pow(BACKOFF_MULTIPLIER, attemptNumber)); delay = Math.min(delay, MAX_DELAY_MS); // Add jitter to prevent thundering herd double jitter = delay * JITTER_FACTOR * (Math.random() - 0.5); delay += (long) jitter; return Math.max(delay, 0); } } ``` #### Key Features - **Exponential Backoff**: Increasing delay between retries - **Jitter**: Random variation to prevent thundering herd - **Maximum Delay**: Cap on maximum delay - **Configurable**: Adjustable parameters ### 3. Database Hygiene **Purpose**: Maintain database performance **Implementation**: Indexing and periodic cleanup #### Implementation Plan ```java // storage/DatabaseMaintenance.java public class DatabaseMaintenance { private final NotificationStorage storage; public void performMaintenance() { // Clean up expired notifications cleanupExpiredNotifications(); // Clean up delivered notifications older than 7 days cleanupDeliveredNotifications(); // Rebuild indexes if needed rebuildIndexesIfNeeded(); // Vacuum database vacuumDatabase(); } private void cleanupExpiredNotifications() { long cutoffTime = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7); storage.deleteExpiredNotifications(cutoffTime); } } ``` #### Key Features - **Periodic Cleanup**: Regular maintenance tasks - **Index Optimization**: Maintain database performance - **Data Retention**: Configurable retention policies - **Performance Monitoring**: Track maintenance impact ## Documentation Updates ### 1. "How it Works" Documentation **Purpose**: Explain the system architecture and flow **Implementation**: Comprehensive architecture documentation #### Implementation Plan ```markdown # How It Works ## System Architecture ### App Launch Flow 1. Android launches MainActivity 2. Capacitor initializes WebView 3. Plugin discovery loads DailyNotificationPlugin 4. JavaScript bridge establishes communication 5. Web interface loads and initializes ### Notification Scheduling Flow 1. User triggers schedule action 2. JavaScript validates input schema 3. Bridge calls native plugin method 4. Use-case class processes request 5. ExactAlarmManager schedules notification 6. WorkManager handles background processing 7. NotificationReceiver displays notification ### Error Handling Flow 1. Error occurs in native code 2. Error mapped to canonical error code 3. Error returned through bridge 4. JavaScript displays user-friendly message 5. Status matrix updated with error state ``` #### Key Features - **Architecture Diagrams**: Visual system overview - **Flow Documentation**: Step-by-step processes - **Error Handling**: Error flow documentation - **Troubleshooting**: Common issues and solutions ### 2. Runbooks **Purpose**: Operational procedures for common issues **Implementation**: Step-by-step troubleshooting guides #### Implementation Plan ```markdown # Runbooks ## No Notifications Firing ### Checks 1. Check status matrix for red indicators 2. Verify POST_NOTIFICATIONS permission 3. Check notification channel status 4. Verify exact alarm permission 5. Check battery optimization settings ### Fix 1. Request missing permissions 2. Enable notification channel 3. Enable exact alarms in settings 4. Disable battery optimization ### Verify 1. Run comprehensive status check 2. Test immediate notification 3. Verify scheduled notification 4. Check logs for errors ``` #### Key Features - **Step-by-Step Procedures**: Clear troubleshooting steps - **Check-Fix-Verify**: Structured problem resolution - **Common Issues**: Frequent problems and solutions - **Escalation Paths**: When to escalate issues ### 3. API Reference **Purpose**: Complete API documentation **Implementation**: Method-by-method documentation #### Implementation Plan ```markdown # API Reference ## scheduleDailyNotification ### Request Schema ```typescript interface ScheduleRequest { time: string; // HH:mm format title: string; // max 100 chars body: string; // max 500 chars sound: boolean; // play sound priority: 'low' | 'default' | 'high'; } ``` ### Response Schema ```typescript interface ScheduleResponse { success: boolean; scheduledAt?: number; error?: { code: string; message: string; hint?: string; }; } ``` ### Error Codes - `E_INVALID_TIME`: Time format invalid - `E_TITLE_TOO_LONG`: Title exceeds 100 characters - `E_BODY_TOO_LONG`: Body exceeds 500 characters - `E_PERMISSION_DENIED`: Required permission not granted - `E_CHANNEL_DISABLED`: Notification channel disabled ``` #### Key Features - **Complete API Coverage**: All methods documented - **Schema Definitions**: Request/response schemas - **Error Codes**: Complete error code reference - **Examples**: Usage examples for each method ## Task Breakdown ### Phase 1: Foundation - [ ] **Status Matrix Module** - Implement `collectRuntimeStatus()` function - Create status matrix UI component - Add "Copy Diagnostics" functionality - [ ] **Input Schema Validation** - Create TypeScript schema definitions - Implement validation at bridge boundary - Add error handling for validation failures - [ ] **Exact-Alarm Gate** - Create `ExactAlarmManager` class - Implement graceful fallback logic - Update status matrix to show exact alarm status - [ ] **BootReceiver Idempotent** - Add migration fence for old schedules - Implement idempotent rescheduling - Add logging for boot recovery - [ ] **Use-Case Classes** - Create `ScheduleDaily` use case - Create `CheckPermissions` use case - Refactor plugin methods to use cases ### Phase 2: Testing & Reliability - [ ] **Test UI Refactoring** - Split 549-line HTML into modules - Create scenario runner framework - Implement named test scenarios - [ ] **Instrumentation Tests** - Test channel disabled path - Test exact alarm denied path - Test boot reschedule functionality - [ ] **Structured Logging** - Add event IDs for all operations - Implement progress logging - Create log export functionality ### Phase 3: Security & Performance - [ ] **Security Hardening** - Add network security measures - Review intent filter security - Implement channel policy enforcement - [ ] **Performance Optimizations** - Implement lazy loading for UI modules - Add worker backoff strategy - Optimize database operations - [ ] **Diagnostics System** - Implement comprehensive diagnostics - Add performance monitoring - Create health check endpoints - [ ] **Documentation Updates** - Create "How it Works" documentation - Write runbooks for common issues - Complete API reference ## Acceptance Criteria ### Status Matrix - [ ] Reports all relevant runtime capabilities - [ ] Shows live channel state - [ ] Provides actionable buttons for issues - [ ] Exports diagnostics as JSON ### Error Handling - [ ] All @PluginMethod calls validate inputs - [ ] Returns stable error codes with hints - [ ] Maps native exceptions to canonical errors - [ ] Provides user-friendly error messages ### Reliability - [ ] Reboot scenarios reliably deliver notifications - [ ] Doze scenarios degrade gracefully - [ ] Clear logs explain system behavior - [ ] User-visible reasoning for failures ### Testing - [ ] Test UI modularized into scenarios - [ ] At least 2 scenarios run as automated tests - [ ] Instrumentation tests cover critical paths - [ ] Unit tests cover use-case classes ### Documentation - [ ] "How it Works" page with lifecycle diagrams - [ ] Runbooks for common issues - [ ] Complete API reference with schemas - [ ] Error codes table with explanations ## Success Metrics ### Code Quality - **Maintainability**: Reduced complexity in test UI - **Testability**: Use-case classes with dependency injection - **Reliability**: Improved error handling and logging - **Security**: Input validation and network security ### User Experience - **Clarity**: Clear status matrix with actionable items - **Reliability**: Consistent notification delivery - **Debugging**: Comprehensive diagnostics and logging - **Performance**: Faster app startup and response ### Developer Experience - **Documentation**: Complete API reference and runbooks - **Testing**: Automated test coverage for critical paths - **Debugging**: Structured logging and diagnostics - **Maintenance**: Modular architecture for easy updates ## Conclusion This implementation plan provides a structured approach to improving the DailyNotification Android test app. The plan focuses on architecture improvements, code organization, testing, and maintainability while maintaining the existing functionality. The phased approach allows for incremental improvements while ensuring each phase delivers value. The acceptance criteria provide clear success metrics for each improvement area. By following this plan, the test app will become more maintainable, reliable, and user-friendly while providing a solid foundation for future enhancements.