diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/MainActivity.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/MainActivity.java index fbeed33502a439..2fb375dbed5c93 100644 --- a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/MainActivity.java +++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/MainActivity.java @@ -7,6 +7,7 @@ import com.matter.tv.server.fragments.ContentAppFragment; import com.matter.tv.server.fragments.QrCodeFragment; import com.matter.tv.server.fragments.TerminalFragment; +import com.matter.tv.server.service.MatterServant; import java.util.LinkedHashMap; public class MainActivity extends AppCompatActivity { @@ -18,13 +19,13 @@ public class MainActivity extends AppCompatActivity { Fragment selectedFragment = null; switch (item.getItemId()) { case R.id.content_app: - selectedFragment = new ContentAppFragment(); + selectedFragment = ContentAppFragment.newInstance(); break; case R.id.qr_code: - selectedFragment = new QrCodeFragment(); + selectedFragment = QrCodeFragment.newInstance(); break; case R.id.terminal: - selectedFragment = new TerminalFragment(); + selectedFragment = TerminalFragment.newInstance(); break; } @@ -40,6 +41,10 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + // MainActivity is needed to launch dialog prompt + // in UserPrompter + MatterServant.get().setActivity(this); + BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_navigation); bottomNavigationView.setOnItemSelectedListener(navListener); 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 new file mode 100644 index 00000000000000..4be7f24903f1cd --- /dev/null +++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/MatterCommissioningPrompter.java @@ -0,0 +1,148 @@ +package com.matter.tv.server; + +import static androidx.core.content.ContextCompat.getSystemService; + +import android.app.Activity; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.os.Build; +import android.util.Log; +import android.widget.EditText; +import androidx.appcompat.app.AlertDialog; +import androidx.core.app.NotificationCompat; +import com.tcl.chip.tvapp.UserPrompter; +import com.tcl.chip.tvapp.UserPrompterResolver; + +public class MatterCommissioningPrompter extends UserPrompterResolver implements UserPrompter { + + private Activity activity; + private NotificationManager notificationManager; + private final String CHANNEL_ID = "MatterCommissioningPrompter.CHANNEL"; + private final int SUCCESS_ID = 0; + private final int FAIL_ID = 1; + + public MatterCommissioningPrompter(Activity activity) { + this.activity = activity; + this.createNotificationChannel(); + } + + 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:" + + vendorId + + " productId:" + + productId + + ". Commissionee: " + + commissioneeName); + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + + 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:" + + vendorId + + " productId:" + + productId + + ". Commissionee: " + + commissioneeName); + EditText editText = new EditText(activity); + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + + 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 promptCommissioningSucceeded(int vendorId, int productId, String commissioneeName) { + Log.d( + TAG, + "Received prompt for success vendor id:" + + vendorId + + " productId:" + + productId + + ". Commissionee: " + + commissioneeName); + NotificationCompat.Builder builder = + new NotificationCompat.Builder(activity, 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 vendor id:" + + vendorId + + " productId:" + + productId + + ". Commissionee: " + + commissioneeName); + NotificationCompat.Builder builder = + new NotificationCompat.Builder(activity, 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()); + } + + 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) { + CharSequence name = "MatterPromptNotificationChannel"; + String description = "Matter Channel for sending notifications"; + int importance = NotificationManager.IMPORTANCE_DEFAULT; + NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance); + channel.setDescription(description); + // Register the channel with the system; you can't change the importance + // or other notification behaviors after this + this.notificationManager = getSystemService(activity, NotificationManager.class); + notificationManager.createNotificationChannel(channel); + } + } +} diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/fragments/ContentAppFragment.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/fragments/ContentAppFragment.java index 89b8804af3677d..c99045fc3ddc28 100644 --- a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/fragments/ContentAppFragment.java +++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/fragments/ContentAppFragment.java @@ -40,12 +40,9 @@ public ContentAppFragment() { * Use this factory method to create a new instance of this fragment using the provided * parameters. * - * @param param1 Parameter 1. - * @param param2 Parameter 2. * @return A new instance of fragment SettingsFragment. */ - // TODO: Rename and change types and number of parameters - public static ContentAppFragment newInstance(String param1, String param2) { + public static ContentAppFragment newInstance() { ContentAppFragment fragment = new ContentAppFragment(); Bundle args = new Bundle(); fragment.setArguments(args); diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/fragments/QrCodeFragment.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/fragments/QrCodeFragment.java index 8ab6e03a0fa542..04b17bd322e955 100644 --- a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/fragments/QrCodeFragment.java +++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/fragments/QrCodeFragment.java @@ -35,12 +35,9 @@ public QrCodeFragment() { * Use this factory method to create a new instance of this fragment using the provided * parameters. * - * @param param1 Parameter 1. - * @param param2 Parameter 2. * @return A new instance of fragment QrCodeFragment. */ - // TODO: Rename and change types and number of parameters - public static QrCodeFragment newInstance(String param1, String param2) { + public static QrCodeFragment newInstance() { QrCodeFragment fragment = new QrCodeFragment(); Bundle args = new Bundle(); fragment.setArguments(args); diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/fragments/TerminalFragment.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/fragments/TerminalFragment.java index 2962e5ce04c890..24045ce6f8c46f 100644 --- a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/fragments/TerminalFragment.java +++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/fragments/TerminalFragment.java @@ -25,12 +25,9 @@ public TerminalFragment() { * Use this factory method to create a new instance of this fragment using the provided * parameters. * - * @param param1 Parameter 1. - * @param param2 Parameter 2. * @return A new instance of fragment CommssionerFragment. */ - // TODO: Rename and change types and number of parameters - public static TerminalFragment newInstance(String param1, String param2) { + public static TerminalFragment newInstance() { TerminalFragment fragment = new TerminalFragment(); Bundle args = new Bundle(); fragment.setArguments(args); @@ -63,10 +60,7 @@ public void onResume() { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setMessage(message).setTitle("Command response:"); - - AlertDialog dialog = builder.create(); - dialog.show(); + builder.setMessage(message).setTitle("Response").create().show(); }); } } diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/MatterServant.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/MatterServant.java index f6994264086033..06fa8fdd06fc82 100644 --- a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/MatterServant.java +++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/MatterServant.java @@ -17,6 +17,7 @@ */ package com.matter.tv.server.service; +import android.app.Activity; import android.content.Context; import android.util.Log; import androidx.annotation.NonNull; @@ -28,6 +29,7 @@ import chip.platform.NsdManagerServiceResolver; import chip.platform.PreferencesConfigurationManager; import chip.platform.PreferencesKeyValueStoreManager; +import com.matter.tv.server.MatterCommissioningPrompter; import com.matter.tv.server.handlers.ContentAppEndpointManagerImpl; import com.matter.tv.server.model.ContentApp; import com.tcl.chip.tvapp.ChannelManagerStub; @@ -66,6 +68,7 @@ public static MatterServant get() { } private Context context; + private Activity activity; public void init(@NonNull Context context) { @@ -113,6 +116,7 @@ public void init(@NonNull Context context) { } }); mTvApp.setDACProvider(new DACProviderStub()); + mTvApp.setUserPrompter(new MatterCommissioningPrompter(activity)); mTvApp.setChipDeviceEventProvider( new DeviceEventProvider() { @@ -152,6 +156,10 @@ public void toggleOnOff() { mIsOn = !mIsOn; } + public void setActivity(Activity activity) { + this.activity = activity; + } + public void sendCustomCommand(String customCommand) { Log.i(MatterServant.class.getName(), customCommand); // TODO: insert logic ot send custom command here diff --git a/examples/tv-app/android/App/platform-app/src/main/res/drawable/ic_baseline_check_24.xml b/examples/tv-app/android/App/platform-app/src/main/res/drawable/ic_baseline_check_24.xml new file mode 100644 index 00000000000000..0432fa69bb7908 --- /dev/null +++ b/examples/tv-app/android/App/platform-app/src/main/res/drawable/ic_baseline_check_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/examples/tv-app/android/App/platform-app/src/main/res/drawable/ic_baseline_clear_24.xml b/examples/tv-app/android/App/platform-app/src/main/res/drawable/ic_baseline_clear_24.xml new file mode 100644 index 00000000000000..16d6d37dd9f765 --- /dev/null +++ b/examples/tv-app/android/App/platform-app/src/main/res/drawable/ic_baseline_clear_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/examples/tv-app/android/BUILD.gn b/examples/tv-app/android/BUILD.gn index 0790280116dde3..b227f97fe47101 100644 --- a/examples/tv-app/android/BUILD.gn +++ b/examples/tv-app/android/BUILD.gn @@ -62,6 +62,10 @@ shared_library("jni") { "java/MediaInputManager.h", "java/MediaPlaybackManager.cpp", "java/MediaPlaybackManager.h", + "java/MyUserPrompter-JNI.cpp", + "java/MyUserPrompter-JNI.h", + "java/MyUserPrompterResolver-JNI.cpp", + "java/MyUserPrompterResolver-JNI.h", "java/OnOffManager.cpp", "java/OnOffManager.h", "java/TVApp-JNI.cpp", @@ -129,6 +133,8 @@ android_library("java") { "java/src/com/tcl/chip/tvapp/OnOffManagerStub.java", "java/src/com/tcl/chip/tvapp/TvApp.java", "java/src/com/tcl/chip/tvapp/TvAppCallback.java", + "java/src/com/tcl/chip/tvapp/UserPrompter.java", + "java/src/com/tcl/chip/tvapp/UserPrompterResolver.java", "java/src/com/tcl/chip/tvapp/WakeOnLanManager.java", "java/src/com/tcl/chip/tvapp/WakeOnLanManagerStub.java", ] diff --git a/examples/tv-app/android/java/AppImpl.cpp b/examples/tv-app/android/java/AppImpl.cpp index e1bed97c35296c..f215c62c024b2b 100644 --- a/examples/tv-app/android/java/AppImpl.cpp +++ b/examples/tv-app/android/java/AppImpl.cpp @@ -46,60 +46,6 @@ using namespace chip; using namespace chip::AppPlatform; -#if CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE -class MyUserPrompter : public UserPrompter -{ - // tv should override this with a dialog prompt - inline void PromptForCommissionOKPermission(uint16_t vendorId, uint16_t productId, const char * commissioneeName) override - { - /* - * Called to prompt the user for consent to allow the given commissioneeName/vendorId/productId to be commissioned. - * For example "[commissioneeName] is requesting permission to cast to this TV, approve?" - * - * If user responds with OK then implementor should call CommissionerRespondOk(); - * If user responds with Cancel then implementor should call CommissionerRespondCancel(); - * - */ - GetCommissionerDiscoveryController()->Ok(); - - /** - * For Demo: Launch Prime Video App - */ - return; - } - - // tv should override this with a dialog prompt - inline void PromptForCommissionPincode(uint16_t vendorId, uint16_t productId, const char * commissioneeName) override - { - /* - * Called to prompt the user to enter the setup pincode displayed by the given commissioneeName/vendorId/productId to be - * commissioned. For example "Please enter pin displayed in casting app." - * - * If user enters with pin then implementor should call CommissionerRespondPincode(uint32_t pincode); - * If user responds with Cancel then implementor should call CommissionerRespondCancel(); - */ - - GetCommissionerDiscoveryController()->CommissionWithPincode(20202021); // dummy pin code - - /** - * For Demo: Launch Prime Video App - */ - return; - } - - // tv should override this with a dialog prompt - inline void PromptCommissioningSucceeded(uint16_t vendorId, uint16_t productId, const char * commissioneeName) override - { - return; - } - - // tv should override this with a dialog prompt - inline void PromptCommissioningFailed(const char * commissioneeName, CHIP_ERROR error) override { return; } -}; - -MyUserPrompter gMyUserPrompter; -#endif // CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE - #if CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED class MyPincodeService : public PincodeService { @@ -456,7 +402,7 @@ void ContentAppFactoryImpl::SendTestMessage(EndpointId epId, const char * messag #endif // CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED -CHIP_ERROR InitVideoPlayerPlatform() +CHIP_ERROR InitVideoPlayerPlatform(JNIMyUserPrompter * userPrompter) { #if CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED ContentAppPlatform::GetInstance().SetupAppPlatform(); @@ -465,10 +411,10 @@ CHIP_ERROR InitVideoPlayerPlatform() #if CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE CommissionerDiscoveryController * cdc = GetCommissionerDiscoveryController(); - if (cdc != nullptr) + if (cdc != nullptr && userPrompter != nullptr) { cdc->SetPincodeService(&gMyPincodeService); - cdc->SetUserPrompter(&gMyUserPrompter); + cdc->SetUserPrompter(userPrompter); cdc->SetPostCommissioningListener(&gMyPostCommissioningListener); } diff --git a/examples/tv-app/android/java/AppImpl.h b/examples/tv-app/android/java/AppImpl.h index c1631bd80673fa..bcd31ccd061fb9 100644 --- a/examples/tv-app/android/java/AppImpl.h +++ b/examples/tv-app/android/java/AppImpl.h @@ -42,6 +42,7 @@ #include "ContentAppCommandDelegate.h" #include "KeypadInputManager.h" #include "MediaPlaybackManager.h" +#include "MyUserPrompter-JNI.h" #include #include #include @@ -51,7 +52,7 @@ #include #include -CHIP_ERROR InitVideoPlayerPlatform(); +CHIP_ERROR InitVideoPlayerPlatform(JNIMyUserPrompter * userPrompter); CHIP_ERROR PreServerInit(); EndpointId AddContentApp(const char * szVendorName, uint16_t vendorId, const char * szApplicationName, uint16_t productId, const char * szApplicationVersion, jobject manager); diff --git a/examples/tv-app/android/java/MyUserPrompter-JNI.cpp b/examples/tv-app/android/java/MyUserPrompter-JNI.cpp new file mode 100644 index 00000000000000..29fff989fdff57 --- /dev/null +++ b/examples/tv-app/android/java/MyUserPrompter-JNI.cpp @@ -0,0 +1,223 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MyUserPrompter-JNI.h" +#include +#include +#include +#include +#include + +using namespace chip; + +JNIMyUserPrompter::JNIMyUserPrompter(jobject provider) +{ + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturn(env != nullptr, ChipLogError(Zcl, "Failed to GetEnvForCurrentThread for JNIMyUserPrompter")); + + mJNIMyUserPrompterObject = env->NewGlobalRef(provider); + VerifyOrReturn(mJNIMyUserPrompterObject != nullptr, ChipLogError(Zcl, "Failed to NewGlobalRef JNIMyUserPrompter")); + + jclass JNIMyUserPrompterClass = env->GetObjectClass(provider); + VerifyOrReturn(JNIMyUserPrompterClass != nullptr, ChipLogError(Zcl, "Failed to get JNIMyUserPrompter Java class")); + + mPromptForCommissionOKPermissionMethod = + env->GetMethodID(JNIMyUserPrompterClass, "promptForCommissionOkPermission", "(IILjava/lang/String;)V"); + if (mPromptForCommissionOKPermissionMethod == nullptr) + { + ChipLogError(Zcl, "Failed to access JNIMyUserPrompter 'promptForCommissionOkPermission' method"); + env->ExceptionClear(); + } + + mPromptForCommissionPincodeMethod = + env->GetMethodID(JNIMyUserPrompterClass, "promptForCommissionPinCode", "(IILjava/lang/String;)V"); + if (mPromptForCommissionPincodeMethod == nullptr) + { + ChipLogError(Zcl, "Failed to access JNIMyUserPrompter 'promptForCommissionPinCode' method"); + env->ExceptionClear(); + } + + mPromptCommissioningSucceededMethod = + env->GetMethodID(JNIMyUserPrompterClass, "promptCommissioningSucceeded", "(IILjava/lang/String;)V"); + if (mPromptCommissioningSucceededMethod == nullptr) + { + ChipLogError(Zcl, "Failed to access JNIMyUserPrompter 'promptCommissioningSucceeded' method"); + env->ExceptionClear(); + } + + mPromptCommissioningFailedMethod = + env->GetMethodID(JNIMyUserPrompterClass, "promptCommissioningFailed", "(Ljava/lang/String;Ljava/lang/String;)V"); + if (mPromptCommissioningFailedMethod == nullptr) + { + ChipLogError(Zcl, "Failed to access JNIMyUserPrompter 'promptCommissioningFailed' method"); + env->ExceptionClear(); + } +} + +/* + * Called to prompt the user for consent to allow the given commissioneeName/vendorId/productId to be commissioned. + * For example "[commissioneeName] is requesting permission to cast to this TV, approve?" + * + * If user responds with OK then implementor calls UserPrompterResolver.OnPromptAccepted; + * If user responds with Cancel then implementor calls calls UserPrompterResolver.OnPromptDeclined(); + * + */ +void JNIMyUserPrompter::PromptForCommissionOKPermission(uint16_t vendorId, uint16_t productId, const char * commissioneeName) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + std::string stringCommissioneeName(commissioneeName); + + VerifyOrExit(mJNIMyUserPrompterObject != nullptr, 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()); + env->ExceptionClear(); + env->CallVoidMethod(mJNIMyUserPrompterObject, mPromptForCommissionOKPermissionMethod, static_cast(vendorId), + static_cast(productId), jniCommissioneeName.jniValue()); + if (env->ExceptionCheck()) + { + ChipLogError(DeviceLayer, "Java exception in PromptForCommissionOKPermission"); + env->ExceptionDescribe(); + env->ExceptionClear(); + err = CHIP_ERROR_INCORRECT_STATE; + goto exit; + } + } + +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "PromptForCommissionOKPermission error: %s", err.AsString()); + } +} + +/* + * Called to prompt the user to enter the setup pincode displayed by the given commissioneeName/vendorId/productId to be + * commissioned. For example "Please enter pin displayed in casting app." + * + * If user responds with OK then implementor calls UserPrompterResolver.OnPinCodeEntered(); + * If user responds with Cancel then implementor calls UserPrompterResolver.OnPinCodeDeclined(); + * + */ +void JNIMyUserPrompter::PromptForCommissionPincode(uint16_t vendorId, uint16_t productId, const char * commissioneeName) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + std::string stringCommissioneeName(commissioneeName); + + VerifyOrExit(mJNIMyUserPrompterObject != nullptr, 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()); + env->ExceptionClear(); + env->CallVoidMethod(mJNIMyUserPrompterObject, mPromptForCommissionPincodeMethod, static_cast(vendorId), + static_cast(productId), jniCommissioneeName.jniValue()); + if (env->ExceptionCheck()) + { + ChipLogError(Zcl, "Java exception in PromptForCommissionPincode"); + env->ExceptionDescribe(); + env->ExceptionClear(); + err = CHIP_ERROR_INCORRECT_STATE; + goto exit; + } + } + +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "PromptForCommissionPincode error: %s", err.AsString()); + } +} + +/* + * Called to notify the user that commissioning succeeded. It can be in form of UI Notification. + */ +void JNIMyUserPrompter::PromptCommissioningSucceeded(uint16_t vendorId, uint16_t productId, const char * commissioneeName) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + std::string stringCommissioneeName(commissioneeName); + + VerifyOrExit(mJNIMyUserPrompterObject != nullptr, 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()); + env->ExceptionClear(); + env->CallVoidMethod(mJNIMyUserPrompterObject, mPromptCommissioningSucceededMethod, static_cast(vendorId), + static_cast(productId), jniCommissioneeName.jniValue()); + + if (env->ExceptionCheck()) + { + ChipLogError(Zcl, "Java exception in PromptCommissioningSucceeded"); + env->ExceptionDescribe(); + env->ExceptionClear(); + err = CHIP_ERROR_INCORRECT_STATE; + goto exit; + } + } + +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "PromptCommissioningSucceeded error: %s", err.AsString()); + } +} + +/* + * Called to notify the user that commissioning failed. It can be in form of UI Notification. + */ +void JNIMyUserPrompter::PromptCommissioningFailed(const char * commissioneeName, CHIP_ERROR error) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + std::string stringCommissioneeName(commissioneeName); + + VerifyOrExit(mJNIMyUserPrompterObject != nullptr, 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()); + env->ExceptionClear(); + env->CallVoidMethod(mJNIMyUserPrompterObject, mPromptCommissioningFailedMethod, jniCommissioneeName.jniValue(), + jniCommissioneeError.jniValue()); + + if (env->ExceptionCheck()) + { + ChipLogError(Zcl, "Java exception in PromptCommissioningFailed"); + env->ExceptionDescribe(); + env->ExceptionClear(); + err = CHIP_ERROR_INCORRECT_STATE; + goto exit; + } + } + +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "PromptCommissioningFailed error: %s", err.AsString()); + } +} diff --git a/examples/tv-app/android/java/MyUserPrompter-JNI.h b/examples/tv-app/android/java/MyUserPrompter-JNI.h new file mode 100644 index 00000000000000..cecdcb305643d2 --- /dev/null +++ b/examples/tv-app/android/java/MyUserPrompter-JNI.h @@ -0,0 +1,38 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "CommissionerMain.h" +#include "lib/support/logging/CHIPLogging.h" +#include + +class JNIMyUserPrompter : public UserPrompter +{ +public: + JNIMyUserPrompter(jobject prompter); + void PromptForCommissionOKPermission(uint16_t vendorId, uint16_t productId, const char * commissioneeName) override; + void PromptForCommissionPincode(uint16_t vendorId, uint16_t productId, const char * commissioneeName) override; + void PromptCommissioningSucceeded(uint16_t vendorId, uint16_t productId, const char * commissioneeName) override; + void PromptCommissioningFailed(const char * commissioneeName, CHIP_ERROR error) override; + +private: + jobject mJNIMyUserPrompterObject = nullptr; + jmethodID mPromptForCommissionOKPermissionMethod = nullptr; + jmethodID mPromptForCommissionPincodeMethod = 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 new file mode 100644 index 00000000000000..d48566813576f7 --- /dev/null +++ b/examples/tv-app/android/java/MyUserPrompterResolver-JNI.cpp @@ -0,0 +1,54 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MyUserPrompterResolver-JNI.h" +#include "TvApp-JNI.h" +#include +#include +#include +#include +#include + +using namespace chip; + +#define JNI_METHOD(RETURN, METHOD_NAME) \ + extern "C" JNIEXPORT RETURN JNICALL Java_com_tcl_chip_tvapp_UserPrompterResolver_##METHOD_NAME + +JNI_METHOD(void, OnPinCodeEntered)(JNIEnv *, jobject, jint jPinCode) +{ + uint32_t pinCode = (uint32_t) jPinCode; + ChipLogProgress(Zcl, "OnPinCodeEntered %d", pinCode); + GetCommissionerDiscoveryController()->CommissionWithPincode(pinCode); +} + +JNI_METHOD(void, OnPinCodeDeclined)(JNIEnv *, jobject) +{ + ChipLogProgress(Zcl, "OnPinCodeDeclined"); + GetCommissionerDiscoveryController()->Cancel(); +} + +JNI_METHOD(void, OnPromptAccepted)(JNIEnv *, jobject) +{ + ChipLogProgress(Zcl, "OnPromptAccepted"); + GetCommissionerDiscoveryController()->Ok(); +} + +JNI_METHOD(void, OnPromptDeclined)(JNIEnv *, jobject) +{ + ChipLogProgress(Zcl, "OnPromptDeclined"); + GetCommissionerDiscoveryController()->Cancel(); +} diff --git a/examples/tv-app/android/java/MyUserPrompterResolver-JNI.h b/examples/tv-app/android/java/MyUserPrompterResolver-JNI.h new file mode 100644 index 00000000000000..2b14ca35873683 --- /dev/null +++ b/examples/tv-app/android/java/MyUserPrompterResolver-JNI.h @@ -0,0 +1,25 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "CommissionerMain.h" +#include "lib/support/logging/CHIPLogging.h" +#include + +class JNIMyUserPrompterResolver +{ +}; diff --git a/examples/tv-app/android/java/TVApp-JNI.cpp b/examples/tv-app/android/java/TVApp-JNI.cpp index fc4b69d702041c..9043a66864ff47 100644 --- a/examples/tv-app/android/java/TVApp-JNI.cpp +++ b/examples/tv-app/android/java/TVApp-JNI.cpp @@ -27,6 +27,7 @@ #include "LowPowerManager.h" #include "MediaInputManager.h" #include "MediaPlaybackManager.h" +#include "MyUserPrompter-JNI.h" #include "OnOffManager.h" #include "WakeOnLanManager.h" #include "credentials/DeviceAttestationCredsProvider.h" @@ -48,6 +49,7 @@ using namespace chip::Credentials; #define JNI_METHOD(RETURN, METHOD_NAME) extern "C" JNIEXPORT RETURN JNICALL Java_com_tcl_chip_tvapp_TvApp_##METHOD_NAME TvAppJNI TvAppJNI::sInstance; +JNIMyUserPrompter * userPrompter = nullptr; void TvAppJNI::InitializeWithObjects(jobject app) { @@ -133,6 +135,11 @@ JNI_METHOD(void, setChannelManager)(JNIEnv *, jobject, jint endpoint, jobject ma ChannelManager::NewManager(endpoint, manager); } +JNI_METHOD(void, setUserPrompter)(JNIEnv *, jobject, jobject prompter) +{ + userPrompter = new JNIMyUserPrompter(prompter); +} + JNI_METHOD(void, setDACProvider)(JNIEnv *, jobject, jobject provider) { if (!chip::Credentials::IsDeviceAttestationCredentialsProviderSet()) @@ -155,7 +162,7 @@ JNI_METHOD(void, postServerInit)(JNIEnv *, jobject app) chip::DeviceLayer::StackLock lock; ChipLogProgress(Zcl, "TvAppJNI::postServerInit"); - InitVideoPlayerPlatform(); + InitVideoPlayerPlatform(userPrompter); } JNI_METHOD(void, setOnOffManager)(JNIEnv *, jobject, jint endpoint, jobject manager) diff --git a/examples/tv-app/android/java/src/com/tcl/chip/tvapp/TvApp.java b/examples/tv-app/android/java/src/com/tcl/chip/tvapp/TvApp.java index f1b3d1a15d7a5e..d79e760f18c05d 100644 --- a/examples/tv-app/android/java/src/com/tcl/chip/tvapp/TvApp.java +++ b/examples/tv-app/android/java/src/com/tcl/chip/tvapp/TvApp.java @@ -79,6 +79,8 @@ public native int addContentApp( public native void sendTestMessage(int endpoint, String message); + public native void setUserPrompter(UserPrompter userPrompter); + static { System.loadLibrary("TvApp"); } diff --git a/examples/tv-app/android/java/src/com/tcl/chip/tvapp/UserPrompter.java b/examples/tv-app/android/java/src/com/tcl/chip/tvapp/UserPrompter.java new file mode 100644 index 00000000000000..2eef058de45886 --- /dev/null +++ b/examples/tv-app/android/java/src/com/tcl/chip/tvapp/UserPrompter.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.tcl.chip.tvapp; + +public interface UserPrompter { + + /* + * Called to prompt the user for consent to allow the given commissioneeName/vendorId/productId to be commissioned. + * For example "[commissioneeName] is requesting permission to cast to this TV, approve?" + * + * If user responds with OK then implementor calls UserPrompterResolver.OnPromptAccepted; + * If user responds with Cancel then implementor calls calls UserPrompterResolver.OnPromptDeclined(); + * + */ + void promptForCommissionOkPermission(int vendorId, int productId, String commissioneeName); + + /* + * Called to prompt the user to enter the setup pincode displayed by the given commissioneeName/vendorId/productId to be + * commissioned. For example "Please enter pin displayed in casting app." + * + * If user responds with OK then implementor calls UserPrompterResolver.OnPinCodeEntered(); + * If user responds with Cancel then implementor calls UserPrompterResolver.OnPinCodeDeclined(); + * + */ + void promptForCommissionPinCode(int vendorId, int productId, String commissioneeName); + + /* + * Called to notify the user that commissioning succeeded. It can be in form of UI Notification. + */ + void promptCommissioningSucceeded(int vendorId, int productId, String commissioneeName); + + /* + * Called to notify the user that commissioning succeeded. It can be in form of UI Notification. + */ + void promptCommissioningFailed(String commissioneeName, String error); +} diff --git a/examples/tv-app/android/java/src/com/tcl/chip/tvapp/UserPrompterResolver.java b/examples/tv-app/android/java/src/com/tcl/chip/tvapp/UserPrompterResolver.java new file mode 100644 index 00000000000000..18c0bd181de770 --- /dev/null +++ b/examples/tv-app/android/java/src/com/tcl/chip/tvapp/UserPrompterResolver.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.tcl.chip.tvapp; + +public class UserPrompterResolver { + + private static final String TAG = "UserPrompterResolver"; + + public native void OnPinCodeEntered(int pinCode); + + public native void OnPinCodeDeclined(); + + public native void OnPromptAccepted(); + + public native void OnPromptDeclined(); + + static { + System.loadLibrary("TvApp"); + } +}