From 28acdc2f7a8b0ebb0c1a27aca9a8dd75496deae6 Mon Sep 17 00:00:00 2001 From: Siu-Tung Tony Yuen Date: Tue, 28 Jan 2025 13:51:24 -0800 Subject: [PATCH 1/7] [Amazon] StopConnect for UDC in Android tv-casting-app Updated Android & iOS tv-casting-app to send stopConnect command when user exits UDC before confirming passcode Changes 1. Added IsPendingPasscodeFromUser() to CastingPlayer. This function will return true if we are still connecting and pending user action for passcode 2. ConnectionExampleFragment.java onDestroy() calls stopConnect() if user has not confirmed passcode 4. MCConnectionExampleView ConnectingView dissapear calls stopConnect() if user has not confirmed passcode Test 1. Manually verified UDC attempt is successful both Android iOS for following 1. Attempt UDC attempt (both commissionee & commissioner generated passcode) 2. Navigate back to discovery page 3. Start new UDC attempt (both commissionee & commissioner generated passcode) 2. Manually verified UDC attempt is successful both Android iOS for following. UDC will finish in the background. 1. Attempt UDC attempt (both commissionee & commissioner generated passcode) 2. Confirm passcode on TV app or casting app depending on who generated passcode 3. Navigate back before commission fnishes. 3. Manual regression test following * UDC attempt happy path (no navigate back) for both commissinee & commissioner generated passcode --- .../casting/ConnectionExampleFragment.java | 16 ++++++++++++++++ .../com/matter/casting/core/CastingPlayer.java | 6 ++++++ .../casting/core/MatterCastingPlayer.java | 3 +++ .../jni/cpp/core/MatterCastingPlayer-JNI.cpp | 12 ++++++++++++ .../MatterTvCastingBridge/MCCastingPlayer.h | 5 +++++ .../MatterTvCastingBridge/MCCastingPlayer.mm | 14 ++++++++++++++ .../TvCasting/MCConnectionExampleView.swift | 3 +++ .../MCConnectionExampleViewModel.swift | 17 +++++++++++++++++ .../tv-casting-common/core/CastingPlayer.cpp | 5 ----- .../tv-casting-common/core/CastingPlayer.h | 8 ++++++++ 10 files changed, 84 insertions(+), 5 deletions(-) diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java index dcc148357ebe26..2f76ce83debf25 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java @@ -262,6 +262,22 @@ public void handle(CommissionerDeclaration cd) { }); } + @Override + public void onDestroy() { + super.onDestroy(); + + // Only stop connection if we are pending passcode confirmation + // We cannot stop connection once continueConnecting() is called + if (targetCastingPlayer.isPendingPasscodeFromUser()) { + MatterError err = targetCastingPlayer.stopConnecting(); + if (err.hasError()) { + Log.e( + TAG, + "Going back before connection finishes but stopConnecting() failed due to: " + err); + } + } + } + private void displayPasscodeInputDialog(Context context) { AlertDialog.Builder builder = new AlertDialog.Builder(context); diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java index d7ba6f00f9888f..114ce65fbf94bd 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java @@ -152,4 +152,10 @@ MatterError verifyOrEstablishConnection( /** @brief Sets the internal connection state of this CastingPlayer to "disconnected" */ void disconnect(); + + /** + * @return true if this CastingPlayer is still pending pass code from user and therefore is not + * ready + */ + boolean isPendingPasscodeFromUser(); } diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java index 29ee17e26db8be..3f9bacb5b94495 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java @@ -267,4 +267,7 @@ public MatterError continueConnecting() { @Override public native void disconnect(); + + @Override + public native boolean isPendingPasscodeFromUser(); } diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp index 5219dbca3a88af..1f12b33478f0e6 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp @@ -165,6 +165,18 @@ JNI_METHOD(void, disconnect) castingPlayer->Disconnect(); } +JNI_METHOD(bool, isPendingPasscodeFromUser) +(JNIEnv * env, jobject thiz) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "MatterCastingPlayer-JNI::isPendingPasscodeFromUser()"); + + CastingPlayer * castingPlayer = support::convertCastingPlayerFromJavaToCpp(thiz); + VerifyOrReturnValue(castingPlayer != nullptr, support::convertMatterErrorFromCppToJava(CHIP_ERROR_INVALID_ARGUMENT)); + + return castingPlayer->IsPendingPasscodeFromUser(); +} + JNI_METHOD(jobject, getEndpoints) (JNIEnv * env, jobject thiz) { diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.h index a8439935a34360..a6a2d286252648 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.h +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.h @@ -148,6 +148,11 @@ */ - (void)disconnect; +/** + * @return true if this CastingPlayer is still pending pass code from user and therefore is not ready + */ +- (bool)isPendingPasscodeFromUser; + - (NSString * _Nonnull)identifier; - (NSString * _Nonnull)deviceName; - (uint16_t)vendorId; diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.mm index 793f76ff7ee791..78afbf3443ac21 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.mm +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.mm @@ -190,6 +190,20 @@ - (void)disconnect }); } +- (bool)isPendingPasscodeFromUser +{ + ChipLogProgress(AppServer, "MCCastingPlayer.isPendingPasscodeFromUser() called"); + VerifyOrReturnValue([[MCCastingApp getSharedInstance] isRunning], false, ChipLogError(AppServer, "MCCastingPlayer.isPendingPasscodeFromUser() MCCastingApp NOT running")); + + dispatch_queue_t workQueue = [[MCCastingApp getSharedInstance] getWorkQueue]; + + __block bool isPending = false; + dispatch_sync(workQueue, ^{ + isPending = _cppCastingPlayer->IsPendingPasscodeFromUser(); + }); + return isPending; +} + - (instancetype _Nonnull)initWithCppCastingPlayer:(matter::casting::memory::Strong)cppCastingPlayer { if (self = [super init]) { diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleView.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleView.swift index 5760fae84aa28c..8e3788c5b961c6 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleView.swift +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleView.swift @@ -88,6 +88,9 @@ struct MCConnectionExampleView: View { viewModel.connect(selectedCastingPlayer: self.selectedCastingPlayer, useCommissionerGeneratedPasscode: self.useCommissionerGeneratedPasscode) } }) + .onDisappear(perform: { + viewModel.cancelConnectionAttempt(selectedCastingPlayer: self.selectedCastingPlayer) + }) } } diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift index 815c79fc381589..5fb508687df2e9 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift @@ -42,6 +42,23 @@ class MCConnectionExampleViewModel: ObservableObject { @Published var errorCodeDescription: String? + func cancelConnectionAttempt(selectedCastingPlayer: MCCastingPlayer?) { + DispatchQueue.main.async { + // Only stop connection if we are pending passcode confirmation + if selectedCastingPlayer?.isPendingPasscodeFromUser() == true { + self.Log.info("MCConnectionExampleViewModel cancelConnect(). User navigating back from ConnectionView") + let err = selectedCastingPlayer?.stopConnecting() + if err == nil { + self.connectionStatus = "User cancelled the connection attempt with CastingPlayer.stopConnecting()." + self.Log.info("MCConnectionExampleViewModel cancelConnect() MCCastingPlayer.stopConnecting() succeeded.") + } else { + self.connectionStatus = "Cancel connection failed due to: \(String(describing: err))." + self.Log.error("MCConnectionExampleViewModel cancelConnect() MCCastingPlayer.stopConnecting() failed due to: \(err)") + } + } + } + } + func connect(selectedCastingPlayer: MCCastingPlayer?, useCommissionerGeneratedPasscode: Bool) { self.Log.info("MCConnectionExampleViewModel.connect() useCommissionerGeneratedPasscode: \(String(describing: useCommissionerGeneratedPasscode))") diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp index 37d442f7142aa2..113a559f4bced6 100644 --- a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp +++ b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp @@ -228,11 +228,6 @@ CHIP_ERROR CastingPlayer::ContinueConnecting() CHIP_ERROR CastingPlayer::StopConnecting() { - VerifyOrReturnValue( - mIdOptions.mCommissionerPasscode, CHIP_ERROR_INCORRECT_STATE, - ChipLogError(AppServer, - "CastingPlayer::StopConnecting() mIdOptions.mCommissionerPasscode == false, ContinueConnecting() should only " - "be called when the CastingPlayer/Commissioner-Generated passcode commissioning flow is in progress.");); // Calling the internal StopConnecting() API with the shouldSendIdentificationDeclarationMessage set to true to notify the // CastingPlayer/Commissioner that the commissioning session was cancelled by the Casting Client/Commissionee user. This will // result in the Casting Client/Commissionee sending a CancelPasscode IdentificationDeclaration message to the CastingPlayer. diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h index b2422b43f5dee6..9cbabc0aae37d1 100644 --- a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h +++ b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h @@ -142,6 +142,14 @@ class CastingPlayer : public std::enable_shared_from_this */ bool IsConnected() const { return mConnectionState == CASTING_PLAYER_CONNECTED; } + /** + * @return true if this CastingPlayer is still pending pass code from user and therefore is not ready + */ + bool IsPendingPasscodeFromUser() const + { + return mConnectionState == CASTING_PLAYER_CONNECTING && !mIdOptions.mCommissionerPasscodeReady; + } + /** * @brief Verifies that a connection exists with this CastingPlayer, or triggers a new commissioning session request. If the * CastingApp does not have the nodeId and fabricIndex of this CastingPlayer cached on disk, this will execute the User Directed From b6e8fd22ef596bd38f4742302f4907da825dad40 Mon Sep 17 00:00:00 2001 From: Siu-Tung Tony Yuen Date: Fri, 28 Feb 2025 16:58:25 -0800 Subject: [PATCH 2/7] [Amazon] StopConnect for UDC in Android tv-casting-app Updated native layer and iOS tv-casting-app to use native layer ConnectionState instead of a pendingPasscode customer check. Changes * Expose native layer ConnectionState to iOS tv-casting-app * Update iOS tv-casting-app to use ConnectionState to determine if we should call stopConnect() * Only call stopConnect() on navigate back from ConnectionExampleView * Updated doc to indicate when a object method will free itself as a side effect Test 1. Manually verified UDC attempt is successful both Android iOS for following 1. Attempt UDC attempt (both commissionee & commissioner generated passcode) 2. Navigate back to discovery page 3. Start new UDC attempt (both commissionee & commissioner generated passcode) 2. Manually verified UDC attempt is successful both Android iOS for following. UDC will finish in the background. 1. Attempt UDC attempt (both commissionee & commissioner generated passcode) 2. Confirm passcode on TV app or casting app depending on who generated passcode 3. Navigate back before commission fnishes. 3. Manual regression test following * UDC attempt happy path (no navigate back) for both commissinee & commissioner generated passcode --- .../MatterTvCastingBridge/MCCastingPlayer.h | 16 +++++++- .../MatterTvCastingBridge/MCCastingPlayer.mm | 29 +++++++++---- .../MCConnectionExampleViewModel.swift | 41 ++++++------------- .../tv-casting-common/core/CastingPlayer.h | 12 +++++- .../core/CastingPlayerDiscovery.h | 1 + .../support/ChipDeviceEventHandler.cpp | 12 ++++++ 6 files changed, 72 insertions(+), 39 deletions(-) diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.h index a6a2d286252648..bfd47804dea371 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.h +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.h @@ -26,6 +26,16 @@ @class MCEndpoint; +/** + * @brief Represents CastingPlayer ConnectionState. + * @note Should be kept up to date with matter::casting::core::ConnectionState. + */ +typedef enum { + MC_CASTING_PLAYER_NOT_CONNECTED, + MC_CASTING_PLAYER_CONNECTING, + MC_CASTING_PLAYER_CONNECTED, +} MCCastingPlayerConnectionState; + /** * @brief MCCastingPlayer represents a Matter commissioner that is able to play media to a physical * output or to a display screen which is part of the device. @@ -149,9 +159,11 @@ - (void)disconnect; /** - * @return true if this CastingPlayer is still pending pass code from user and therefore is not ready + * @brief Get the CastingPlayer's connection state + * @param state The current connection state that will be return. + * @return nil if request submitted successfully, otherwise a NSError object corresponding to the error. */ -- (bool)isPendingPasscodeFromUser; +- (NSError * _Nullable)getConnectionState:(MCCastingPlayerConnectionState * _Nonnull)state; - (NSString * _Nonnull)identifier; - (NSString * _Nonnull)deviceName; diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.mm index 78afbf3443ac21..3a812704c774cd 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.mm +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.mm @@ -190,18 +190,33 @@ - (void)disconnect }); } -- (bool)isPendingPasscodeFromUser +- (NSError * _Nullable)getConnectionState:(MCCastingPlayerConnectionState * _Nonnull)state; { - ChipLogProgress(AppServer, "MCCastingPlayer.isPendingPasscodeFromUser() called"); - VerifyOrReturnValue([[MCCastingApp getSharedInstance] isRunning], false, ChipLogError(AppServer, "MCCastingPlayer.isPendingPasscodeFromUser() MCCastingApp NOT running")); + ChipLogProgress(AppServer, "MCCastingPlayer.getConnectionState() called"); + VerifyOrReturnValue([[MCCastingApp getSharedInstance] isRunning], [MCErrorUtils NSErrorFromChipError:CHIP_ERROR_INCORRECT_STATE], ChipLogError(AppServer, "MCCastingPlayer.getConnectionState() MCCastingApp NOT running")); + __block matter::casting::core::ConnectionState native_state = matter::casting::core::ConnectionState::CASTING_PLAYER_NOT_CONNECTED; dispatch_queue_t workQueue = [[MCCastingApp getSharedInstance] getWorkQueue]; - - __block bool isPending = false; dispatch_sync(workQueue, ^{ - isPending = _cppCastingPlayer->IsPendingPasscodeFromUser(); + native_state = _cppCastingPlayer->GetConnectionState(); }); - return isPending; + + switch (native_state) { + case matter::casting::core::ConnectionState::CASTING_PLAYER_NOT_CONNECTED: + *state = MC_CASTING_PLAYER_NOT_CONNECTED; + break; + case matter::casting::core::ConnectionState::CASTING_PLAYER_CONNECTING: + *state = MC_CASTING_PLAYER_CONNECTING; + break; + case matter::casting::core::ConnectionState::CASTING_PLAYER_CONNECTED: + *state = MC_CASTING_PLAYER_CONNECTED; + break; + default: + [NSException raise:@"Unhandled matter::casting::core::ConnectionState" format:@"%d is not handled", native_state]; + break; + } + + return nil; } - (instancetype _Nonnull)initWithCppCastingPlayer:(matter::casting::memory::Strong)cppCastingPlayer diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift index 5fb508687df2e9..8848cf456cd521 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift @@ -45,7 +45,13 @@ class MCConnectionExampleViewModel: ObservableObject { func cancelConnectionAttempt(selectedCastingPlayer: MCCastingPlayer?) { DispatchQueue.main.async { // Only stop connection if we are pending passcode confirmation - if selectedCastingPlayer?.isPendingPasscodeFromUser() == true { + var connectionState = MC_CASTING_PLAYER_NOT_CONNECTED; + let err = selectedCastingPlayer?.getConnectionState(&connectionState) + if err != nil { + self.Log.error("MCConnectionExampleViewModel cancelConnect() MCCastingPlayer.getConnectionState() failed due to: \(err)") + } + + if connectionState == MC_CASTING_PLAYER_CONNECTING { self.Log.info("MCConnectionExampleViewModel cancelConnect(). User navigating back from ConnectionView") let err = selectedCastingPlayer?.stopConnecting() if err == nil { @@ -125,17 +131,9 @@ class MCConnectionExampleViewModel: ObservableObject { dataSource.update(newCommissionableData) self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, Updated MCAppParametersDataSource instance with new MCCommissionableData.") } else { - self.Log.error("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, InitializationExample.getAppParametersDataSource() failed, calling stopConnecting()") - self.connectionStatus = "Failed to update the MCAppParametersDataSource with the user entered passcode: \n\nRoute back and try again." + self.Log.error("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, InitializationExample.getAppParametersDataSource() failed") + self.connectionStatus = "Failed to update the MCAppParametersDataSource with the user entered passcode: \n\nRoute back to disconnect and try again." self.connectionSuccess = false - // Since we failed to update the passcode, Attempt to cancel the connection attempt with - // the CastingPlayer/Commissioner. - let err = selectedCastingPlayer?.stopConnecting() - if err == nil { - self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, InitializationExample.getAppParametersDataSource() failed, then stopConnecting() succeeded") - } else { - self.Log.error("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, InitializationExample.getAppParametersDataSource() failed, then stopConnecting() failed due to: \(err)") - } return } @@ -144,28 +142,13 @@ class MCConnectionExampleViewModel: ObservableObject { if errContinue == nil { self.connectionStatus = "Continuing to connect with user entered passcode: \(userEnteredPasscode)" } else { - self.connectionStatus = "Continue Connecting to Casting Player failed with: \(String(describing: errContinue)) \n\nRoute back and try again." + self.connectionStatus = "Continue Connecting to Casting Player failed with: \(String(describing: errContinue)) \n\nRoute back to disconnect and try again." self.Log.error("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, MCCastingPlayer.continueConnecting() failed due to: \(errContinue)") - // Since continueConnecting() failed, Attempt to cancel the connection attempt with - // the CastingPlayer/Commissioner. - let err = selectedCastingPlayer?.stopConnecting() - if err == nil { - self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, MCCastingPlayer.continueConnecting() failed, then stopConnecting() succeeded") - } else { - self.Log.error("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, MCCastingPlayer.continueConnecting() failed, then stopConnecting() failed due to: \(err)") - } } }, cancelConnecting: { - self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, Connection attempt cancelled by the user, calling MCCastingPlayer.stopConnecting()") - let err = selectedCastingPlayer?.stopConnecting() + self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, Connection attempt cancelled by the user") self.connectionSuccess = false - if err == nil { - self.connectionStatus = "User cancelled the connection attempt with CastingPlayer. \n\nRoute back to exit." - self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, User cancelled the connection attempt with MCCastingPlayer, MCCastingPlayer.stopConnecting() succeeded.") - } else { - self.connectionStatus = "Cancel connection failed due to: \(String(describing: err)) \n\nRoute back to exit." - self.Log.error("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, MCCastingPlayer.stopConnecting() failed due to: \(err)") - } + self.connectionStatus = "User cancelled the connection attempt with CastingPlayer. \n\nRoute back to disconnect & exit." }) } } diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h index 9cbabc0aae37d1..801b629eec4263 100644 --- a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h +++ b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h @@ -218,11 +218,15 @@ class CastingPlayer : public std::enable_shared_from_this * otherwise. StopConnecting() can only be called by the client during the CastingPlayer/Commissioner-Generated passcode * commissioning flow. Calling StopConnecting() during the Client/Commissionee-Generated commissioning flow will return a * CHIP_ERROR_INCORRECT_STATE error. + * + * @note This method will free the calling object as a side effect. */ CHIP_ERROR StopConnecting(); /** * @brief Sets the internal connection state of this CastingPlayer to "disconnected" + * + * @note This method will free the calling object as a side effect. */ void Disconnect(); @@ -286,7 +290,11 @@ class CastingPlayer : public std::enable_shared_from_this /** * @brief Return the current state of the CastingPlayer */ - ConnectionState GetConnectionState() const { return mConnectionState; } + ConnectionState GetConnectionState() const + { + ChipLogError(AppServer, "CastingPlayer::GetConnectionState() state: %d", mConnectionState); + return mConnectionState; + } private: std::vector> mEndpoints; @@ -326,6 +334,8 @@ class CastingPlayer : public std::enable_shared_from_this /** * @brief resets this CastingPlayer's state and calls mOnCompleted with the CHIP_ERROR. Also, after calling mOnCompleted, it * clears mOnCompleted by setting it to a nullptr. + * + * @note This method will free the calling object as a side effect. */ void resetState(CHIP_ERROR err); diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingPlayerDiscovery.h b/examples/tv-casting-app/tv-casting-common/core/CastingPlayerDiscovery.h index 3a98472bd8c6d1..0a6fdb9da56ec5 100644 --- a/examples/tv-casting-app/tv-casting-common/core/CastingPlayerDiscovery.h +++ b/examples/tv-casting-app/tv-casting-common/core/CastingPlayerDiscovery.h @@ -108,6 +108,7 @@ class CastingPlayerDiscovery static CastingPlayerDiscovery * GetInstance(); ~CastingPlayerDiscovery() { + ChipLogError(AppServer, "CastingPlayerDiscovery destructor() called"); mCastingPlayers.clear(); mCastingPlayersInternal.clear(); } diff --git a/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.cpp b/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.cpp index 91c0da0173e693..b83201fc5c22cf 100644 --- a/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.cpp +++ b/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.cpp @@ -37,6 +37,18 @@ void ChipDeviceEventHandler::Handle(const chip::DeviceLayer::ChipDeviceEvent * e { ChipLogProgress(AppServer, "ChipDeviceEventHandler::Handle() called"); + // Make sure we have not disconnected from the TargetCastingPlayer when handling incoming messages. + // Sometimes the tv-app will still send messages after we clean up the TargetCastingPlayer. + // Exmaple: Call stopConnecting() after immediately after calling continueConnectiong() + // + // A proper fix would be to either not let user navigate back when running post commission or update commissioner, commissionee + // communication protocal to better handle asynchronous disconnects + if (CastingPlayer::GetTargetCastingPlayer() == nullptr) + { + ChipLogError(AppServer, "ChipDeviceEventHandler::Handler() TargetCastingPlayer is null for event: %u", event->Type); + return; + } + bool runPostCommissioning = false; chip::NodeId targetNodeId = 0; chip::FabricIndex targetFabricIndex = 0; From 208e4d6a3ebb21c250ad82540da9c852e4a7bb58 Mon Sep 17 00:00:00 2001 From: Siu-Tung Tony Yuen Date: Fri, 28 Feb 2025 19:29:22 -0800 Subject: [PATCH 3/7] [Amazon] StopConnect for UDC in Android tv-casting-app Updated JNI and Android tv-casting-app to use native layer ConnectionState instead of a pendingPasscode customer check. Changes * Expose native layer ConnectionState to Android tv-casting-app * Update Android tv-casting-app to use ConnectionState to determine if we should call stopConnect() * Only call stopConnect() on navigate back from ConnectionExampleFragmnet Test 1. Manually verified UDC attempt is successful both Android iOS for following 1. Attempt UDC attempt (both commissionee & commissioner generated passcode) 2. Navigate back to discovery page 3. Start new UDC attempt (both commissionee & commissioner generated passcode) 2. Manually verified UDC attempt is successful both Android iOS for following. UDC will finish in the background. 1. Attempt UDC attempt (both commissionee & commissioner generated passcode) 2. Confirm passcode on TV app or casting app depending on who generated passcode 3. Navigate back before commission fnishes. 3. Manual regression test following * UDC attempt happy path (no navigate back) for both commissinee & commissioner generated passcode --- .../casting/ConnectionExampleFragment.java | 36 +++++-------------- .../matter/casting/core/CastingPlayer.java | 11 +++++- .../casting/core/MatterCastingPlayer.java | 7 +++- .../jni/cpp/core/MatterCastingPlayer-JNI.cpp | 22 +++++++++--- .../tv-casting-common/core/CastingPlayer.h | 8 ----- 5 files changed, 43 insertions(+), 41 deletions(-) diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java index 2f76ce83debf25..89ab412f1776ea 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java @@ -263,12 +263,11 @@ public void handle(CommissionerDeclaration cd) { } @Override - public void onDestroy() { - super.onDestroy(); - - // Only stop connection if we are pending passcode confirmation - // We cannot stop connection once continueConnecting() is called - if (targetCastingPlayer.isPendingPasscodeFromUser()) { + public void onStop() { + super.onStop(); + // Only stop connection if we are still connecting to the device + if (targetCastingPlayer.getConnectionState() == CastingPlayer.ConnectionState.CONNECTING) { + // NOTE, once stopConnecting() is called, the targetCastingPlayer's native object is freed MatterError err = targetCastingPlayer.stopConnecting(); if (err.hasError()) { Log.e( @@ -352,18 +351,12 @@ public void onClick(DialogInterface dialog, int which) { connectionFragmentStatusTextView.setText( "Casting Player CONTINUE CONNECTING failed due to: " + finalErr - + "\n\n"); + + ". Route back to disconnect & try again. \n\n"); }); Log.e( TAG, - "displayPasscodeInputDialog() continueConnecting() failed, calling stopConnecting() due to: " + "displayPasscodeInputDialog() continueConnecting() failed due to: " + err); - // Since continueConnecting() failed, Attempt to cancel the connection attempt with - // the CastingPlayer/Commissioner. - err = targetCastingPlayer.stopConnecting(); - if (err.hasError()) { - Log.e(TAG, "displayPasscodeInputDialog() stopConnecting() failed due to: " + err); - } } } }); @@ -375,20 +368,9 @@ public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) { Log.i( TAG, - "displayPasscodeInputDialog() user cancelled the CastingPlayer/Commissioner-Generated Passcode input dialog. Calling stopConnecting()"); + "displayPasscodeInputDialog() user cancelled the CastingPlayer/Commissioner-Generated Passcode input dialog"); connectionFragmentStatusTextView.setText( - "Connection attempt with Casting Player cancelled by the Casting Client/Commissionee user. \n\nRoute back to exit. \n\n"); - MatterError err = targetCastingPlayer.stopConnecting(); - if (err.hasError()) { - MatterError finalErr = err; - getActivity() - .runOnUiThread( - () -> { - connectionFragmentStatusTextView.setText( - "Casting Player CANCEL failed due to: " + finalErr + "\n\n"); - }); - Log.e(TAG, "displayPasscodeInputDialog() stopConnecting() failed due to: " + err); - } + "Connection attempt with Casting Player cancelled by the Casting Client/Commissionee user. \n\nRoute back to disconnect & exit. \n\n"); dialog.cancel(); } }); diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java index 114ce65fbf94bd..fc22f9c39fcfe9 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java @@ -29,6 +29,13 @@ * about the service discovered/resolved. */ public interface CastingPlayer { + + public enum ConnectionState { + NOT_CONNECTED, + CONNECTING, + CONNECTED, + } + boolean isConnected(); String getDeviceId(); @@ -157,5 +164,7 @@ MatterError verifyOrEstablishConnection( * @return true if this CastingPlayer is still pending pass code from user and therefore is not * ready */ - boolean isPendingPasscodeFromUser(); + ConnectionState getConnectionState(); + + String getConnectionStateNative(); } diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java index 3f9bacb5b94495..4943fe6cacc824 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java @@ -32,6 +32,7 @@ */ public class MatterCastingPlayer implements CastingPlayer { private static final String TAG = MatterCastingPlayer.class.getSimpleName(); + /** * Time (in sec) to keep the commissioning window open, if commissioning is required. Must be >= 3 * minutes. @@ -269,5 +270,9 @@ public MatterError continueConnecting() { public native void disconnect(); @Override - public native boolean isPendingPasscodeFromUser(); + public ConnectionState getConnectionState() { + return ConnectionState.valueOf(getConnectionStateNative()); + } + + public native String getConnectionStateNative(); } diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp index 1f12b33478f0e6..56bb9fe00598d7 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp @@ -165,16 +165,30 @@ JNI_METHOD(void, disconnect) castingPlayer->Disconnect(); } -JNI_METHOD(bool, isPendingPasscodeFromUser) +JNI_METHOD(jstring, getConnectionStateNative) (JNIEnv * env, jobject thiz) { chip::DeviceLayer::StackLock lock; - ChipLogProgress(AppServer, "MatterCastingPlayer-JNI::isPendingPasscodeFromUser()"); + ChipLogProgress(AppServer, "MatterCastingPlayer-JNI::getConnectionState()"); CastingPlayer * castingPlayer = support::convertCastingPlayerFromJavaToCpp(thiz); - VerifyOrReturnValue(castingPlayer != nullptr, support::convertMatterErrorFromCppToJava(CHIP_ERROR_INVALID_ARGUMENT)); + VerifyOrReturnValue(castingPlayer != nullptr, env->NewStringUTF("Cast Player is nullptr")); - return castingPlayer->IsPendingPasscodeFromUser(); + matter::casting::core::ConnectionState state = castingPlayer->GetConnectionState(); + switch (state) + { + case matter::casting::core::ConnectionState::CASTING_PLAYER_NOT_CONNECTED: + return env->NewStringUTF("NOT_CONNECTED"); + case matter::casting::core::ConnectionState::CASTING_PLAYER_CONNECTING: + return env->NewStringUTF("CONNECTING"); + case matter::casting::core::ConnectionState::CASTING_PLAYER_CONNECTED: + return env->NewStringUTF("CONNECTED"); + default: { + char error_str[50]; + snprintf(error_str, sizeof(error_str), "Unsupported Connection State: %d", state); + return env->NewStringUTF(error_str); + } + } } JNI_METHOD(jobject, getEndpoints) diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h index 801b629eec4263..558ae301bc06fc 100644 --- a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h +++ b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h @@ -142,14 +142,6 @@ class CastingPlayer : public std::enable_shared_from_this */ bool IsConnected() const { return mConnectionState == CASTING_PLAYER_CONNECTED; } - /** - * @return true if this CastingPlayer is still pending pass code from user and therefore is not ready - */ - bool IsPendingPasscodeFromUser() const - { - return mConnectionState == CASTING_PLAYER_CONNECTING && !mIdOptions.mCommissionerPasscodeReady; - } - /** * @brief Verifies that a connection exists with this CastingPlayer, or triggers a new commissioning session request. If the * CastingApp does not have the nodeId and fabricIndex of this CastingPlayer cached on disk, this will execute the User Directed From b89bcbcb81dacdceb5122b8c6622481da30682cd Mon Sep 17 00:00:00 2001 From: Siu-Tung Tony Yuen Date: Fri, 28 Feb 2025 20:05:48 -0800 Subject: [PATCH 4/7] [Amazon] StopConnect for UDC in Android tv-casting-app Applied restyle formatting --- .../main/jni/com/matter/casting/core/MatterCastingPlayer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java index 4943fe6cacc824..75e385c2736cbf 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java @@ -271,7 +271,7 @@ public MatterError continueConnecting() { @Override public ConnectionState getConnectionState() { - return ConnectionState.valueOf(getConnectionStateNative()); + return ConnectionState.valueOf(getConnectionStateNative()); } public native String getConnectionStateNative(); From e1c6340a6bba344e7b864bdce8ac6eff01105de5 Mon Sep 17 00:00:00 2001 From: Siu-Tung Tony Yuen Date: Fri, 28 Feb 2025 20:10:40 -0800 Subject: [PATCH 5/7] [Amazon] StopConnect for UDC in Android tv-casting-app Fix restyle CI error --- .../java/com/matter/casting/ConnectionExampleFragment.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java index 89ab412f1776ea..6320c48d0dc3fb 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java @@ -353,10 +353,7 @@ public void onClick(DialogInterface dialog, int which) { + finalErr + ". Route back to disconnect & try again. \n\n"); }); - Log.e( - TAG, - "displayPasscodeInputDialog() continueConnecting() failed due to: " - + err); + Log.e(TAG, "displayPasscodeInputDialog() continueConnecting() failed due to: " + err); } } }); From d179e925cc7f57723494f58a825a5b80655def78 Mon Sep 17 00:00:00 2001 From: Siu-Tung Tony Yuen Date: Mon, 3 Mar 2025 17:14:35 -0800 Subject: [PATCH 6/7] [Amazon] StopConnect for UDC in Android tv-casting-app Updated JNI and Android tv-casting-app to use CharToStringUTF util function to create jstring and handle NULL JNIEnv interface Changes * JNI to use CharToStringUTF util function to create jstring * Updated documentation for new JNI functions Test 1. Manually verified UDC attempt is successful both Android iOS for following 1. Attempt UDC attempt (both commissionee & commissioner generated passcode) 2. Navigate back to discovery page 3. Start new UDC attempt (both commissionee & commissioner generated passcode) 2. Manually verified UDC attempt is successful both Android iOS for following. UDC will finish in the background. 1. Attempt UDC attempt (both commissionee & commissioner generated passcode) 2. Confirm passcode on TV app or casting app depending on who generated passcode 3. Navigate back before commission fnishes. 3. Manual regression test following * UDC attempt happy path (no navigate back) for both commissinee & commissioner generated passcode --- .../com/matter/casting/core/CastingPlayer.java | 14 ++++++++++++-- .../casting/core/MatterCastingPlayer.java | 17 +++++++++++++++++ .../jni/cpp/core/MatterCastingPlayer-JNI.cpp | 13 ++++++++++++- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java index fc22f9c39fcfe9..7dba783ff8dcbb 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java @@ -30,9 +30,15 @@ */ public interface CastingPlayer { + /** @brief CastingPlayer's Conenction State */ public enum ConnectionState { + /** State when CastingPlayer is not connected */ NOT_CONNECTED, + + /** State when CastingPlayer is attempting to connect */ CONNECTING, + + /** State when CastingPlayer is connnected */ CONNECTED, } @@ -161,10 +167,14 @@ MatterError verifyOrEstablishConnection( void disconnect(); /** - * @return true if this CastingPlayer is still pending pass code from user and therefore is not - * ready + * @brief Get CastingPlayer's current ConnectionState. + * @return Current ConnectionState. */ ConnectionState getConnectionState(); + /** + * @brief Get the Current ConnectionState of a CastingPlayer from the native layer. + * @returns A String representation of the CastingPlayer's current connectation. + */ String getConnectionStateNative(); } diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java index 75e385c2736cbf..6bfc36c4e7cbeb 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java @@ -269,10 +269,27 @@ public MatterError continueConnecting() { @Override public native void disconnect(); + /** + * @brief Get CastingPlayer's current ConnectionState. + * @throws IllegalArgumentException or NullPointerException when native layer returns invalid + * state. + * @return Current ConnectionState. + */ @Override public ConnectionState getConnectionState() { return ConnectionState.valueOf(getConnectionStateNative()); } + /* + * @brief Get the Current ConnectionState of a CastingPlayer from the native layer. + * + * @returns A String representation of the CastingPlayer's current connectation. + * Possible return values are + * - "NOT_CONNECTED" + * - "CONNECTING" + * - "CONNECTED" + * - Error message or NULL on error + */ + @Override public native String getConnectionStateNative(); } diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp index 56bb9fe00598d7..5cd8170c06e505 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp @@ -168,6 +168,14 @@ JNI_METHOD(void, disconnect) JNI_METHOD(jstring, getConnectionStateNative) (JNIEnv * env, jobject thiz) { + if (NULL == env) + { + jobject err_jstr = nullptr; + LogErrorOnFailure( + chip::JniReferences::GetInstance().CharToStringUTF(CharSpan::fromCharString("JNIEnv interface is NULL"), err_jstr)); + return static_cast(err_jstr); + } + chip::DeviceLayer::StackLock lock; ChipLogProgress(AppServer, "MatterCastingPlayer-JNI::getConnectionState()"); @@ -186,7 +194,10 @@ JNI_METHOD(jstring, getConnectionStateNative) default: { char error_str[50]; snprintf(error_str, sizeof(error_str), "Unsupported Connection State: %d", state); - return env->NewStringUTF(error_str); + + jobject err_jstr = nullptr; + LogErrorOnFailure(chip::JniReferences::GetInstance().CharToStringUTF(CharSpan::fromCharString(error_str), err_jstr)); + return static_cast(err_jstr); } } } From 883b69a8f68887b72659905f2e727ed164697c86 Mon Sep 17 00:00:00 2001 From: Siu-Tung Tony Yuen Date: Mon, 3 Mar 2025 17:27:15 -0800 Subject: [PATCH 7/7] [Amazon] StopConnect for UDC in Android tv-casting-app Updated JNI and Android tv-casting-app to use CharToStringUTF util function to create jstring and handle NULL JNIEnv interface Changes * JNI to use CharToStringUTF util function to create jstring for all ConnectionState and not just errors Test 1. Manually verified UDC attempt is successful both Android iOS for following 1. Attempt UDC attempt (both commissionee & commissioner generated passcode) 2. Navigate back to discovery page 3. Start new UDC attempt (both commissionee & commissioner generated passcode) 2. Manually verified UDC attempt is successful both Android iOS for following. UDC will finish in the background. 1. Attempt UDC attempt (both commissionee & commissioner generated passcode) 2. Confirm passcode on TV app or casting app depending on who generated passcode 3. Navigate back before commission fnishes. 3. Manual regression test following * UDC attempt happy path (no navigate back) for both commissinee & commissioner generated passcode --- .../jni/cpp/core/MatterCastingPlayer-JNI.cpp | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp index 5cd8170c06e505..f16700b45997f8 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp @@ -168,12 +168,14 @@ JNI_METHOD(void, disconnect) JNI_METHOD(jstring, getConnectionStateNative) (JNIEnv * env, jobject thiz) { + char error_str[50]; + jobject jstr_obj = nullptr; + if (NULL == env) { - jobject err_jstr = nullptr; LogErrorOnFailure( - chip::JniReferences::GetInstance().CharToStringUTF(CharSpan::fromCharString("JNIEnv interface is NULL"), err_jstr)); - return static_cast(err_jstr); + chip::JniReferences::GetInstance().CharToStringUTF(CharSpan::fromCharString("JNIEnv interface is NULL"), jstr_obj)); + return static_cast(jstr_obj); } chip::DeviceLayer::StackLock lock; @@ -186,20 +188,20 @@ JNI_METHOD(jstring, getConnectionStateNative) switch (state) { case matter::casting::core::ConnectionState::CASTING_PLAYER_NOT_CONNECTED: - return env->NewStringUTF("NOT_CONNECTED"); + LogErrorOnFailure(chip::JniReferences::GetInstance().CharToStringUTF(CharSpan::fromCharString("NOT_CONNECTED"), jstr_obj)); + break; case matter::casting::core::ConnectionState::CASTING_PLAYER_CONNECTING: - return env->NewStringUTF("CONNECTING"); + LogErrorOnFailure(chip::JniReferences::GetInstance().CharToStringUTF(CharSpan::fromCharString("CONNECTING"), jstr_obj)); + break; case matter::casting::core::ConnectionState::CASTING_PLAYER_CONNECTED: - return env->NewStringUTF("CONNECTED"); - default: { - char error_str[50]; + LogErrorOnFailure(chip::JniReferences::GetInstance().CharToStringUTF(CharSpan::fromCharString("CONNECTED"), jstr_obj)); + break; + default: snprintf(error_str, sizeof(error_str), "Unsupported Connection State: %d", state); - - jobject err_jstr = nullptr; - LogErrorOnFailure(chip::JniReferences::GetInstance().CharToStringUTF(CharSpan::fromCharString(error_str), err_jstr)); - return static_cast(err_jstr); - } + LogErrorOnFailure(chip::JniReferences::GetInstance().CharToStringUTF(CharSpan::fromCharString(error_str), jstr_obj)); + break; } + return static_cast(jstr_obj); } JNI_METHOD(jobject, getEndpoints)