001// SPDX-License-Identifier: GPL-3.0-or-later
002// Copyright © 2026 Aurélien Pierre
003
004package com.aurelienpierre.sioul;
005
006import android.app.Notification;
007import android.app.NotificationManager;
008import android.app.PendingIntent;
009import android.content.Context;
010import android.content.Intent;
011import android.graphics.drawable.Icon;
012import android.net.Uri;
013import android.util.Log;
014
015import org.json.JSONException;
016import org.json.JSONObject;
017
018/**
019 * New mail at the times it may come (docs/porch.md, "Notifications"; Rust
020 * decides what and when: crates/sioul-app/src/mailnote.rs): one notification
021 * for a batch, "Two letters", the first senders with their subjects below,
022 * the next batch in its place. The "New mail" channel, at low importance: no
023 * sound, no banner, yours to change in Android's settings. A batch with
024 * someone Always through in it (Rust's `through`), while one of Sioul's modes
025 * silences the phone: its twin that passes do-not-disturb, at Android's
026 * default importance (its sound, no banner), made then (PauseMode). A tap,
027 * or Open, brings Sioul up on the Porch (ReminderOpener). On the lock
028 * screen, the count alone. From any process: the window's, or the one that
029 * fetches mail while Sioul is away.
030 */
031final class MailNotes
032{
033    /** The "New mail" channel. */
034    static final String CHANNEL = "mail";
035    private static final String TAG = "mail";
036    private static final int NOTE = 1;
037    /** Gone from the shade after half a day: the Porch keeps the mail. */
038    private static final long KEPT_MS = 12 * 60 * 60 * 1000L;
039
040    private MailNotes() {}
041
042    /** Rust's notification: JSON {title, body, through, words {mail, mail-through, open}}. Any thread. */
043    static void show(Context context, String json)
044    {
045        final JSONObject said;
046        try {
047            said = new JSONObject(json);
048        } catch (JSONException e) {
049            Log.e(DoseAlarms.TAG, "Mail: a notification that does not read. " + e);
050            return;
051        }
052        JSONObject words = said.optJSONObject("words");
053        String usual = words == null ? "" : words.optString("mail");
054        String passing = words == null ? "" : words.optString("mail-through");
055        String openWord = words == null ? "" : words.optString("open");
056        context.getSystemService(NotificationManager.class).createNotificationChannel(
057            Channels.quiet(CHANNEL, usual.isEmpty() ? EventAlarms.word(context, "mail") : usual, NotificationManager.IMPORTANCE_LOW));
058        String channel = PauseMode.mailChannelNow(context, said.optBoolean("through", false), passing.isEmpty() ? EventAlarms.word(context, "mail-through") : passing);
059        String title = said.optString("title");
060        String body = said.optString("body");
061        Intent porch = new Intent(context, ReminderOpener.class).setData(Uri.fromParts(ReminderOpener.SCHEME, "porch", null)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
062        PendingIntent open = PendingIntent.getActivity(context, NOTE, porch, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
063        // On the lock screen: how many, never who.
064        Notification count = new Notification.Builder(context, channel)
065            .setSmallIcon(R.drawable.sioul_notification)
066            .setColor(context.getColor(R.color.sioul_green))
067            .setContentTitle(title)
068            .build();
069        Notification note = new Notification.Builder(context, channel)
070            .setSmallIcon(R.drawable.sioul_notification)
071            .setColor(context.getColor(R.color.sioul_green))
072            .setContentTitle(title)
073            .setContentText(body)
074            .setStyle(new Notification.BigTextStyle().bigText(body))
075            .setCategory(Notification.CATEGORY_EMAIL)
076            .setVisibility(Notification.VISIBILITY_PRIVATE)
077            .setPublicVersion(count)
078            .setContentIntent(open)
079            .setAutoCancel(true)
080            .setTimeoutAfter(KEPT_MS)
081            .addAction(new Notification.Action.Builder(Icon.createWithResource(context, R.drawable.sioul_notification), openWord.isEmpty() ? EventAlarms.word(context, "open") : openWord, open).build())
082            .build();
083        context.getSystemService(NotificationManager.class).notify(TAG, NOTE, note);
084    }
085}