diff --git a/examples/tv-casting-app/APIs.md b/examples/tv-casting-app/APIs.md index f0fdb32484a72e..c11cc816a8cb6e 100644 --- a/examples/tv-casting-app/APIs.md +++ b/examples/tv-casting-app/APIs.md @@ -1042,10 +1042,8 @@ vendorIDAttribute!.read(nil) { context, before, after, err in ### Subscriptions -_{Complete Attribute subscription examples: -[Linux](linux/simple-app-helper.cpp)}_ - -_{Complete Attribute Read examples: [Linux](linux/simple-app-helper.cpp) | +_{Complete Attribute subscription examples: [Linux](linux/simple-app-helper.cpp) +| [iOS](darwin/TvCasting/TvCasting/MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift)}_ A Casting Client may subscribe to an attribute on an `Endpoint` of the diff --git a/examples/tv-casting-app/android/App/.idea/misc.xml b/examples/tv-casting-app/android/App/.idea/misc.xml index 2a4d5b521db9a0..d435c773610b77 100644 --- a/examples/tv-casting-app/android/App/.idea/misc.xml +++ b/examples/tv-casting-app/android/App/.idea/misc.xml @@ -1,5 +1,16 @@ + + + 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 9db82781d855b8..851513d970e8e2 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 @@ -11,6 +11,7 @@ import com.chip.casting.TvCastingApp; import com.chip.casting.util.GlobalCastingConstants; import com.chip.casting.util.PreferencesConfigurationManager; +import com.matter.casting.ActionSelectorFragment; import com.matter.casting.ConnectionExampleFragment; import com.matter.casting.DiscoveryExampleFragment; import com.matter.casting.InitializationExample; @@ -22,7 +23,8 @@ public class MainActivity extends AppCompatActivity ConnectionFragment.Callback, SelectClusterFragment.Callback, DiscoveryExampleFragment.Callback, - ConnectionExampleFragment.Callback { + ConnectionExampleFragment.Callback, + ActionSelectorFragment.Callback { private static final String TAG = MainActivity.class.getSimpleName(); @@ -73,9 +75,12 @@ public void handleCommissioningComplete() { @Override public void handleConnectionComplete(CastingPlayer castingPlayer) { Log.i(TAG, "MainActivity.handleConnectionComplete() called "); + showFragment(ActionSelectorFragment.newInstance(castingPlayer)); + } - // TODO: Implement in following PRs. Select Cluster Fragment. - // showFragment(SelectClusterFragment.newInstance(tvCastingApp)); + @Override + public void handleContentLauncherLaunchURLSelected() { + showFragment(ContentLauncherFragment.newInstance(tvCastingApp)); } @Override @@ -95,7 +100,10 @@ public void handleMediaPlaybackSelected() { @Override public void handleDisconnect() { - showFragment(CommissionerDiscoveryFragment.newInstance(tvCastingApp)); + showFragment( + GlobalCastingConstants.ChipCastingSimplified + ? DiscoveryExampleFragment.newInstance() + : CommissionerDiscoveryFragment.newInstance(tvCastingApp)); } /** diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/util/GlobalCastingConstants.java b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/util/GlobalCastingConstants.java index d063cc7e6f78c5..754d3bbe5ff4fd 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/util/GlobalCastingConstants.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/util/GlobalCastingConstants.java @@ -6,5 +6,5 @@ public class GlobalCastingConstants { public static final int SetupPasscode = 20202021; public static final int Discriminator = 0xF00; public static final boolean ChipCastingSimplified = - false; // set this flag to true to demo simplified casting APIs + true; // set this flag to true to demo simplified casting APIs } diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ActionSelectorFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ActionSelectorFragment.java new file mode 100644 index 00000000000000..7eb3a5d9740220 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ActionSelectorFragment.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2024 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.matter.casting; + +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import com.R; +import com.matter.casting.core.CastingPlayer; + +/** An interstitial {@link Fragment} to select one of the supported media actions to perform */ +public class ActionSelectorFragment extends Fragment { + private static final String TAG = ActionSelectorFragment.class.getSimpleName(); + + private final CastingPlayer selectedCastingPlayer; + + private View.OnClickListener selectContentLauncherButtonClickListener; + private View.OnClickListener disconnectButtonClickListener; + + public ActionSelectorFragment(CastingPlayer selectedCastingPlayer) { + this.selectedCastingPlayer = selectedCastingPlayer; + } + + /** + * Use this factory method to create a new instance of this fragment using the provided + * parameters. + * + * @param selectedCastingPlayer CastingPlayer that the casting app connected to + * @return A new instance of fragment SelectActionFragment. + */ + public static ActionSelectorFragment newInstance(CastingPlayer selectedCastingPlayer) { + return new ActionSelectorFragment(selectedCastingPlayer); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + ActionSelectorFragment.Callback callback = (ActionSelectorFragment.Callback) this.getActivity(); + this.selectContentLauncherButtonClickListener = + v -> { + Log.d(TAG, "handle() called on selectContentLauncherButtonClickListener"); + callback.handleContentLauncherLaunchURLSelected(); + }; + + this.disconnectButtonClickListener = + v -> { + Log.d(TAG, "Disconnecting from current casting player"); + selectedCastingPlayer.disconnect(); + callback.handleDisconnect(); + }; + + return inflater.inflate(R.layout.fragment_matter_action_selector, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + Log.d(TAG, "ActionSelectorFragment.onViewCreated called"); + getView() + .findViewById(R.id.selectContentLauncherButton) + .setOnClickListener(selectContentLauncherButtonClickListener); + + getView().findViewById(R.id.disconnectButton).setOnClickListener(disconnectButtonClickListener); + } + + /** Interface for notifying the host. */ + public interface Callback { + /** Notifies listener to trigger transition on selection of Content Launcher cluster */ + void handleContentLauncherLaunchURLSelected(); + + /** Notifies listener to trigger transition on click of the Disconnect button */ + void handleDisconnect(); + } +} diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java index c6462cd52690e5..fa89b028beef95 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java @@ -100,26 +100,24 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { new EndpointFilter(null, 65521, new ArrayList()); // The desired commissioning window timeout and EndpointFilter are optional. CompletableFuture completableFuture = - targetCastingPlayer.VerifyOrEstablishConnection( + targetCastingPlayer.verifyOrEstablishConnection( MIN_CONNECTION_TIMEOUT_SEC, desiredEndpointFilter); Log.d(TAG, "onViewCreated() verifyOrEstablishConnection() called"); + Log.d( + TAG, + "onViewCreated() verifyOrEstablishConnection() completableFuture == null? " + + (completableFuture == null)); + completableFuture - .thenRun( - () -> { + .thenAccept( + (response) -> { Log.i( TAG, - "CompletableFuture.thenRun(), connected to CastingPlayer with deviceId: " + "CompletableFuture.thenAccept(), connected to CastingPlayer with deviceId: " + targetCastingPlayer.getDeviceId()); - getActivity() - .runOnUiThread( - () -> { - connectionFragmentStatusTextView.setText( - "Connected to Casting Player with device name: " - + targetCastingPlayer.getDeviceName()); - connectionFragmentNextButton.setEnabled(true); - }); + connectionFragmentNextButton.setEnabled(true); }) .exceptionally( exc -> { diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ContentLauncherLaunchURLExampleFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ContentLauncherLaunchURLExampleFragment.java new file mode 100644 index 00000000000000..f3f3d81b1b6046 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ContentLauncherLaunchURLExampleFragment.java @@ -0,0 +1,116 @@ +package com.matter.casting; + +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 android.widget.TextView; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import com.R; +import com.chip.casting.ContentApp; +import com.chip.casting.MatterCallbackHandler; +import com.chip.casting.MatterError; +import com.chip.casting.TvCastingApp; +import com.matter.casting.clusters.MatterClusters; +import com.matter.casting.clusters.MatterCommands; +import com.matter.casting.core.CastingPlayer; +import com.matter.casting.core.Endpoint; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +/** A {@link Fragment} to send Content Launcher LaunchURL command from the TV Casting App. */ +public class ContentLauncherLaunchURLExampleFragment extends Fragment { + private static final String TAG = ContentLauncherLaunchURLExampleFragment.class.getSimpleName(); + private static final Integer SAMPLE_ENDPOINT_VID = 65521; + + private final CastingPlayer selectedCastingPlayer; + + private View.OnClickListener launchUrlButtonClickListener; + + private static final ContentApp kContentApp = new ContentApp((short) 4, null); + + public ContentLauncherLaunchURLExampleFragment(CastingPlayer selectedCastingPlayer) { + this.selectedCastingPlayer = selectedCastingPlayer; + } + + /** + * Use this factory method to create a new instance of this fragment using the provided + * parameters. + * + * @param selectedCastingPlayer CastingPlayer that the casting app connected to + * @return A new instance of fragment ContentLauncherLaunchURLExampleFragment. + */ + public static ContentLauncherLaunchURLExampleFragment newInstance(CastingPlayer selectedCastingPlayer) { + return new ContentLauncherLaunchURLExampleFragment(selectedCastingPlayer); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + this.launchUrlButtonClickListener = + v -> { + List endpoints = selectedCastingPlayer.getEndpoints(); + if(endpoints == null) + { + Log.e(TAG, "No Endpoints found on CastingPlayer"); + return; + } + + Endpoint endpoint = endpoints.stream().filter(e -> SAMPLE_ENDPOINT_VID.equals(e.getVendorId())).findFirst().get(); + if(endpoint == null) + { + Log.e(TAG, "No Endpoint with chosen vendorID: " + SAMPLE_ENDPOINT_VID + " found on CastingPlayer"); + return; + } + + if(!endpoint.hasCluster(MatterClusters.ContentLauncherCluster.class)) + { + Log.e(TAG, "Endpoint with chosen vendorID does not support ContentLauncher cluster"); + return; + } + + MatterClusters.ContentLauncherCluster cluster = endpoint.getCluster(MatterClusters.ContentLauncherCluster.class); + MatterCommands.ContentLauncherClusterLaunchURLCommand command = cluster.getCommand(MatterCommands.ContentLauncherClusterLaunchURLCommand.class); + if(command == null) + { + Log.e(TAG, "ContentLauncher cluster on Endpoint with chosen vendorID does not support LaunchURL command"); + return; + } + + MatterCommands.ContentLauncherClusterLaunchURLRequest request = new MatterCommands.ContentLauncherClusterLaunchURLRequest(); + request.contentURL = ((EditText) getView().findViewById(R.id.contentUrlEditText)).getText().toString(); + request.displayString = ((EditText) getView().findViewById(R.id.contentDisplayStringEditText)).getText().toString(); + CompletableFuture responseFuture = command.invoke(request, null, 5000); + responseFuture.thenAccept(response -> { + Log.d(TAG, "Command response " + response); + TextView launchUrlStatus = getView().findViewById(R.id.launchUrlStatus); + getActivity().runOnUiThread(() -> launchUrlStatus.setText("Success! Response data: " + response.data)); + }) + .exceptionally(exc -> { + Log.e(TAG, "Command failure: " + exc.getMessage()); + TextView launchUrlStatus = getView().findViewById(R.id.launchUrlStatus); + getActivity().runOnUiThread(() -> launchUrlStatus.setText("Command failure: " + exc.getMessage())); + return null; + }); + }; + + 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, "ContentLauncherLaunchURLExampleFragment.onViewCreated called"); + getView().findViewById(R.id.launchUrlButton).setOnClickListener(launchUrlButtonClickListener); + } +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/clusters/MatterClusters.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/clusters/MatterClusters.java new file mode 100644 index 00000000000000..31c7febc3c286d --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/clusters/MatterClusters.java @@ -0,0 +1,7 @@ +package com.matter.casting.clusters; + +import com.matter.casting.core.MatterCluster; + +public class MatterClusters { + public static class ContentLauncherCluster extends MatterCluster {} +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/clusters/MatterCommands.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/clusters/MatterCommands.java new file mode 100644 index 00000000000000..9a33e113e9871c --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/clusters/MatterCommands.java @@ -0,0 +1,20 @@ +package com.matter.casting.clusters; + +import com.matter.casting.core.MatterCommand; + +public class MatterCommands { + public static class ContentLauncherClusterLaunchURLRequest { + public String contentURL; + public String displayString; + // Optional brandingInformation; + } + + public static class ContentLauncherClusterResponse { + public String data; + public Integer status; + } + + public static class ContentLauncherClusterLaunchURLCommand + extends MatterCommand< + ContentLauncherClusterLaunchURLRequest, ContentLauncherClusterResponse> {} +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java index 723f1b8e93b1a5..8f53820d39bf67 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java @@ -48,6 +48,8 @@ public interface CastingPlayer { long getDeviceType(); + List getEndpoints(); + @Override String toString(); @@ -75,7 +77,7 @@ public interface CastingPlayer { * com.matter.casting.core.CastingException. If the VerifyOrEstablishConnection fails, the * CastingException will contain the error code and message from the CastingApp. */ - CompletableFuture VerifyOrEstablishConnection( + CompletableFuture verifyOrEstablishConnection( long commissioningWindowTimeoutSec, EndpointFilter desiredEndpointFilter); /** @@ -90,5 +92,7 @@ CompletableFuture VerifyOrEstablishConnection( * com.matter.casting.core.CastingException. If the VerifyOrEstablishConnection fails, the * CastingException will contain the error code and message from the CastingApp. */ - CompletableFuture VerifyOrEstablishConnection(); + CompletableFuture verifyOrEstablishConnection(); + + void disconnect(); } diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/Cluster.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/Cluster.java new file mode 100644 index 00000000000000..1d90af1fcd6880 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/Cluster.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2020-24 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.matter.casting.core; + +public interface Cluster { + Endpoint getEndpoint(); + + > T getCommand(Class commandClass); +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/ClusterProvider.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/ClusterProvider.java new file mode 100644 index 00000000000000..87aa9f4136a3b2 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/ClusterProvider.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2020-24 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.matter.casting.core; + +public interface ClusterProvider { + T getCluster(Class clusterClass); + + boolean hasCluster(Class clusterClass); +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/Command.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/Command.java new file mode 100644 index 00000000000000..aabc4fa09a94db --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/Command.java @@ -0,0 +1,7 @@ +package com.matter.casting.core; + +import java.util.concurrent.CompletableFuture; + +public interface Command { + CompletableFuture invoke(Request request, Object context, Integer timedInvokeTimeoutMs); +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/Endpoint.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/Endpoint.java new file mode 100644 index 00000000000000..2ccca7cf33d2e7 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/Endpoint.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020-24 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.matter.casting.core; + +import com.matter.casting.support.DeviceTypeStruct; +import java.util.List; + +public interface Endpoint { + int getId(); + + int getVendorId(); + + int getProductId(); + + List getDeviceTypeList(); + + CastingPlayer getCastingPlayer(); + + T getCluster(Class clusterClass); + + boolean hasCluster(Class clusterClass); +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java index d5d93c3204ec34..7fd7c07bc92a72 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java @@ -130,6 +130,9 @@ public long getDeviceType() { return this.deviceType; } + @Override + public native List getEndpoints(); + @Override public String toString() { return this.deviceId; @@ -167,7 +170,7 @@ public boolean equals(Object o) { * CastingException will contain the error code and message from the CastingApp. */ @Override - public native CompletableFuture VerifyOrEstablishConnection( + public native CompletableFuture verifyOrEstablishConnection( long commissioningWindowTimeoutSec, EndpointFilter desiredEndpointFilter); /** @@ -183,7 +186,11 @@ public native CompletableFuture VerifyOrEstablishConnection( * CastingException will contain the error code and message from the CastingApp. */ @Override - public CompletableFuture VerifyOrEstablishConnection() { - return VerifyOrEstablishConnection(MIN_CONNECTION_TIMEOUT_SEC, null); + public CompletableFuture verifyOrEstablishConnection() { + return verifyOrEstablishConnection(MIN_CONNECTION_TIMEOUT_SEC, null); } + + /** Sets the internal connection state of this CastingPlayer to "disconnected" */ + @Override + public native void disconnect(); } diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCluster.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCluster.java new file mode 100644 index 00000000000000..06ffd6bff0d437 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCluster.java @@ -0,0 +1,11 @@ +package com.matter.casting.core; + +public abstract class MatterCluster implements Cluster { + protected long _cppCluster; + + @Override + public native Endpoint getEndpoint(); + + @Override + public native > T getCommand(Class commandClass); +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCommand.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCommand.java new file mode 100644 index 00000000000000..c0a8ccb38f31f3 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCommand.java @@ -0,0 +1,11 @@ +package com.matter.casting.core; + +import java.util.concurrent.CompletableFuture; + +public abstract class MatterCommand implements Command { + protected long _cppCommand; + + @Override + public native CompletableFuture invoke( + Request request, Object context, Integer timedInvokeTimeoutMs); +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterEndpoint.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterEndpoint.java new file mode 100644 index 00000000000000..c6d48b2c498192 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterEndpoint.java @@ -0,0 +1,50 @@ +package com.matter.casting.core; + +import com.matter.casting.support.DeviceTypeStruct; +import java.util.List; +import java.util.Objects; + +public class MatterEndpoint implements Endpoint { + protected long _cppEndpoint; + + @Override + public native int getId(); + + @Override + public native int getVendorId(); + + @Override + public native int getProductId(); + + @Override + public native List getDeviceTypeList(); + + @Override + public native CastingPlayer getCastingPlayer(); + + @Override + public native T getCluster(Class clusterClass); + + @Override + public boolean hasCluster(Class clusterClass) { + return getCluster(clusterClass) != null; + } + + @Override + public String toString() { + return "MatterEndpoint{" + "id=" + getId() + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MatterEndpoint that = (MatterEndpoint) o; + return getId() == that.getId(); + } + + @Override + public int hashCode() { + return Objects.hash(getId()); + } +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/UnsupportedCommandException.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/UnsupportedCommandException.java new file mode 100644 index 00000000000000..45bc72e84dbf1f --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/UnsupportedCommandException.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020-24 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.matter.casting.support; + +public class UnsupportedCommandException extends Exception { + private static final long serialVersionUID = 1L; + + public UnsupportedCommandException() { + super(); + } + + public UnsupportedCommandException(String message) { + super(message); + } +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/clusters/Commands-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/clusters/Commands-JNI.cpp new file mode 100644 index 00000000000000..7cd6cc990fbd8e --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/clusters/Commands-JNI.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 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 "Commands-JNI.h" + +#include "../JNIDACProvider.h" +#include "../support/CastingPlayerConverter-JNI.h" +#include "../support/EndpointConverter-JNI.h" +#include "../support/ErrorConverter-JNI.h" +#include "../support/RotatingDeviceIdUniqueIdProvider-JNI.h" +#include "core/CastingApp.h" // from tv-casting-common + +#include +#include +#include +#include +#include + +using namespace chip; + +#define JNI_METHOD(RETURN, METHOD_NAME) \ + extern "C" JNIEXPORT RETURN JNICALL \ + Java_com_matter_casting_clusters_MatterCommands_ContentLauncherClusterLaunchURLCommand_##METHOD_NAME + +namespace matter { +namespace casting { +namespace core { + +JNI_METHOD(jobject, invoke) +(JNIEnv * env, jobject thiz, jobject request, jobject context, jobject timedInvokeTimeoutMs) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "Commands-JNI::invoke() called"); + void * command = CommandsJNI().GetCommand(thiz); + VerifyOrReturnValue(command != nullptr, 0, ChipLogError(AppServer, "Commands-JNI::invoke() command == nullptr")); + return nullptr; +} + +/** + * @brief Get the matter::casting::core::Command object from the jobject jCommandObject + */ +void * CommandsJNI::GetCommand(jobject jCommandObject) +{ + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + jclass commandClass = env->GetObjectClass(jCommandObject); + jfieldID _cppCommandFieldId = env->GetFieldID(commandClass, "_cppCommand", "J"); + VerifyOrReturnValue(_cppCommandFieldId != nullptr, nullptr, + ChipLogError(AppServer, "Commands-JNI::GetCommand() _cppCommand == nullptr")); + jlong _cppCommandValue = env->GetLongField(jCommandObject, _cppCommandFieldId); + return reinterpret_cast(_cppCommandValue); +} + +}; // namespace core +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/clusters/Commands-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/clusters/Commands-JNI.h new file mode 100644 index 00000000000000..3e0390a043bcc6 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/clusters/Commands-JNI.h @@ -0,0 +1,45 @@ +/* + * + * Copyright (c) 2024 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 "core/Command.h" // from tv-casting-common + +#include + +namespace matter { +namespace casting { +namespace core { + +class CommandsJNI +{ +public: + void * GetCommand(jobject jCommandObject); + +private: + friend CommandsJNI & CommandsJNIMgr(); + static CommandsJNI sInstance; +}; + +inline class CommandsJNI & CommandsJNIMgr() +{ + return CommandsJNI::sInstance; +} +}; // namespace core +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayer-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayer-JNI.cpp index 2c0fe914232778..a8f984728f6cf6 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayer-JNI.cpp +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayer-JNI.cpp @@ -20,10 +20,10 @@ #include "../JNIDACProvider.h" #include "../support/CastingPlayerConverter-JNI.h" +#include "../support/EndpointConverter-JNI.h" #include "../support/ErrorConverter-JNI.h" #include "../support/RotatingDeviceIdUniqueIdProvider-JNI.h" #include "core/CastingApp.h" // from tv-casting-common -#include "core/CastingPlayer.h" // from tv-casting-common #include "core/CastingPlayerDiscovery.h" // from tv-casting-common #include @@ -41,24 +41,16 @@ namespace matter { namespace casting { namespace core { -JNI_METHOD(jobject, VerifyOrEstablishConnection) +JNI_METHOD(jobject, verifyOrEstablishConnection) (JNIEnv * env, jobject thiz, jlong commissioningWindowTimeoutSec, jobject desiredEndpointFilterJavaObject) { chip::DeviceLayer::StackLock lock; - ChipLogProgress(AppServer, "CastingPlayer-JNI::VerifyOrEstablishConnection() called with a timeout of: %ld seconds", + ChipLogProgress(AppServer, "CastingPlayer-JNI::verifyOrEstablishConnection() called with a timeout of: %ld seconds", static_cast(commissioningWindowTimeoutSec)); - // Convert the CastingPlayer jlong to a CastingPlayer pointer - jclass castingPlayerClass = env->GetObjectClass(thiz); - jfieldID _cppCastingPlayerFieldId = env->GetFieldID(castingPlayerClass, "_cppCastingPlayer", "J"); - VerifyOrReturnValue( - _cppCastingPlayerFieldId != nullptr, nullptr, - ChipLogError(AppServer, "CastingPlayer-JNI::VerifyOrEstablishConnection() _cppCastingPlayerFieldId == nullptr")); - - jlong _cppCastingPlayerValue = env->GetLongField(thiz, _cppCastingPlayerFieldId); - CastingPlayer * castingPlayer = reinterpret_cast(_cppCastingPlayerValue); + CastingPlayer * castingPlayer = CastingPlayerJNIMgr().GetCastingPlayer(thiz); VerifyOrReturnValue(castingPlayer != nullptr, nullptr, - ChipLogError(AppServer, "CastingPlayer-JNI::VerifyOrEstablishConnection() castingPlayer == nullptr")); + ChipLogError(AppServer, "CastingPlayer-JNI::verifyOrEstablishConnection() castingPlayer == nullptr")); // Create a new Java CompletableFuture jclass completableFutureClass = env->FindClass("java/util/concurrent/CompletableFuture"); @@ -67,24 +59,37 @@ JNI_METHOD(jobject, VerifyOrEstablishConnection) jobject completableFutureObjGlobalRef = env->NewGlobalRef(completableFutureObj); VerifyOrReturnValue( completableFutureObjGlobalRef != nullptr, nullptr, - ChipLogError(AppServer, "CastingPlayer-JNI::VerifyOrEstablishConnection() completableFutureObjGlobalRef == nullptr")); + ChipLogError(AppServer, "CastingPlayer-JNI::verifyOrEstablishConnection() completableFutureObjGlobalRef == nullptr")); ConnectCallback callback = [completableFutureObjGlobalRef](CHIP_ERROR err, CastingPlayer * playerPtr) { - ChipLogProgress(AppServer, "CastingPlayer-JNI::VerifyOrEstablishConnection() ConnectCallback called"); - VerifyOrReturn(completableFutureObjGlobalRef != nullptr, - ChipLogError(AppServer, "ConnectCallback, completableFutureObjGlobalRef == nullptr")); + ChipLogProgress(AppServer, "CastingPlayer-JNI::verifyOrEstablishConnection() ConnectCallback called"); JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); VerifyOrReturn(env != nullptr, ChipLogError(AppServer, "ConnectCallback, env == nullptr")); + + ChipLogProgress(AppServer, "CastingPlayer-JNI::verifyOrEstablishConnection() before JniLocalReferenceManager manager(env)"); // Ensures proper cleanup of local references to Java objects. JniLocalReferenceManager manager(env); - // Ensures proper cleanup of global references to Java objects. - JniGlobalRefWrapper globalRefWrapper(completableFutureObjGlobalRef); + // ChipLogProgress(AppServer, "CastingPlayer-JNI::verifyOrEstablishConnection() before JniGlobalRefWrapper + // globalRefWrapper"); + // Ensures proper cleanup of global references to Java objects. + // JniGlobalRefWrapper globalRefWrapper(completableFutureObjGlobalRef); + // ChipLogProgress(AppServer, "CastingPlayer-JNI::verifyOrEstablishConnection() after JniGlobalRefWrapper + // globalRefWrapper"); + VerifyOrReturn(completableFutureObjGlobalRef != nullptr, + ChipLogError(AppServer, "ConnectCallback, completableFutureObjGlobalRef == nullptr")); + + /*ChipLogProgress(AppServer, "CastingPlayer-JNI::verifyOrEstablishConnection() before + env->IsSameObject(completableFutureObjGlobalRef, nullptr)"); if (env->IsSameObject(completableFutureObjGlobalRef, nullptr)) + { + ChipLogError(AppServer, "ConnectCallback, IsSameObject(completableFutureObjGlobalRef, nullptr)") + } + ChipLogProgress(AppServer, "CastingPlayer-JNI::verifyOrEstablishConnection() after + env->IsSameObject(completableFutureObjGlobalRef, nullptr)");*/ jclass completableFutureClass = env->FindClass("java/util/concurrent/CompletableFuture"); VerifyOrReturn(completableFutureClass != nullptr, - ChipLogError(AppServer, "ConnectCallback, completableFutureClass == nullptr"); - env->DeleteGlobalRef(completableFutureObjGlobalRef);); + ChipLogError(AppServer, "ConnectCallback, completableFutureClass == nullptr")); if (err == CHIP_NO_ERROR) { @@ -93,7 +98,10 @@ JNI_METHOD(jobject, VerifyOrEstablishConnection) VerifyOrReturn(completeMethod != nullptr, ChipLogError(AppServer, "ConnectCallback, completeMethod == nullptr")); chip::DeviceLayer::StackUnlock unlock; + ChipLogProgress(AppServer, "ConnectCallback, before env->CallBooleanMethod"); env->CallBooleanMethod(completableFutureObjGlobalRef, completeMethod, nullptr); + ChipLogProgress(AppServer, "ConnectCallback, after env->CallBooleanMethod"); + // env->DeleteGlobalRef(completableFutureObjGlobalRef); } else { @@ -116,13 +124,14 @@ JNI_METHOD(jobject, VerifyOrEstablishConnection) chip::DeviceLayer::StackUnlock unlock; env->CallBooleanMethod(completableFutureObjGlobalRef, completeExceptionallyMethod, throwableObject); + // env->DeleteGlobalRef(completableFutureObjGlobalRef); } }; if (desiredEndpointFilterJavaObject == nullptr) { ChipLogProgress(AppServer, - "CastingPlayer-JNI::VerifyOrEstablishConnection() calling CastingPlayer::VerifyOrEstablishConnection() on " + "CastingPlayer-JNI::verifyOrEstablishConnection() calling CastingPlayer::VerifyOrEstablishConnection() on " "Casting Player with device ID: %s", castingPlayer->GetId()); castingPlayer->VerifyOrEstablishConnection(callback, static_cast(commissioningWindowTimeoutSec)); @@ -156,16 +165,82 @@ JNI_METHOD(jobject, VerifyOrEstablishConnection) // passing an empty list of DeviceTypeStruct. ChipLogProgress(AppServer, - "CastingPlayer-JNI::VerifyOrEstablishConnection() calling " + "CastingPlayer-JNI::verifyOrEstablishConnection() calling " "CastingPlayer::VerifyOrEstablishConnection() on Casting Player with device ID: %s", castingPlayer->GetId()); - castingPlayer->VerifyOrEstablishConnection(callback, static_cast(commissioningWindowTimeoutSec), - desiredEndpointFilter); + + CastingPlayerConnectionContext * context = new CastingPlayerConnectionContext(); + context->castingPlayer = castingPlayer; + context->callback = callback; + context->commissioningWindowTimeoutSec = static_cast(commissioningWindowTimeoutSec); + context->desiredEndpointFilter = desiredEndpointFilter; + + chip::DeviceLayer::SystemLayer().ScheduleWork(CastingPlayerJNI::verifyOrEstablishConnectionTask, context); + // castingPlayer->VerifyOrEstablishConnection(callback, static_cast(commissioningWindowTimeoutSec), + // desiredEndpointFilter); } + ChipLogProgress(AppServer, "CastingPlayer-JNI::verifyOrEstablishConnection() returning"); return completableFutureObjGlobalRef; } +void CastingPlayerJNI::verifyOrEstablishConnectionTask(chip::System::Layer * aSystemLayer, void * context) +{ + ChipLogProgress(AppServer, "CastingPlayer-JNI::verifyOrEstablishConnectionTask() called"); + if (context != nullptr) + { + CastingPlayerConnectionContext * _context = static_cast(context); + _context->castingPlayer->VerifyOrEstablishConnection(_context->callback, _context->commissioningWindowTimeoutSec, + _context->desiredEndpointFilter); + } +} + +JNI_METHOD(void, disconnect) +(JNIEnv * env, jobject thiz) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "CastingPlayer-JNI::disconnect()"); + + CastingPlayer * castingPlayer = CastingPlayerJNIMgr().GetCastingPlayer(thiz); + VerifyOrReturn(castingPlayer != nullptr, ChipLogError(AppServer, "CastingPlayer-JNI::disconnect() castingPlayer == nullptr")); + + castingPlayer->Disconnect(); +} + +JNI_METHOD(jobject, getEndpoints) +(JNIEnv * env, jobject thiz) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "CastingPlayer-JNI::getEndpoints() called"); + + CastingPlayer * castingPlayer = CastingPlayerJNIMgr().GetCastingPlayer(thiz); + VerifyOrReturnValue(castingPlayer != nullptr, nullptr, + ChipLogError(AppServer, "CastingPlayer-JNI::getEndpoints() castingPlayer == nullptr")); + + const std::vector> endpoints = castingPlayer->GetEndpoints(); + jobject jEndpointList = nullptr; + chip::JniReferences::GetInstance().CreateArrayList(jEndpointList); + for (memory::Strong endpoint : endpoints) + { + jobject matterEndpointJavaObject = support::createJEndpoint(endpoint); + VerifyOrReturnValue(matterEndpointJavaObject != nullptr, jEndpointList, + ChipLogError(AppServer, "CastingPlayer-JNI::getEndpoints(): Could not create Endpoint jobject")); + chip::JniReferences::GetInstance().AddToList(jEndpointList, matterEndpointJavaObject); + } + return jEndpointList; +} + +CastingPlayer * CastingPlayerJNI::GetCastingPlayer(jobject jCastingPlayerObject) +{ + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + jclass castingPlayerClass = env->GetObjectClass(jCastingPlayerObject); + jfieldID _cppCastingPlayerFieldId = env->GetFieldID(castingPlayerClass, "_cppCastingPlayer", "J"); + VerifyOrReturnValue(_cppCastingPlayerFieldId != nullptr, nullptr, + ChipLogError(AppServer, "CastingPlayerJNI-JNI::GetCastingPlayer() _cppCastingPlayerFieldId == nullptr")); + jlong _cppCastingPlayerValue = env->GetLongField(jCastingPlayerObject, _cppCastingPlayerFieldId); + return reinterpret_cast(_cppCastingPlayerValue); +} + }; // namespace core }; // namespace casting }; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayer-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayer-JNI.h index 2870866895c868..780476d6c66599 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayer-JNI.h +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayer-JNI.h @@ -18,21 +18,35 @@ #pragma once +#include "core/CastingPlayer.h" // from tv-casting-common + #include namespace matter { namespace casting { namespace core { +class CastingPlayerConnectionContext +{ +public: + CastingPlayer * castingPlayer; + ConnectCallback callback; + unsigned long long int commissioningWindowTimeoutSec; + EndpointFilter desiredEndpointFilter; +}; + class CastingPlayerJNI { public: + static void verifyOrEstablishConnectionTask(chip::System::Layer * aSystemLayer, void * context); + CastingPlayer * GetCastingPlayer(jobject jCastingPlayerObject); + private: - friend CastingPlayerJNI & CastingAppJNIMgr(); + friend CastingPlayerJNI & CastingPlayerJNIMgr(); static CastingPlayerJNI sInstance; }; -inline class CastingPlayerJNI & CastingAppJNIMgr() +inline class CastingPlayerJNI & CastingPlayerJNIMgr() { return CastingPlayerJNI::sInstance; } diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/Cluster-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/Cluster-JNI.cpp new file mode 100644 index 00000000000000..ca7ba11b02a34b --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/Cluster-JNI.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024 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 "Cluster-JNI.h" + +#include "../JNIDACProvider.h" +#include "../support/CastingPlayerConverter-JNI.h" +#include "../support/CommandConverter-JNI.h" +#include "../support/EndpointConverter-JNI.h" +#include "../support/ErrorConverter-JNI.h" +#include "../support/RotatingDeviceIdUniqueIdProvider-JNI.h" +#include "core/CastingApp.h" // from tv-casting-common + +#include +#include +#include +#include +#include + +using namespace chip; + +#define JNI_METHOD(RETURN, METHOD_NAME) extern "C" JNIEXPORT RETURN JNICALL Java_com_matter_casting_core_MatterCluster_##METHOD_NAME + +namespace matter { +namespace casting { +namespace core { + +JNI_METHOD(jobject, getEndpoint) +(JNIEnv * env, jobject thiz) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "Cluster-JNI::getEndpoint() called"); + BaseCluster * cluster = ClusterJNIMgr().GetCluster(thiz); + VerifyOrReturnValue(cluster != nullptr, 0, ChipLogError(AppServer, "Cluster-JNI::getEndpoint() cluster == nullptr")); + return support::createJEndpoint(std::shared_ptr(cluster->GetEndpoint().lock())); +} + +JNI_METHOD(jobject, getCommand) +(JNIEnv * env, jobject thiz, jclass commandClass) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "Cluster-JNI::getCommand() called"); + + jclass clsClass = env->GetObjectClass(commandClass); // Class + jmethodID mid = env->GetMethodID(clsClass, "getName", "()Ljava/lang/String;"); + jstring jClassName = (jstring) env->CallObjectMethod(commandClass, mid); + const char * className = env->GetStringUTFChars(jClassName, nullptr); + ChipLogProgress(AppServer, "Cluster-JNI::getCommand() className: %s", className); + if (strcmp(className, "com.matter.casting.clusters.MatterCommands$ContentLauncherClusterLaunchURLCommand") == 0) + { + BaseCluster * cluster = ClusterJNIMgr().GetCluster(thiz); + VerifyOrReturnValue(cluster != nullptr, 0, ChipLogError(AppServer, "Cluster-JNI::getCommand() cluster == nullptr")); + void * command = cluster->GetCommand(chip::app::Clusters::ContentLauncher::Commands::LaunchURL::Id); + + jobject commandJavaObject = + support::createJCommand(command, "com/matter/casting/clusters/MatterCommands$ContentLauncherClusterLaunchURLCommand"); + env->ReleaseStringUTFChars(jClassName, className); + return commandJavaObject; + } + + env->ReleaseStringUTFChars(jClassName, className); + return nullptr; +} + +/** + * @brief Get the matter::casting::core::Cluster object from the jobject jClusterObject + */ +BaseCluster * ClusterJNI::GetCluster(jobject jClusterObject) +{ + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + jclass clusterClass = env->GetObjectClass(jClusterObject); + jfieldID _cppClusterFieldId = env->GetFieldID(clusterClass, "_cppCluster", "J"); + VerifyOrReturnValue(_cppClusterFieldId != nullptr, nullptr, + ChipLogError(AppServer, "Cluster-JNI::GetCluster() _cppCluster == nullptr")); + jlong _cppClusterValue = env->GetLongField(jClusterObject, _cppClusterFieldId); + return reinterpret_cast(_cppClusterValue); +} + +}; // namespace core +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/Cluster-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/Cluster-JNI.h new file mode 100644 index 00000000000000..f082f9c40ed501 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/Cluster-JNI.h @@ -0,0 +1,45 @@ +/* + * + * Copyright (c) 2020-24 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 "core/BaseCluster.h" // from tv-casting-common + +#include + +namespace matter { +namespace casting { +namespace core { + +class ClusterJNI +{ +public: + BaseCluster * GetCluster(jobject jClusterObject); + +private: + friend ClusterJNI & ClusterJNIMgr(); + static ClusterJNI sInstance; +}; + +inline class ClusterJNI & ClusterJNIMgr() +{ + return ClusterJNI::sInstance; +} +}; // namespace core +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/Command-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/Command-JNI.cpp new file mode 100644 index 00000000000000..c475a844656ea1 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/Command-JNI.cpp @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2024 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 "Command-JNI.h" + +#include "../support/CommandConverter-JNI.h" + +#include +#include +#include + +using namespace chip; + +#define JNI_METHOD(RETURN, METHOD_NAME) extern "C" JNIEXPORT RETURN JNICALL Java_com_matter_casting_core_MatterCommand_##METHOD_NAME + +namespace matter { +namespace casting { +namespace core { + +CHIP_ERROR convertRequestFromJavaToCpp(jobject inRequest, + chip::app::Clusters::ContentLauncher::Commands::LaunchURL::Type & outRequest) +{ + VerifyOrReturnError(inRequest != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + + jclass jRequestClass; + ReturnErrorOnFailure(JniReferences::GetInstance().GetClassRef( + env, "com/matter/casting/clusters/MatterCommands$ContentLauncherClusterLaunchURLRequest", jRequestClass)); + + jfieldID jContentURLField = env->GetFieldID(jRequestClass, "contentURL", "Ljava/lang/String;"); + jstring jContentURLObj = (jstring) env->GetObjectField(inRequest, jContentURLField); + VerifyOrReturnError(jContentURLObj != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + outRequest.contentURL = CharSpan::fromCharString(env->GetStringUTFChars(jContentURLObj, 0)); + + jfieldID jDisplayStringField = env->GetFieldID(jRequestClass, "displayString", "Ljava/lang/String;"); + jstring jDisplayStringObj = (jstring) env->GetObjectField(inRequest, jDisplayStringField); + if (jDisplayStringObj != nullptr) + { + const char * nativeValue = env->GetStringUTFChars(jDisplayStringObj, 0); + outRequest.displayString = chip::Optional(CharSpan::fromCharString(nativeValue)); + } + else + { + outRequest.displayString = chip::NullOptional; + } + + // TODO: translate brandingInformation + outRequest.brandingInformation = + chip::MakeOptional(chip::app::Clusters::ContentLauncher::Structs::BrandingInformationStruct::Type()); + return CHIP_NO_ERROR; +} + +jobject +convertResponseFromCppToJava(const chip::app::Clusters::ContentLauncher::Commands::LaunchURL::Type::ResponseType * responseData) +{ + VerifyOrReturnValue(responseData != nullptr, nullptr); + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + + jclass responseTypeClass = nullptr; + VerifyOrReturnValue( + JniReferences::GetInstance().GetClassRef(env, "com/matter/casting/clusters/MatterCommands$ContentLauncherClusterResponse", + responseTypeClass) != CHIP_NO_ERROR, + nullptr); + + jmethodID constructor = env->GetMethodID(responseTypeClass, "", "()V"); + jobject jResponseObj = env->NewObject(responseTypeClass, constructor); + + if (responseData->data.HasValue()) + { + jfieldID dataField = env->GetFieldID(responseTypeClass, "data", "Ljava/lang/String;"); + env->SetObjectField(jResponseObj, dataField, env->NewStringUTF(responseData->data.Value().data())); + } + + jfieldID statusField = env->GetFieldID(responseTypeClass, "status", "Ljava/lang/Integer;"); + env->SetIntField(jResponseObj, statusField, static_cast(responseData->status)); + + return jResponseObj; +} + +JNI_METHOD(jobject, invoke) +(JNIEnv * env, jobject thiz, jobject jRequest, jobject jContext, jobject jTimedInvokeTimeoutMs) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "Command-JNI::invoke() called"); + void * command = CommandJNIMgr().GetCommand(thiz); + VerifyOrReturnValue(command != nullptr, 0, ChipLogError(AppServer, "Command-JNI::invoke() command == nullptr")); + + // Create a new Java CompletableFuture + jclass completableFutureClass = env->FindClass("java/util/concurrent/CompletableFuture"); + jmethodID completableFutureConstructor = env->GetMethodID(completableFutureClass, "", "()Ljava/lang/Object;"); + jobject completableFutureObj = env->NewObject(completableFutureClass, completableFutureConstructor); + jobject completableFutureObjGlobalRef = env->NewGlobalRef(completableFutureObj); + VerifyOrReturnValue(completableFutureObjGlobalRef != nullptr, nullptr, + ChipLogError(AppServer, "Command-JNI::invoke() completableFutureObjGlobalRef == nullptr")); + + jmethodID completeMethod = env->GetMethodID(completableFutureClass, "complete", "(Ljava/lang/Object;)Z"); + VerifyOrReturnValue(completeMethod != nullptr, nullptr, ChipLogError(AppServer, "ConnectCallback, completeMethod == nullptr")); + + jmethodID completeExceptionallyMethod = + env->GetMethodID(completableFutureClass, "completeExceptionally", "(Ljava/lang/Throwable;)Z"); + VerifyOrReturnValue(completeExceptionallyMethod != nullptr, nullptr, + ChipLogError(AppServer, "ConnectCallback, completeExceptionallyMethod == nullptr")); + + jclass requestClass = env->GetObjectClass(jRequest); // Request.class + jclass clsClass = env->GetObjectClass(requestClass); // Class + jmethodID mid = env->GetMethodID(clsClass, "getName", "()Ljava/lang/String;"); + jstring jClassName = (jstring) env->CallObjectMethod(requestClass, mid); + const char * className = env->GetStringUTFChars(jClassName, nullptr); + ChipLogProgress(AppServer, "Command-JNI::invoke() request className: %s", className); + if (strcmp(className, "com.matter.casting.clusters.MatterCommands$ContentLauncherClusterLaunchURLRequest") == 0) + { + matter::casting::core::Command * launchURLCommand = + static_cast *>(command); + + // create the LaunchURL request + chip::app::Clusters::ContentLauncher::Commands::LaunchURL::Type cppRequest; + convertRequestFromJavaToCpp(jRequest, cppRequest); + + // call Invoke on launchURLCommand while passing in success/failure callbacks + launchURLCommand->Invoke( + cppRequest, jContext, + [completableFutureObjGlobalRef, completeMethod]( + void * context, const chip::app::Clusters::ContentLauncher::Commands::LaunchURL::Type::ResponseType & response) { + ChipLogProgress(AppServer, "LaunchURL Success with response.data: %.*s", + static_cast(response.data.Value().size()), response.data.Value().data()); + + jobject jResponse = convertResponseFromCppToJava(&response); + + chip::DeviceLayer::StackUnlock unlock; + ChipLogProgress(AppServer, "LaunchURL Success, before env->CallBooleanMethod"); + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + env->CallBooleanMethod(completableFutureObjGlobalRef, completeMethod, jResponse); + }, + [completableFutureObjGlobalRef, completeExceptionallyMethod](void * context, CHIP_ERROR error) { + ChipLogError(AppServer, "LaunchURL Failure with err %" CHIP_ERROR_FORMAT, error.Format()); + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + + jclass throwableClass = env->FindClass("java/lang/RuntimeException"); + VerifyOrReturn(throwableClass != nullptr, ChipLogError(AppServer, "LaunchURL failure, throwableClass == nullptr")); + jmethodID throwableConstructor = env->GetMethodID(throwableClass, "", "(Ljava/lang/String;)V"); + VerifyOrReturn(throwableConstructor != nullptr, + ChipLogError(AppServer, "LaunchURL failure, throwableConstructor == nullptr")); + jstring errorMessage = env->NewStringUTF(error.Format()); + VerifyOrReturn(errorMessage != nullptr, ChipLogError(AppServer, "LaunchURL failure, errorMessage == nullptr")); + jobject throwableObject = env->NewObject(throwableClass, throwableConstructor, errorMessage); + VerifyOrReturn(throwableObject != nullptr, + ChipLogError(AppServer, "LaunchURL failure, throwableObject == nullptr")); + + chip::DeviceLayer::StackUnlock unlock; + env->CallBooleanMethod(completableFutureObjGlobalRef, completeExceptionallyMethod, throwableObject); + }, + chip::MakeOptional(static_cast(5000))); // time out after kTimedInvokeCommandTimeoutMs + } + + return completableFutureObjGlobalRef; +} + +/** + * @brief Get the matter::casting::core::Command object from the jobject jCommandObject + */ +void * CommandJNI::GetCommand(jobject jCommandObject) +{ + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + jclass commandClass = env->GetObjectClass(jCommandObject); + jfieldID _cppCommandFieldId = env->GetFieldID(commandClass, "_cppCommand", "J"); + VerifyOrReturnValue(_cppCommandFieldId != nullptr, nullptr, + ChipLogError(AppServer, "Command-JNI::GetCommand() _cppCommand == nullptr")); + jlong _cppCommandValue = env->GetLongField(jCommandObject, _cppCommandFieldId); + return reinterpret_cast(_cppCommandValue); +} + +}; // namespace core +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/Command-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/Command-JNI.h new file mode 100644 index 00000000000000..11797a433d2ef4 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/Command-JNI.h @@ -0,0 +1,52 @@ +/* + * + * Copyright (c) 2020-24 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 + +namespace matter { +namespace casting { +namespace core { + +/*class CommandContext +{ +public: + jobject completableFuture; + ConnectCallback callback; + unsigned long long int commissioningWindowTimeoutSec; + EndpointFilter desiredEndpointFilter; +};*/ + +class CommandJNI +{ +public: + void * GetCommand(jobject jCommandObject); + +private: + friend CommandJNI & CommandJNIMgr(); + static CommandJNI sInstance; +}; + +inline class CommandJNI & CommandJNIMgr() +{ + return CommandJNI::sInstance; +} +}; // namespace core +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/Endpoint-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/Endpoint-JNI.cpp new file mode 100644 index 00000000000000..98620c90c7d094 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/Endpoint-JNI.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2024 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 "Endpoint-JNI.h" + +#include "../JNIDACProvider.h" +#include "../support/CastingPlayerConverter-JNI.h" +#include "../support/ClusterConverter-JNI.h" +#include "../support/ErrorConverter-JNI.h" +#include "../support/RotatingDeviceIdUniqueIdProvider-JNI.h" +#include "clusters/Clusters.h" // from tv-casting-common +#include "core/CastingApp.h" // from tv-casting-common +#include "core/CastingPlayer.h" // from tv-casting-common +#include "core/CastingPlayerDiscovery.h" // from tv-casting-common +#include "core/Endpoint.h" // from tv-casting-common + +#include +#include +#include +#include +#include + +using namespace chip; + +#define JNI_METHOD(RETURN, METHOD_NAME) \ + extern "C" JNIEXPORT RETURN JNICALL Java_com_matter_casting_core_MatterEndpoint_##METHOD_NAME + +namespace matter { +namespace casting { +namespace core { + +JNI_METHOD(jint, getId) +(JNIEnv * env, jobject thiz) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "Endpoint-JNI::getId() called"); + Endpoint * endpoint = EndpointJNIMgr().GetEndpoint(thiz); + VerifyOrReturnValue(endpoint != nullptr, 0, ChipLogError(AppServer, "Endpoint-JNI::getId() endpoint == nullptr")); + return endpoint->GetId(); +} + +JNI_METHOD(jint, getProductId) +(JNIEnv * env, jobject thiz) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "Endpoint-JNI::getProductId() called"); + Endpoint * endpoint = EndpointJNIMgr().GetEndpoint(thiz); + VerifyOrReturnValue(endpoint != nullptr, 0, ChipLogError(AppServer, "Endpoint-JNI::getProductId() endpoint == nullptr")); + return endpoint->GetProductId(); +} + +JNI_METHOD(jint, getVendorId) +(JNIEnv * env, jobject thiz) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "Endpoint-JNI::getVendorId() called"); + Endpoint * endpoint = EndpointJNIMgr().GetEndpoint(thiz); + VerifyOrReturnValue(endpoint != nullptr, 0, ChipLogError(AppServer, "Endpoint-JNI::getVendorId() endpoint == nullptr")); + return endpoint->GetVendorId(); +} + +JNI_METHOD(jobject, getCastingPlayer) +(JNIEnv * env, jobject thiz) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "Endpoint-JNI::getCastingPlayer() called"); + Endpoint * endpoint = EndpointJNIMgr().GetEndpoint(thiz); + VerifyOrReturnValue(endpoint != nullptr, 0, ChipLogError(AppServer, "Endpoint-JNI::getCastingPlayer() endpoint == nullptr")); + return support::createJCastingPlayer(std::shared_ptr(endpoint->GetCastingPlayer())); +} + +JNI_METHOD(jobject, getCluster) +(JNIEnv * env, jobject thiz, jclass clusterClass) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "Endpoint-JNI::getCluster() called"); + Endpoint * endpoint = EndpointJNIMgr().GetEndpoint(thiz); + VerifyOrReturnValue(endpoint != nullptr, 0, ChipLogError(AppServer, "Endpoint-JNI::getCluster() endpoint == nullptr")); + + jclass clsClass = env->GetObjectClass(clusterClass); // Class + jmethodID mid = env->GetMethodID(clsClass, "getName", "()Ljava/lang/String;"); + jstring jClassName = (jstring) env->CallObjectMethod(clusterClass, mid); + const char * className = env->GetStringUTFChars(jClassName, nullptr); + ChipLogProgress(AppServer, "Endpoint-JNI::getCluster() className: %s", className); + if (strcmp(className, "com.matter.casting.clusters.MatterClusters$ContentLauncherCluster") == 0) + { + matter::casting::memory::Strong + contentLauncherCluster = endpoint->GetCluster(); + + jobject clusterJavaObject = + support::createJCluster(contentLauncherCluster, "com/matter/casting/clusters/MatterClusters$ContentLauncherCluster"); + env->ReleaseStringUTFChars(jClassName, className); + return clusterJavaObject; + } + + env->ReleaseStringUTFChars(jClassName, className); + return nullptr; +} + +/** + * @brief Get the matter::casting::core::Endpoint object from the jobject jEndpointObject + */ +Endpoint * EndpointJNI::GetEndpoint(jobject jEndpointObject) +{ + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + jclass endpointClass = env->GetObjectClass(jEndpointObject); + jfieldID _cppEndpointFieldId = env->GetFieldID(endpointClass, "_cppEndpoint", "J"); + VerifyOrReturnValue(_cppEndpointFieldId != nullptr, nullptr, + ChipLogError(AppServer, "Endpoint-JNI::GetEndpoint() _cppEndpointFieldId == nullptr")); + jlong _cppEndpointValue = env->GetLongField(jEndpointObject, _cppEndpointFieldId); + return reinterpret_cast(_cppEndpointValue); +} + +}; // namespace core +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/Endpoint-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/Endpoint-JNI.h new file mode 100644 index 00000000000000..6c9609d670a321 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/Endpoint-JNI.h @@ -0,0 +1,45 @@ +/* + * + * Copyright (c) 2020-24 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 "core/Endpoint.h" // from tv-casting-common + +#include + +namespace matter { +namespace casting { +namespace core { + +class EndpointJNI +{ +public: + Endpoint * GetEndpoint(jobject jEndpointObject); + +private: + friend EndpointJNI & EndpointJNIMgr(); + static EndpointJNI sInstance; +}; + +inline class EndpointJNI & EndpointJNIMgr() +{ + return EndpointJNI::sInstance; +} +}; // namespace core +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CastingPlayerConverter-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CastingPlayerConverter-JNI.cpp index 72c3677f357707..68d970b7b922b5 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CastingPlayerConverter-JNI.cpp +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CastingPlayerConverter-JNI.cpp @@ -82,7 +82,7 @@ jobject createJCastingPlayer(matter::casting::memory::Strong + +namespace matter { +namespace casting { +namespace support { + +using namespace chip; + +jobject createJCluster(matter::casting::memory::Strong cluster, const char * className) +{ + ChipLogProgress(AppServer, "ClusterConverter-JNI.createJCluster() called"); + VerifyOrReturnValue(cluster.get() != nullptr, nullptr, + ChipLogError(AppServer, "ClusterConverter-JNI::createJCluster() cluster.get() == nullptr")); + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + + // Get a reference to the cluster's Java class + jclass clusterJavaClass = env->FindClass(className); + if (clusterJavaClass == nullptr) + { + ChipLogError(AppServer, "ClusterConverter-JNI.createJCluster() could not locate cluster's Java class"); + return nullptr; + } + + // Get the constructor for the cluster's Java class + jmethodID constructor = env->GetMethodID(clusterJavaClass, "", "()V"); + if (constructor == nullptr) + { + ChipLogError(AppServer, "ClusterConverter-JNI.createJCluster() could not locate cluster's Java class constructor"); + return nullptr; + } + + // Create a new instance of the cluster's Java class + jobject jMatterCluster = nullptr; + jMatterCluster = env->NewObject(clusterJavaClass, constructor); + if (jMatterCluster == nullptr) + { + ChipLogError(AppServer, "ClusterConverter-JNI.createJCluster(): Could not create cluster's Java object"); + return jMatterCluster; + } + // Set the value of the _cppEndpoint field in the Java object to the C++ Endpoint pointer. + jfieldID longFieldId = env->GetFieldID(clusterJavaClass, "_cppCluster", "J"); + env->SetLongField(jMatterCluster, longFieldId, reinterpret_cast(cluster.get())); + return jMatterCluster; +} + +}; // namespace support +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/ClusterConverter-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/ClusterConverter-JNI.h new file mode 100644 index 00000000000000..f6847cd1d50f6b --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/ClusterConverter-JNI.h @@ -0,0 +1,38 @@ +/* + * + * Copyright (c) 2023-2024 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 "core/BaseCluster.h" + +#include + +#include + +namespace matter { +namespace casting { +namespace support { + +/** + * @brief Converts a native Cluster into a MatterCluster jobject + * + * @return pointer to the Cluster jobject if created successfully, nullptr otherwise. + */ +jobject createJCluster(matter::casting::memory::Strong cluster, const char * className); + +}; // namespace support +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CommandConverter-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CommandConverter-JNI.cpp new file mode 100644 index 00000000000000..949b8d1d22618a --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CommandConverter-JNI.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2020-24 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 "CommandConverter-JNI.h" +#include + +namespace matter { +namespace casting { +namespace support { + +using namespace chip; + +jobject createJCommand(void * command, const char * className) +{ + ChipLogProgress(AppServer, "CommandConverter-JNI.createJCommand() called"); + VerifyOrReturnValue(command != nullptr, nullptr, + ChipLogError(AppServer, "CommandConverter-JNI::createJCommand() command == nullptr")); + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + + // Get a reference to the command's Java class + jclass commandJavaClass = env->FindClass(className); + if (commandJavaClass == nullptr) + { + ChipLogError(AppServer, "CommandConverter-JNI.createJCommand() could not locate command's Java class"); + return nullptr; + } + + // Get the constructor for the command's Java class + jmethodID constructor = env->GetMethodID(commandJavaClass, "", "()V"); + if (constructor == nullptr) + { + ChipLogError(AppServer, "CommandConverter-JNI.createJCommand() could not locate command's Java class constructor"); + return nullptr; + } + + // Create a new instance of the command's Java class + jobject jMatterCommand = env->NewObject(commandJavaClass, constructor); + if (jMatterCommand == nullptr) + { + ChipLogError(AppServer, "CommandConverter-JNI.createJCommand(): Could not create command's Java object"); + return jMatterCommand; + } + // Set the value of the _cppEndpoint field in the Java object to the C++ Endpoint pointer. + jfieldID longFieldId = env->GetFieldID(commandJavaClass, "_cppCommand", "J"); + env->SetLongField(jMatterCommand, longFieldId, reinterpret_cast(command)); + return jMatterCommand; +} + +}; // namespace support +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CommandConverter-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CommandConverter-JNI.h new file mode 100644 index 00000000000000..42daa8fa2a3c28 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CommandConverter-JNI.h @@ -0,0 +1,38 @@ +/* + * + * Copyright (c) 2023-2024 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 "core/Command.h" + +#include + +#include + +namespace matter { +namespace casting { +namespace support { + +/** + * @brief Converts a native Command into a MatterCommand jobject + * + * @return pointer to the Command jobject if created successfully, nullptr otherwise. + */ +jobject createJCommand(void * command, const char * className); + +}; // namespace support +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/EndpointConverter-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/EndpointConverter-JNI.cpp new file mode 100644 index 00000000000000..1745a3ef21fa29 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/EndpointConverter-JNI.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020-24 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 "EndpointConverter-JNI.h" +#include + +namespace matter { +namespace casting { +namespace support { + +using namespace chip; + +jobject createJEndpoint(matter::casting::memory::Strong endpoint) +{ + ChipLogProgress(AppServer, "EndpointConverter-JNI.createJEndpoint() called"); + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + + // Get a reference to the MatterEndpoint Java class + jclass matterEndpointJavaClass = env->FindClass("com/matter/casting/core/MatterEndpoint"); + if (matterEndpointJavaClass == nullptr) + { + ChipLogError(AppServer, "EndpointConverter-JNI.createJEndpoint() could not locate MatterEndpoint Java class"); + return nullptr; + } + + // Get the constructor for the com/matter/casting/core/MatterEndpoint Java class + jmethodID constructor = env->GetMethodID(matterEndpointJavaClass, "", "()V"); + if (constructor == nullptr) + { + ChipLogError(AppServer, "EndpointConverter-JNI.createJEndpoint() could not locate MatterEndpoint Java class constructor"); + return nullptr; + } + + // Create a new instance of the MatterEndpoint Java class + jobject jMatterEndpoint = nullptr; + jMatterEndpoint = env->NewObject(matterEndpointJavaClass, constructor); + if (jMatterEndpoint == nullptr) + { + ChipLogError(AppServer, "EndpointConverter-JNI.createJEndpoint(): Could not create MatterEndpoint Java object"); + return jMatterEndpoint; + } + // Set the value of the _cppEndpoint field in the Java object to the C++ Endpoint pointer. + jfieldID longFieldId = env->GetFieldID(matterEndpointJavaClass, "_cppEndpoint", "J"); + env->SetLongField(jMatterEndpoint, longFieldId, reinterpret_cast(endpoint.get())); + return jMatterEndpoint; +} + +}; // namespace support +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/EndpointConverter-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/EndpointConverter-JNI.h new file mode 100644 index 00000000000000..bb5e61f552ab73 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/EndpointConverter-JNI.h @@ -0,0 +1,38 @@ +/* + * + * Copyright (c) 2023-2024 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 "core/Endpoint.h" + +#include + +#include + +namespace matter { +namespace casting { +namespace support { + +/** + * @brief Converts a native Endpoint into a MatterEndpoint jobject + * + * @return pointer to the Endpoint jobject if created successfully, nullptr otherwise. + */ +jobject createJEndpoint(matter::casting::memory::Strong endpoint); + +}; // namespace support +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_matter_action_selector.xml b/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_matter_action_selector.xml new file mode 100644 index 00000000000000..2aa3874c39b514 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_matter_action_selector.xml @@ -0,0 +1,39 @@ + + + + + + + +