From 1d429eaba679f73d2ee06ea5c688344dd9048468 Mon Sep 17 00:00:00 2001 From: chrisdecenzo <61757564+chrisdecenzo@users.noreply.github.com> Date: Fri, 23 Feb 2024 13:32:42 -0800 Subject: [PATCH] TV Android Sample App: Dialogs for new commissioning feedback and message cluster (#32281) * Dialogs for new commissioning feedback and message cluster * Address comments * Restyle TV Android Sample App: Dialogs for new commissioning feedback and message cluster (#32282) * Restyled by whitespace * Restyled by google-java-format --------- Co-authored-by: Restyled.io --------- Co-authored-by: restyled-io[bot] <32688539+restyled-io[bot]@users.noreply.github.com> Co-authored-by: Restyled.io --- .../server/MatterCommissioningPrompter.java | 311 ++++++++++++++---- .../tv-app/android/java/MessagesManager.cpp | 6 +- .../android/java/MyUserPrompter-JNI.cpp | 165 ++++++++-- .../tv-app/android/java/MyUserPrompter-JNI.h | 3 + .../java/MyUserPrompterResolver-JNI.cpp | 35 ++ .../tv/server/tvapp/MessagesManagerStub.java | 19 +- .../matter/tv/server/tvapp/UserPrompter.java | 31 ++ .../tv/server/tvapp/UserPrompterResolver.java | 16 + .../linux/CastingShellCommands.cpp | 7 + .../CommissionerDiscoveryController.cpp | 4 +- 10 files changed, 507 insertions(+), 90 deletions(-) diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/MatterCommissioningPrompter.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/MatterCommissioningPrompter.java index 29690a408c562f..7879af41158438 100644 --- a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/MatterCommissioningPrompter.java +++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/MatterCommissioningPrompter.java @@ -12,6 +12,7 @@ import androidx.appcompat.app.AlertDialog; import androidx.core.app.NotificationCompat; import com.matter.tv.server.service.MatterServant; +import com.matter.tv.server.tvapp.Message; import com.matter.tv.server.tvapp.UserPrompter; import com.matter.tv.server.tvapp.UserPrompterResolver; @@ -26,6 +27,7 @@ public class MatterCommissioningPrompter extends UserPrompterResolver implements public MatterCommissioningPrompter(Context context) { this.context = context; this.createNotificationChannel(); + setUserPrompter(this); } private Activity getActivity() { @@ -34,7 +36,6 @@ private Activity getActivity() { public void promptForCommissionOkPermission( int vendorId, int productId, String commissioneeName) { - // TODO: find app by vendorId and productId Log.d( TAG, "Received prompt for OK permission vendor id:" @@ -43,28 +44,33 @@ public void promptForCommissionOkPermission( + productId + ". Commissionee: " + commissioneeName); - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - - builder - .setMessage(commissioneeName + " is requesting permission to cast to this device, approve?") - .setTitle("Allow access to " + commissioneeName) - .setPositiveButton( - "Ok", - (dialog, which) -> { - OnPromptAccepted(); - }) - .setNegativeButton( - "Cancel", - (dialog, which) -> { - OnPromptDeclined(); - }) - .create() - .show(); + + getActivity() + .runOnUiThread( + () -> { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder + .setMessage( + commissioneeName + + " is requesting permission to cast to this device, approve?") + .setTitle("Allow access to " + commissioneeName) + .setPositiveButton( + "Ok", + (dialog, which) -> { + OnPromptAccepted(); + }) + .setNegativeButton( + "Cancel", + (dialog, which) -> { + OnPromptDeclined(); + }) + .create() + .show(); + }); } @Override public void promptForCommissionPinCode(int vendorId, int productId, String commissioneeName) { - // TODO: find app by vendorId and productId Log.d( TAG, "Received prompt for PIN code vendor id:" @@ -73,26 +79,143 @@ public void promptForCommissionPinCode(int vendorId, int productId, String commi + productId + ". Commissionee: " + commissioneeName); - EditText editText = new EditText(getActivity()); - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - - builder - .setMessage("Please enter PIN displayed in casting app.") - .setTitle("Allow access to " + commissioneeName) - .setView(editText) - .setPositiveButton( - "Ok", - (dialog, which) -> { - String pinCode = editText.getText().toString(); - OnPinCodeEntered(Integer.parseInt(pinCode)); - }) - .setNegativeButton( - "Cancel", - (dialog, which) -> { - OnPinCodeDeclined(); - }) - .create() - .show(); + + getActivity() + .runOnUiThread( + () -> { + EditText editText = new EditText(getActivity()); + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + + builder + .setMessage("Please enter PIN displayed in casting app.") + .setTitle("Allow access to " + commissioneeName) + .setView(editText) + .setPositiveButton( + "Ok", + (dialog, which) -> { + String pinCode = editText.getText().toString(); + OnPinCodeEntered(Integer.parseInt(pinCode)); + }) + .setNegativeButton( + "Cancel", + (dialog, which) -> { + OnPinCodeDeclined(); + }) + .create() + .show(); + }); + } + + public void hidePromptsOnCancel(int vendorId, int productId, String commissioneeName) { + Log.d( + TAG, + "Received Cancel from vendor id:" + + vendorId + + " productId:" + + productId + + ". Commissionee: " + + commissioneeName); + + getActivity() + .runOnUiThread( + () -> { + AlertDialog.Builder abuilder = new AlertDialog.Builder(getActivity()); + abuilder + .setMessage("Cancelled connection to " + commissioneeName) + .setTitle("Connection Cancelled") + .create() + .show(); + + NotificationCompat.Builder builder = + new NotificationCompat.Builder(getActivity(), CHANNEL_ID) + .setSmallIcon(R.drawable.ic_baseline_check_24) + .setContentTitle("Connection Cancelled") + .setContentText("Cancelled connection to " + commissioneeName) + .setPriority(NotificationCompat.PRIORITY_DEFAULT); + + notificationManager.notify(SUCCESS_ID, builder.build()); + }); + } + + public void promptWithCommissionerPasscode( + int vendorId, + int productId, + String commissioneeName, + long passcode, + int pairingHint, + String pairingInstruction) { + Log.d( + TAG, + "Received prompt for Commissioner Passcode:" + + passcode + + " vendor id:" + + vendorId + + " productId:" + + productId + + ". Commissionee: " + + commissioneeName); + + getActivity() + .runOnUiThread( + () -> { + EditText editText = new EditText(getActivity()); + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + + builder + .setMessage( + "Please enter " + + passcode + + " in " + + commissioneeName + + " app. " + + pairingInstruction + + " [" + + pairingHint + + "]") + .setTitle("Passcode" + passcode) + .setPositiveButton( + "Ok", + (dialog, which) -> { + OnCommissionerPasscodeOK(); + }) + .setNegativeButton( + "Cancel", + (dialog, which) -> { + OnCommissionerPasscodeCancel(); + }) + .create() + .show(); + }); + } + + public void promptCommissioningStarted(int vendorId, int productId, String commissioneeName) { + Log.d( + TAG, + "Received prompt for started vendor id:" + + vendorId + + " productId:" + + productId + + ". Commissionee: " + + commissioneeName); + getActivity() + .runOnUiThread( + () -> { + AlertDialog.Builder abuilder = new AlertDialog.Builder(getActivity()); + abuilder + .setMessage("Starting connection to " + commissioneeName) + .setTitle("Connection Starting") + .create() + .show(); + + NotificationCompat.Builder builder = + new NotificationCompat.Builder(getActivity(), CHANNEL_ID) + .setSmallIcon(R.drawable.ic_baseline_check_24) + .setContentTitle("Connection Starting") + .setContentText("Starting connection to " + commissioneeName) + .setPriority(NotificationCompat.PRIORITY_DEFAULT); + + notificationManager.notify(SUCCESS_ID, builder.build()); + }); } public void promptCommissioningSucceeded(int vendorId, int productId, String commissioneeName) { @@ -104,35 +227,105 @@ public void promptCommissioningSucceeded(int vendorId, int productId, String com + productId + ". Commissionee: " + commissioneeName); - NotificationCompat.Builder builder = - new NotificationCompat.Builder(getActivity(), CHANNEL_ID) - .setSmallIcon(R.drawable.ic_baseline_check_24) - .setContentTitle("Connection Complete") - .setContentText( - "Success. " - + commissioneeName - + " can now cast to this device. Visit settings to manage access control for casting.") - .setPriority(NotificationCompat.PRIORITY_DEFAULT); - - notificationManager.notify(SUCCESS_ID, builder.build()); + getActivity() + .runOnUiThread( + () -> { + AlertDialog.Builder abuilder = new AlertDialog.Builder(getActivity()); + abuilder + .setMessage( + "Success. " + + commissioneeName + + " can now cast to this device. Visit settings to manage access control for casting.") + .setTitle("Connection Complete") + .create() + .show(); + + NotificationCompat.Builder builder = + new NotificationCompat.Builder(getActivity(), CHANNEL_ID) + .setSmallIcon(R.drawable.ic_baseline_check_24) + .setContentTitle("Connection Complete") + .setContentText( + "Success. " + + commissioneeName + + " can now cast to this device. Visit settings to manage access control for casting.") + .setPriority(NotificationCompat.PRIORITY_DEFAULT); + + notificationManager.notify(SUCCESS_ID, builder.build()); + }); } public void promptCommissioningFailed(String commissioneeName, String error) { Log.d(TAG, "Received prompt for failure Commissionee: " + commissioneeName); - NotificationCompat.Builder builder = - new NotificationCompat.Builder(getActivity(), CHANNEL_ID) - .setSmallIcon(R.drawable.ic_baseline_clear_24) - .setContentTitle("Connection Failed") - .setContentText("Failed. " + commissioneeName + " experienced error: " + error + ".") - .setPriority(NotificationCompat.PRIORITY_DEFAULT); - - notificationManager.notify(FAIL_ID, builder.build()); + getActivity() + .runOnUiThread( + () -> { + AlertDialog.Builder abuilder = new AlertDialog.Builder(getActivity()); + abuilder + .setMessage("Failed. " + commissioneeName + " experienced error: " + error + ".") + .setTitle("Connection Failed") + .create() + .show(); + + NotificationCompat.Builder builder = + new NotificationCompat.Builder(getActivity(), CHANNEL_ID) + .setSmallIcon(R.drawable.ic_baseline_clear_24) + .setContentTitle("Connection Failed") + .setContentText( + "Failed. " + commissioneeName + " experienced error: " + error + ".") + .setPriority(NotificationCompat.PRIORITY_DEFAULT); + + notificationManager.notify(FAIL_ID, builder.build()); + }); + } + + public void promptWithMessage(Message message) { + Log.d(TAG, "Received message prompt for " + message.messageText); + getActivity() + .runOnUiThread( + () -> { + if (message.responseOptions.length != 2) { + AlertDialog.Builder abuilder = new AlertDialog.Builder(getActivity()); + abuilder + .setMessage("" + message.messageId + ":" + message.messageText) + .setTitle("New Message from Test") + .setPositiveButton( + "Ok", + (dialog, which) -> { + OnMessageResponse(message.messageId, 0); // ack + }) + .setNegativeButton( + "Ignore", + (dialog, which) -> { + OnMessageResponse(message.messageId, -1); // ignore + }) + .create() + .show(); + } else { + AlertDialog.Builder abuilder = new AlertDialog.Builder(getActivity()); + abuilder + .setMessage("" + message.messageId + ":" + message.messageText) + .setTitle("New Message from Test") + .setPositiveButton( + message.responseOptions[0].label, + (dialog, which) -> { + OnMessageResponse(message.messageId, message.responseOptions[0].id); + }) + .setNegativeButton( + message.responseOptions[1].label, + (dialog, which) -> { + OnMessageResponse(message.messageId, message.responseOptions[1].id); + }) + .create() + .show(); + } + }); } private void createNotificationChannel() { // Create the NotificationChannel, but only on API 26+ because // the NotificationChannel class is new and not in the support library if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Log.d(TAG, " ------------- createNotificationChannel"); CharSequence name = "MatterPromptNotificationChannel"; String description = "Matter Channel for sending notifications"; int importance = NotificationManager.IMPORTANCE_DEFAULT; @@ -142,6 +335,8 @@ private void createNotificationChannel() { // or other notification behaviors after this this.notificationManager = getSystemService(context, NotificationManager.class); notificationManager.createNotificationChannel(channel); + } else { + Log.d(TAG, " ------------- NOT createNotificationChannel"); } } } diff --git a/examples/tv-app/android/java/MessagesManager.cpp b/examples/tv-app/android/java/MessagesManager.cpp index dbcb5c5994504b..680c96b3a34807 100644 --- a/examples/tv-app/android/java/MessagesManager.cpp +++ b/examples/tv-app/android/java/MessagesManager.cpp @@ -382,11 +382,11 @@ CHIP_ERROR MessagesManager::HandlePresentMessagesRequest( return CHIP_ERROR_INTERNAL; } - jobject jlong = env->NewObject(longClass, longCtor, response.messageResponseID.Value()); - VerifyOrReturnError(jlong != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "Could not create Long")); + jobject jlongobj = env->NewObject(longClass, longCtor, static_cast(response.messageResponseID.Value())); + VerifyOrReturnError(jlongobj != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "Could not create Long")); // add to HashMap - env->CallObjectMethod(joptions, hashMapPut, jlong, jlabel); + env->CallObjectMethod(joptions, hashMapPut, jlongobj, jlabel); if (env->ExceptionCheck()) { ChipLogError(DeviceLayer, "Java exception in MessagesManager::HandlePresentMessagesRequest"); diff --git a/examples/tv-app/android/java/MyUserPrompter-JNI.cpp b/examples/tv-app/android/java/MyUserPrompter-JNI.cpp index 82b06e210d7853..6d586d99c1c67f 100644 --- a/examples/tv-app/android/java/MyUserPrompter-JNI.cpp +++ b/examples/tv-app/android/java/MyUserPrompter-JNI.cpp @@ -51,6 +51,29 @@ JNIMyUserPrompter::JNIMyUserPrompter(jobject provider) env->ExceptionClear(); } + mHidePromptsOnCancelMethod = env->GetMethodID(JNIMyUserPrompterClass, "hidePromptsOnCancel", "(IILjava/lang/String;)V"); + if (mHidePromptsOnCancelMethod == nullptr) + { + ChipLogError(Zcl, "Failed to access JNIMyUserPrompter 'hidePromptsOnCancel' method"); + env->ExceptionClear(); + } + + mPromptWithCommissionerPasscodeMethod = + env->GetMethodID(JNIMyUserPrompterClass, "promptWithCommissionerPasscode", "(IILjava/lang/String;JILjava/lang/String;)V"); + if (mPromptWithCommissionerPasscodeMethod == nullptr) + { + ChipLogError(Zcl, "Failed to access JNIMyUserPrompter 'promptWithCommissionerPasscode' method"); + env->ExceptionClear(); + } + + mPromptCommissioningStartedMethod = + env->GetMethodID(JNIMyUserPrompterClass, "promptCommissioningStarted", "(IILjava/lang/String;)V"); + if (mPromptCommissioningStartedMethod == nullptr) + { + ChipLogError(Zcl, "Failed to access JNIMyUserPrompter 'promptCommissioningStarted' method"); + env->ExceptionClear(); + } + mPromptCommissioningSucceededMethod = env->GetMethodID(JNIMyUserPrompterClass, "promptCommissioningSucceeded", "(IILjava/lang/String;)V"); if (mPromptCommissioningSucceededMethod == nullptr) @@ -81,17 +104,18 @@ void JNIMyUserPrompter::PromptForCommissionOKPermission(uint16_t vendorId, uint1 DeviceLayer::StackUnlock unlock; CHIP_ERROR err = CHIP_NO_ERROR; JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); - std::string stringCommissioneeName(commissioneeName); VerifyOrExit(mJNIMyUserPrompterObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE); VerifyOrExit(mPromptForCommissionOKPermissionMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE); VerifyOrExit(env != nullptr, err = CHIP_JNI_ERROR_NO_ENV); { - UtfString jniCommissioneeName(env, stringCommissioneeName.data()); + jstring jcommissioneeName = env->NewStringUTF(commissioneeName); + VerifyOrExit(jcommissioneeName != nullptr, ChipLogError(Zcl, "Could not create jstring"); err = CHIP_ERROR_INTERNAL); + env->ExceptionClear(); env->CallVoidMethod(mJNIMyUserPrompterObject.ObjectRef(), mPromptForCommissionOKPermissionMethod, - static_cast(vendorId), static_cast(productId), jniCommissioneeName.jniValue()); + static_cast(vendorId), static_cast(productId), jcommissioneeName); if (env->ExceptionCheck()) { ChipLogError(DeviceLayer, "Java exception in PromptForCommissionOKPermission"); @@ -123,17 +147,18 @@ void JNIMyUserPrompter::PromptForCommissionPasscode(uint16_t vendorId, uint16_t DeviceLayer::StackUnlock unlock; CHIP_ERROR err = CHIP_NO_ERROR; JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); - std::string stringCommissioneeName(commissioneeName); VerifyOrExit(mJNIMyUserPrompterObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE); VerifyOrExit(mPromptForCommissionPincodeMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE); VerifyOrExit(env != nullptr, err = CHIP_JNI_ERROR_NO_ENV); { - UtfString jniCommissioneeName(env, stringCommissioneeName.data()); + jstring jcommissioneeName = env->NewStringUTF(commissioneeName); + VerifyOrExit(jcommissioneeName != nullptr, ChipLogError(Zcl, "Could not create jstring"); err = CHIP_ERROR_INTERNAL); + env->ExceptionClear(); env->CallVoidMethod(mJNIMyUserPrompterObject.ObjectRef(), mPromptForCommissionPincodeMethod, static_cast(vendorId), - static_cast(productId), jniCommissioneeName.jniValue()); + static_cast(productId), jcommissioneeName); if (env->ExceptionCheck()) { ChipLogError(Zcl, "Java exception in PromptForCommissionPincode"); @@ -158,8 +183,38 @@ void JNIMyUserPrompter::PromptForCommissionPasscode(uint16_t vendorId, uint16_t */ void JNIMyUserPrompter::HidePromptsOnCancel(uint16_t vendorId, uint16_t productId, const char * commissioneeName) { - // TODO - ChipLogError(Zcl, "JNIMyUserPrompter::HidePromptsOnCancel Needs Implementation"); + ChipLogError(Zcl, "JNIMyUserPrompter::HidePromptsOnCancel"); + + DeviceLayer::StackUnlock unlock; + CHIP_ERROR err = CHIP_NO_ERROR; + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + + VerifyOrExit(mJNIMyUserPrompterObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE); + VerifyOrExit(mPromptForCommissionOKPermissionMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + VerifyOrExit(env != nullptr, err = CHIP_JNI_ERROR_NO_ENV); + + { + jstring jcommissioneeName = env->NewStringUTF(commissioneeName); + VerifyOrExit(jcommissioneeName != nullptr, ChipLogError(Zcl, "Could not create jstring"); err = CHIP_ERROR_INTERNAL); + + env->ExceptionClear(); + env->CallVoidMethod(mJNIMyUserPrompterObject.ObjectRef(), mHidePromptsOnCancelMethod, static_cast(vendorId), + static_cast(productId), jcommissioneeName); + if (env->ExceptionCheck()) + { + ChipLogError(DeviceLayer, "Java exception in HidePromptsOnCancel"); + env->ExceptionDescribe(); + env->ExceptionClear(); + err = CHIP_ERROR_INCORRECT_STATE; + goto exit; + } + } + +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "HidePromptsOnCancel error: %s", err.AsString()); + } } /** @@ -168,7 +223,6 @@ void JNIMyUserPrompter::HidePromptsOnCancel(uint16_t vendorId, uint16_t productI */ bool JNIMyUserPrompter::DisplaysPasscodeAndQRCode() { - // TODO ChipLogError(Zcl, "JNIMyUserPrompter::DisplaysPasscodeAndQRCode Needs Implementation"); return false; } @@ -182,8 +236,42 @@ bool JNIMyUserPrompter::DisplaysPasscodeAndQRCode() void JNIMyUserPrompter::PromptWithCommissionerPasscode(uint16_t vendorId, uint16_t productId, const char * commissioneeName, uint32_t passcode, uint16_t pairingHint, const char * pairingInstruction) { - // TODO - ChipLogError(Zcl, "JNIMyUserPrompter::PromptWithCommissionerPasscode Needs Implementation"); + ChipLogError(Zcl, "JNIMyUserPrompter::PromptWithCommissionerPasscode"); + + DeviceLayer::StackUnlock unlock; + CHIP_ERROR err = CHIP_NO_ERROR; + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + + VerifyOrExit(mJNIMyUserPrompterObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE); + VerifyOrExit(mPromptForCommissionOKPermissionMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + VerifyOrExit(env != nullptr, err = CHIP_JNI_ERROR_NO_ENV); + + { + jstring jcommissioneeName = env->NewStringUTF(commissioneeName); + VerifyOrExit(jcommissioneeName != nullptr, ChipLogError(Zcl, "Could not create jstring"); err = CHIP_ERROR_INTERNAL); + + jstring jpairingInstruction = env->NewStringUTF(pairingInstruction); + VerifyOrExit(jpairingInstruction != nullptr, ChipLogError(Zcl, "Could not create jstring"); err = CHIP_ERROR_INTERNAL); + + env->ExceptionClear(); + env->CallVoidMethod(mJNIMyUserPrompterObject.ObjectRef(), mPromptWithCommissionerPasscodeMethod, + static_cast(vendorId), static_cast(productId), jcommissioneeName, + static_cast(passcode), static_cast(pairingHint), jpairingInstruction); + if (env->ExceptionCheck()) + { + ChipLogError(DeviceLayer, "Java exception in PromptWithCommissionerPasscode"); + env->ExceptionDescribe(); + env->ExceptionClear(); + err = CHIP_ERROR_INCORRECT_STATE; + goto exit; + } + } + +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "PromptWithCommissionerPasscode error: %s", err.AsString()); + } } /** @@ -191,8 +279,38 @@ void JNIMyUserPrompter::PromptWithCommissionerPasscode(uint16_t vendorId, uint16 */ void JNIMyUserPrompter::PromptCommissioningStarted(uint16_t vendorId, uint16_t productId, const char * commissioneeName) { - // TODO - ChipLogError(Zcl, "JNIMyUserPrompter::PromptCommissioningStarted Needs Implementation"); + ChipLogError(Zcl, "JNIMyUserPrompter::PromptCommissioningStarted"); + + DeviceLayer::StackUnlock unlock; + CHIP_ERROR err = CHIP_NO_ERROR; + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + + VerifyOrExit(mJNIMyUserPrompterObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE); + VerifyOrExit(mPromptForCommissionOKPermissionMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + VerifyOrExit(env != nullptr, err = CHIP_JNI_ERROR_NO_ENV); + + { + jstring jcommissioneeName = env->NewStringUTF(commissioneeName); + VerifyOrExit(jcommissioneeName != nullptr, ChipLogError(Zcl, "Could not create jstring"); err = CHIP_ERROR_INTERNAL); + + env->ExceptionClear(); + env->CallVoidMethod(mJNIMyUserPrompterObject.ObjectRef(), mPromptCommissioningStartedMethod, static_cast(vendorId), + static_cast(productId), jcommissioneeName); + if (env->ExceptionCheck()) + { + ChipLogError(DeviceLayer, "Java exception in PromptCommissioningStarted"); + env->ExceptionDescribe(); + env->ExceptionClear(); + err = CHIP_ERROR_INCORRECT_STATE; + goto exit; + } + } + +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "PromptCommissioningStarted error: %s", err.AsString()); + } } /* @@ -203,17 +321,18 @@ void JNIMyUserPrompter::PromptCommissioningSucceeded(uint16_t vendorId, uint16_t DeviceLayer::StackUnlock unlock; CHIP_ERROR err = CHIP_NO_ERROR; JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); - std::string stringCommissioneeName(commissioneeName); VerifyOrExit(mJNIMyUserPrompterObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE); VerifyOrExit(mPromptCommissioningSucceededMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE); VerifyOrExit(env != nullptr, err = CHIP_JNI_ERROR_NO_ENV); { - UtfString jniCommissioneeName(env, stringCommissioneeName.data()); + jstring jcommissioneeName = env->NewStringUTF(commissioneeName); + VerifyOrExit(jcommissioneeName != nullptr, ChipLogError(Zcl, "Could not create jstring"); err = CHIP_ERROR_INTERNAL); + env->ExceptionClear(); env->CallVoidMethod(mJNIMyUserPrompterObject.ObjectRef(), mPromptCommissioningSucceededMethod, static_cast(vendorId), - static_cast(productId), jniCommissioneeName.jniValue()); + static_cast(productId), jcommissioneeName); if (env->ExceptionCheck()) { @@ -240,19 +359,21 @@ void JNIMyUserPrompter::PromptCommissioningFailed(const char * commissioneeName, DeviceLayer::StackUnlock unlock; CHIP_ERROR err = CHIP_NO_ERROR; JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); - std::string stringCommissioneeName(commissioneeName); VerifyOrExit(mJNIMyUserPrompterObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE); VerifyOrExit(mPromptCommissioningFailedMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE); VerifyOrExit(env != nullptr, err = CHIP_JNI_ERROR_NO_ENV); { - std::string stringError(error.AsString()); - UtfString jniCommissioneeName(env, stringCommissioneeName.data()); - UtfString jniCommissioneeError(env, stringError.data()); + jstring jcommissioneeError = env->NewStringUTF(error.AsString()); + VerifyOrExit(jcommissioneeError != nullptr, ChipLogError(Zcl, "Could not create jstring"); err = CHIP_ERROR_INTERNAL); + + jstring jcommissioneeName = env->NewStringUTF(commissioneeName); + VerifyOrExit(jcommissioneeName != nullptr, ChipLogError(Zcl, "Could not create jstring"); err = CHIP_ERROR_INTERNAL); + env->ExceptionClear(); - env->CallVoidMethod(mJNIMyUserPrompterObject.ObjectRef(), mPromptCommissioningFailedMethod, jniCommissioneeName.jniValue(), - jniCommissioneeError.jniValue()); + env->CallVoidMethod(mJNIMyUserPrompterObject.ObjectRef(), mPromptCommissioningFailedMethod, jcommissioneeName, + jcommissioneeError); if (env->ExceptionCheck()) { diff --git a/examples/tv-app/android/java/MyUserPrompter-JNI.h b/examples/tv-app/android/java/MyUserPrompter-JNI.h index 03fb88d2fd7887..408346326a6fd6 100644 --- a/examples/tv-app/android/java/MyUserPrompter-JNI.h +++ b/examples/tv-app/android/java/MyUserPrompter-JNI.h @@ -41,6 +41,9 @@ class JNIMyUserPrompter : public UserPrompter chip::JniGlobalReference mJNIMyUserPrompterObject; jmethodID mPromptForCommissionOKPermissionMethod = nullptr; jmethodID mPromptForCommissionPincodeMethod = nullptr; + jmethodID mHidePromptsOnCancelMethod = nullptr; + jmethodID mPromptWithCommissionerPasscodeMethod = nullptr; + jmethodID mPromptCommissioningStartedMethod = nullptr; jmethodID mPromptCommissioningSucceededMethod = nullptr; jmethodID mPromptCommissioningFailedMethod = nullptr; }; diff --git a/examples/tv-app/android/java/MyUserPrompterResolver-JNI.cpp b/examples/tv-app/android/java/MyUserPrompterResolver-JNI.cpp index 2e6bd0aa1f54e3..94f56f98e49db5 100644 --- a/examples/tv-app/android/java/MyUserPrompterResolver-JNI.cpp +++ b/examples/tv-app/android/java/MyUserPrompterResolver-JNI.cpp @@ -64,3 +64,38 @@ JNI_METHOD(void, OnPromptDeclined)(JNIEnv *, jobject) GetCommissionerDiscoveryController()->Cancel(); #endif } + +JNI_METHOD(void, OnCommissionerPasscodeOK)(JNIEnv *, jobject) +{ +#if CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE + chip::DeviceLayer::StackLock lock; + ChipLogProgress(Zcl, "OnCommissionerPasscodeOK"); + // GetCommissionerDiscoveryController()->Ok(); +#endif +} + +JNI_METHOD(void, OnCommissionerPasscodeCancel)(JNIEnv *, jobject) +{ +#if CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE + chip::DeviceLayer::StackLock lock; + ChipLogProgress(Zcl, "OnCommissionerPasscodeCancel"); + GetCommissionerDiscoveryController()->Cancel(); +#endif +} + +JNI_METHOD(void, OnMessageResponse)(JNIEnv * env, jobject, jstring jMessageId, jlong jOptionId) +{ + chip::DeviceLayer::StackLock lock; + uint32_t optionid = static_cast(jOptionId); + ChipLogProgress(Zcl, "OnMessageResponse option id: %u", optionid); + + JniUtfString messageId(env, jMessageId); + if (jMessageId != nullptr) + { + ChipLogProgress(Zcl, "OnMessageResponse message id: %s", messageId.c_str()); + } + else + { + ChipLogProgress(Zcl, "OnMessageResponse message id null"); + } +} diff --git a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/MessagesManagerStub.java b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/MessagesManagerStub.java index 55d94209197559..054218a78c6a54 100644 --- a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/MessagesManagerStub.java +++ b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/MessagesManagerStub.java @@ -61,15 +61,24 @@ public boolean presentMessages( int i = 0; for (Map.Entry set : responseOptions.entrySet()) { - Log.d(TAG, "presentMessages option: key:" + set.getKey() + " value:" + set.getValue()); + Log.d( + TAG, + "presentMessages option: key:" + set.getKey().longValue() + " value:" + set.getValue()); options[i] = new MessageResponseOption(set.getKey().longValue(), set.getValue()); i++; } - messages.put( - messageId, - new Message( - messageId, priority, messageControl, startTime, duration, messageText, options)); + Message message = + new Message(messageId, priority, messageControl, startTime, duration, messageText, options); + messages.put(messageId, message); + + UserPrompter prompter = UserPrompterResolver.getUserPrompter(); + if (prompter != null) { + prompter.promptWithMessage(message); + } else { + Log.d(TAG, "presentMessages no global user prompter"); + } + return true; } diff --git a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/UserPrompter.java b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/UserPrompter.java index d0aeb5c97df412..dc6fb34ba61261 100644 --- a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/UserPrompter.java +++ b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/UserPrompter.java @@ -39,6 +39,32 @@ public interface UserPrompter { */ void promptForCommissionPinCode(int vendorId, int productId, String commissioneeName); + /** + * Called to when CancelCommissioning is received via UDC. Indicates that commissioner can stop + * showing the passcode entry or display dialog. For example, can show text such as "Commissioning + * cancelled by client" before hiding dialog. + */ + void hidePromptsOnCancel(int vendorId, int productId, String commissioneeName); + + /** + * Called to display the given setup passcode to the user, for commissioning the given + * commissioneeName with the given vendorId and productId, and provide instructions for where to + * enter it in the commissionee (when pairingHint and pairingInstruction are provided). For + * example "Casting Passcode: [passcode]. For more instructions, click here." + */ + void promptWithCommissionerPasscode( + int vendorId, + int productId, + String commissioneeName, + long passcode, + int pairingHint, + String pairingInstruction); + + /* + * Called to notify the user that commissioning succeeded. It can be in form of UI Notification. + */ + void promptCommissioningStarted(int vendorId, int productId, String commissioneeName); + /* * Called to notify the user that commissioning succeeded. It can be in form of UI Notification. */ @@ -48,4 +74,9 @@ public interface UserPrompter { * Called to notify the user that commissioning succeeded. It can be in form of UI Notification. */ void promptCommissioningFailed(String commissioneeName, String error); + + /* + * Called to display a message on the screen + */ + void promptWithMessage(Message message); } diff --git a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/UserPrompterResolver.java b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/UserPrompterResolver.java index f90c18aba1648a..4d81173aca6f9d 100644 --- a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/UserPrompterResolver.java +++ b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/UserPrompterResolver.java @@ -29,7 +29,23 @@ public class UserPrompterResolver { public native void OnPromptDeclined(); + public native void OnCommissionerPasscodeOK(); + + public native void OnCommissionerPasscodeCancel(); + + public native void OnMessageResponse(String messageId, long optionid); + static { System.loadLibrary("TvApp"); } + + static UserPrompter globalPrompter = null; + + public static void setUserPrompter(UserPrompter prompter) { + globalPrompter = prompter; + } + + public static UserPrompter getUserPrompter() { + return globalPrompter; + } } diff --git a/examples/tv-casting-app/linux/CastingShellCommands.cpp b/examples/tv-casting-app/linux/CastingShellCommands.cpp index f6b245b42dc13a..bf20ccdec73b7f 100644 --- a/examples/tv-casting-app/linux/CastingShellCommands.cpp +++ b/examples/tv-casting-app/linux/CastingShellCommands.cpp @@ -192,6 +192,13 @@ static CHIP_ERROR CastingHandler(int argc, char ** argv) { id.SetPairingInst(argv[5]); } + if (argc > 6) + { + uint16_t vid = (uint16_t) strtol(argv[6], &eptr, 10); + Protocols::UserDirectedCommissioning::TargetAppInfo info; + info.vendorId = vid; + id.AddTargetAppInfo(info); + } return Server::GetInstance().SendUserDirectedCommissioningRequest(chip::Transport::PeerAddress::UDP(commissioner, port), id); } diff --git a/src/controller/CommissionerDiscoveryController.cpp b/src/controller/CommissionerDiscoveryController.cpp index c1c6492636ecd0..86849a32f91271 100644 --- a/src/controller/CommissionerDiscoveryController.cpp +++ b/src/controller/CommissionerDiscoveryController.cpp @@ -47,7 +47,7 @@ void CommissionerDiscoveryController::OnUserDirectedCommissioningRequest(UDCClie { if (!mReady) { - ChipLogDetail(Controller, "CommissionerDiscoveryController not read. Current instance=%s", mCurrentInstance); + ChipLogDetail(Controller, "CommissionerDiscoveryController not ready. Current instance=%s", mCurrentInstance); return; } // first check if this is a cancel @@ -163,7 +163,7 @@ void CommissionerDiscoveryController::Ok() } } // handle NoAppsFound CDC case - if (!hasTargetApp) + if (!hasTargetApp && client->GetNoPasscode()) { ChipLogError(AppServer, "UX Ok: target apps specified but none found, sending CDC"); CommissionerDeclaration cd;