Skip to content

Commit

Permalink
tv-casting-app: simplified android connection API (project-chip#31617)
Browse files Browse the repository at this point in the history
  • Loading branch information
pgregorr-amazon authored Jan 27, 2024
1 parent 93c5213 commit e03a83c
Show file tree
Hide file tree
Showing 16 changed files with 584 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.ConnectionExampleFragment;
import com.matter.casting.DiscoveryExampleFragment;
import com.matter.casting.InitializationExample;
import com.matter.casting.core.CastingPlayer;
Expand All @@ -20,7 +21,8 @@ public class MainActivity extends AppCompatActivity
implements CommissionerDiscoveryFragment.Callback,
ConnectionFragment.Callback,
SelectClusterFragment.Callback,
DiscoveryExampleFragment.Callback {
DiscoveryExampleFragment.Callback,
ConnectionExampleFragment.Callback {

private static final String TAG = MainActivity.class.getSimpleName();

Expand Down Expand Up @@ -58,17 +60,24 @@ public void handleCommissioningButtonClicked(DiscoveredNodeData commissioner) {
}

@Override
public void handleConnectionButtonClicked(CastingPlayer player) {
public void handleConnectionButtonClicked(CastingPlayer castingPlayer) {
Log.i(TAG, "MainActivity.handleConnectionButtonClicked() called");
// TODO: In future PR, show fragment that connects to the player.
// showFragment(ConnectionFragment.newInstance(CastingPlayer player));
showFragment(ConnectionExampleFragment.newInstance(castingPlayer));
}

@Override
public void handleCommissioningComplete() {
showFragment(SelectClusterFragment.newInstance(tvCastingApp));
}

@Override
public void handleConnectionComplete(CastingPlayer castingPlayer) {
Log.i(TAG, "MainActivity.handleConnectionComplete() called ");

// TODO: Implement in following PRs. Select Cluster Fragment.
// showFragment(SelectClusterFragment.newInstance(tvCastingApp));
}

@Override
public void handleContentLauncherSelected() {
showFragment(ContentLauncherFragment.newInstance(tvCastingApp));
Expand Down Expand Up @@ -115,7 +124,7 @@ private boolean initJni() {
private void showFragment(Fragment fragment, boolean showOnBack) {
Log.d(
TAG,
"showFragment called with " + fragment.getClass().getSimpleName() + " and " + showOnBack);
"showFragment() called with " + fragment.getClass().getSimpleName() + " and " + showOnBack);
FragmentTransaction fragmentTransaction =
getSupportFragmentManager()
.beginTransaction()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* 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 android.widget.Button;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.R;
import com.matter.casting.core.CastingPlayer;
import com.matter.casting.support.DeviceTypeStruct;
import com.matter.casting.support.EndpointFilter;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;

/** A {@link Fragment} to Verify or establish a connection with a selected Casting Player. */
public class ConnectionExampleFragment extends Fragment {
private static final String TAG = ConnectionExampleFragment.class.getSimpleName();
// Time (in sec) to keep the commissioning window open, if commissioning is required.
// Must be >= 3 minutes.
private static final long MIN_CONNECTION_TIMEOUT_SEC = 3 * 60;
private final CastingPlayer targetCastingPlayer;
private TextView connectionFragmentStatusTextView;
private Button connectionFragmentNextButton;

public ConnectionExampleFragment(CastingPlayer targetCastingPlayer) {
Log.i(TAG, "ConnectionExampleFragment() called with target CastingPlayer");
this.targetCastingPlayer = targetCastingPlayer;
}

/**
* Use this factory method to create a new instance of this fragment using the provided
* parameters.
*
* @return A new instance of fragment ConnectionExampleFragment.
*/
public static ConnectionExampleFragment newInstance(CastingPlayer castingPlayer) {
Log.i(TAG, "newInstance() called");
return new ConnectionExampleFragment(castingPlayer);
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, "onCreate() called");
}

@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.i(TAG, "onCreateView() called");
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_matter_connection_example, container, false);
}

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Log.i(TAG, "onViewCreated() called");

connectionFragmentStatusTextView = getView().findViewById(R.id.connectionFragmentStatusText);
connectionFragmentStatusTextView.setText(
"Verifying or establishing connection with Casting Player with device name: "
+ targetCastingPlayer.getDeviceName());

connectionFragmentNextButton = getView().findViewById(R.id.connectionFragmentNextButton);
Callback callback = (ConnectionExampleFragment.Callback) this.getActivity();
connectionFragmentNextButton.setOnClickListener(
v -> {
Log.i(TAG, "onViewCreated() NEXT clicked. Calling handleConnectionComplete()");
callback.handleConnectionComplete(targetCastingPlayer);
});

Executors.newSingleThreadExecutor()
.submit(
() -> {
Log.d(TAG, "onViewCreated() calling verifyOrEstablishConnection()");

EndpointFilter desiredEndpointFilter =
new EndpointFilter(null, 65521, new ArrayList<DeviceTypeStruct>());
// The desired commissioning window timeout and EndpointFilter are optional.
CompletableFuture<Void> completableFuture =
targetCastingPlayer.VerifyOrEstablishConnection(
MIN_CONNECTION_TIMEOUT_SEC, desiredEndpointFilter);

Log.d(TAG, "onViewCreated() verifyOrEstablishConnection() called");

completableFuture
.thenRun(
() -> {
Log.i(
TAG,
"CompletableFuture.thenRun(), connected to CastingPlayer with deviceId: "
+ targetCastingPlayer.getDeviceId());
getActivity()
.runOnUiThread(
() -> {
connectionFragmentStatusTextView.setText(
"Connected to Casting Player with device name: "
+ targetCastingPlayer.getDeviceName());
connectionFragmentNextButton.setEnabled(true);
});
})
.exceptionally(
exc -> {
Log.e(
TAG,
"CompletableFuture.exceptionally(), CastingPLayer connection failed: "
+ exc.getMessage());
getActivity()
.runOnUiThread(
() -> {
connectionFragmentStatusTextView.setText(
"Casting Player connection failed due to: "
+ exc.getMessage());
});
return null;
});
});
}

/** Interface for notifying the host. */
public interface Callback {
/** Notifies listener to trigger transition on completion of connection */
void handleConnectionComplete(CastingPlayer castingPlayer);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -338,11 +338,10 @@ public View getView(int i, View view, ViewGroup viewGroup) {
CastingPlayer castingPlayer = playerList.get(i);
Log.d(
TAG,
"OnItemClickListener.onClick() called for castingPlayer with deviceId: "
"OnClickListener.onClick() called for CastingPlayer with deviceId: "
+ castingPlayer.getDeviceId());
DiscoveryExampleFragment.Callback callback1 = (DiscoveryExampleFragment.Callback) context;
// TODO: In following PRs. Implement CastingPlayer connection
// callback1.handleCommissioningButtonClicked(castingPlayer);
callback1.handleConnectionButtonClicked(castingPlayer);
};
playerDescription.setOnClickListener(clickListener);
return view;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
*/
package com.matter.casting.core;

import com.matter.casting.support.EndpointFilter;
import java.net.InetAddress;
import java.util.List;
import java.util.concurrent.CompletableFuture;

/**
* The CastingPlayer interface defines a Matter commissioner that is able to play media to a
Expand Down Expand Up @@ -55,24 +57,38 @@ public interface CastingPlayer {
@Override
int hashCode();

// TODO: Implement in following PRs. Related to player connection implementation.
// List<Endpoint> getEndpoints();
//
// ConnectionState getConnectionState();
//
// CompletableFuture<Void> connect(long timeout);
//
// static class ConnectionState extends Observable {
// private boolean connected;
//
// void setConnected(boolean connected) {
// this.connected = connected;
// setChanged();
// notifyObservers(this.connected);
// }
//
// boolean isConnected() {
// return connected;
// }
// }
/**
* Verifies that a connection exists with this CastingPlayer, or triggers a new session request.
* If the CastingApp does not have the nodeId and fabricIndex of this CastingPlayer cached on
* disk, this will execute the user directed commissioning process.
*
* @param commissioningWindowTimeoutSec (Optional) time (in sec) to keep the commissioning window
* open, if commissioning is required. Needs to be >= MIN_CONNECTION_TIMEOUT_SEC.
* @param desiredEndpointFilter (Optional) Attributes (such as VendorId) describing an Endpoint
* that the client wants to interact with after commissioning. If this value is passed in, the
* VerifyOrEstablishConnection will force User Directed Commissioning, in case the desired
* Endpoint is not found in the on device CastingStore.
* @return A CompletableFuture that completes when the VerifyOrEstablishConnection is completed.
* The CompletableFuture will be completed with a Void value if the
* VerifyOrEstablishConnection is successful. Otherwise, the CompletableFuture will be
* completed with an Exception. The Exception will be of type
* com.matter.casting.core.CastingException. If the VerifyOrEstablishConnection fails, the
* CastingException will contain the error code and message from the CastingApp.
*/
CompletableFuture<Void> VerifyOrEstablishConnection(
long commissioningWindowTimeoutSec, EndpointFilter desiredEndpointFilter);

/**
* Verifies that a connection exists with this CastingPlayer, or triggers a new session request.
* If the CastingApp does not have the nodeId and fabricIndex of this CastingPlayer cached on
* disk, this will execute the user directed commissioning process.
*
* @return A CompletableFuture that completes when the VerifyOrEstablishConnection is completed.
* The CompletableFuture will be completed with a Void value if the
* VerifyOrEstablishConnection is successful. Otherwise, the CompletableFuture will be
* completed with an Exception. The Exception will be of type
* com.matter.casting.core.CastingException. If the VerifyOrEstablishConnection fails, the
* CastingException will contain the error code and message from the CastingApp.
*/
CompletableFuture<Void> VerifyOrEstablishConnection();
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@
*/
package com.matter.casting.core;

import com.matter.casting.support.EndpointFilter;
import java.net.InetAddress;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;

/**
* A Matter Casting Player represents a Matter commissioner that is able to play media to a physical
Expand All @@ -27,6 +29,13 @@
* the service discovered/resolved.
*/
public class MatterCastingPlayer implements CastingPlayer {
private static final String TAG = MatterCastingPlayer.class.getSimpleName();
/**
* Time (in sec) to keep the commissioning window open, if commissioning is required. Must be >= 3
* minutes.
*/
public static final long MIN_CONNECTION_TIMEOUT_SEC = 3 * 60;

private boolean connected;
private String deviceId;
private String deviceName;
Expand All @@ -37,6 +46,7 @@ public class MatterCastingPlayer implements CastingPlayer {
private int productId;
private int vendorId;
private long deviceType;
protected long _cppCastingPlayer;

public MatterCastingPlayer(
boolean connected,
Expand Down Expand Up @@ -137,4 +147,43 @@ public boolean equals(Object o) {
MatterCastingPlayer that = (MatterCastingPlayer) o;
return Objects.equals(this.deviceId, that.deviceId);
}

/**
* Verifies that a connection exists with this CastingPlayer, or triggers a new session request.
* If the CastingApp does not have the nodeId and fabricIndex of this CastingPlayer cached on
* disk, this will execute the user directed commissioning process.
*
* @param commissioningWindowTimeoutSec (Optional) time (in sec) to keep the commissioning window
* open, if commissioning is required. Needs to be >= MIN_CONNECTION_TIMEOUT_SEC.
* @param desiredEndpointFilter (Optional) Attributes (such as VendorId) describing an Endpoint
* that the client wants to interact with after commissioning. If this value is passed in, the
* VerifyOrEstablishConnection will force User Directed Commissioning, in case the desired
* Endpoint is not found in the on device CastingStore.
* @return A CompletableFuture that completes when the VerifyOrEstablishConnection is completed.
* The CompletableFuture will be completed with a Void value if the
* VerifyOrEstablishConnection is successful. Otherwise, the CompletableFuture will be
* completed with an Exception. The Exception will be of type
* com.matter.casting.core.CastingException. If the VerifyOrEstablishConnection fails, the
* CastingException will contain the error code and message from the CastingApp.
*/
@Override
public native CompletableFuture<Void> VerifyOrEstablishConnection(
long commissioningWindowTimeoutSec, EndpointFilter desiredEndpointFilter);

/**
* Verifies that a connection exists with this CastingPlayer, or triggers a new session request.
* If the CastingApp does not have the nodeId and fabricIndex of this CastingPlayer cached on
* disk, this will execute the user directed commissioning process.
*
* @return A CompletableFuture that completes when the VerifyOrEstablishConnection is completed.
* The CompletableFuture will be completed with a Void value if the
* VerifyOrEstablishConnection is successful. Otherwise, the CompletableFuture will be
* completed with an Exception. The Exception will be of type
* com.matter.casting.core.CastingException. If the VerifyOrEstablishConnection fails, the
* CastingException will contain the error code and message from the CastingApp.
*/
@Override
public CompletableFuture<Void> VerifyOrEstablishConnection() {
return VerifyOrEstablishConnection(MIN_CONNECTION_TIMEOUT_SEC, null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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.support;

/** A class to describe a Matter device type. */
public class DeviceTypeStruct {
public long deviceType;
public int revision;

public DeviceTypeStruct(long deviceType, int revision) {
this.deviceType = deviceType;
this.revision = revision;
}
}
Loading

0 comments on commit e03a83c

Please sign in to comment.