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

## Current State Summary (2025-10-24)

**Progress**: ~90% Complete

### ✅ **Completed Features**
- **Modular Test App Architecture**: Vue 3 app with views, components, stores, and lib modules
- **Schema Validation**: Zod-based validation at JavaScript bridge boundary (`src/services/NotificationValidationService.ts`, `test-apps/daily-notification-test/src/lib/schema-validation.ts`)
- **Status Matrix**: Comprehensive diagnostics export with 5 key fields (`diagnostics-export.ts`)
- **Native Plugin Architecture**: Modular Java classes (34 classes) with specialized managers
- **Security**: HTTPS enforcement, input validation, proper manifest configuration
- **Performance**: Performance optimizer, rolling window, TTL enforcement
- **Logging**: Structured event IDs throughout codebase

### 🚧 **In Progress**
- **Instrumentation Tests**: Basic tests exist; need expansion for specific scenarios
- **Documentation**: Runbooks and expanded API reference

### 🎯 **Remaining Work**
- Add instrumentation tests for critical paths (channel disabled, exact alarm denied, boot recovery)
- Write operational runbooks for common troubleshooting scenarios
- Expand API reference documentation with complete method signatures

## 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
- [x] ~~Create status matrix module~~ **COMPLETED**: Modular test app architecture exists (`test-apps/daily-notification-test/src/`)
- [x] ~~Add input schema validation~~ **COMPLETED**: `test-apps/daily-notification-test/src/lib/schema-validation.ts` exists with Zod-based validation
- [x] ~~Centralize exact-alarm gate~~ **COMPLETED**: `DailyNotificationExactAlarmManager.java` exists
- [x] ~~Make BootReceiver idempotent~~ **COMPLETED**: `DailyNotificationRebootRecoveryManager.java` exists
- [x] ~~Introduce use-case classes~~ **PARTIALLY COMPLETED**: Architecture is modular but not fully organized into use-case classes

### Phase 2: Testing & Reliability
**Focus**: Testing infrastructure and reliability improvements
- [x] ~~Refactor test UI into modular scenarios~~ **COMPLETED**: Vue 3 modular architecture (`test-apps/daily-notification-test/src/views/`, `src/components/`, `src/lib/`)
- [ ] Add instrumentation tests - **TODO**: Expand beyond basic `ExampleInstrumentedTest.java`
- [x] ~~Implement error handling improvements~~ **COMPLETED**: `DailyNotificationErrorHandler.java` exists
- [x] ~~Add structured logging~~ **COMPLETED**: Event IDs implemented throughout codebase

### Phase 3: Security & Performance
**Focus**: Security hardening and performance optimization
- [x] ~~Implement security hardening~~ **COMPLETED**: `PermissionManager.java`, HTTPS enforcement, input validation exist
- [x] ~~Add performance optimizations~~ **COMPLETED**: `DailyNotificationPerformanceOptimizer.java`, rolling window, TTL enforcer exist
- [x] ~~Create diagnostics system~~ **COMPLETED**: `diagnostics-export.ts` with comprehensive system information
- [ ] Update documentation - **IN PROGRESS**: This document and related docs being updated

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

### 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
```java
// 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
```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

### 0. PendingIntent Security

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

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

```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
```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 (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
```java
// 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
```xml
<!-- 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="org.timesafari.dailynotification.DailyNotificationReceiver"
    android:enabled="true"
    android:exported="false">
    <intent-filter>
        <action android:name="org.timesafari.daily.NOTIFICATION" />
    </intent-filter>
</receiver>

    <receiver
        android:name="org.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="org.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

```java
// 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
```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
- **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
```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
- [x] **Status Matrix Module** **COMPLETED**
  - [x] Implement `collectRuntimeStatus()` function - **COMPLETED**: `test-apps/daily-notification-test/src/lib/diagnostics-export.ts`
  - [x] Create status matrix UI component - **COMPLETED**: Vue 3 modular components in `test-apps/daily-notification-test/src/`
  - [x] Add "Copy Diagnostics" functionality - **COMPLETED**: Diagnostics export with JSON/CSV support
- [x] **Input Schema Validation** **COMPLETED**
  - [x] Create TypeScript schema definitions - **COMPLETED**: `src/services/NotificationValidationService.ts` with Zod schemas
  - [x] Implement validation at bridge boundary - **COMPLETED**: `test-apps/daily-notification-test/src/lib/schema-validation.ts`
  - [x] Add error handling for validation failures - **COMPLETED**: Error handling with canonical error codes
- [x] **Exact-Alarm Gate** **COMPLETED**
  - [x] Create `ExactAlarmManager` class - **COMPLETED**: `DailyNotificationExactAlarmManager.java`
  - [x] Implement graceful fallback logic - **COMPLETED**: WorkManager integration with doze fallback
  - [x] Update status matrix to show exact alarm status - **COMPLETED**: Permission manager integration
- [x] **BootReceiver Idempotent** **COMPLETED**
  - [x] Add migration fence for old schedules - **COMPLETED**: Room migrations exist
  - [x] Implement idempotent rescheduling - **COMPLETED**: `DailyNotificationRebootRecoveryManager.java`
  - [x] Add logging for boot recovery - **COMPLETED**: Structured logging with event IDs
- [x] **Use-Case Classes** **PARTIALLY COMPLETED**
  - Architecture is modular but could benefit from explicit use-case classes
  - Plugin methods delegate to specialized managers (scheduler, fetcher, error handler)

### Phase 2: Testing & Reliability
- [x] **Test UI Refactoring** **COMPLETED**
  - [x] Split UI into modules - **COMPLETED**: Vue 3 architecture with views, components, stores
  - [x] Create scenario runner framework - **COMPLETED**: Typed plugin interface with scenarios
  - [x] Implement named test scenarios - **COMPLETED**: Multiple views for different test scenarios
- [ ] **Instrumentation Tests** **IN PROGRESS**
  - [ ] Test channel disabled path - **TODO**: Expand instrumentation tests
  - [ ] Test exact alarm denied path - **TODO**: Add specific test scenarios
  - [ ] Test boot reschedule functionality - **TODO**: Add reboot recovery tests
- [x] **Structured Logging** **COMPLETED**
  - [x] Add event IDs for all operations - **COMPLETED**: `DN|*` prefix pattern throughout codebase
  - [x] Implement progress logging - **COMPLETED**: Comprehensive logging in all components
  - [x] Create log export functionality - **COMPLETED**: Diagnostics export with event tracking

**Event IDs (Implemented)**
- `DN|PLUGIN_LOAD_START` / `DN|PLUGIN_LOAD_OK` / `DN|PLUGIN_LOAD_ERR`
- `DN|SCHEDULE_REQUEST` / `DN|SCHEDULE_OK` / `DN|SCHEDULE_FAIL`
- `DN|BOOT_REHYDRATE_START` / `DN|BOOT_REHYDRATE_DONE`
- `DN|DISPLAY_START` / `DN|DISPLAY_OK` / `DN|DISPLAY_FAIL`
- `DN|WORK_START` / `DN|WORK_OK` / `DN|WORK_FAIL`
- `DN|DOZE_FALLBACK_TAKEN` / `DN|WORK_RETRY`

### Phase 3: Security & Performance
- [x] **Security Hardening** **COMPLETED**
  - [x] Add network security measures - **COMPLETED**: HTTPS enforcement in `DailyNotificationFetcher.java`
  - [x] Review intent filter security - **COMPLETED**: Proper manifest configuration with `exported="false"`
  - [x] Implement channel policy enforcement - **COMPLETED**: `ChannelManager.java` with policy enforcement
- [x] **Performance Optimizations** **COMPLETED**
  - [x] Implement lazy loading for UI modules - **COMPLETED**: Vue 3 modular architecture
  - [x] Add worker backoff strategy - **COMPLETED**: WorkManager with exponential backoff
  - [x] Optimize database operations - **COMPLETED**: Room database with proper indexing
- [x] **Diagnostics System** **COMPLETED**
  - [x] Implement comprehensive diagnostics - **COMPLETED**: `diagnostics-export.ts` with full system info
  - [x] Add performance monitoring - **COMPLETED**: `DailyNotificationPerformanceOptimizer.java`
  - [x] Create health check endpoints - **COMPLETED**: Status matrix with 5 key fields
- [x] **Documentation Updates** **IN PROGRESS**
  - [x] Create "How it Works" documentation - **COMPLETED**: `doc/platform/android/APP_ANALYSIS.md`
  - [ ] Write runbooks for common issues - **TODO**: Add operational runbooks
  - [ ] Complete API reference - **TODO**: Expand API documentation

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