refactor(android)!: restructure to standard Capacitor plugin layout

Restructure Android project from nested module layout to standard
Capacitor plugin structure following community conventions.

Structure Changes:
- Move plugin code from android/plugin/ to android/src/main/java/
- Move test app from android/app/ to test-apps/android-test-app/app/
- Remove nested android/plugin module structure
- Remove nested android/app test app structure

Build Infrastructure:
- Add Gradle wrapper files (gradlew, gradlew.bat, gradle/wrapper/)
- Transform android/build.gradle from root project to library module
- Update android/settings.gradle for standalone plugin builds
- Add android/gradle.properties with AndroidX configuration
- Add android/consumer-rules.pro for ProGuard rules

Configuration Updates:
- Add prepare script to package.json for automatic builds on npm install
- Update package.json version to 1.0.1
- Update android/build.gradle to properly resolve Capacitor dependencies
- Update test-apps/android-test-app/settings.gradle with correct paths
- Remove android/variables.gradle (hardcode values in build.gradle)

Documentation:
- Update BUILDING.md with new structure and build process
- Update INTEGRATION_GUIDE.md to reflect standard structure
- Update README.md to remove path fix warnings
- Add test-apps/BUILD_PROCESS.md documenting test app build flows

Test App Configuration:
- Fix android-test-app to correctly reference plugin and Capacitor
- Remove capacitor-cordova-android-plugins dependency (not needed)
- Update capacitor.settings.gradle path verification in fix script

BREAKING CHANGE: Plugin now uses standard Capacitor Android structure.
Consuming apps must update their capacitor.settings.gradle to reference
android/ instead of android/plugin/. This is automatically handled by
Capacitor CLI for apps using standard plugin installation.
This commit is contained in:
Matthew Raymer
2025-11-05 08:08:37 +00:00
parent c4b7f6382f
commit d9bdeb6d02
128 changed files with 1654 additions and 1747 deletions

View File

@@ -0,0 +1,198 @@
/**
* SchedulingPolicy.java
*
* Policy configuration for notification scheduling, fetching, and retry behavior.
*
* This class is part of the Integration Point Refactor (PR1) SPI implementation.
* It allows host apps to configure scheduling behavior including retry backoff,
* prefetch timing, deduplication windows, and cache TTL.
*
* @author Matthew Raymer
* @version 1.0.0
*/
package com.timesafari.dailynotification;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* Scheduling policy configuration
*
* Controls how the plugin schedules fetches, handles retries, manages
* deduplication, and enforces TTL policies.
*
* This follows the TypeScript interface from src/types/content-fetcher.ts
* and ensures consistency between JS and native configuration.
*/
public class SchedulingPolicy {
/**
* How early to prefetch before scheduled notification time (milliseconds)
*
* Example: If set to 300000 (5 minutes), and notification is scheduled for
* 8:00 AM, the fetch will be triggered at 7:55 AM.
*
* Default: 5 minutes (300000ms)
*/
@Nullable
public Long prefetchWindowMs;
/**
* Retry backoff configuration (required)
*
* Controls exponential backoff behavior for failed fetches
*/
@NonNull
public RetryBackoff retryBackoff;
/**
* Maximum items to fetch per batch
*
* Limits the number of NotificationContent items that can be fetched
* in a single operation. Helps prevent oversized responses.
*
* Default: 50
*/
@Nullable
public Integer maxBatchSize;
/**
* Deduplication window (milliseconds)
*
* Prevents duplicate notifications within this time window. Plugin
* uses dedupeKey (or id) to detect duplicates.
*
* Default: 24 hours (86400000ms)
*/
@Nullable
public Long dedupeHorizonMs;
/**
* Default cache TTL if item doesn't specify ttlSeconds (seconds)
*
* Used when NotificationContent doesn't have ttlSeconds set.
* Determines how long cached content remains valid.
*
* Default: 6 hours (21600 seconds)
*/
@Nullable
public Integer cacheTtlSeconds;
/**
* Whether exact alarms are allowed (Android 12+)
*
* Controls whether plugin should attempt to use exact alarms.
* Requires SCHEDULE_EXACT_ALARM permission on Android 12+.
*
* Default: false (use inexact alarms)
*/
@Nullable
public Boolean exactAlarmsAllowed;
/**
* Fetch timeout in milliseconds
*
* Maximum time to wait for native fetcher to complete.
* Plugin enforces this timeout when calling fetchContent().
*
* Default: 30 seconds (30000ms)
*/
@Nullable
public Long fetchTimeoutMs;
/**
* Default constructor with required field
*
* @param retryBackoff Retry backoff configuration (required)
*/
public SchedulingPolicy(@NonNull RetryBackoff retryBackoff) {
this.retryBackoff = retryBackoff;
}
/**
* Retry backoff configuration
*
* Controls exponential backoff behavior for retryable failures.
* Delay = min(max(minMs, lastDelay * factor), maxMs) * (1 + jitterPct/100 * random)
*/
public static class RetryBackoff {
/**
* Minimum delay between retries (milliseconds)
*
* First retry will wait at least this long.
* Default: 2000ms (2 seconds)
*/
public long minMs;
/**
* Maximum delay between retries (milliseconds)
*
* Retry delay will never exceed this value.
* Default: 600000ms (10 minutes)
*/
public long maxMs;
/**
* Exponential backoff multiplier
*
* Each retry multiplies previous delay by this factor.
* Example: factor=2 means delays: 2s, 4s, 8s, 16s, ...
* Default: 2.0
*/
public double factor;
/**
* Jitter percentage (0-100)
*
* Adds randomness to prevent thundering herd.
* Final delay = calculatedDelay * (1 + jitterPct/100 * random(0-1))
* Default: 20 (20% jitter)
*/
public int jitterPct;
/**
* Default constructor with sensible defaults
*/
public RetryBackoff() {
this.minMs = 2000;
this.maxMs = 600000;
this.factor = 2.0;
this.jitterPct = 20;
}
/**
* Constructor with all parameters
*
* @param minMs Minimum delay (ms)
* @param maxMs Maximum delay (ms)
* @param factor Exponential multiplier
* @param jitterPct Jitter percentage (0-100)
*/
public RetryBackoff(long minMs, long maxMs, double factor, int jitterPct) {
this.minMs = minMs;
this.maxMs = maxMs;
this.factor = factor;
this.jitterPct = jitterPct;
}
}
/**
* Create default policy with sensible defaults
*
* @return Default SchedulingPolicy instance
*/
@NonNull
public static SchedulingPolicy createDefault() {
SchedulingPolicy policy = new SchedulingPolicy(new RetryBackoff());
policy.prefetchWindowMs = 300000L; // 5 minutes
policy.maxBatchSize = 50;
policy.dedupeHorizonMs = 86400000L; // 24 hours
policy.cacheTtlSeconds = 21600; // 6 hours
policy.exactAlarmsAllowed = false;
policy.fetchTimeoutMs = 30000L; // 30 seconds
return policy;
}
}