001// SPDX-License-Identifier: GPL-3.0-or-later 002// Copyright © 2026 Aurélien Pierre 003 004package com.aurelienpierre.sioul; 005 006import android.app.NotificationManager; 007 008import java.util.Map; 009 010/** 011 * What a change of the phone's own do-not-disturb means for Sioul's switch 012 * (docs/do-not-disturb.md, "Both ways"; DndReceiver), decided in Java so that 013 * most changes end without loading Sioul's library. Pure, checked on a JVM 014 * (android/jvm-checks/HeardCheck.java). 015 * 016 * - The phone's do-not-disturb is one fact: on (the interruption filter is 017 * not ALL) or off. A change of it from the state last seen, read once 018 * Sioul's own change settled, that disagrees with Sioul's do-not-disturb 019 * here is a press; one that agrees is Sioul's own coming back, or nothing. 020 * Rust has the last word (`everywhere::heard_from_system`). 021 * - Sioul's own modes say their own state too 022 * (ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED): one turned off while Sioul 023 * wants it on is an "off", even while another mode keeps the phone 024 * silenced; one turned on while Sioul wants none, an "on". Each is 025 * confirmed against the mode as Android has it now (`live`): a status that 026 * came late, after Sioul's own change, is no press. 027 */ 028final class DndHeard 029{ 030 /** Look again once Sioul's own change settled: this, then the milliseconds to wait. */ 031 static final String LATER = "later:"; 032 033 /** A mode of Sioul's as Android has it now ({@link PauseMode#live}). */ 034 static final int LIVE_UNKNOWN = 0; 035 static final int LIVE_ON = 1; 036 static final int LIVE_OFF = 2; 037 static final int LIVE_DISABLED = 3; 038 static final int LIVE_GONE = 4; 039 040 private DndHeard() {} 041 042 /** 043 * `kept`: PauseMode's record ({@code on.<kind>}, `sioul_on`, `changing_until`, 044 * `seen_on`); `action`: the broadcast's; `status`, `kind` and `live`: the 045 * mode's, for a status; `filter`: the interruption filter now; `now`: 046 * SystemClock.elapsedRealtime(). Answers "on", "off", LATER and a delay, 047 * or "" (nothing for Sioul). The caller notes the filter seen (`seen`). 048 */ 049 static String decide(boolean access, Map<String, ?> kept, String action, int status, String kind, int live, int filter, long now) 050 { 051 if (!access) 052 return ""; 053 long until = number(kept.get("changing_until")); 054 if (now < until) 055 return LATER + (until - now + 300); 056 boolean sioul = Boolean.TRUE.equals(kept.get("sioul_on")); 057 if (NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED.equals(action)) { 058 if (kind == null) 059 return ""; 060 boolean wanted = Boolean.TRUE.equals(kept.get("on." + kind)); 061 switch (status) { 062 case NotificationManager.AUTOMATIC_RULE_STATUS_DEACTIVATED: 063 return wanted && (live == LIVE_OFF || live == LIVE_DISABLED || live == LIVE_GONE) ? "off" : ""; 064 case NotificationManager.AUTOMATIC_RULE_STATUS_DISABLED: 065 return wanted && (live == LIVE_DISABLED || live == LIVE_GONE) ? "off" : ""; 066 case NotificationManager.AUTOMATIC_RULE_STATUS_REMOVED: 067 return wanted && live == LIVE_GONE ? "off" : ""; 068 case NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED: 069 return !wanted && !sioul && live == LIVE_ON ? "on" : ""; 070 default: 071 return ""; 072 } 073 } 074 // The filter: any change of the phone's do-not-disturb, whatever caused it. 075 if (filter == NotificationManager.INTERRUPTION_FILTER_UNKNOWN) 076 return ""; 077 boolean on = seen(filter); 078 Object before = kept.get("seen_on"); 079 // First sight (found, not heard), or no change of on and off. 080 if (!(before instanceof Boolean) || (Boolean) before == on) 081 return ""; 082 return on == sioul ? "" : on ? "on" : "off"; 083 } 084 085 /** 086 * The state noted as seen at a restart or an update ({@link PauseMode#noteSeen}), 087 * so that the next change counts from it: on or off; null when Android 088 * does not say (nothing noted). 089 */ 090 static Boolean noted(int filter) 091 { 092 return filter == NotificationManager.INTERRUPTION_FILTER_UNKNOWN ? null : seen(filter); 093 } 094 095 /** The phone's do-not-disturb on for this filter. */ 096 static boolean seen(int filter) 097 { 098 return filter != NotificationManager.INTERRUPTION_FILTER_ALL && filter != NotificationManager.INTERRUPTION_FILTER_UNKNOWN; 099 } 100 101 private static long number(Object value) 102 { 103 return value instanceof Number ? ((Number) value).longValue() : 0; 104 } 105}