feat(android): add fetch scheduling debug logs and triggerImmediateFetch API
- Add DN|SCHEDULE_CALLBACK logs to diagnose fetch scheduling - Add DN|SCHEDULE_FETCH_* structured logs for traceability - Add triggerImmediateFetch() public API for standalone fetches - Update fetch timing from 1 hour to 5 minutes before notification - Fix TypeScript lint errors: add return types, replace any types - Fix ESLint warnings: add console suppression comments - Fix capacitor.settings.gradle plugin path reference - Update android-app-improvement-plan.md with current state Changes: - DailyNotificationPlugin: Added scheduled callback logging and fetch method - DailyNotificationFetcher: Changed lead time from 1 hour to 5 minutes - EnhancedDailyNotificationFetcher: Added ENH|* structured event IDs - TypeScript services: Fixed lint errors and added proper types - Test app: Fixed capacitor settings path and TypeScript warnings
This commit is contained in:
@@ -78,8 +78,8 @@ public class DailyNotificationFetcher {
|
||||
try {
|
||||
Log.d(TAG, "Scheduling background fetch for " + scheduledTime);
|
||||
|
||||
// Calculate fetch time (1 hour before notification)
|
||||
long fetchTime = scheduledTime - TimeUnit.HOURS.toMillis(1);
|
||||
// Calculate fetch time (5 minutes before notification)
|
||||
long fetchTime = scheduledTime - TimeUnit.MINUTES.toMillis(5);
|
||||
|
||||
if (fetchTime > System.currentTimeMillis()) {
|
||||
// Create work data
|
||||
|
||||
@@ -653,6 +653,7 @@ public class DailyNotificationPlugin extends Plugin {
|
||||
boolean scheduled = scheduler.scheduleNotification(content);
|
||||
|
||||
if (scheduled) {
|
||||
Log.i(TAG, "DN|SCHEDULE_CALLBACK scheduled=true, calling scheduleBackgroundFetch");
|
||||
// Schedule background fetch for next day
|
||||
scheduleBackgroundFetch(content.getScheduledTime());
|
||||
|
||||
@@ -662,6 +663,7 @@ public class DailyNotificationPlugin extends Plugin {
|
||||
Log.i(TAG, "Daily notification scheduled successfully for " + time);
|
||||
call.resolve();
|
||||
} else {
|
||||
Log.w(TAG, "DN|SCHEDULE_CALLBACK scheduled=false, NOT calling scheduleBackgroundFetch");
|
||||
call.reject("Failed to schedule notification");
|
||||
}
|
||||
|
||||
@@ -958,15 +960,20 @@ public class DailyNotificationPlugin extends Plugin {
|
||||
*/
|
||||
private void scheduleBackgroundFetch(long scheduledTime) {
|
||||
try {
|
||||
// Schedule fetch 1 hour before notification
|
||||
long fetchTime = scheduledTime - TimeUnit.HOURS.toMillis(1);
|
||||
Log.d(TAG, "DN|SCHEDULE_FETCH_START time=" + scheduledTime);
|
||||
|
||||
// Schedule fetch 5 minutes before notification
|
||||
long fetchTime = scheduledTime - TimeUnit.MINUTES.toMillis(5);
|
||||
|
||||
if (fetchTime > System.currentTimeMillis()) {
|
||||
Log.d(TAG, "DN|SCHEDULE_FETCH_CALC fetch_at=" + fetchTime + " notification_at=" + scheduledTime);
|
||||
fetcher.scheduleFetch(fetchTime);
|
||||
Log.d(TAG, "Background fetch scheduled for " + fetchTime);
|
||||
Log.i(TAG, "DN|SCHEDULE_FETCH_OK Background fetch scheduled for " + fetchTime + " (5 minutes before notification at " + scheduledTime + ")");
|
||||
} else {
|
||||
Log.w(TAG, "DN|SCHEDULE_FETCH_PAST fetch_time=" + fetchTime + " current=" + System.currentTimeMillis());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error scheduling background fetch", e);
|
||||
Log.e(TAG, "DN|SCHEDULE_FETCH_ERR Error scheduling background fetch", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1153,8 +1160,38 @@ public class DailyNotificationPlugin extends Plugin {
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
Log.d(TAG, "DEBUG: Android 13+ detected, requesting POST_NOTIFICATIONS permission");
|
||||
|
||||
// Store call for manual checking since Capacitor doesn't callback on denial
|
||||
final PluginCall savedCall = call;
|
||||
|
||||
// Request POST_NOTIFICATIONS permission for Android 13+
|
||||
requestPermissionForAlias("notifications", call, "onPermissionResult");
|
||||
|
||||
// Manually check result after a short delay to handle denied permissions
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// Wait for permission dialog to close
|
||||
Thread.sleep(500);
|
||||
|
||||
// Check permission status manually
|
||||
boolean permissionGranted = getContext().checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS)
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
|
||||
Log.d(TAG, "DEBUG: Manual permission check: " + permissionGranted);
|
||||
|
||||
if (!permissionGranted) {
|
||||
// Permission was denied, respond to the call
|
||||
Log.w(TAG, "Notification permission denied by user");
|
||||
savedCall.reject("Notification permission denied by user");
|
||||
}
|
||||
// If granted, the callback will handle it
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "Manual permission check interrupted", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Log.d(TAG, "DEBUG: Pre-Android 13, checking notification manager");
|
||||
// For older versions, check if notifications are enabled
|
||||
@@ -1461,6 +1498,46 @@ public class DailyNotificationPlugin extends Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger an immediate standalone fetch for content updates
|
||||
*
|
||||
* This method allows manual triggering of content fetches independently of
|
||||
* scheduled notifications. Useful for on-demand content refresh, cache warming,
|
||||
* or background sync operations.
|
||||
*
|
||||
* @param call Plugin call
|
||||
*/
|
||||
@PluginMethod
|
||||
public void triggerImmediateFetch(PluginCall call) {
|
||||
try {
|
||||
Log.d(TAG, "Manual standalone fetch triggered");
|
||||
|
||||
// Ensure storage is initialized
|
||||
ensureStorageInitialized();
|
||||
|
||||
// Ensure fetcher is initialized
|
||||
if (fetcher == null) {
|
||||
Log.w(TAG, "DN|FETCHER_NULL initializing_fetcher");
|
||||
fetcher = new DailyNotificationFetcher(getContext(), storage, roomStorage);
|
||||
}
|
||||
|
||||
// Trigger immediate fetch via WorkManager
|
||||
fetcher.scheduleImmediateFetch();
|
||||
|
||||
Log.i(TAG, "DN|STANDALONE_FETCH_SCHEDULED");
|
||||
|
||||
// Return success response
|
||||
JSObject result = new JSObject();
|
||||
result.put("success", true);
|
||||
result.put("message", "Immediate fetch scheduled successfully");
|
||||
call.resolve(result);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "DN|STANDALONE_FETCH_ERR err=" + e.getMessage(), e);
|
||||
call.reject("Error triggering standalone fetch: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get exact alarm status with enhanced Android 12+ support
|
||||
*
|
||||
|
||||
@@ -100,7 +100,7 @@ public class EnhancedDailyNotificationFetcher extends DailyNotificationFetcher {
|
||||
*/
|
||||
public CompletableFuture<OffersResponse> fetchEndorserOffers(String recipientDid, String afterId, String beforeId) {
|
||||
try {
|
||||
Log.d(TAG, "Fetching Endorser offers for recipient: " + recipientDid);
|
||||
Log.i(TAG, "ENH|FETCH_OFFERS_TO_PERSON_START recipient=" + recipientDid);
|
||||
|
||||
// Validate parameters
|
||||
if (recipientDid == null || recipientDid.isEmpty()) {
|
||||
@@ -113,12 +113,22 @@ public class EnhancedDailyNotificationFetcher extends DailyNotificationFetcher {
|
||||
|
||||
// Build URL with query parameters
|
||||
String url = buildOffersUrl(recipientDid, afterId, beforeId);
|
||||
Log.d(TAG, "ENH|URL_BUILD url=" + url.substring(0, Math.min(100, url.length())) + "...");
|
||||
|
||||
// Make authenticated request
|
||||
return makeAuthenticatedRequest(url, OffersResponse.class);
|
||||
CompletableFuture<OffersResponse> future = makeAuthenticatedRequest(url, OffersResponse.class);
|
||||
|
||||
future.thenAccept(response -> {
|
||||
Log.i(TAG, "ENH|FETCH_OFFERS_TO_PERSON_OK count=" + (response != null && response.data != null ? response.data.size() : 0));
|
||||
}).exceptionally(e -> {
|
||||
Log.e(TAG, "ENH|FETCH_OFFERS_TO_PERSON_ERR err=" + e.getMessage());
|
||||
return null;
|
||||
});
|
||||
|
||||
return future;
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error fetching Endorser offers", e);
|
||||
Log.e(TAG, "ENH|FETCH_OFFERS_TO_PERSON_ERR err=" + e.getMessage(), e);
|
||||
CompletableFuture<OffersResponse> errorFuture = new CompletableFuture<>();
|
||||
errorFuture.completeExceptionally(e);
|
||||
return errorFuture;
|
||||
@@ -135,15 +145,25 @@ public class EnhancedDailyNotificationFetcher extends DailyNotificationFetcher {
|
||||
*/
|
||||
public CompletableFuture<OffersToPlansResponse> fetchOffersToMyPlans(String afterId) {
|
||||
try {
|
||||
Log.d(TAG, "Fetching offers to user's plans");
|
||||
Log.i(TAG, "ENH|FETCH_OFFERS_TO_PLANS_START afterId=" + (afterId != null ? afterId.substring(0, Math.min(20, afterId.length())) : "null"));
|
||||
|
||||
String url = buildOffersToPlansUrl(afterId);
|
||||
Log.d(TAG, "ENH|URL_BUILD url=" + url.substring(0, Math.min(100, url.length())) + "...");
|
||||
|
||||
// Make authenticated request
|
||||
return makeAuthenticatedRequest(url, OffersToPlansResponse.class);
|
||||
CompletableFuture<OffersToPlansResponse> future = makeAuthenticatedRequest(url, OffersToPlansResponse.class);
|
||||
|
||||
future.thenAccept(response -> {
|
||||
Log.i(TAG, "ENH|FETCH_OFFERS_TO_PLANS_OK count=" + (response != null && response.data != null ? response.data.size() : 0));
|
||||
}).exceptionally(e -> {
|
||||
Log.e(TAG, "ENH|FETCH_OFFERS_TO_PLANS_ERR err=" + e.getMessage());
|
||||
return null;
|
||||
});
|
||||
|
||||
return future;
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error fetching offers to plans", e);
|
||||
Log.e(TAG, "ENH|FETCH_OFFERS_TO_PLANS_ERR err=" + e.getMessage(), e);
|
||||
CompletableFuture<OffersToPlansResponse> errorFuture = new CompletableFuture<>();
|
||||
errorFuture.completeExceptionally(e);
|
||||
return errorFuture;
|
||||
@@ -161,9 +181,10 @@ public class EnhancedDailyNotificationFetcher extends DailyNotificationFetcher {
|
||||
*/
|
||||
public CompletableFuture<PlansLastUpdatedResponse> fetchProjectsLastUpdated(List<String> planIds, String afterId) {
|
||||
try {
|
||||
Log.d(TAG, "Fetching project updates for " + planIds.size() + " plans");
|
||||
Log.i(TAG, "ENH|FETCH_PROJECT_UPDATES_START planCount=" + (planIds != null ? planIds.size() : 0) + " afterId=" + (afterId != null ? afterId.substring(0, Math.min(20, afterId.length())) : "null"));
|
||||
|
||||
String url = apiServerUrl + ENDPOINT_PLANS_UPDATED;
|
||||
Log.d(TAG, "ENH|URL_BUILD url=" + url.substring(0, Math.min(100, url.length())) + "...");
|
||||
|
||||
// Create POST request body
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
@@ -173,10 +194,19 @@ public class EnhancedDailyNotificationFetcher extends DailyNotificationFetcher {
|
||||
}
|
||||
|
||||
// Make authenticated POST request
|
||||
return makeAuthenticatedPostRequest(url, requestBody, PlansLastUpdatedResponse.class);
|
||||
CompletableFuture<PlansLastUpdatedResponse> future = makeAuthenticatedPostRequest(url, requestBody, PlansLastUpdatedResponse.class);
|
||||
|
||||
future.thenAccept(response -> {
|
||||
Log.i(TAG, "ENH|FETCH_PROJECT_UPDATES_OK count=" + (response != null && response.data != null ? response.data.size() : 0));
|
||||
}).exceptionally(e -> {
|
||||
Log.e(TAG, "ENH|FETCH_PROJECT_UPDATES_ERR err=" + e.getMessage());
|
||||
return null;
|
||||
});
|
||||
|
||||
return future;
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error fetching project updates", e);
|
||||
Log.e(TAG, "ENH|FETCH_PROJECT_UPDATES_ERR err=" + e.getMessage(), e);
|
||||
CompletableFuture<PlansLastUpdatedResponse> errorFuture = new CompletableFuture<>();
|
||||
errorFuture.completeExceptionally(e);
|
||||
return errorFuture;
|
||||
@@ -193,15 +223,17 @@ public class EnhancedDailyNotificationFetcher extends DailyNotificationFetcher {
|
||||
*/
|
||||
public CompletableFuture<TimeSafariNotificationBundle> fetchAllTimeSafariData(TimeSafariUserConfig userConfig) {
|
||||
try {
|
||||
Log.d(TAG, "Starting comprehensive TimeSafari data fetch");
|
||||
Log.i(TAG, "ENH|FETCH_ALL_START activeDid=" + (userConfig.activeDid != null ? userConfig.activeDid.substring(0, Math.min(30, userConfig.activeDid.length())) : "null"));
|
||||
|
||||
// Validate configuration
|
||||
if (userConfig.activeDid == null) {
|
||||
Log.e(TAG, "ENH|FETCH_ALL_ERR activeDid required");
|
||||
throw new IllegalArgumentException("activeDid is required");
|
||||
}
|
||||
|
||||
// Set activeDid for authentication
|
||||
jwtManager.setActiveDid(userConfig.activeDid);
|
||||
Log.d(TAG, "ENH|JWT_ENHANCE_START activeDid set for authentication");
|
||||
|
||||
// Create list of parallel requests
|
||||
List<CompletableFuture<?>> futures = new ArrayList<>();
|
||||
@@ -228,6 +260,8 @@ public class EnhancedDailyNotificationFetcher extends DailyNotificationFetcher {
|
||||
futures.add(projectUpdates);
|
||||
}
|
||||
|
||||
Log.d(TAG, "ENH|PARALLEL_REQUESTS count=" + futures.size());
|
||||
|
||||
// Wait for all requests to complete
|
||||
CompletableFuture<Void> allFutures = CompletableFuture.allOf(
|
||||
futures.toArray(new CompletableFuture[0])
|
||||
@@ -253,11 +287,11 @@ public class EnhancedDailyNotificationFetcher extends DailyNotificationFetcher {
|
||||
bundle.fetchTimestamp = System.currentTimeMillis();
|
||||
bundle.success = true;
|
||||
|
||||
Log.i(TAG, "TimeSafari data fetch completed successfully");
|
||||
Log.i(TAG, "ENH|FETCH_ALL_OK timestamp=" + bundle.fetchTimestamp);
|
||||
return bundle;
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error processing TimeSafari data", e);
|
||||
Log.e(TAG, "ENH|FETCH_ALL_ERR processing err=" + e.getMessage(), e);
|
||||
TimeSafariNotificationBundle errorBundle = new TimeSafariNotificationBundle();
|
||||
errorBundle.success = false;
|
||||
errorBundle.error = e.getMessage();
|
||||
@@ -266,7 +300,7 @@ public class EnhancedDailyNotificationFetcher extends DailyNotificationFetcher {
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error starting TimeSafari data fetch", e);
|
||||
Log.e(TAG, "ENH|FETCH_ALL_ERR start err=" + e.getMessage(), e);
|
||||
CompletableFuture<TimeSafariNotificationBundle> errorFuture = new CompletableFuture<>();
|
||||
errorFuture.completeExceptionally(e);
|
||||
return errorFuture;
|
||||
@@ -320,7 +354,7 @@ public class EnhancedDailyNotificationFetcher extends DailyNotificationFetcher {
|
||||
private <T> CompletableFuture<T> makeAuthenticatedRequest(String url, Class<T> responseClass) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
Log.d(TAG, "Making authenticated GET request to: " + url);
|
||||
Log.d(TAG, "ENH|HTTP_GET_START url=" + url.substring(0, Math.min(100, url.length())) + "...");
|
||||
|
||||
// Create HTTP connection
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
@@ -330,19 +364,23 @@ public class EnhancedDailyNotificationFetcher extends DailyNotificationFetcher {
|
||||
|
||||
// Enhance with JWT authentication
|
||||
jwtManager.enhanceHttpClientWithJWT(connection);
|
||||
Log.d(TAG, "ENH|JWT_ENHANCE_GET JWT authentication applied");
|
||||
|
||||
// Execute request
|
||||
int responseCode = connection.getResponseCode();
|
||||
Log.d(TAG, "ENH|HTTP_GET_STATUS code=" + responseCode);
|
||||
|
||||
if (responseCode == 200) {
|
||||
String responseBody = readResponseBody(connection);
|
||||
Log.d(TAG, "ENH|HTTP_GET_OK bodySize=" + (responseBody != null ? responseBody.length() : 0));
|
||||
return parseResponse(responseBody, responseClass);
|
||||
} else {
|
||||
Log.e(TAG, "ENH|HTTP_GET_ERR code=" + responseCode);
|
||||
throw new IOException("HTTP error: " + responseCode);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error in authenticated request", e);
|
||||
Log.e(TAG, "ENH|HTTP_GET_ERR exception err=" + e.getMessage(), e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
@@ -359,7 +397,7 @@ public class EnhancedDailyNotificationFetcher extends DailyNotificationFetcher {
|
||||
private <T> CompletableFuture<T> makeAuthenticatedPostRequest(String url, Map<String, Object> requestBody, Class<T> responseChallass) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
Log.d(TAG, "Making authenticated POST request to: " + url);
|
||||
Log.d(TAG, "ENH|HTTP_POST_START url=" + url.substring(0, Math.min(100, url.length())) + "...");
|
||||
|
||||
// Create HTTP connection
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
@@ -371,23 +409,28 @@ public class EnhancedDailyNotificationFetcher extends DailyNotificationFetcher {
|
||||
// Enhance with JWT authentication
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
jwtManager.enhanceHttpClientWithJWT(connection);
|
||||
Log.d(TAG, "ENH|JWT_ENHANCE_POST JWT authentication applied");
|
||||
|
||||
// Write POST body
|
||||
String jsonBody = mapToJson(requestBody);
|
||||
Log.d(TAG, "ENH|HTTP_POST_BODY bodySize=" + jsonBody.length());
|
||||
connection.getOutputStream().write(jsonBody.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Execute request
|
||||
int responseCode = connection.getResponseCode();
|
||||
Log.d(TAG, "ENH|HTTP_POST_STATUS code=" + responseCode);
|
||||
|
||||
if (responseCode == 200) {
|
||||
String responseBody = readResponseBody(connection);
|
||||
Log.d(TAG, "ENH|HTTP_POST_OK bodySize=" + (responseBody != null ? responseBody.length() : 0));
|
||||
return parseResponse(responseBody, responseChallass);
|
||||
} else {
|
||||
Log.e(TAG, "ENH|HTTP_POST_ERR code=" + responseCode);
|
||||
throw new IOException("HTTP error: " + responseCode);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error in authenticated POST request", e);
|
||||
Log.e(TAG, "ENH|HTTP_POST_ERR exception err=" + e.getMessage(), e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user