Skip to content

Commit

Permalink
Merge e1c6340 into 1612d52
Browse files Browse the repository at this point in the history
  • Loading branch information
yuenton-amazon authored Mar 3, 2025
2 parents 1612d52 + e1c6340 commit 3266523
Show file tree
Hide file tree
Showing 12 changed files with 169 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,21 @@ public void handle(CommissionerDeclaration cd) {
});
}

@Override
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(
TAG,
"Going back before connection finishes but stopConnecting() failed due to: " + err);
}
}
}

private void displayPasscodeInputDialog(Context context) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);

Expand Down Expand Up @@ -336,18 +351,9 @@ 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: "
+ 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);
}
Log.e(TAG, "displayPasscodeInputDialog() continueConnecting() failed due to: " + err);
}
}
});
Expand All @@ -359,20 +365,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();
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@
* about the service discovered/resolved.
*/
public interface CastingPlayer {

public enum ConnectionState {
NOT_CONNECTED,
CONNECTING,
CONNECTED,
}

boolean isConnected();

String getDeviceId();
Expand Down Expand Up @@ -152,4 +159,12 @@ 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
*/
ConnectionState getConnectionState();

String getConnectionStateNative();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -267,4 +268,11 @@ public MatterError continueConnecting() {

@Override
public native void disconnect();

@Override
public ConnectionState getConnectionState() {
return ConnectionState.valueOf(getConnectionStateNative());
}

public native String getConnectionStateNative();
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,32 @@ JNI_METHOD(void, disconnect)
castingPlayer->Disconnect();
}

JNI_METHOD(jstring, getConnectionStateNative)
(JNIEnv * env, jobject thiz)
{
chip::DeviceLayer::StackLock lock;
ChipLogProgress(AppServer, "MatterCastingPlayer-JNI::getConnectionState()");

CastingPlayer * castingPlayer = support::convertCastingPlayerFromJavaToCpp(thiz);
VerifyOrReturnValue(castingPlayer != nullptr, env->NewStringUTF("Cast Player is nullptr"));

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)
(JNIEnv * env, jobject thiz)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -148,6 +158,13 @@
*/
- (void)disconnect;

/**
* @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.
*/
- (NSError * _Nullable)getConnectionState:(MCCastingPlayerConnectionState * _Nonnull)state;

- (NSString * _Nonnull)identifier;
- (NSString * _Nonnull)deviceName;
- (uint16_t)vendorId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,35 @@ - (void)disconnect
});
}

- (NSError * _Nullable)getConnectionState:(MCCastingPlayerConnectionState * _Nonnull)state;
{
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];
dispatch_sync(workQueue, ^{
native_state = _cppCastingPlayer->GetConnectionState();
});

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<matter::casting::core::CastingPlayer>)cppCastingPlayer
{
if (self = [super init]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ struct MCConnectionExampleView: View {
viewModel.connect(selectedCastingPlayer: self.selectedCastingPlayer, useCommissionerGeneratedPasscode: self.useCommissionerGeneratedPasscode)
}
})
.onDisappear(perform: {
viewModel.cancelConnectionAttempt(selectedCastingPlayer: self.selectedCastingPlayer)
})
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,29 @@ class MCConnectionExampleViewModel: ObservableObject {

@Published var errorCodeDescription: String?

func cancelConnectionAttempt(selectedCastingPlayer: MCCastingPlayer?) {
DispatchQueue.main.async {
// Only stop connection if we are pending passcode confirmation
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 {
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))")

Expand Down Expand Up @@ -108,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
}

Expand All @@ -127,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."
})
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
12 changes: 11 additions & 1 deletion examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,15 @@ class CastingPlayer : public std::enable_shared_from_this<CastingPlayer>
* 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();

Expand Down Expand Up @@ -278,7 +282,11 @@ class CastingPlayer : public std::enable_shared_from_this<CastingPlayer>
/**
* @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<memory::Strong<Endpoint>> mEndpoints;
Expand Down Expand Up @@ -318,6 +326,8 @@ class CastingPlayer : public std::enable_shared_from_this<CastingPlayer>
/**
* @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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ class CastingPlayerDiscovery
static CastingPlayerDiscovery * GetInstance();
~CastingPlayerDiscovery()
{
ChipLogError(AppServer, "CastingPlayerDiscovery destructor() called");
mCastingPlayers.clear();
mCastingPlayersInternal.clear();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 3266523

Please sign in to comment.