Skip to content

[Android] Expose IDataViewer as public Java API - #1534

Open
KartikDhawaniya wants to merge 4 commits into
microsoft:mainfrom
KartikDhawaniya:user/kdhawaniya/android-idataviewer-api
Open

KartikDhawaniya wants to merge 4 commits into
microsoft:mainfrom
KartikDhawaniya:user/kdhawaniya/android-idataviewer-api

Conversation

@KartikDhawaniya

Copy link
Copy Markdown

Summary

Expose the native 1DS IDataViewer extension point through the Android Java/JNI API so Android consumers can register a product-owned viewer without changing the bundled DefaultDataViewer implementation.

This enables consumers such as Teams Android to keep broad Network Security Configuration cleartext traffic disabled while owning any product-specific diagnostic transport outside the SDK.

Changes

  • Add the public Android IDataViewer callback contract.
  • Add registerDataViewer and unregisterDataViewer to ILogManager and LogManagerProvider.LogManagerImpl.
  • Add a JNI-backed C++ IDataViewer proxy that:
    • retains the Java implementation with a global reference;
    • attaches native SDK worker threads to the JVM when required;
    • forwards encoded packet payloads as byte[];
    • caches a stable viewer name;
    • isolates and clears Java callback exceptions; and
    • releases JNI references deterministically.
  • Retain registered proxies per native LogManager and clean them up on unregister/close.
  • Reject invalid, empty, duplicate, or unknown viewer registrations with explicit failure results.
  • Add consumer R8/ProGuard rules for reverse-JNI callbacks.
  • Add Android instrumentation coverage for registration, duplicate rejection, callback dispatch, callback exception isolation, and unregistration.

Scope

This is an additive Android API bridge over the existing native extension point. It does not change:

  • production telemetry upload or routing;
  • endpoint selection or authentication;
  • the existing DefaultDataViewer APIs; or
  • product-specific transport, endpoint validation, queueing, retry, or feature-gating policy.

Validation

  • :maesdk:assembleDebug
  • :app:compileDebugAndroidTestJavaWithJavac
  • Native Android builds for arm64-v8a, armeabi-v7a, x86, and x86_64

The instrumentation test was compiled but not executed because no Android device was connected.

@KartikDhawaniya
KartikDhawaniya requested a review from a team as a code owner September 11, 2026 21:48
@KartikDhawaniya

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree company="Microsoft"

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The public interface change breaks source compatibility, and callback lifecycle documentation and unregistration coverage need correction.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds a public Android/JNI bridge for registering product-owned IDataViewer implementations.

Changes:

  • Adds the Java callback API and LogManager registration methods.
  • Implements JNI proxy lifecycle, callback forwarding, and cleanup.
  • Adds shrinker rules and instrumentation coverage.
File summaries
File Description
lib/jni/LogManager_jni.cpp Manages viewer registration and cleanup.
lib/jni/JavaDataViewerProxy.hpp Declares the JNI viewer proxy.
lib/jni/JavaDataViewerProxy.cpp Implements Java callback forwarding.
lib/CMakeLists.txt Builds the new proxy source.
lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/LogManagerProvider.java Implements Java registration APIs.
lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/ILogManager.java Exposes registration publicly.
lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/IDataViewer.java Defines the callback contract.
lib/android_build/maesdk/consumer-rules.pro Preserves reverse-JNI callback methods.
lib/android_build/app/src/androidTest/java/com/microsoft/applications/events/maesdktest/LogManagerDDVUnitTest.java Tests registration and dispatch behavior.
Review details
  • Files reviewed: 9/9 changed files
  • Comments generated: 3
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +13 to +14
* Callbacks can occur on an SDK worker thread and should return promptly. Implementations must not
* register or unregister viewers from within a callback.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@KartikDhawaniya
KartikDhawaniya force-pushed the user/kdhawaniya/android-idataviewer-api branch from 103ad8e to 866832b Compare September 16, 2026 10:27
@bmehta001

Copy link
Copy Markdown
Contributor

I reviewed whether this API is necessary and proportionate. The capability is justified, but the PR should not merge unchanged.

Android currently has no Java mechanism for registering a product-owned implementation of the native IDataViewer extension point. The existing DefaultDataViewer path owns its HTTP transport and requires a cleartext http:// private-subnet endpoint, so it does not address the stated product need without broader cleartext configuration.

The JNI bridge is therefore a reasonable direction, but the existing inline findings are merge blockers:

  • Adding abstract methods to public ILogManager creates an avoidable source-compatibility break; use source-compatible defaults or a separate extension interface.
  • Reentrant close() from receiveData() can remove viewers while native dispatch is iterating the collection.
  • The instrumentation test must verify that callbacks actually stop after unregistering and should be executed on a device/emulator.

With those addressed, the scope is proportionate to the demonstrated gap.

kdhawaniya and others added 3 commits September 21, 2026 16:58
Preserves source compatibility, fixes reentrant dispatch, and strengthens
the unregistration test.

registerDataViewer and unregisterDataViewer become default methods on
ILogManager returning false. Adding abstract methods to a public
interface would break every consumer-owned implementation and test double
on upgrade, despite the change being additive in intent. LogManagerImpl
overrides both, so the native path is unaffected.

DispatchDataViewerEvent now iterates a snapshot of the viewer collection
rather than the member vector. m_dataViewerMapLock is recursive, so a
viewer that reenters the SDK from ReceiveData - closing the owning
LogManager, which unregisters every viewer - was admitted back in and
erased the vector while dispatch was still walking it, invalidating the
iterator. Exposing IDataViewer to arbitrary Java implementations makes
that reachable from outside the SDK, so the hazard is fixed rather than
only documented. Holding shared_ptr copies also keeps each viewer alive
across its own callback. The IDataViewer contract now prohibits closing
the owning manager from a callback, and a unit test covers a viewer that
unregisters everything from ReceiveData.

The instrumentation test asserted only the native return value of
unregisterDataViewer, so a bridge that dropped its bookkeeping entry but
left the proxy registered in DataViewerCollection would have passed. It
now drives a second dispatch after unregistering, using the still
registered throwing viewer as the witness that a dispatch really
occurred, and asserts the unregistered viewer's callback count does not
increase.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
IDataViewer returns the endpoint by const reference, so the referent must
outlive the call and must not be mutated while a caller holds it. The
proxy updated a shared member under a mutex and then returned a reference
to it, releasing the lock on return: two concurrent callers could read and
write the same string at once, so the mutex gave no protection.

Use a thread_local buffer instead, which gives each calling thread its own
storage and removes the need for the lock. Behaviour is unchanged: the
endpoint is still read from Java on every call, so a viewer that changes
endpoints still reports the current one.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Callback dispatch can deadlock through inverted locks, and JNI allocation failures can leave pending exceptions uncleared.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity · 1 Low severity

Open (3)
Resolved since last review (2)

// erase from the very vector being iterated here and invalidate the iterator.
// Holding shared_ptr copies additionally keeps each viewer alive for the duration of
// its own callback, even if that callback drops the last other reference to it.
const auto viewers = m_dataViewerCollection;
}

auto packet = env->NewByteArray(static_cast<jsize>(packetData.size()));
if (packet == nullptr || ClearPendingException(env, "receiveData allocation"))

This branch has not been deployed

No deployments
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.

3 participants