diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CommissionerDiscoveryFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CommissionerDiscoveryFragment.java index 70c1157dc10fee..428867f35f497a 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CommissionerDiscoveryFragment.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CommissionerDiscoveryFragment.java @@ -94,7 +94,7 @@ public void run() { /** Interface for notifying the host. */ public interface Callback { - /** Notifies listener of Skip to manual Commissioning Button click. */ + /** Notifies listener of Commissioning Button click. */ void handleCommissioningButtonClicked(DiscoveredNodeData selectedCommissioner); } } diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CommissioningFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CommissioningFragment.java index 21218d7dc170d6..5bb7aa9babbed7 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CommissioningFragment.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CommissioningFragment.java @@ -10,6 +10,7 @@ import androidx.fragment.app.Fragment; import com.chip.casting.TvCastingApp; import com.chip.casting.dnssd.DiscoveredNodeData; +import com.chip.casting.platform.MatterCallbackHandler; import com.chip.casting.util.GlobalCastingConstants; /** A {@link Fragment} to get the TV Casting App commissioned. */ @@ -19,6 +20,10 @@ public class CommissioningFragment extends Fragment { private final TvCastingApp tvCastingApp; private final DiscoveredNodeData selectedCommissioner; + private boolean initServerSuccess; + private boolean openCommissioningWindowSuccess; + private boolean sendUdcSuccess; + public CommissioningFragment(TvCastingApp tvCastingApp, DiscoveredNodeData selectedCommissioner) { this.tvCastingApp = tvCastingApp; this.selectedCommissioner = selectedCommissioner; @@ -43,40 +48,68 @@ public void onCreate(Bundle savedInstanceState) { @Override public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - // Inflate the layout for this fragment + Callback callback = (CommissioningFragment.Callback) this.getActivity(); + this.initServerSuccess = + tvCastingApp.initServer( + new MatterCallbackHandler() { + @Override + public boolean handle(boolean success) { + Log.d( + TAG, "handle() called on CommissioningComplete event with success " + success); + if (success) { + callback.handleCommissioningComplete(); + } + return true; + } + }); + + if (this.initServerSuccess) { + this.openCommissioningWindowSuccess = + tvCastingApp.openBasicCommissioningWindow( + GlobalCastingConstants.CommissioningWindowDurationSecs); + if (this.openCommissioningWindowSuccess) { + if (selectedCommissioner != null && selectedCommissioner.getNumIPs() > 0) { + String ipAddress = selectedCommissioner.getIpAddresses().get(0).getHostAddress(); + Log.d( + TAG, + "CommissioningFragment calling tvCastingApp.sendUserDirectedCommissioningRequest with IP: " + + ipAddress + + " port: " + + selectedCommissioner.getPort()); + + this.sendUdcSuccess = + tvCastingApp.sendUserDirectedCommissioningRequest( + ipAddress, selectedCommissioner.getPort()); + } + } + } return inflater.inflate(R.layout.fragment_commissioning, container, false); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - - tvCastingApp.initServer(); - String commissioningWindowStatus = "Failed to open commissioning window"; - if (tvCastingApp.openBasicCommissioningWindow( - GlobalCastingConstants.CommissioningWindowDurationSecs)) { - commissioningWindowStatus = "Commissioning window has been opened. Commission manually."; - if (selectedCommissioner != null && selectedCommissioner.getNumIPs() > 0) { - String ipAddress = selectedCommissioner.getIpAddresses().get(0).getHostAddress(); - Log.d( - TAG, - "CommissioningFragment calling tvCastingApp.sendUserDirectedCommissioningRequest with IP: " - + ipAddress - + " port: " - + selectedCommissioner.getPort()); - - if (tvCastingApp.sendUserDirectedCommissioningRequest( - ipAddress, selectedCommissioner.getPort())) { + if (this.initServerSuccess) { + if (this.openCommissioningWindowSuccess) { + commissioningWindowStatus = "Commissioning window has been opened. Commission manually."; + if (this.sendUdcSuccess) { commissioningWindowStatus = "Commissioning window has been opened. Commissioning requested from " + selectedCommissioner.getDeviceName(); } + TextView onboardingPayloadView = getView().findViewById(R.id.onboardingPayload); + onboardingPayloadView.setText("Onboarding PIN: " + GlobalCastingConstants.SetupPasscode); } - TextView onboardingPayloadView = getView().findViewById(R.id.onboardingPayload); - onboardingPayloadView.setText("Onboarding PIN: " + GlobalCastingConstants.SetupPasscode); } + TextView commissioningWindowStatusView = getView().findViewById(R.id.commissioningWindowStatus); commissioningWindowStatusView.setText(commissioningWindowStatus); } + + /** Interface for notifying the host. */ + public interface Callback { + /** Notifies listener to trigger transition on completion of commissioning */ + void handleCommissioningComplete(); + } } diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/ContentLauncherFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/ContentLauncherFragment.java new file mode 100644 index 00000000000000..8d279641618784 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/ContentLauncherFragment.java @@ -0,0 +1,65 @@ +package com.chip.casting.app; + +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import com.chip.casting.TvCastingApp; + +/** A {@link Fragment} to send Content Launcher commands from the TV Casting App. */ +public class ContentLauncherFragment extends Fragment { + private static final String TAG = ContentLauncherFragment.class.getSimpleName(); + + private final TvCastingApp tvCastingApp; + + private View.OnClickListener launchUrlButtonClickListener; + + public ContentLauncherFragment(TvCastingApp tvCastingApp) { + this.tvCastingApp = tvCastingApp; + } + + /** + * Use this factory method to create a new instance of this fragment using the provided + * parameters. + * + * @param tvCastingApp TV Casting App (JNI) + * @return A new instance of fragment ContentLauncherFragment. + */ + public static ContentLauncherFragment newInstance(TvCastingApp tvCastingApp) { + return new ContentLauncherFragment(tvCastingApp); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + this.launchUrlButtonClickListener = + new View.OnClickListener() { + @Override + public void onClick(View v) { + EditText contentUrl = getView().findViewById(R.id.contentUrlEditText); + EditText contentDisplayString = + getView().findViewById(R.id.contentDisplayStringEditText); + tvCastingApp.contentLauncherLaunchURL( + contentUrl.getText().toString(), contentDisplayString.getText().toString()); + } + }; + + return inflater.inflate(R.layout.fragment_content_launcher, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + Log.d(TAG, "ContentLauncherFragment.onViewCreated called"); + getView().findViewById(R.id.launchUrlButton).setOnClickListener(launchUrlButtonClickListener); + } +} diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java index 2ff702744110b7..6a9ca6d2b25aa8 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java @@ -19,7 +19,7 @@ import com.chip.casting.util.GlobalCastingConstants; public class MainActivity extends AppCompatActivity - implements CommissionerDiscoveryFragment.Callback { + implements CommissionerDiscoveryFragment.Callback, CommissioningFragment.Callback { private ChipAppServer chipAppServer; private TvCastingApp tvCastingApp; @@ -43,6 +43,11 @@ public void handleCommissioningButtonClicked(DiscoveredNodeData commissioner) { showFragment(CommissioningFragment.newInstance(tvCastingApp, commissioner)); } + @Override + public void handleCommissioningComplete() { + showFragment(ContentLauncherFragment.newInstance(tvCastingApp)); + } + private void initJni() { tvCastingApp = new TvCastingApp((app, clusterId, duration) -> app.openBasicCommissioningWindow(duration)); diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/platform/MatterCallbackHandler.java b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/platform/MatterCallbackHandler.java new file mode 100644 index 00000000000000..57995ee5203796 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/platform/MatterCallbackHandler.java @@ -0,0 +1,5 @@ +package com.chip.casting.platform; + +public interface MatterCallbackHandler { + boolean handle(boolean success); +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java index 376ea89e751e96..f48737f5070362 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java @@ -45,7 +45,9 @@ private void postClusterInit(int clusterId, int endpoint) { public native boolean discoverCommissioners(); - public native void initServer(); + public native boolean initServer(Object commissioningCompleteHandler); + + public native void contentLauncherLaunchURL(String contentUrl, String contentDisplayStr); static { System.loadLibrary("TvCastingApp"); diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/CallbackHelper.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/CallbackHelper.cpp new file mode 100644 index 00000000000000..a8d73094767854 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/CallbackHelper.cpp @@ -0,0 +1,73 @@ +/* + * + * 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. + */ + +#include "CallbackHelper.h" + +#include + +using namespace chip; + +struct MatterCallbackHandler gCommissioningCompleteHandler; + +CHIP_ERROR SetUpMatterCallbackHandler(JNIEnv * env, jobject inHandler, MatterCallbackHandler & callback) +{ + ChipLogProgress(AppServer, "SetUpMatterCallbackHandler called"); + CHIP_ERROR err = CHIP_NO_ERROR; + + callback.object = env->NewGlobalRef(inHandler); + VerifyOrExit(callback.object != nullptr, ChipLogError(AppServer, "Failed to NewGlobalRef for handler object")); + + callback.clazz = env->GetObjectClass(callback.object); + VerifyOrExit(callback.clazz != nullptr, ChipLogError(AppServer, "Failed to get handler Java class")); + + callback.method = env->GetMethodID(callback.clazz, "handle", "(Z)Z"); + if (callback.method == nullptr) + { + ChipLogError(AppServer, "Failed to access 'handle' method"); + env->ExceptionClear(); + } + +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "SetUpMatterCallbackHandler error: %s", err.AsString()); + return err; + } + + return err; +} + +CHIP_ERROR CommissioningCompleteHandler() +{ + ChipLogProgress(AppServer, "CommissioningCompleteHandler called"); + + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + CHIP_ERROR err = CHIP_NO_ERROR; + VerifyOrExit(gCommissioningCompleteHandler.object != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + VerifyOrExit(gCommissioningCompleteHandler.method != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + VerifyOrExit(env->CallBooleanMethod(gCommissioningCompleteHandler.object, gCommissioningCompleteHandler.method, + static_cast(true)) != JNI_FALSE, + err = CHIP_ERROR_INCORRECT_STATE); +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "CommissioningCompleteHandler status error: %s", err.AsString()); + } + + return err; +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/CallbackHelper.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/CallbackHelper.h new file mode 100644 index 00000000000000..8ba46fda81d6ee --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/CallbackHelper.h @@ -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. + */ + +#pragma once + +#include +#include + +struct MatterCallbackHandler +{ + jobject object = nullptr; + jclass clazz = nullptr; + jmethodID method = nullptr; +}; + +extern struct MatterCallbackHandler gCommissioningCompleteHandler; + +CHIP_ERROR SetUpMatterCallbackHandler(JNIEnv * env, jobject inHandler, MatterCallbackHandler & callback); + +CHIP_ERROR CommissioningCompleteHandler(); diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.cpp index 058fc4b061a37e..c9b3447506beec 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.cpp +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.cpp @@ -17,6 +17,7 @@ */ #include "TvCastingApp-JNI.h" +#include "CallbackHelper.h" #include "CastingServer.h" #include "JNIDACProvider.h" @@ -146,8 +147,30 @@ JNI_METHOD(jboolean, discoverCommissioners)(JNIEnv *, jobject) return true; } -JNI_METHOD(void, initServer)(JNIEnv *, jobject) +JNI_METHOD(jboolean, initServer)(JNIEnv * env, jobject, jobject jCommissioningCompleteHandler) { ChipLogProgress(AppServer, "JNI_METHOD initServer called"); - CastingServer::GetInstance()->InitServer(); + CHIP_ERROR err = SetUpMatterCallbackHandler(env, jCommissioningCompleteHandler, gCommissioningCompleteHandler); + if (err == CHIP_NO_ERROR) + { + CastingServer::GetInstance()->InitServer(CommissioningCompleteHandler); + return true; + } + else + { + ChipLogError(AppServer, "initServer error: %s", err.AsString()); + return false; + } +} + +JNI_METHOD(void, contentLauncherLaunchURL)(JNIEnv * env, jobject, jstring contentUrl, jstring contentDisplayStr) +{ + ChipLogProgress(AppServer, "JNI_METHOD contentLauncherLaunchURL called"); + const char * nativeContentUrl = env->GetStringUTFChars(contentUrl, 0); + const char * nativeContentDisplayStr = env->GetStringUTFChars(contentDisplayStr, 0); + + CastingServer::GetInstance()->ContentLauncherLaunchURL(nativeContentUrl, nativeContentDisplayStr); + + env->ReleaseStringUTFChars(contentUrl, nativeContentUrl); + env->ReleaseStringUTFChars(contentDisplayStr, nativeContentDisplayStr); } diff --git a/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_content_launcher.xml b/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_content_launcher.xml new file mode 100644 index 00000000000000..79b66d8900b47b --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_content_launcher.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +