diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index 29d56105e..18004224e 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -60,6 +60,7 @@ endif()
if(MATSDK_BUILD_JNI_WRAPPER)
list(APPEND SRCS
+ jni/JavaDataViewerProxy.cpp
jni/JniConvertors.cpp
jni/LogManager_jni.cpp
jni/Logger_jni.cpp
diff --git a/lib/android_build/app/src/androidTest/java/com/microsoft/applications/events/maesdktest/LogManagerDDVUnitTest.java b/lib/android_build/app/src/androidTest/java/com/microsoft/applications/events/maesdktest/LogManagerDDVUnitTest.java
index 405749318..3eb659588 100644
--- a/lib/android_build/app/src/androidTest/java/com/microsoft/applications/events/maesdktest/LogManagerDDVUnitTest.java
+++ b/lib/android_build/app/src/androidTest/java/com/microsoft/applications/events/maesdktest/LogManagerDDVUnitTest.java
@@ -25,6 +25,7 @@
import com.microsoft.applications.events.DebugEventType;
import com.microsoft.applications.events.DiagLevel;
import com.microsoft.applications.events.HttpClient;
+import com.microsoft.applications.events.IDataViewer;
import com.microsoft.applications.events.ILogConfiguration;
import com.microsoft.applications.events.ILogManager;
import com.microsoft.applications.events.ILogger;
@@ -42,7 +43,10 @@
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -247,6 +251,93 @@ public void startDDVonLogManager() {
LogManager.flushAndTeardown();
}
+ @Test
+ public void registerDataViewer_whenCallbackThrows_continuesDispatchAndSupportsUnregister()
+ throws Exception {
+ System.loadLibrary("maesdk");
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ if (s_client == null) {
+ s_client = new MockHttpClient(appContext);
+ }
+ OfflineRoom.connectContext(appContext);
+
+ final String token =
+ "0123456789abcdef9123456789abcdef-01234567-0123-0123-0123-0123456789ab-0124";
+ final String factoryName = "JavaDataViewer" + System.nanoTime();
+ ILogConfiguration custom = LogManager.logConfigurationFactory();
+ custom.set(LogConfigurationKey.CFG_STR_PRIMARY_TOKEN, token);
+ custom.set(LogConfigurationKey.CFG_STR_COLLECTOR_URL, "https://viewer.contoso.com/");
+ custom.set(LogConfigurationKey.CFG_STR_FACTORY_NAME, factoryName);
+ custom.set(LogConfigurationKey.CFG_STR_CACHE_FILE_PATH, factoryName);
+
+ ILogManager manager = LogManagerProvider.createLogManager(custom);
+ CountDownLatch receivedPacket = new CountDownLatch(1);
+ AtomicInteger receivedByteCount = new AtomicInteger();
+ IDataViewer throwingViewer =
+ new IDataViewer() {
+ @Override
+ public void receiveData(byte[] packetData) {
+ throw new IllegalStateException("Expected callback failure");
+ }
+
+ @Override
+ public String getName() {
+ return "throwing-viewer";
+ }
+
+ @Override
+ public boolean isTransmissionEnabled() {
+ return true;
+ }
+
+ @Override
+ public String getCurrentEndpoint() {
+ return "";
+ }
+ };
+ IDataViewer receivingViewer =
+ new IDataViewer() {
+ @Override
+ public void receiveData(byte[] packetData) {
+ receivedByteCount.set(packetData.length);
+ receivedPacket.countDown();
+ }
+
+ @Override
+ public String getName() {
+ return "receiving-viewer";
+ }
+
+ @Override
+ public boolean isTransmissionEnabled() {
+ return true;
+ }
+
+ @Override
+ public String getCurrentEndpoint() {
+ return "http://127.0.0.1";
+ }
+ };
+
+ try {
+ assertThat(manager.registerDataViewer(throwingViewer), is(true));
+ assertThat(manager.registerDataViewer(receivingViewer), is(true));
+ assertThat(manager.registerDataViewer(receivingViewer), is(false));
+
+ ILogger logger = manager.getLogger(token, "java-data-viewer-test", "");
+ logger.logEvent("javaDataViewerCallback");
+ manager.uploadNow();
+
+ assertThat(receivedPacket.await(5, TimeUnit.SECONDS), is(true));
+ assertThat(receivedByteCount.get(), greaterThan(0));
+ assertThat(manager.unregisterDataViewer("receiving-viewer"), is(true));
+ assertThat(manager.unregisterDataViewer("receiving-viewer"), is(false));
+ assertThat(manager.unregisterDataViewer("throwing-viewer"), is(true));
+ } finally {
+ manager.close();
+ }
+ }
+
/*
Disabling this test since it requires private modules.
diff --git a/lib/android_build/maesdk/consumer-rules.pro b/lib/android_build/maesdk/consumer-rules.pro
index e69de29bb..09006a474 100644
--- a/lib/android_build/maesdk/consumer-rules.pro
+++ b/lib/android_build/maesdk/consumer-rules.pro
@@ -0,0 +1,4 @@
+-keep interface com.microsoft.applications.events.IDataViewer { *; }
+-keep class * implements com.microsoft.applications.events.IDataViewer {
+ public *;
+}
\ No newline at end of file
diff --git a/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/IDataViewer.java b/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/IDataViewer.java
new file mode 100644
index 000000000..d2289a748
--- /dev/null
+++ b/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/IDataViewer.java
@@ -0,0 +1,30 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+//
+package com.microsoft.applications.events;
+
+import androidx.annotation.Keep;
+
+/**
+ * Receives copies of packets uploaded by the SDK.
+ *
+ *
Implementations must return a stable, unique name for the lifetime of the registration.
+ * Callbacks can occur on an SDK worker thread and should return promptly. Implementations must not
+ * register or unregister viewers from within a callback.
+ */
+@Keep
+public interface IDataViewer {
+
+ /** Receives an encoded telemetry packet after it has been prepared for upload. */
+ void receiveData(byte[] packetData);
+
+ /** Returns the stable, unique name used to register this viewer. */
+ String getName();
+
+ /** Returns whether this viewer is currently accepting packet callbacks. */
+ boolean isTransmissionEnabled();
+
+ /** Returns the endpoint currently used by this viewer, or an empty string when disabled. */
+ String getCurrentEndpoint();
+}
diff --git a/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/ILogManager.java b/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/ILogManager.java
index 332331cb1..9ee5d7d0b 100644
--- a/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/ILogManager.java
+++ b/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/ILogManager.java
@@ -59,6 +59,21 @@ public interface ILogManager extends AutoCloseable {
public String getCurrentEndpoint();
+ /**
+ * Registers a caller-provided data viewer with this LogManager.
+ *
+ * @return {@code true} when the viewer was registered, {@code false} for invalid input or a
+ * duplicate viewer name
+ */
+ public boolean registerDataViewer(IDataViewer dataViewer);
+
+ /**
+ * Unregisters a caller-provided data viewer by its unique name.
+ *
+ * @return {@code true} when the viewer was unregistered, {@code false} when it was not registered
+ */
+ public boolean unregisterDataViewer(String viewerName);
+
public LogSessionData getLogSessionData();
public void setLevelFilter(int defaultLevel, int[] allowedLevels);
diff --git a/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/LogManagerProvider.java b/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/LogManagerProvider.java
index ba5d41e74..777f47d73 100644
--- a/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/LogManagerProvider.java
+++ b/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/LogManagerProvider.java
@@ -226,6 +226,28 @@ public String getCurrentEndpoint() {
return nativeGetCurrentEndpoint(nativeLogManager);
}
+ protected native boolean nativeRegisterDataViewer(
+ long nativeLogManager, IDataViewer dataViewer);
+
+ @Override
+ public boolean registerDataViewer(IDataViewer dataViewer) {
+ if (dataViewer == null) {
+ return false;
+ }
+ return nativeRegisterDataViewer(nativeLogManager, dataViewer);
+ }
+
+ protected native boolean nativeUnregisterDataViewer(
+ long nativeLogManager, String viewerName);
+
+ @Override
+ public boolean unregisterDataViewer(String viewerName) {
+ if (viewerName == null || viewerName.isEmpty()) {
+ return false;
+ }
+ return nativeUnregisterDataViewer(nativeLogManager, viewerName);
+ }
+
protected static class LogSessionDataImpl implements LogSessionData {
@Keep
private long m_first_time;
diff --git a/lib/jni/JavaDataViewerProxy.cpp b/lib/jni/JavaDataViewerProxy.cpp
new file mode 100644
index 000000000..de3b4765c
--- /dev/null
+++ b/lib/jni/JavaDataViewerProxy.cpp
@@ -0,0 +1,261 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+//
+#include "JavaDataViewerProxy.hpp"
+
+#include
+#include
+#include
+
+namespace MAT_NS_BEGIN
+{
+ namespace
+ {
+ constexpr const char* LOG_TAG = "MAE.JavaDataViewer";
+ }
+
+ std::shared_ptr JavaDataViewerProxy::Create(
+ JNIEnv* env,
+ jobject dataViewer) noexcept
+ {
+ if (env == nullptr || dataViewer == nullptr)
+ {
+ return nullptr;
+ }
+
+ auto proxy = std::shared_ptr(new JavaDataViewerProxy());
+ if (env->GetJavaVM(&proxy->m_javaVm) != JNI_OK)
+ {
+ return nullptr;
+ }
+
+ auto dataViewerClass = env->GetObjectClass(dataViewer);
+ if (dataViewerClass == nullptr || env->ExceptionCheck())
+ {
+ env->ExceptionClear();
+ return nullptr;
+ }
+
+ proxy->m_receiveData = env->GetMethodID(dataViewerClass, "receiveData", "([B)V");
+ if (proxy->ClearPendingException(env, "receiveData lookup"))
+ {
+ env->DeleteLocalRef(dataViewerClass);
+ return nullptr;
+ }
+ proxy->m_getName = env->GetMethodID(dataViewerClass, "getName", "()Ljava/lang/String;");
+ if (proxy->ClearPendingException(env, "getName lookup"))
+ {
+ env->DeleteLocalRef(dataViewerClass);
+ return nullptr;
+ }
+ proxy->m_isTransmissionEnabled =
+ env->GetMethodID(dataViewerClass, "isTransmissionEnabled", "()Z");
+ if (proxy->ClearPendingException(env, "isTransmissionEnabled lookup"))
+ {
+ env->DeleteLocalRef(dataViewerClass);
+ return nullptr;
+ }
+ proxy->m_getCurrentEndpoint =
+ env->GetMethodID(dataViewerClass, "getCurrentEndpoint", "()Ljava/lang/String;");
+ if (proxy->ClearPendingException(env, "getCurrentEndpoint lookup"))
+ {
+ env->DeleteLocalRef(dataViewerClass);
+ return nullptr;
+ }
+ env->DeleteLocalRef(dataViewerClass);
+
+ if (proxy->m_receiveData == nullptr ||
+ proxy->m_getName == nullptr ||
+ proxy->m_isTransmissionEnabled == nullptr ||
+ proxy->m_getCurrentEndpoint == nullptr)
+ {
+ return nullptr;
+ }
+
+ proxy->m_dataViewer = env->NewGlobalRef(dataViewer);
+ if (proxy->m_dataViewer == nullptr || env->ExceptionCheck())
+ {
+ env->ExceptionClear();
+ return nullptr;
+ }
+
+ if (!proxy->ReadString(env, proxy->m_getName, proxy->m_name) || proxy->m_name.empty())
+ {
+ return nullptr;
+ }
+ return proxy;
+ }
+
+ JavaDataViewerProxy::~JavaDataViewerProxy() noexcept
+ {
+ if (m_dataViewer == nullptr)
+ {
+ return;
+ }
+
+ bool attached = false;
+ auto env = GetEnv(attached);
+ if (env != nullptr)
+ {
+ env->DeleteGlobalRef(m_dataViewer);
+ }
+ m_dataViewer = nullptr;
+ DetachIfNeeded(attached);
+ }
+
+ void JavaDataViewerProxy::ReceiveData(const std::vector& packetData) noexcept
+ {
+ if (packetData.size() > static_cast(std::numeric_limits::max()))
+ {
+ __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Packet is too large for a Java byte array");
+ return;
+ }
+
+ bool attached = false;
+ auto env = GetEnv(attached);
+ if (env == nullptr)
+ {
+ return;
+ }
+
+ auto packet = env->NewByteArray(static_cast(packetData.size()));
+ if (packet == nullptr || ClearPendingException(env, "receiveData allocation"))
+ {
+ DetachIfNeeded(attached);
+ return;
+ }
+ if (!packetData.empty())
+ {
+ env->SetByteArrayRegion(
+ packet,
+ 0,
+ static_cast(packetData.size()),
+ reinterpret_cast(packetData.data()));
+ }
+
+ if (!ClearPendingException(env, "receiveData copy"))
+ {
+ env->CallVoidMethod(m_dataViewer, m_receiveData, packet);
+ ClearPendingException(env, "receiveData");
+ }
+ env->DeleteLocalRef(packet);
+ DetachIfNeeded(attached);
+ }
+
+ const char* JavaDataViewerProxy::GetName() const noexcept
+ {
+ return m_name.c_str();
+ }
+
+ bool JavaDataViewerProxy::IsTransmissionEnabled() const noexcept
+ {
+ bool attached = false;
+ auto env = GetEnv(attached);
+ if (env == nullptr)
+ {
+ return false;
+ }
+
+ auto enabled = env->CallBooleanMethod(m_dataViewer, m_isTransmissionEnabled);
+ if (ClearPendingException(env, "isTransmissionEnabled"))
+ {
+ enabled = JNI_FALSE;
+ }
+ DetachIfNeeded(attached);
+ return enabled == JNI_TRUE;
+ }
+
+ const std::string& JavaDataViewerProxy::GetCurrentEndpoint() const noexcept
+ {
+ std::lock_guard lock(m_endpointMutex);
+ bool attached = false;
+ auto env = GetEnv(attached);
+ if (env != nullptr)
+ {
+ std::string endpoint;
+ if (ReadString(env, m_getCurrentEndpoint, endpoint))
+ {
+ m_currentEndpoint = std::move(endpoint);
+ }
+ else
+ {
+ m_currentEndpoint.clear();
+ }
+ DetachIfNeeded(attached);
+ }
+ return m_currentEndpoint;
+ }
+
+ JNIEnv* JavaDataViewerProxy::GetEnv(bool& attached) const noexcept
+ {
+ attached = false;
+ if (m_javaVm == nullptr)
+ {
+ return nullptr;
+ }
+
+ JNIEnv* env = nullptr;
+ auto result = m_javaVm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6);
+ if (result == JNI_OK)
+ {
+ return env;
+ }
+ if (result != JNI_EDETACHED || m_javaVm->AttachCurrentThread(&env, nullptr) != JNI_OK)
+ {
+ return nullptr;
+ }
+ attached = true;
+ return env;
+ }
+
+ void JavaDataViewerProxy::DetachIfNeeded(bool attached) const noexcept
+ {
+ if (attached && m_javaVm != nullptr)
+ {
+ m_javaVm->DetachCurrentThread();
+ }
+ }
+
+ bool JavaDataViewerProxy::ClearPendingException(
+ JNIEnv* env,
+ const char* methodName) const noexcept
+ {
+ if (!env->ExceptionCheck())
+ {
+ return false;
+ }
+ env->ExceptionClear();
+ __android_log_print(
+ ANDROID_LOG_ERROR,
+ LOG_TAG,
+ "Java IDataViewer callback failed: %s",
+ methodName);
+ return true;
+ }
+
+ bool JavaDataViewerProxy::ReadString(
+ JNIEnv* env,
+ jmethodID method,
+ std::string& value) const noexcept
+ {
+ auto javaValue = static_cast(env->CallObjectMethod(m_dataViewer, method));
+ if (ClearPendingException(env, "string callback") || javaValue == nullptr)
+ {
+ return false;
+ }
+
+ auto chars = env->GetStringUTFChars(javaValue, nullptr);
+ if (chars == nullptr)
+ {
+ ClearPendingException(env, "string conversion");
+ env->DeleteLocalRef(javaValue);
+ return false;
+ }
+ value.assign(chars);
+ env->ReleaseStringUTFChars(javaValue, chars);
+ env->DeleteLocalRef(javaValue);
+ return !ClearPendingException(env, "string conversion");
+ }
+
+} MAT_NS_END
diff --git a/lib/jni/JavaDataViewerProxy.hpp b/lib/jni/JavaDataViewerProxy.hpp
new file mode 100644
index 000000000..aee92d9fe
--- /dev/null
+++ b/lib/jni/JavaDataViewerProxy.hpp
@@ -0,0 +1,50 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+//
+#ifndef JAVADATAVIEWERPROXY_HPP
+#define JAVADATAVIEWERPROXY_HPP
+
+#include "IDataViewer.hpp"
+
+#include
+#include
+#include
+#include
+
+namespace MAT_NS_BEGIN
+{
+ class JavaDataViewerProxy final : public IDataViewer
+ {
+ public:
+ static std::shared_ptr Create(JNIEnv* env, jobject dataViewer) noexcept;
+
+ ~JavaDataViewerProxy() noexcept override;
+
+ void ReceiveData(const std::vector& packetData) noexcept override;
+ const char* GetName() const noexcept override;
+ bool IsTransmissionEnabled() const noexcept override;
+ const std::string& GetCurrentEndpoint() const noexcept override;
+
+ private:
+ JavaDataViewerProxy() = default;
+
+ JNIEnv* GetEnv(bool& attached) const noexcept;
+ void DetachIfNeeded(bool attached) const noexcept;
+ bool ClearPendingException(JNIEnv* env, const char* methodName) const noexcept;
+ bool ReadString(JNIEnv* env, jmethodID method, std::string& value) const noexcept;
+
+ JavaVM* m_javaVm = nullptr;
+ jobject m_dataViewer = nullptr;
+ jmethodID m_receiveData = nullptr;
+ jmethodID m_getName = nullptr;
+ jmethodID m_isTransmissionEnabled = nullptr;
+ jmethodID m_getCurrentEndpoint = nullptr;
+ std::string m_name;
+ mutable std::mutex m_endpointMutex;
+ mutable std::string m_currentEndpoint;
+ };
+
+} MAT_NS_END
+
+#endif
diff --git a/lib/jni/LogManager_jni.cpp b/lib/jni/LogManager_jni.cpp
index 22b4244ee..75e50f207 100644
--- a/lib/jni/LogManager_jni.cpp
+++ b/lib/jni/LogManager_jni.cpp
@@ -27,6 +27,7 @@
#include
#include "callbacks/DebugSourceInternal.hpp"
+#include "JavaDataViewerProxy.hpp"
#include "JniConvertors.hpp"
#include "LogManagerBase.hpp"
#include "WrapperLogManager.hpp"
@@ -35,6 +36,9 @@
#endif
#include "config/RuntimeConfig_Default.hpp"
+#include
+#include
+
using namespace MAT;
template <>
@@ -869,12 +873,16 @@ namespace
ILogConfiguration config;
ILogManager* manager;
std::shared_ptr ddv;
+ std::mutex javaDataViewersMutex;
+ std::unordered_map> javaDataViewers;
};
#else
struct ManagerAndConfig
{
ILogConfiguration config;
ILogManager* manager;
+ std::mutex javaDataViewersMutex;
+ std::unordered_map> javaDataViewers;
};
#endif
@@ -882,6 +890,53 @@ namespace
static MCVector jniManagers;
static std::mutex jniManagersMutex;
+
+ ManagerAndConfig* getManagerAndConfig(jlong nativeLogManager)
+ {
+ std::lock_guard lock(jniManagersMutex);
+ if (nativeLogManager < 0 ||
+ nativeLogManager >= static_cast(jniManagers.size()))
+ {
+ return nullptr;
+ }
+ return jniManagers[nativeLogManager].get();
+ }
+
+ void closeJavaDataViewers(ManagerAndConfig& managerAndConfig)
+ {
+ ILogManager* manager;
+ std::unordered_map> dataViewers;
+ {
+ std::lock_guard lock(managerAndConfig.javaDataViewersMutex);
+ manager = managerAndConfig.manager;
+ {
+ std::lock_guard managersLock(jniManagersMutex);
+ managerAndConfig.manager = nullptr;
+ }
+ dataViewers.swap(managerAndConfig.javaDataViewers);
+ }
+
+ if (manager == nullptr)
+ {
+ return;
+ }
+ for (const auto& dataViewer : dataViewers)
+ {
+ try
+ {
+ manager->GetDataViewerCollection().UnregisterViewer(dataViewer.first.c_str());
+ }
+ catch (const std::exception& exception)
+ {
+ __android_log_print(
+ ANDROID_LOG_WARN,
+ "MAE.JavaDataViewer",
+ "Failed to unregister Java IDataViewer '%s': %s",
+ dataViewer.first.c_str(),
+ exception.what());
+ }
+ }
+ }
}
extern "C" JNIEXPORT jlong JNICALL
@@ -979,17 +1034,14 @@ Java_com_microsoft_applications_events_LogManagerProvider_00024LogManagerImpl_na
jobject /* this */,
jlong nativeLogManager)
{
+ auto managerAndConfig = getManagerAndConfig(nativeLogManager);
+ if (managerAndConfig == nullptr)
{
- std::lock_guard lock(jniManagersMutex);
- if (nativeLogManager < 0 || nativeLogManager >= static_cast(jniManagers.size()))
- {
- return;
- }
- // we reset the manager member of the ManagerAndConfig,
- // but the ManagerAndConfig itself will survive until
- // the static jniManagers array is destroyed.
- jniManagers[nativeLogManager]->manager = nullptr;
+ return;
}
+
+ // The ManagerAndConfig survives until the static jniManagers array is destroyed.
+ closeJavaDataViewers(*managerAndConfig);
}
extern "C" JNIEXPORT jobject JNICALL
@@ -1526,6 +1578,121 @@ Java_com_microsoft_applications_events_LogManagerProvider_00024LogManagerImpl_na
#endif
}
+extern "C" JNIEXPORT jboolean JNICALL
+Java_com_microsoft_applications_events_LogManagerProvider_00024LogManagerImpl_nativeRegisterDataViewer(
+ JNIEnv* env,
+ jobject /* this */,
+ jlong native_log_manager,
+ jobject data_viewer)
+{
+ auto proxy = JavaDataViewerProxy::Create(env, data_viewer);
+ if (!proxy)
+ {
+ return false;
+ }
+
+ auto manager_and_config = getManagerAndConfig(native_log_manager);
+ if (manager_and_config == nullptr)
+ {
+ return false;
+ }
+
+ std::lock_guard lock(manager_and_config->javaDataViewersMutex);
+ if (manager_and_config->manager == nullptr ||
+ manager_and_config->javaDataViewers.find(proxy->GetName()) !=
+ manager_and_config->javaDataViewers.end())
+ {
+ return false;
+ }
+
+ bool collectionRegistered = false;
+ try
+ {
+ manager_and_config->manager->GetDataViewerCollection().RegisterViewer(proxy);
+ collectionRegistered = true;
+ manager_and_config->javaDataViewers.emplace(proxy->GetName(), proxy);
+ return true;
+ }
+ catch (const std::exception& exception)
+ {
+ if (collectionRegistered)
+ {
+ try
+ {
+ manager_and_config->manager->GetDataViewerCollection().UnregisterViewer(
+ proxy->GetName());
+ }
+ catch (const std::exception& rollbackException)
+ {
+ __android_log_print(
+ ANDROID_LOG_ERROR,
+ "MAE.JavaDataViewer",
+ "Failed to roll back Java IDataViewer '%s': %s",
+ proxy->GetName(),
+ rollbackException.what());
+ }
+ }
+ __android_log_print(
+ ANDROID_LOG_WARN,
+ "MAE.JavaDataViewer",
+ "Failed to register Java IDataViewer '%s': %s",
+ proxy->GetName(),
+ exception.what());
+ return false;
+ }
+}
+
+extern "C" JNIEXPORT jboolean JNICALL
+Java_com_microsoft_applications_events_LogManagerProvider_00024LogManagerImpl_nativeUnregisterDataViewer(
+ JNIEnv* env,
+ jobject /* this */,
+ jlong native_log_manager,
+ jstring viewer_name)
+{
+ std::string name;
+ if (!TryJStringToStdString(env, viewer_name, name) || name.empty())
+ {
+ return false;
+ }
+
+ auto manager_and_config = getManagerAndConfig(native_log_manager);
+ if (manager_and_config == nullptr)
+ {
+ return false;
+ }
+
+ ILogManager* manager;
+ std::shared_ptr proxy;
+ {
+ std::lock_guard lock(manager_and_config->javaDataViewersMutex);
+ auto viewer = manager_and_config->javaDataViewers.find(name);
+ if (manager_and_config->manager == nullptr ||
+ viewer == manager_and_config->javaDataViewers.end())
+ {
+ return false;
+ }
+ manager = manager_and_config->manager;
+ proxy = std::move(viewer->second);
+ manager_and_config->javaDataViewers.erase(viewer);
+ }
+
+ try
+ {
+ manager->GetDataViewerCollection().UnregisterViewer(name.c_str());
+ return true;
+ }
+ catch (const std::exception& exception)
+ {
+ __android_log_print(
+ ANDROID_LOG_WARN,
+ "MAE.JavaDataViewer",
+ "Failed to unregister Java IDataViewer '%s': %s",
+ name.c_str(),
+ exception.what());
+ return false;
+ }
+}
+
extern "C" JNIEXPORT void JNICALL
Java_com_microsoft_applications_events_LogManagerProvider_00024LogManagerImpl_nativeGetLogSessionData(
JNIEnv* env,