Review 1 of 6: Android platform feasibility

Reviewer lens: are the Android assumptions in PRD-original.md and DESIGN-DRAFT.md actually true?

Date: 2026-10-03. Method: primary sources only. That means AOSP main source pulled from android.googlesource.com, developer.android.com, Google help pages, and Google blogs. Where the only evidence was a secondary article or a search-engine summary, the item is marked so.

Legend:


1. Google Messages notification structure (design §C)

#AssumptionStatusEvidence / notes
1.1Google Messages posts MessagingStyle notifications, category=msg, pkg com.google.android.apps.messagingUNVERIFIEDNo primary source documents Google Messages' notification payload. The package name is right. Everything about style, category, channel names, and tag/id scheme is a spike item.
1.2The extra keys exist as namedVERIFIED (framework)Keys: android.title, android.text, android.messages (bundles with text, time, sender, sender_person), android.people.list, android.conversationTitle, android.isGroupConversation, android.messagingUser, android.messages.historic. Source: Notification.java EXTRA_* constants and MessagingStyle.Message KEY_SENDER="sender", KEY_SENDER_PERSON="sender_person". https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/app/Notification.java. Whether Google Messages fills each one is UNVERIFIED.
1.3android.people.list Person uri is tel: or a contacts lookup URI, which lets us infer "in contacts" without READ_CONTACTSPARTLY CONTRADICTEDSee the note after this table.
1.4The raw number is available for unknown senders and for saved contactsUNVERIFIED, likely asymmetricFor an unknown sender, android.title is probably the formatted number. For a saved contact, title and sender are probably the display name, so the raw number may not be present at all unless Person.uri is tel:. Consequence: keying sender state on HMAC(number) breaks for contacts. See design change D3.
1.5android.messages includes the user's own recent replies (sender == null or == messagingUser)VERIFIED (framework) / UNVERIFIED (Google Messages)MessagingStyle supports it: a null sender means "the user". Whether Google Messages includes outbound messages in the tail is a spike item.
1.6contentIntent, shortcutId, Reply (RemoteInput) and Mark-as-read actionsUNVERIFIEDGoogle Messages is very likely conversation-shortcut based (Android 11+ conversations). Spike must log it.
1.7Verified-business (RBM) badge, RCS metadata, attachments not visibleVERIFIED by absenceThe framework has no extras for any of these. Inline image MMS may surface as a uri/type field on a MessagingStyle message (framework supports setData). UNVERIFIED for Google Messages.
1.8Lock-screen "hide sensitive content" redacts what the listener receives (PRD §6.2 items 6–7)CONTRADICTED (good news)See the note after this table.
1.9NEW: Lockdown mode stops deliveryVERIFIEDNotificationManagerService skips listener posts while isInLockDownMode(userId) (lines ~11497, 13052, 13336, same file as row 1.8). Messages received during Lockdown are never seen. Treat them as completeness gaps.
1.10NEW: We can scope the listener to Google Messages onlyCONTRADICTED (in part)NotificationListenerFilter (Android 12+) is a type filter plus a disallow list. There is no OS-level "only package X" allow list (isVisibleToListener, NotificationManagerService). The system grants access to all notifications, and we filter by package in code. The consent screen and disclosure must say that honestly. Use META_DATA_DEFAULT_FILTER_TYPES = `conversations\alerting` to reduce the volume.

Note on 1.3 (Person uri). The Person.setUri javadoc says: "should be specified by the String representation of a ContactsContract.Contacts#CONTENT_LOOKUP_URI … The system will also attempt to resolve mailto: and tel: schema URIs. The path part of these URIs must exist in the contacts database … or the reference will be discarded as invalid." Source: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/app/Person.java

What this means:

Note on 1.8 (lock screen). The listener post path (notifyPostedLocked / isVisibleToListener in NotificationManagerService.java) applies no keyguard or lock-screen-visibility condition. Lock-screen redaction is SystemUI-only. It uses publicVersion and the allow-secure-notifications-on-lockscreen setting, which does not reach listeners. Source: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/services/core/java/com/android/server/notification/NotificationManagerService.java. Exception: an app-level setting inside Google Messages that changes what it posts would apply. That part is UNVERIFIED.

2. Android 15 sensitive-notification (OTP) redaction (design §C, §A rule 1)

VERIFIED, but the design's understanding of it is wrong in three ways.

Sources:

Who is exempt (any one of these is enough):

A sideloaded sidecar is untrusted unless Byron creates a CDM association with a real device or sets the appop over adb. Both are possible hacks. Both are rejected: redaction protects OTPs, which the PRD wants.

What the listener actually receives (redactStatusBarNotification):

Corrections to the design:

3. Restricted settings and sideload install path (design §C last bullet)

#AssumptionStatusEvidence
3.1Android 13+: a sideloaded app needs App info → ⋮ → "Allow restricted settings" before Notification Access can be enabledVERIFIEDhttps://support.google.com/android/answer/12623953
3.2Android 15 Enhanced Confirmation Mode (ECM) replaces this with an installer-based decisionVERIFIEDSee the note after this table.
3.3adb install is not guardedUNVERIFIEDSee the note after this table.
3.4NEW: Restricted settings are additionally blocked during a call with a non-contactVERIFIED (code)ECM UNTRUSTED_CALL_RESTRICTED_SETTINGS. Irrelevant to Byron. Worth noting only because it shows the posture: Google treats "notification access" as a scam-assist vector.
3.5NEW: Play Protect blocks internet-sideloaded apps that declare a notification listenerVERIFIED (select markets)See the note after this table.
3.6NEW: Android developer verificationVERIFIED, adb status UNVERIFIEDSee the note after this table.

Note on 3.2 (ECM). The ECM service isPackageEcmGuarded() (https://android.googlesource.com/platform/packages/modules/Permission/+/refs/heads/main/service/java/com/android/ecm/EnhancedConfirmationService.java) makes these settings protected:

A package is always guarded if its packageSource is PACKAGE_SOURCE_LOCAL_FILE or PACKAGE_SOURCE_DOWNLOADED_FILE, i.e. a browser or file-manager install. Allow-listed installers and preinstalled installers are trusted.

Note on 3.3 (adb install). An adb install has packageSource UNSPECIFIED. The outcome then depends on getInstallingPackageName(), which is null or com.android.shell, and on the trustPackagesInstalledViaNonAllowlistedInstallers flag. The code doesn't settle it. Spike action: install the probe with adb install from the Mac, then check whether the Notification Access toggle is greyed out.

Note on 3.5 (Play Protect). Play Protect "enhanced fraud protection" automatically blocks installation of apps from "web browsers, messaging apps, or file managers" that declare RECEIVE_SMS, READ_SMS, NOTIFICATION_LISTENER or ACCESSIBILITY. It is active "in select markets", and the US is not stated. https://developers.google.com/android/play-protect/warning-dev-guidance. Never distribute the APK by browser download or by messaging it to the phone. Use adb.

Note on 3.6 (developer verification). Certified devices begin requiring developer-verified apps on 2026-09-30 in BR, ID, SG and TH, and globally in 2027. There are limited-distribution (hobbyist, ≤20 devices) accounts and a power-user "advanced flow". https://developer.android.com/developer-verification. Whether adb installs are exempt is not stated on that page. Plan for a limited-distribution account before 2027 if Byron wants this to keep working on a certified phone.

4. Firing Google Messages' contentIntent from our activity (design §D "Open conversation")

VERIFIED feasible, with a required opt-in.

https://developer.android.com/guide/components/activities/background-starts

5. "Ask who this is" via ACTION_SENDTO smsto: (design §D)

#AssumptionStatusEvidence
5.1ACTION_SENDTO + smsto:<n> opens a compose screen without sendingVERIFIED (contract)https://developer.android.com/guide/components/intents-common#Messaging
5.2The body is passed in sms_bodyCONTRADICTED (docs)The official docs specify Intent.EXTRA_TEXT as the body extra. sms_body is a legacy de facto extra. Set both. Whether Google Messages honors either is UNVERIFIED; spike test.
5.3It opens Google MessagesNot guaranteedThe intent resolves to whatever handles SENDTO smsto:, which is normally the default SMS app. On a Samsung where Samsung Messages is default, it opens Samsung Messages. Use setPackage("com.google.android.apps.messaging") only if it resolves, otherwise use the chooser.
5.4It doesn't need a number we may not haveCONTRADICTED for saved contactsSee 1.4. For RCS-only or short-code senders, smsto: with a short code may produce an unsendable draft. Disable "Ask who this is" when no dialable number is known.
5.5We never fire RemoteInputDesign choice; goodNote: the redacted actions still carry RemoteInput (see §2). The code must never touch actions[] except to log their count.

6. Interruption control: cancel, snooze, importance (design §E)

#AssumptionStatusEvidence
6.1A listener can cancel or snooze another app's notificationVERIFIEDcancelNotification(key), cancelNotifications(keys), snoozeNotification(key, durationMs). See the note after this table.
6.2A listener cannot change another app's importance or alertingVERIFIEDSee the note after this table.
6.3Alerting has already happened by onNotificationPostedVERIFIED (by architecture)The listener is notified in parallel with or after SystemUI. There is no pre-alert hook for listeners. Only the Notification Assistant gets onNotificationEnqueued before posting.
6.4Rebind after process deathVERIFIED, partialSee the note after this table.
6.5Samsung battery killersVERIFIED (secondary but authoritative in practice)https://dontkillmyapp.com/samsung. Covers Sleeping / Deep sleeping apps, auto-optimization, and settings re-applied after firmware updates. Onboarding must walk through: "Never sleeping apps", Battery = Unrestricted, and turning off "Put unused apps to sleep".
6.6DozeLow riskNotification delivery to a bound listener is not deferred by Doze, because the post wakes the system. UNVERIFIED on Samsung; that is a spike item.

Note on 6.1 (cancel and snooze).

Note on 6.2 (importance). updateNotificationChannel(pkg, user, channel) exists on the listener, but it requires "an associated device [CDM] or be the notification assistant" (NotificationListenerService.java javadoc). BIND_NOTIFICATION_ASSISTANT_SERVICE is signature.

Note on 6.4 (rebind).

7. Spam-filed messages and Google Messages' own protection

#AssumptionStatusEvidence
7.1Messages Google Messages files as spam post no notificationUNVERIFIED (likely)See the note after this table.
7.2Google Messages already ships on-device conversational Scam DetectionVERIFIEDSee the note after this table.
7.3Google Messages also does link warnings for unknown senders and has an international non-contact filterVERIFIED (regional pilots, 2024)Same Oct 2024 blog post as 7.1.
7.4Reporting spam sends the number and the last 10 messages to GoogleVERIFIEDhttps://support.google.com/messages/answer/9061432. The UI must say this before the user taps Report in Google Messages.

Note on 7.1 (spam-filed messages). The primary source only says "When Google Messages suspects a potential scam text, it will automatically move the message into your spam folder or warn you." (https://security.googleblog.com/2024/10/5-new-protections-on-google-messages.html). The "no notification for spam-folder messages" wording came from a search summary, not a primary page.

The implications are the same either way:

Note on 7.2 (Scam Detection). It is on by default as part of Spam Protection. It only applies to conversations with non-contacts. It runs in English in the US, UK and Canada, and covers SMS, MMS and RCS. https://blog.google/security/new-ai-powered-scam-detection-features/

The design claims "No single-message filter sees this" about trajectory. That is CONTRADICTED in spirit: Google already does conversation-level detection. The differentiator must be explainability + the personal context ledger + "unknown ≠ malicious" verdicts, not trajectory detection per se.

Spike action: check whether Google Messages' scam warning shows up as a notification change, e.g. an updated text or extra.

8. Work profile and Private Space

#AssumptionStatusEvidence
8.1A personal-profile listener sees work-profile notificationsVERIFIED: yes, by defaultSee the note after this table.
8.2Private Space (Android 15)UNVERIFIEDNot investigated in source. Spike if Byron uses it.

Note on 8.1 (work profile). The DevicePolicyManager.setPermittedCrossProfileNotificationListeners javadoc says "By default all packages are permitted … When zero or more packages have been added, notification listeners installed on the primary user that are not in the list … won't receive events for managed profile notifications." https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/app/admin/DevicePolicyManager.java

So the sidecar will receive work-profile notifications unless the employer's DPC restricts it. That is a data-minimization and possibly employer-policy problem.

9. Play Store policy (only if ever published)

VERIFIED with limits.


Design changes required

  1. contact lookup key from Person.uri
  2. Person.key
  3. shortcutId
  4. title

Record which one was used. identity_confidence must treat "title is a name" as weak evidence of being a contact, not proof. That is UNVERIFIED until the spike: Google Messages may show caller-ID or business names for non-contacts.

What cannot be done without being the default SMS app

The same list applies to anything without READ_SMS, which the PRD forbids anyway.

  1. Read message history or the full thread. You get only the MessagingStyle tail Google Messages chose to post. That excludes outbound history beyond that tail, and messages read on another device or the web before a notification was posted.
  2. See messages that never notify. That covers spam-filed messages (likely), conversations the user muted or archived, messages arriving while Google Messages notifications are off, messages during Lockdown, and messages during our own downtime (unrecoverable afterwards).
  3. Act before alerting. You cannot suppress, delay or re-rank Google Messages' sound or vibration before it fires. You can only snooze or cancel after the fact, or ask the user to mute the channel.
  4. Block or unblock numbers programmatically. BlockedNumberContract: "Only the system, the default SMS application, and the default phone app … and carrier apps can read, and write to the blockednumber provider." https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/provider/BlockedNumberContract.java
  5. Report spam to Google or the carrier, delete, or archive. All of these must be done by the user inside Google Messages.
  6. Get attachments, RCS metadata, verified-business badges, read or delivery state, or canonical thread IDs.
  7. Get the OTP / sensitive content of notifications the OS redacts. Being the default SMS app would give you the raw SMS, which is exactly what we don't want.

Caveat that strengthens the sidecar choice: even a default SMS app gets no RCS. There is no public RCS API for third-party SMS apps, so becoming default would cost Byron RCS chats entirely. Notification listening is therefore not a stopgap. It is the only way to observe RCS content at all.

Spike checklist additions (beyond PRD §6.2)

← Back to the proposal