Class AppNotes

All Implemented Interfaces:
ComponentCallbacks, ComponentCallbacks2

public final class AppNotes extends NotificationListenerService
Other apps' notifications held until their time (docs/android.md, "Notifications from other apps"; crates/sioul-app/src/appnotes.rs, which decides; crates/sioul-core/src/appnotes.rs, how). Android's notification listener, bound by the system once the person gives Sioul "Notification access", in a process of its own (":listener"): Qt's window ends its whole process as it closes (System.exit in QtActivityBase.onDestroy), which would unbind the listener at each close, and the background service's process (":steps") runs only when its own setting is on and does long work a notification must not wait for. Here, Sioul's library without Qt's window (DoseAlarms.load(android.content.Context)), as for the doses. For each notification of another app: what Android says of it, its words included, handed to Rust in memory on a thread of its own; Rust answers how long it waits, and Android holds it that long (snoozeNotification): it comes back whole, with its own tap and actions, alerting as its channel says. Nothing is ever cancelled, kept or logged; of a conversation, only where its page is in Android (its app, shortcut and channel, a week), for the Other apps tab to open it. Nothing is answered or marked as read but what you ask from a computer, once, through the notification's own Reply or Mark as read (answer). Never asked about: Sioul's own, ongoing ones (a call, music, a download), those that cannot be cleared. What is held is worked out again (`review`) when something changes that moves its time: a pause ends, the switch of do-not-disturb, the hours, the choices. Sioul's files are watched for that. Replies from your computers (Replies.java; crates/sioul-app/src/replies.rs): what each relayed notification offers (a Reply, a Mark as read) is said to Rust with it, and its going when Android takes it away; what the background step hands over in Sioul's private replies folder is sent here, the listener alone holding the notifications, each once (answer).
  • Field Details

    • TAG

      static final String TAG
      The log's tag, the doses' own: `adb logcat -s sioul` shows both.
      See Also:
    • REDACTED

      private static final String REDACTED
      Android's words in a notification it redacted for an untrusted listener (Android 15).
      See Also:
    • ALERTED_MS

      private static final long ALERTED_MS
      A sound or vibration this recent was the notification's own, as it came.
      See Also:
    • SETTLE_MS

      private static final long SETTLE_MS
      Changes to Sioul's files settle this long before what is held is worked out again.
      See Also:
    • WATCHED

      private static final String[] WATCHED
      The files whose change may move what is held: the pause and free time, the switch of do-not-disturb and its people, a focus session, the hours and settings (who may reach you when among them), the lists of senders, the choices, Health's meals and nights, today's slots of time for you (which the window writes as it lays out the day).
    • connected

      private static volatile boolean connected
      Bound by Android, the access given: notifications are decided and held only then.
    • thread

      The listener's own thread, made in onCreate, ended in onDestroy: nothing is decided on Android's main thread.
    • worker

      private Handler worker
      That thread's queue: each notification, each review and the files' watch go through it, one at a time.
    • watches

      private final List<FileObserver> watches
      The watches of Sioul's folders (`watch`), stopped when the listener is unbound or ends.
    • labels

      private final Map<String,String> labels
      Each app's name as the phone shows it, by package, found once a process (`label`).
    • redactedWords

      Android's words in a redacted notification, in the phone's language; read once (`redacted`).
    • smsApp

      private String smsApp
      The phone's default SMS app's package, "" for none (`smsApp`), and when it was last asked.
    • smsAppAt

      private long smsAppAt
    • review

      private final Runnable review
      What is held worked out again after Sioul's files changed: one runnable, so that a burst of changes runs it once.
    • answer

      private final Runnable answer
      What is due sent after the step wrote it: one runnable, so that a burst of changes runs it once.
    • REPLIES

      private static final String REPLIES
      Sioul's private folder for replies, in its state (Rust's `replies::private_folder`).
      See Also:
    • DUE

      private static final String DUE
      The file of what is due there, written whole by the background step.
      See Also:
    • ANSWERED

      private static final String ANSWERED
      The conversations answered from a computer lately, there (Rust's `replies::ANSWERED`): what is held of them comes now.
      See Also:
    • DUE_SETTLE_MS

      private static final long DUE_SETTLE_MS
      Changes to what is due settle this long before it is read.
      See Also:
    • TALKS

      private static final String TALKS
      What Android says of each conversation seen lately, by Rust's key for it (crates/sioul-core/src/appnotes.rs, `talk_key` of its app and its shortcut): its app, its shortcut and its channel, so that the Other apps tab can open its page in Android, where a conversation set to Always through is marked important (priority), which Sioul's modes let through (PauseMode). This phone's alone, in Sioul's private storage, never shared; forgotten after a week unseen, as the tab forgets its name.
      See Also:
    • TALK_KEPT_MS

      private static final long TALK_KEPT_MS
      A conversation unseen for eight days is forgotten (`forgotten`).
      See Also:
    • TALKS_AT_MOST

      private static final int TALKS_AT_MOST
      At most this many conversations kept; past them, the oldest seen go first.
      See Also:
    • TALK_AGAIN_MS

      private static final long TALK_AGAIN_MS
      Written again at most this often when nothing changed: not at each message.
      See Also:
    • CONVERSATIONS_PAGE

      private static final String CONVERSATIONS_PAGE
      Android's list of conversations (Settings ▸ Notifications ▸ Conversations), Android 11 and later.
      See Also:
  • Constructor Details

  • Method Details

    • nativeDecide

      static String nativeDecide(String json)
      Rust's answer for one notification: {"hold": milliseconds, 0 to let it come, "why", "line"}.
    • nativeReview

      static String nativeReview(String json)
      What is held, worked out again: {"snooze": [{"key", "ms"}]}, for the keys Android holds now.
    • nativeReply

      static String nativeReply(String verb, String json)
      Replies from your computers (crates/sioul-app/src/replies.rs): "due" (what is due now: {due: [{key, verb, notice}]}), "check" ({key, held, posted}: the live notification found for one, {go, crossed} or {go: false, state}), "taken" ({key}: written and flushed first, {go, words}), "result" ({key, state, crossed, held}), "removed" ({package, key}: a relayed notification gone, {shared}).
    • call

      static String call(Context context, String verb, String json)
      Rust's question (android/main.cpp), from the window's process: the access, Android's pages for it and for each app's, channel's or conversation's notifications, the contacts' permission, what Android says of the conversations seen lately. Any thread.
    • component

      private static ComponentName component(Context context)
      This listener, as Android's settings and NotificationManager name it.
    • granted

      static boolean granted(Context context)
      Whether the person gave Sioul "Notification access".
    • openAccess

      private static boolean openAccess(Context context)
      Android's page for Sioul's notification access: its own switch from Android 11, else the list.
    • open

      private static boolean open(Context context, Intent page)
      One of Android's pages opened, in a task of its own; false when this phone has no such page or refuses it.
    • talks

      private static SharedPreferences talks(Context context)
      Written by the listener's process, read by the window's.
    • talkKey

      static String talkKey(String pkg, String id)
      A conversation's key as Rust's `talk_key` makes it: FNV-1a (64 bits) of its app, U+001F and its id, in hexadecimal. Pure, checked on a JVM against Rust's values (android/jvm-checks/ModeCheck.java).
    • forgotten

      static List<String> forgotten(Map<String,?> kept, long now)
      The keys to forget among those kept (each a JSON text with its time "at"): those unseen for a week, then the oldest past the most kept. Pure, checked on a JVM (android/jvm-checks/ModeCheck.java).
    • remember

      private static void remember(Context context, String pkg, String shortcut, String channel, NotificationChannel given, boolean conversation, long now)
      A conversation seen (the listener, on its worker thread): its app, its shortcut, the channel it was posted on (its parent's when Android gave it a channel of its own), whether Android takes it as a conversation, whether it is marked important there, and whether it has a page of its own there yet (customized once). Written when something changed, or once a day.
    • talk

      private static JSONObject talk(Context context, String key)
      One conversation as kept, by Rust's key; null when not seen lately.
    • conversations

      static JSONObject conversations(Context context) throws JSONException
      What Android says of the conversations seen lately, for the Other apps tab: {api, conversations: {key: {important, page}}}: marked important there; `page`, Android takes it as a conversation, whose page Sioul can open (Android 11 and later).
      Throws:
      JSONException
    • pages

      static String[] pages(int api, boolean conversation, boolean own, boolean channel)
      Which of Android's pages a conversation is opened at, in order, the next one when a page does not open: "conversation", its own page (where Priority is), once Android gave it one; "conversations", the list of conversations, where one not changed yet shows among the recent ones; "app", the app's notifications. Pure, checked on a JVM (android/jvm-checks/ModeCheck.java).
    • openConversation

      private static boolean openConversation(Context context, JSONObject asked)
      A conversation's page in Android: {key} (Rust's key for it), or {package, shortcut, channel}. False when no page opened.
    • onCreate

      public void onCreate()
      The listener's own thread started, before Android binds it.
      Overrides:
      onCreate in class Service
    • onDestroy

      public void onDestroy()
      Ended: nothing held from here on, the watches stopped, the thread let finish what it was doing.
      Overrides:
      onDestroy in class NotificationListenerService
    • onListenerConnected

      public void onListenerConnected()
      Bound, the access given (at the phone's start, when the access is given, after Android restarted the listener): Sioul's files watched, and what Android holds already worked out again.
      Overrides:
      onListenerConnected in class NotificationListenerService
    • onInterruptionFilterChanged

      public void onInterruptionFilterChanged(int interruptionFilter)
      The phone's do-not-disturb changed, whatever changed it: Android tells a bound listener of every change (NotificationManagerService, notifyInterruptionFilterChanged), a word surer than the broadcast to the packages with Do Not Disturb access. Decided in Sioul's own process, where its modes are kept (DndReceiver.HEARD), as the broadcast is.
      Overrides:
      onInterruptionFilterChanged in class NotificationListenerService
    • onListenerDisconnected

      public void onListenerDisconnected()
      Unbound: the access taken back, or Android stopping the listener.
      Overrides:
      onListenerDisconnected in class NotificationListenerService
    • onNotificationPosted

      Another app's notification, posted or updated: asked about on the worker thread with the ranking Android gave with it. Never Sioul's own, an ongoing one (a call, music, a download), or one that cannot be cleared.
      Overrides:
      onNotificationPosted in class NotificationListenerService
    • onNotificationRemoved

      A notification gone, as Android says it: for one relayed to your computers, its going said there (Rust's `replies::went`), so that they no longer offer its Reply. Never for Sioul's own holding (REASON_SNOOZED), which is no going.
      Overrides:
      onNotificationRemoved in class NotificationListenerService
    • removed

      private void removed(String pkg, String key)
      A notification's going said to Rust, and a step soon when your computers have it to read. On the worker thread.
    • decide

      Rust asked, the answer followed: held so long, or left as it came. On the worker thread.
    • snooze

      private void snooze(String key, long ms)
      A notification held by Android for `ms` (a second at least), then given back whole, as it came.
    • review

      private void review(String reason)
      What is held, worked out again; Android told the new times. On the worker thread. One calm line in the log each time (`adb logcat -s sioul`): how many are held and released, by kind, why, and the next release (Rust's `log_line`: kinds, counts and times, never a name, a number, an app or a message's words); or, when Rust could not answer, that in plain words.
    • watch

      private void watch()
      The folders of Sioul's state, configuration and data, watched for the files that move what is held.
    • watchReplies

      private void watchReplies()
      The private folder of replies watched for what is due (made first, so that it can be watched before the step writes there): the background step writes it whole beside, then puts it in place. And for the conversations answered lately: what Sioul holds of one is worked out again, to come now.
    • unwatch

      private void unwatch()
      The watches of Sioul's folders stopped.
    • answer

      private void answer()
      Each reply or Mark as read due now (handed over by the background step, which decided it once): sent through its notification's own action, or said why not (answerOne(org.json.JSONObject)); a step soon, so that what became of it reaches your computers. On the worker thread.
    • answerOne

      private boolean answerOne(JSONObject due) throws JSONException
      One due: its notification found again live by the name the line gave it, among what Android shows, then what Sioul holds, of this phone's own profile; Rust's check that it is still the conversation answered; its Reply (or Mark as read) chosen by what it means and refused when it may not go; "taken" written and flushed; then sent once, as the shade sends one. Whether anything was said to Rust.
      Throws:
      JSONException
    • result

      private boolean result(String key, String state, boolean crossed, boolean held) throws JSONException
      What became of one, said to Rust, which writes it (never the words); always true.
      Throws:
      JSONException
    • silencesTexts

      private boolean silencesTexts()
      Whether do-not-disturb silences every text now (`textsSilenced`), as Android says it.
    • textsSilenced

      static boolean textsSilenced(int filter, boolean known, int categories, int conversationSenders)
      Whether a filter silences every text (review of part 4, S-2). Android ranks no held notification, so a held text cannot be asked whether it would ring: "nothing" and "alarms only" silence every text; "priority" only when its policy, as Android 15 and later give it in force (`getConsolidatedNotificationPolicy`, Sioul's mode or yours), lets neither messages nor conversations ring; an older Android's "priority" may let starred contacts' texts ring, so it reads as not silenced, and a text held then waits (docs/android.md). At posting, each text's own ranking says it (`intercepted`).
    • ranking

      The ranking Android gives a notification now; null when it gives none (a held one).
    • describe

      The notification as Rust reads it (crates/sioul-core/src/appnotes.rs, `Posted`).
      Throws:
      JSONException
    • text

      private static String text(Bundle extras, String key)
      A text of the extras, a CharSequence or a String; "" when none.
    • messages

      private JSONArray messages(Bundle extras, boolean contacts) throws JSONException
      A conversation's messages (MessagingStyle), each with its sender as the app names them; the phone's address book asked about the last one's sender only, the one Rust judges.
      Throws:
      JSONException
    • people

      private JSONArray people(Bundle extras, boolean contacts) throws JSONException
      The people a notification names (EXTRA_PEOPLE_LIST), and the older addresses (EXTRA_PEOPLE); the phone's address book asked about the first only, the one Rust judges when no message names a sender.
      Throws:
      JSONException
    • person

      private JSONObject person(CharSequence name, String key, String uri, boolean bot, boolean contacts) throws JSONException
      A person, with what the phone's address book says of them when Sioul may read it: a contact's lookup address resolved to its numbers and addresses; a name alone, the numbers of the one contact of that name (`by_name`: counted only for apps that name people as the address book does, Rust decides).
      Throws:
      JSONException
    • contactOfLookup

      private long contactOfLookup(String uri)
      The contact a lookup address names; -1 when none.
    • contactOfName

      private long contactOfName(String name)
      The one contact with exactly this name; -1 when none, or when several share it.
    • data

      private JSONArray data(long id, Uri table, String contact, String column)
      A contact's numbers or addresses.
    • label

      private String label(String pkg, Bundle extras)
      The app's name as the phone shows it, kept; else the one its notification carries; else its package.
    • redacted

      private boolean redacted(String title, String text, String app)
      Whether Android gave Sioul a redacted copy (Android 15, for a notification its assistant judged sensitive: a code): its title is the app's name and its text Android's own words for it.
    • smsApp

      private String smsApp()
      The phone's default SMS app, asked again each minute at most.