本文へスキップ
すべての記事

Android 16のLive Updates: Mintlyの集中タイマーをロック画面に表示する

Android 16の進捗中心型通知の実践ガイド — ProgressStyleのセグメント、昇格されたオンゴーイング通知、そして実行中のポモドーロタイマーにそれらが何をもたらすか。

MFKAPPS 1 分で読めます

アプリの中だけで動いているタイマーは、誰も確認しないタイマーだ。ポモドーロセッションの意義は、それを見張るのをやめられることにある——作業から顔を上げたときにロック画面をちらっと見て、残りの集中時間を確認し、また作業に戻る。Android 16は、オンゴーイング通知においてこの「ちらっと見る」動作にようやく本当の答えを用意した。それがLive Updatesだ。ProgressStyleを中心に構築された昇格型の通知で、通知シェードに埋もれる代わりにロック画面の一角とステータスバーのコンパクトなチップを獲得する。ここでは、それが実際に何をするのか、そしてMintlyがセッション中に表示する通知をどう変えたのかを説明する。

Live Updatesが通常のオンゴーイング通知に対して追加するもの

進捗バー付きの、消去できないオンゴーイング通知はAndroidで何年も前から動いている——これがLive Updates以前にMintlyのタイマー通知を動かしていたものだ。それが得られなかったのは優先的な配置だった。通常のオンゴーイング通知はシェードの中で他のすべてと並んで置かれ、通知リストが混雑した端末では数ある行の一つに過ぎない。

Live Updateは同じ内容に対してNotification.Builderの呼び出し一つ分の違いしかないが、システムはそれを異なる扱いにする。他の通知が折りたたまれていてもロック画面に表示される資格があり、対応端末ではステータスバーの時計付近に小さなライブチップとして描画されることもある。その昇格された配置を実際に許可するかどうかはシステムが決める——リクエストしても保証はされない——が、Androidがこのために設計した通知の種類(近づいてくる配車、配達中の荷物、カウントダウンするタイマー)にとっては、「ユーザーがロックを解除してシェードを引き下ろす必要がある」のと「ユーザーが何も触れずにロック画面で読み取れる」ことの違いになる。

ProgressStyleをタイマー通知に組み込む

スタイル自体は、すでに構築している通知への小さな追加にすぎない。

val progressStyle = NotificationCompat.ProgressStyle()
    .setProgress(elapsedSeconds)
    .setProgressMax(totalSeconds)
    .setProgressIndeterminate(false)

val notification = NotificationCompat.Builder(context, CHANNEL_FOCUS_SESSION)
    .setContentTitle("Focus session")
    .setContentText(formatRemaining(totalSeconds - elapsedSeconds))
    .setSmallIcon(R.drawable.ic_mintly_timer)
    .setOngoing(true)
    .setOnlyAlertOnce(true)
    .setStyle(progressStyle)
    .setRequestPromotedOngoing(true)
    .build()

立ち止まって考える価値がある設定はsetRequestPromotedOngoing(true)だけだ。これはスイッチではなくリクエストであり、プラットフォームは拒否できる。ユーザーが今まさに能動的に待っている何かを本当に表しているような通知のために用意されたものであって、順番を割り込む汎用の手段ではない。実際に動作しているタイマーはこれに該当する。10分前に終わったセッションから残った古い通知は該当しない。実際には何も進行していないのにこれをリクエストするアプリからの昇格リクエストは、プラットフォーム自身のヒューリスティクスによって無視され始める。

セグメントがバーを実際のセッションマップに変える

Mintlyにとってこれを採用する価値があったのは、ロック画面への配置そのものではなく、ProgressStyleのセグメントサポートだった。単一の連続したバーの代わりに、進捗を色分けされた範囲の連なりとして記述できる。これはポモドーロセッションが実際にどう構成されているか——作業ブロック、短い休憩、作業ブロック、長めの休憩——に直接対応する。

val segments = listOf(
    ProgressStyle.Segment(25 * 60).setColor(mintlyFocusColor),
    ProgressStyle.Segment(5 * 60).setColor(mintlyBreakColor),
    ProgressStyle.Segment(25 * 60).setColor(mintlyFocusColor),
    ProgressStyle.Segment(15 * 60).setColor(mintlyLongBreakColor),
)

progressStyle.setProgressSegments(segments)

この一つの変更が、以前のフラットな進捗バーでは決して答えられなかった問いに答える。「このセッションの残り時間はどれくらいか」だけでなく、「サイクルの中で自分は今どこにいて、次の本当の休憩までどれくらいか」だ。ポイントはProgressStyleが提供するもう一つの要素で、バーに沿った小さなマーカーだ。中断のチェックポイントに自然に馴染むが、Mintlyはまだ使っていない。

陥りやすい失敗: これを装飾として扱ってしまうこと

APIの表面は小さく、見た目の向上は本物なので、アプリが持つすべてのオンゴーイング通知にProgressStyleと昇格リクエストを付け加えたくなる。それは間違った直感だ。Live Updatesは狭いカテゴリのために存在する——時間的に区切られていて、実際に進行中で、ユーザーが今まさに気にかけている何か——そして、そのカテゴリの外側で昇格をリクエストするアプリをプラットフォームは監視している。進捗バーのふりをした「3日間これを開いていません」というリマインダーを昇格させるアプリは、まさにこの機能が防ごうとしている乱用のケースであり、悪質なリクエストが繰り返されれば、そのアプリの将来の昇格リクエストが静かに優先度を下げられることにもつながりかねない。

setRequestPromotedOngoing(true)をどこかに追加する前の正直なテストはこうだ。この通知の背後に、バー自体からユーザーが予測できる具体的な時刻に終わる、本物の実行中プロセスがあるか。集中タイマーであれば、あらゆる点でイエスだ。典型的なローカルファーストアプリの他のほとんどの通知タイプでは、ノーだ——そしてすでに持っているシンプルな通知こそ、そのまま維持すべきものだ。

これが置き換えないもの

Live Updatesはタイマー通知がどこで見られるかを変える。そもそもそれが確実に発火するかどうかは変えない——それは依然としてMintlyがDozeやプロセスの強制終了を生き延びるためにすでに頼っているフォアグラウンドサービスと壁時計ベースの終了時刻計算の話だ。ProgressStyleと昇格リクエストはその基盤の上に乗っているにすぎない。それらはプレゼンテーション層であり、根底にあるタイマーを正しく作ることの代わりにはならない。まず信頼できるバージョンを構築すること。ロック画面のチップは、それがもう疑いようのないものになってから加える価値がある——その前ではない。