001// SPDX-License-Identifier: GPL-3.0-or-later 002// Copyright © 2026 Aurélien Pierre 003 004package com.aurelienpierre.sioul; 005 006import android.content.BroadcastReceiver; 007import android.content.Context; 008import android.content.Intent; 009import android.os.Handler; 010import android.os.Looper; 011import android.util.Log; 012 013import java.util.concurrent.atomic.AtomicBoolean; 014 015/** 016 * A button of the time running (TimeNote): Pause, Go on or Stop, done by 017 * Rust's side (crates/sioul-app/src/timenote.rs) on a thread of its own, the 018 * broadcast held open meanwhile (goAsync), Sioul's window running or not. 019 * Rust reads first what your other devices did, so that a button a moment 020 * behind changes nothing of it, then shows the note as it follows itself: 021 * two buttons pressed in a row show in their order. Android gives a button's 022 * broadcast ten seconds: past eight it is let go, and Rust's note comes when 023 * it is done. 024 */ 025public final class TimeReceiver extends BroadcastReceiver 026{ 027 private static final long DEADLINE_MS = 8_000; 028 029 @Override 030 public void onReceive(Context context, Intent intent) 031 { 032 final String action = word(intent.getAction()); 033 if (action.isEmpty()) 034 return; 035 final Context app = context.getApplicationContext(); 036 final PendingResult broadcast = goAsync(); 037 final AtomicBoolean finished = new AtomicBoolean(); 038 final Runnable letGo = () -> { 039 if (finished.compareAndSet(false, true)) 040 broadcast.finish(); 041 }; 042 final Handler main = new Handler(Looper.getMainLooper()); 043 main.postDelayed(letGo, DEADLINE_MS); 044 new Thread(() -> { 045 try { 046 DoseAlarms.load(app); 047 String note = TimeNote.nativeAction(action); 048 Log.i(DoseAlarms.TAG, "Time: " + action + (note == null || note.isEmpty() ? ", no session left." : ", done.")); 049 } catch (Throwable e) { 050 // Sioul's library that does not load, among others: the note stays as it was. 051 Log.e(DoseAlarms.TAG, "Time: " + action + " not done", e); 052 } 053 main.removeCallbacks(letGo); 054 letGo.run(); 055 }, "sioul-time").start(); 056 } 057 058 /** The button's action as Rust reads it: "pause", "resume", "stop"; "" for any other. */ 059 private static String word(String action) 060 { 061 if (TimeNote.PAUSE.equals(action)) 062 return "pause"; 063 if (TimeNote.RESUME.equals(action)) 064 return "resume"; 065 if (TimeNote.STOP.equals(action)) 066 return "stop"; 067 return ""; 068 } 069}