Skip to content

fix: [SDK-4900] expose Android notification SetExtender - #193

Merged
fadi-george merged 7 commits into
mainfrom
fadi/sdk-4900
Jul 23, 2026
Merged

fix: [SDK-4900] expose Android notification SetExtender#193
fadi-george merged 7 commits into
mainfrom
fadi/sdk-4900

Conversation

@fadi-george

@fadi-george fadi-george commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Description

One Line Summary

Expose Android notification extenders in the .NET binding and add a runnable service-extension example.

Details

Motivation

The native Android SDK exposes IMutableNotification.setExtender, but the .NET binding generator omitted it because NotificationCompat.Extender could not be resolved. This prevented .NET apps from customizing notifications through an Android notification service extension. Addresses #92.

The duplicate-delivery and deferred PreventDefault timeout behavior also discussed in #92 is not changed by this PR. Current bundled Android SDKs expose PreventDefault(true) to permanently discard a notification; that existing behavior was validated while testing this change.

Scope

  • Add the AndroidX Core dependency to the binding project and published NuGet dependency list so SetExtender(IExtender) is generated and consumable.
  • Add a runnable examples/android-service-ext app showing stable JNI registration, manifest configuration, notification customization, push subscription display, .env setup, permission error handling, and device launch tooling.
  • No changes to the native OneSignal binaries, PreventDefault behavior, or cross-platform notification API.

Testing

Unit testing

No unit tests were added because the API surface is generated from the native AAR. The runnable sample provides compile-time coverage for the generated SetExtender method.

Manual testing

  • Built the Android SDK and service-extension sample in Release with 0 warnings and 0 errors.
  • Removed the AndroidX Core dependency and performed a clean rebuild; the sample failed with the expected missing SetExtender compiler error. Restored the dependency and confirmed the build passes.
  • Ran the sample on an Android emulator and received notifications in foreground/background flows.
  • Confirmed the extender changes the title to [Extended] Test and applies the blue accent while preserving rich media and actions.
  • Confirmed PreventDefault(true) suppresses display without restoration or the 30-second timeout; generation and receive-receipt workers completed successfully.

Affected code checklist

  • Notifications
    • Display
    • Open
    • Push Processing
    • Confirm Deliveries
  • Outcomes
  • Sessions
  • In-App Messaging
  • REST API requests
  • Public API changes

Checklist

Overview

  • I have filled out all REQUIRED sections above
  • PR does one thing
  • Any Public API changes are explained in the PR details and conform to existing APIs

Testing

  • I have included test coverage for these changes, or explained why they are not needed
  • All automated tests pass, or I explained why that is not possible
  • I have personally tested this on my device, or explained why that is not possible

Final pass

  • Code is as readable as possible.
  • I have reviewed this PR myself, ensuring it meets each checklist item

Made with Cursor

@fadi-george
fadi-george requested a review from a team as a code owner July 22, 2026 23:18

@abdulraqeeb33 abdulraqeeb33 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multi-model adversarial review

Verdict: Approve with changes — the binding fix is correct; the teaching sample needs fixes before merge.

Reviewed with packaging (Opus), bindings/API (GPT), example quality (Sonnet), Bugbot, and Security. Scoped to this PR’s 11-file diff.

What held up

  • javap confirms IMutableNotification.setExtender(NotificationCompat.Extender) in core-release.aar.
  • Placing Xamarin.AndroidX.Core 1.12.0.1 on Core.Binding + the Android nuspec group is the right fix.
  • AppCompat’s Core floor is [1.8.0.1,)1.12.0.1 resolves cleanly (no NU1107/NU1605).
  • Android-only binding exposure (no cross-platform wrapper) is appropriate for #92.
  • Security: launcher Exported = true is required on API 31+; extension is meta-data only; sample embeds public App ID only. No medium+ security findings.
  • Bugbot: no bugs.

Blocking

  1. Manifest permissions (examples/android-service-ext/Properties/AndroidManifest.xml)
    Only the NotificationServiceExtension meta-data is declared. Sibling examples declare INTERNET, ACCESS_NETWORK_STATE, POST_NOTIFICATIONS, etc. Debug may inject INTERNET; Release and API 33+ POST_NOTIFICATIONS will not. Align with sibling manifests (at least POST_NOTIFICATIONS + network).

  2. Linker / trim preserve for JNI-registered type (NotificationServiceExtension.cs + README)
    The class is only reached via manifest string + native reflection. No [Preserve], trim root, or ILLink descriptor. README tells integrators to copy this into MAUI apps that ship trimmed Release builds — silent strip → extender never runs, no compile error. Add preserve guidance next to the copy instructions.

  3. Title-overwrite footgun (NotificationServiceExtension.cs)
    SetContentTitle("[Extended] Test") teaches destroying campaign titles. Prefer color-only (or prefixing the existing title) so the sample proves SetExtender without a destructive default.

Should-fix before merge

  1. Await / surface errors from RequestPermissionAsync like demo / demo-no-location (avoid fire-and-forget _ =).
  2. In the PR / #92 discussion: state that the duplicate-delivery / deferred PreventDefault timeout half of #92 is out of scope so this isn’t read as a full close.
  3. Split or drop AGENTS.md — csharpier policy is unrelated to SDK-4900.

Non-blocking

  • Mirror Xamarin.AndroidX.Core in OneSignalSDK.DotNet.Android.csproj or document the intentional exception (every other AndroidX nuspec dep is mirrored there; Core is only on binding + nuspec).
  • CI currently only runs csharpier; a Release build of the binding + sample would catch SetExtender generation and trim issues. Sample ProjectReference also never validates the nuspec dependency graph.

Summary table

Severity Location Finding
High AndroidManifest.xml Missing permissions vs sibling examples; API 33+ / Release footgun
High NotificationServiceExtension.cs + README Reflective JNI type with no preserve; copy-paste into trimmed apps
Medium NotificationServiceExtension.cs SetContentTitle overwrite is a teaching footgun
Medium ci.yml / sample csproj No CI/build proof of binding generation or nuspec graph
Medium PR body / #92 Only half of #92 addressed
Low Android.csproj vs nuspec Convention drift on Core PackageReference
Low MainActivity.cs Fire-and-forget RequestPermissionAsync
Low AGENTS.md Scope creep

Happy to re-review once the sample blockers are addressed — the dependency change itself looks merge-ready.

Co-authored-by: Cursor <cursoragent@cursor.com>
@fadi-george

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review. I pushed permission error handling and clarified that the duplicate/timeout behavior is outside this PR's code changes. The Release merged manifest already contains INTERNET, ACCESS_NETWORK_STATE, and POST_NOTIFICATIONS from dependency manifests. The registered extension types are retained in the Release output; Android.Runtime.Preserve is obsolete in modern .NET Android, so I did not add it. I also kept the title override and AGENTS.md intentionally. The Release sample build passes with 0 warnings and 0 errors.

@fadi-george

Copy link
Copy Markdown
Contributor Author

Addressed:

  • Await and surface notification permission request failures.
  • Clarify that duplicate delivery and deferred PreventDefault timeout behavior are outside this PR's code changes.
  • Revalidated the Release build and generated SetExtender API.

Not changing:

  • Explicit manifest permissions, because the merged Release manifest already includes INTERNET, ACCESS_NETWORK_STATE, and POST_NOTIFICATIONS.
  • Preserve annotations, because Android.Runtime.Preserve is obsolete and the registered Java peer types are retained in Release output.
  • The hardcoded title override and AGENTS.md, which are intentional for this PR.
  • A duplicate Xamarin.AndroidX.Core reference in Android.csproj; the binding project needs it for generation and the nuspec supplies it to consumers.
  • CI expansion, since the existing checks pass and the sample Release build was validated locally.

@abdulraqeeb33 abdulraqeeb33 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review

Thanks for the follow-up — looks good to approve.

Addressed

  • RequestPermissionAsync is now awaited with a surfaced failure toast.
  • OnDestroy only unsubscribes when subscribed.
  • Scope note on duplicate delivery / deferred PreventDefault timeout is clear in the comment thread.
  • Binding + nuspec Xamarin.AndroidX.Core placement remains correct.

Accepting as intentional / locally validated

  • Manifest permissions via merged Release dependencies (not duplicated in the sample manifest).
  • No [Preserve] given obsolete API + Release retention of registered peers.
  • Hardcoded [Extended] Test title as an obvious demo signal.
  • AGENTS.md, no Android.csproj Core mirror, and no CI expansion for this PR.

Approve — the SetExtender exposure is the important change and the sample is in good enough shape to land.

@fadi-george
fadi-george merged commit 7fdfb21 into main Jul 23, 2026
6 checks passed
@fadi-george
fadi-george deleted the fadi/sdk-4900 branch July 23, 2026 18:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants