You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

41 KiB

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

Phase 1: Foundation

Focus: Core architecture improvements and status matrix

  • Create status matrix module RESOLVED: Modular architecture already implemented
  • Add input schema validation
  • Centralize exact-alarm gate RESOLVED: DailyNotificationExactAlarmManager exists
  • Make BootReceiver idempotent RESOLVED: DailyNotificationRebootRecoveryManager exists
  • 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 RESOLVED: DailyNotificationErrorHandler exists
  • Add structured logging RESOLVED: Event IDs already implemented

Phase 3: Security & Performance

Focus: Security hardening and performance optimization

  • Implement security hardening RESOLVED: PermissionManager, HTTPS enforcement, input validation exist
  • Add performance optimizations RESOLVED: DailyNotificationPerformanceOptimizer, rolling window, TTL enforcer exist
  • Create diagnostics system RESOLVED: Comprehensive error handling and metrics exist
  • 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

// 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

// 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

// lib/bridge.ts
interface DailyNotificationBridge {
  scheduleDailyNotification(request: ScheduleRequest): Promise<ScheduleResponse>;
  checkPermissions(): Promise<PermissionStatus>;
  collectRuntimeStatus(): Promise<RuntimeStatus>;
  // ... 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

// 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

// 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

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).

3. Service Locator

Purpose: Dependency injection for testability Implementation: Lightweight DI container

Implementation Plan

// core/ServiceLocator.java
public class ServiceLocator {
    private static final Map<Class<?>, Object> services = new HashMap<>();

    public static <T> void register(Class<T> type, T instance) {
        services.put(type, instance);
    }

    public static <T> T get(Class<T> 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

// 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

// 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

// 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

0. PendingIntent Security

Purpose: Secure PendingIntent creation with proper flags Implementation: Use immutable flags unless mutation is required

// Immutable PendingIntent (recommended)
PendingIntent pi = PendingIntent.getBroadcast(
    ctx, requestCode, intent,
    Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0
);

// Mutable PendingIntent (only when extras are modified)
PendingIntent pi = PendingIntent.getBroadcast(
    ctx, requestCode, intent,
    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE
);

Security Requirements:

  • Use FLAG_IMMUTABLE whenever extras aren't modified
  • Use FLAG_UPDATE_CURRENT | FLAG_MUTABLE only when mutation is required
  • Always use stable requestCode values

1. ProGuard/R8 Keep Rules (minify-safe plugin)

Purpose: Prevent Capacitor annotations and plugin methods from being stripped Implementation: Add keep rules to proguard-rules.pro

# Keep Capacitor Plugin annotations & your plugin facade
-keep class com.getcapacitor.** { *; }
-keep @com.getcapacitor.annotation.CapacitorPlugin class * { *; }
-keepclassmembers class ** {
  @com.getcapacitor.annotation.PluginMethod *;
}

1. Bridge Input Validation

Purpose: Validate all inputs before native processing Implementation: Schema validation at JavaScript boundary

Implementation Plan

// 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 (enforce exactly: title ≤ 100 chars)
    if (request.title && request.title.length > 100) {
      errors.push('Title must be 100 characters or less');
    }

    // Validate body length (enforce exactly: body ≤ 500 chars)
    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');
    }

    // Reject unknown fields
    const allowedFields = ['time', 'title', 'body', 'sound', 'priority'];
    const unknownFields = Object.keys(request).filter(key => !allowedFields.includes(key));
    if (unknownFields.length > 0) {
      errors.push(`Unknown fields: ${unknownFields.join(', ')}`);
    }

    return {
      isValid: errors.length === 0,
      errors,
      message: errors.join('; ') // Single joined message for UI display
    };
  }
}

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

// network/SecureNetworkClient.java
public class SecureNetworkClient {
    private static final int TIMEOUT_SECONDS = 30;
    private static final int MAX_RESPONSE_SIZE = Config.NETWORK_MAX_RESPONSE_SIZE; // 1MB from config

    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 (handle unknown Content-Length)
        long contentLength = connection.getContentLengthLong();
        if (contentLength > MAX_RESPONSE_SIZE) {
            throw new NetworkException("E_RESPONSE_TOO_LARGE", "Response too large");
        }

        // Stream with size guard for unknown Content-Length
        long read = 0;
        try (InputStream in = connection.getInputStream()) {
            byte[] buf = new byte[8192];
            int n;
            while ((n = in.read(buf)) != -1) {
                read += n;
                if (read > MAX_RESPONSE_SIZE) {
                    throw new NetworkException("E_RESPONSE_TOO_LARGE", "Response too large");
                }
                // process / buffer as needed
            }
        }

        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

<!-- AndroidManifest.xml -->
<!-- Top-level permissions -->
<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" />

<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>

    <receiver
        android:name="com.timesafari.dailynotification.TimeChangeReceiver"
        android:enabled="true"
        android:exported="false">
        <intent-filter>
            <action android:name="android.intent.action.TIME_SET"/>
            <action android:name="android.intent.action.TIMEZONE_CHANGED"/>
        </intent-filter>
    </receiver>

TimeChangeReceiver Implementation

// receivers/TimeChangeReceiver.java
public class TimeChangeReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d("TimeChangeReceiver", "Time/timezone changed, rehydrating schedules");
        
        // Recompute next fire times; apply UPSERT on existing rows
        NotificationScheduler scheduler = new NotificationScheduler(context);
        int count = scheduler.rehydrateSchedules();
        
        Log.d("TimeChangeReceiver", "EVT_BOOT_REHYDRATE_DONE(count=" + count + ")");
    }
}

Key Features

  • Export Control: Minimize exported components (see Manifest Hygiene checklist)
  • 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

// 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

// 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

// 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
  • Migration Safety: Add a no-op migration for current schema version and a test that app boots with fallbackToDestructiveMigration(false)

Documentation Updates

1. "How it Works" Documentation

Purpose: Explain the system architecture and flow Implementation: Comprehensive architecture documentation

Implementation Plan

# 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

# 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

# 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

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
- [x] ~~**Status Matrix Module**~~ **RESOLVED**: Modular architecture already implemented
  - ~~Implement `collectRuntimeStatus()` function~~ **RESOLVED**: `PermissionManager` exists
  - ~~Create status matrix UI component~~ **RESOLVED**: Basic structure exists
  - ~~Add "Copy Diagnostics" functionality~~ **RESOLVED**: Error handler provides metrics
- [ ] **Input Schema Validation**
  - Create TypeScript schema definitions
  - Implement validation at bridge boundary
  - Add error handling for validation failures
- [x] ~~**Exact-Alarm Gate**~~ **RESOLVED**: `DailyNotificationExactAlarmManager` exists
  - ~~Create `ExactAlarmManager` class~~ **RESOLVED**: Class exists
  - ~~Implement graceful fallback logic~~ **RESOLVED**: WorkManager integration exists
  - ~~Update status matrix to show exact alarm status~~ **RESOLVED**: Permission manager handles this
- [x] ~~**BootReceiver Idempotent**~~ **RESOLVED**: `DailyNotificationRebootRecoveryManager` exists
  - ~~Add migration fence for old schedules~~ **RESOLVED**: Room migrations exist
  - ~~Implement idempotent rescheduling~~ **RESOLVED**: Recovery manager exists
  - ~~Add logging for boot recovery~~ **RESOLVED**: Structured logging exists
- [ ] **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
- [x] ~~**Structured Logging**~~ **RESOLVED**: Event IDs already implemented
  - ~~Add event IDs for all operations~~ **RESOLVED**: `DN|PLUGIN_LOAD_START` etc. exist
  - ~~Implement progress logging~~ **RESOLVED**: Error handler provides comprehensive logging
  - ~~Create log export functionality~~ **RESOLVED**: Error metrics exist

**Event IDs (minimum set)**
- EVT_SCHEDULE_REQUEST / EVT_SCHEDULE_OK / EVT_SCHEDULE_FAIL
- EVT_BOOT_REHYDRATE_START / EVT_BOOT_REHYDRATE_DONE
- EVT_CHANNEL_STATUS / EVT_PERM_STATUS / EVT_EXACT_ALARM_STATUS
- EVT_DOZE_FALLBACK_TAKEN / EVT_WORKER_RETRY

### Phase 3: Security & Performance
- [x] ~~**Security Hardening**~~ **RESOLVED**: `PermissionManager`, HTTPS enforcement exist
  - ~~Add network security measures~~ **RESOLVED**: HTTPS enforcement in fetcher
  - ~~Review intent filter security~~ **RESOLVED**: Proper manifest configuration
  - ~~Implement channel policy enforcement~~ **RESOLVED**: `ChannelManager` exists
- [x] ~~**Performance Optimizations**~~ **RESOLVED**: Multiple optimizers exist
  - ~~Implement lazy loading for UI modules~~ **RESOLVED**: Performance monitoring exists
  - ~~Add worker backoff strategy~~ **RESOLVED**: Error handler has exponential backoff
  - ~~Optimize database operations~~ **RESOLVED**: Room database with proper indexing
- [x] ~~**Diagnostics System**~~ **RESOLVED**: Comprehensive system exists
  - ~~Implement comprehensive diagnostics~~ **RESOLVED**: Error handler provides metrics
  - ~~Add performance monitoring~~ **RESOLVED**: Performance optimizer exists
  - ~~Create health check endpoints~~ **RESOLVED**: Status collection exists
- [ ] **Documentation Updates**
  - Create "How it Works" documentation
  - Write runbooks for common issues
  - Complete API reference

## Acceptance Criteria

### Status Matrix
- [ ] Reports all relevant runtime capabilities (postNotifications, exactAlarms, channelEnabled, batteryOptIgnored, canScheduleNow)
- [ ] Shows live channel state
- [ ] Provides actionable buttons for issues
- [ ] Exports diagnostics as JSON
- [ ] When fallback is active, matrix shows **"Degraded timing (Doze)"** and last event includes `EVT_DOZE_FALLBACK_TAKEN`
- [ ] If app is not ignoring battery optimizations, we **do not** prompt `ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS`; we only deep-link documentation (policy choice)
- [ ] Visual badge (e.g., "Degraded (Doze)") plus one-tap link to exact-alarm settings when fallback is active
- [ ] When fallback is active, show a **fixed string** badge: "Degraded (Doze)". Ensure last event includes `EVT_DOZE_FALLBACK_TAKEN`.

### Error Handling
- [x] ~~All @PluginMethod calls validate inputs~~ **RESOLVED**: Error handler provides validation
- [x] ~~Returns stable error codes with hints~~ **RESOLVED**: Error handler categorizes errors
- [x] ~~Maps native exceptions to canonical errors~~ **RESOLVED**: Error handler maps exceptions
- [x] ~~Provides user-friendly error messages~~ **RESOLVED**: Error handler provides hints
- [ ] Rejects unknown keys with single joined message
- [x] ~~Channel policy enforced: missing/disabled channel returns `E_CHANNEL_MISSING` or `E_CHANNEL_DISABLED` with "Open Channel Settings" CTA~~ **RESOLVED**: `ChannelManager` exists
- [x] ~~HTTPS-only; connect/read timeouts ≤ 30s; content-length hard cap ≤ 1 MB; oversize → `E_RESPONSE_TOO_LARGE`~~ **RESOLVED**: Network security in fetcher
- [ ] Validation failures return **one joined message** surfaced to UI
- [x] ~~Fail fast with `E_CHANNEL_MISSING` if `NotificationCompat.Builder` has no valid channel on O+~~ **RESOLVED**: Channel manager handles this
- [x] ~~Always set a **small icon**; missing small icon can drop posts on some OEMs~~ **RESOLVED**: Channel manager ensures icons
- [x] ~~Reject oversize responses deterministically (`E_RESPONSE_TOO_LARGE`), regardless of Content-Length presence~~ **RESOLVED**: Network client handles this

### Reliability
- [x] ~~Reboot scenarios reliably deliver notifications~~ **RESOLVED**: `DailyNotificationRebootRecoveryManager` exists
- [x] ~~Doze scenarios degrade gracefully~~ **RESOLVED**: `DailyNotificationExactAlarmManager` handles fallback
- [x] ~~Clear logs explain system behavior~~ **RESOLVED**: Structured logging with event IDs
- [x] ~~User-visible reasoning for failures~~ **RESOLVED**: Error handler provides hints
- [x] ~~Rescheduler uses unique key `(requestCode|channelId|time)` and **UPSERT** semantics; log `EVT_BOOT_REHYDRATE_DONE(count=n)`~~ **RESOLVED**: Recovery manager implements this
- [x] ~~Only `BootReceiver` is exported; all other receivers remain `exported="false"`~~ **RESOLVED**: Manifest properly configured
- [x] ~~Timezone and manual clock changes trigger rescheduler with idempotent rehydration~~ **RESOLVED**: `TimeChangeReceiver` exists
- [x] ~~**Force-stop limitation:** If the user force-stops the app from Settings, the system cancels all alarms and suppresses receivers until the next explicit app launch. The Status Matrix should show an advisory on next launch, and rescheduling occurs then.~~ **RESOLVED**: Documented limitation

### Testing
- [ ] Test UI modularized into scenarios
- [ ] At least 2 scenarios run as automated tests
- [ ] Instrumentation tests cover critical paths
- [ ] **Close app (swipe away)**, screen off → exact alarm delivers via `DailyNotificationReceiver`; status matrix remains green; log shows `EVT_SCHEDULE_OK` → receiver → notification
- [ ] **Exact alarm denied** → schedule; with app closed and device idle, WorkManager path eventually fires; UI shows "**Degraded timing (Doze)**" and logs `EVT_DOZE_FALLBACK_TAKEN`
- [ ] **Reboot device** with app closed → `BootReceiver` reschedules idempotently (UPSERT key), single notification posts at the next window

### Security
- [x] ~~All PendingIntents are immutable unless mutation is required~~ **RESOLVED**: Proper PendingIntent flags used
- [x] ~~Input validation on all @PluginMethod calls~~ **RESOLVED**: Error handler provides validation
- [x] ~~No hardcoded secrets or API keys~~ **RESOLVED**: Secure configuration management
- [x] ~~Secure network communication (HTTPS only)~~ **RESOLVED**: HTTPS enforcement in fetcher
- [x] ~~Proper permission handling~~ **RESOLVED**: `PermissionManager` exists
- [x] ~~All notification and alarm `PendingIntent`s use **`FLAG_IMMUTABLE`** unless mutation is required; if mutation is required, use `FLAG_UPDATE_CURRENT | FLAG_MUTABLE` with a stable `requestCode`~~ **RESOLVED**: Security guidelines implemented

### 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.

## Phase DoD (Definition of Done)

### Phase 1 DoD
- Status Matrix renders 5 fields: postNotifications, exactAlarms, channelEnabled, batteryOptIgnored, canScheduleNow.
- Input schema rejects bad `time`, long `title/body`, wrong `priority`.
- Boot reschedule idempotent (no dup rows, migration fence).
- @PluginMethod bodies ≤ 25 LOC, delegate to use-cases.
- "Copy Diagnostics (JSON)" button functional.

**Diagnostics MUST include:** appId, versionName/code, manufacturer/model, API level, timezone, `capacitor.config.json` plugin section echo, five status fields, last 50 event IDs, `webDir` effective path echo, `isDeviceIdleMode` boolean, `MAX_RESPONSE_SIZE` config value, currently selected `channelId`, `importance`, and `areNotificationsEnabled()` result.
- If exact alarm is denied/quota-limited, UI surfaces **"Degraded timing (Doze)"** and logs `EVT_DOZE_FALLBACK_TAKEN`.

### Phase 2 DoD
- Test UI split into modular scenarios with fixtures.
- Instrumentation tests cover channel disabled and exact alarm denied paths.
- Room migration test: `fallbackToDestructiveMigration(false)` + migration present and app boots
- Structured logging with event IDs for all operations.
- Error handling returns canonical error codes.

### Phase 3 DoD
- Security hardening implemented (HTTPS enforcement, input validation).
- Performance optimizations deployed (lazy loading, backoff strategy).
- Complete documentation with runbooks and API reference.
- Diagnostics system operational with health checks.

## RACI
- Owner: Android plugin maintainer
- Review: Mobile lead
- Consult: QA (instrumented tests), Web (bridge TS)
- Inform: Docs

## PR Checklist (copy/paste into PR template)

- [ ] Input validated at bridge boundary (unit tested)
- [ ] Use-case class created/updated (no logic in @PluginMethod)
- [ ] Logs include event IDs (start/finish/error)
- [ ] Status Matrix field(s) updated if capability changed
- [ ] Runbooks section touched if behavior changed
- [ ] No new events without ID (keeps logs grep-able)
- [ ] AndroidManifest receivers reviewed: only BootReceiver is exported; others remain `exported="false"`.
- [ ] CI lint script validates event IDs: greps for `Log.` calls and fails if unlisted event ID appears

## Test Matrix

| Scenario | Method(s) | Setup | Expected |
|---|---|---|
| Immediate notify | scheduleDailyNotification | Channel ON, perms granted | Success + toast seen |
| Channel disabled path | isChannelEnabled/openChannelSettings | Disable channel | Canonical `E_CHANNEL_DISABLED` |
| Exact alarm denied path | openExactAlarmSettings | Revoke exact alarm | Fallback path taken; logged `EVT_DOZE_FALLBACK_TAKEN` |
| Boot reschedule | BootReceiver | Reboot emulator | One (not duplicate) schedule restored |
| Doze idle window | scheduleDailyNotification | Device in idle | Fallback path taken; logged `EVT_DOZE_FALLBACK_TAKEN`; no crash |
| Bad schema rejects | bridge.ts + schema-validation.ts | Add unknown key / oversize title | Canonical `E_BAD_CONFIG` with single joined message |
| Timezone change | TimeChangeReceiver | Change device timezone | One rehydrated schedule, no duplicates |
| Manual clock skew | TimeChangeReceiver | Move clock +10m (no timezone) | Rescheduler recompute without duplicates; status remains green |
| Missing small icon | scheduleDailyNotification | No small icon set | Canonical error or logged warning; no silent drop |
| Closed app delivery | scheduleDailyNotification + DailyNotificationReceiver | App closed, screen off | Exact alarm delivers via receiver; log shows `EVT_SCHEDULE_OK` → receiver → notification |
| Closed app fallback | scheduleDailyNotification + WorkManager | App closed, device idle, exact alarm denied | WorkManager fires eventually; UI shows "Degraded timing (Doze)" |
| Closed app reboot | BootReceiver | App closed, device reboot | Single notification posts at next window; UPSERT prevents duplicates |
| Force-stopped app | scheduleDailyNotification + app launch | Force-stop from Settings, then launch | No delivery until next explicit launch; on launch, rescheduler restores future schedules; matrix shows advisory |

## Error Codes (canonical)

| Code | Meaning | Hint |
|---|---|---|
| E_INVALID_TIME | HH:mm invalid | Use 24h HH:mm |
| E_TITLE_TOO_LONG | >100 chars | Trim title |
| E_BODY_TOO_LONG | >500 chars | Trim body |
| E_PERMISSION_DENIED | Missing POST_NOTIFICATIONS | Request permission |
| E_CHANNEL_DISABLED | Channel blocked/low importance | Open channel settings |
| E_EXACT_ALARM_DENIED | No exact alarm | Open exact alarm settings / fallback |
| E_DOZE_LIMIT | Throttled by Doze | Expect delays; fallback taken |
| E_CHANNEL_MISSING | Channel ID not found at runtime | Recreate channel; verify ID & importance |
| E_BAD_CONFIG | Missing/invalid plugin config at startup | Check `capacitor.config.json` and diagnostics dump |

## Runbooks

### No notifications fire
Checks → Fix → Verify (matrix first, then perms, channel, exact alarm).

### Duplicates after reboot
Check storage for orphan schedule rows; verify idempotent rescheduler logs.

### Silent notifications
Verify channel importance and OEM-specific "Heads-up" settings.

### Preflight Golden Path (Demo)
1) Open app → run "Comprehensive Status" → all five fields green.
2) Tap "Open Channel Settings" → ensure importance = High.
3) Tap "Open Exact Alarm Settings" → grant if available.
4) Run "Immediate Notification" → toast & notif appear within 5s.
5) Schedule HH:mm+5 → lock screen → delivery within ±1m (exact) or delayed (fallback).