blob: 9dd0fa500d9e2baf2ad9c81b92b7fe6dca7113fb [file] [log] [blame]
/*
* Copyright (c) 2022 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.chip.casting;
import android.content.Context;
import android.net.nsd.NsdManager;
import android.net.wifi.WifiManager;
import android.util.Log;
import chip.appserver.ChipAppServer;
import chip.platform.AndroidBleManager;
import chip.platform.AndroidChipPlatform;
import chip.platform.ChipMdnsCallbackImpl;
import chip.platform.DiagnosticDataProviderImpl;
import chip.platform.NsdManagerServiceBrowser;
import chip.platform.NsdManagerServiceResolver;
import chip.platform.PreferencesKeyValueStoreManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
public class TvCastingApp {
private static final String TAG = TvCastingApp.class.getSimpleName();
private static final String DISCOVERY_TARGET_SERVICE_TYPE = "_matterd._udp.";
private static final List<Long> DISCOVERY_TARGET_DEVICE_TYPE_FILTER =
Arrays.asList(35L); // Video player = 35;
// delay before which we assume undiscovered cached players may be in STR mode
private static final long CHIP_DEVICE_CONFIG_STR_DISCOVERY_DELAY_SEC = 5;
private static TvCastingApp sInstance;
private Context applicationContext;
private ChipAppServer chipAppServer;
private NsdManagerServiceResolver.NsdManagerResolverAvailState nsdManagerResolverAvailState;
private boolean discoveryStarted = false;
private Object discoveryLock = new Object();
private List<DiscoveredNodeData> discoveredPlayers;
private ScheduledFuture<?> reportSleepingVideoPlayerCommissionersFuture;
private WifiManager.MulticastLock multicastLock;
private NsdManager nsdManager;
private NsdDiscoveryListener nsdDiscoveryListener;
private TvCastingApp() {}
public static TvCastingApp getInstance() {
if (sInstance == null) {
sInstance = new TvCastingApp();
}
return sInstance;
}
public boolean initApp(Context applicationContext, AppParameters appParameters) {
if (applicationContext == null
|| appParameters == null
|| appParameters.getConfigurationManager() == null) {
return false;
}
this.applicationContext = applicationContext;
nsdManagerResolverAvailState = new NsdManagerServiceResolver.NsdManagerResolverAvailState();
NsdManagerServiceResolver nsdManagerServiceResolver =
new NsdManagerServiceResolver(applicationContext, nsdManagerResolverAvailState);
AndroidChipPlatform chipPlatform =
new AndroidChipPlatform(
new AndroidBleManager(),
new PreferencesKeyValueStoreManager(applicationContext),
appParameters.getConfigurationManager(),
nsdManagerServiceResolver,
new NsdManagerServiceBrowser(applicationContext),
new ChipMdnsCallbackImpl(),
new DiagnosticDataProviderImpl(applicationContext));
boolean ret =
chipPlatform.updateCommissionableDataProviderData(
appParameters.getSpake2pVerifierBase64(),
appParameters.getSpake2pSaltBase64(),
appParameters.getSpake2pIterationCount(),
appParameters.getSetupPasscode(),
appParameters.getDiscriminator());
if (!ret) {
Log.e(
TAG,
"TvCastingApp.initApp failed to updateCommissionableDataProviderData on chipPlatform");
return ret;
}
ret = preInitJni(appParameters);
if (!ret) {
Log.e(TAG, "TvCastingApp.initApp failed in preInitJni");
return ret;
}
chipAppServer = new ChipAppServer();
ret = chipAppServer.startApp();
if (!ret) {
Log.e(TAG, "TvCastingApp.initApp failed in start chipAppServer");
return ret;
}
return initJni(appParameters);
}
public native void setDACProvider(DACProvider provider);
private native boolean preInitJni(AppParameters appParameters);
private native boolean initJni(AppParameters appParameters);
public void discoverVideoPlayerCommissioners(
SuccessCallback<DiscoveredNodeData> discoverySuccessCallback,
FailureCallback discoveryFailureCallback) {
synchronized (discoveryLock) {
Log.d(TAG, "TvCastingApp.discoverVideoPlayerCommissioners called");
if (this.discoveryStarted) {
Log.d(TAG, "Discovery already started, stopping before starting again");
stopVideoPlayerDiscovery();
}
List<VideoPlayer> preCommissionedVideoPlayers = readCachedVideoPlayers();
WifiManager wifiManager =
(WifiManager) applicationContext.getSystemService(Context.WIFI_SERVICE);
multicastLock = wifiManager.createMulticastLock("multicastLock");
multicastLock.setReferenceCounted(true);
multicastLock.acquire();
nsdManager = (NsdManager) applicationContext.getSystemService(Context.NSD_SERVICE);
discoveredPlayers = new ArrayList<>();
nsdDiscoveryListener =
new NsdDiscoveryListener(
nsdManager,
DISCOVERY_TARGET_SERVICE_TYPE,
DISCOVERY_TARGET_DEVICE_TYPE_FILTER,
preCommissionedVideoPlayers,
new SuccessCallback<DiscoveredNodeData>() {
@Override
public void handle(DiscoveredNodeData commissioner) {
Log.d(TAG, "Discovered commissioner added " + commissioner);
discoveredPlayers.add(commissioner);
discoverySuccessCallback.handle(commissioner);
}
},
discoveryFailureCallback,
nsdManagerResolverAvailState);
nsdManager.discoverServices(
DISCOVERY_TARGET_SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, nsdDiscoveryListener);
Log.d(TAG, "TvCastingApp.discoverVideoPlayerCommissioners started");
/**
* Surface players (as DiscoveredNodeData objects on discoverySuccessCallback) that we
* previously connected to and received their WakeOnLAN MACAddress, but could not discover
* over DNS-SD this time in CHIP_DEVICE_CONFIG_STR_DISCOVERY_DELAY_SEC. This API will also
* ensure that the reported players were previously discoverable within
* CHIP_DEVICE_CONFIG_STR_CACHE_LAST_DISCOVERED_HOURS.
*
* <p>The DiscoveredNodeData object for such players will have the IsAsleep attribute set to
* true, which can optionally be used for any special UX treatment when displaying them.
*
* <p>Surfacing such players as discovered will allow displaying them to the user, who may
* want to cast to them. In such a case, the VerifyOrEstablishConnection API will turn them on
* over WakeOnLan.
*/
this.reportSleepingVideoPlayerCommissionersFuture =
Executors.newScheduledThreadPool(1)
.schedule(
() -> {
Log.d(
TAG,
"Scheduling reportSleepingCommissioners with commissioner count "
+ (preCommissionedVideoPlayers != null
? preCommissionedVideoPlayers.size()
: 0));
reportSleepingVideoPlayerCommissioners(
preCommissionedVideoPlayers, discoverySuccessCallback);
},
CHIP_DEVICE_CONFIG_STR_DISCOVERY_DELAY_SEC * 1000,
TimeUnit.MILLISECONDS);
this.discoveryStarted = true;
}
}
private void reportSleepingVideoPlayerCommissioners(
List<VideoPlayer> cachedVideoPlayers,
SuccessCallback<DiscoveredNodeData> discoverySuccessCallback) {
Log.d(
TAG,
"TvCastingApp.reportSleepingVideoPlayerCommissioners called with commissioner count "
+ (cachedVideoPlayers != null ? cachedVideoPlayers.size() : 0));
if (cachedVideoPlayers == null) {
Log.d(TAG, "No cached video players available.");
return;
}
for (VideoPlayer player : cachedVideoPlayers) {
Log.d(TAG, "Cached Video Player: " + player);
// do NOT surface this cached Player if we don't have its MACAddress
if (player.getMACAddress() == null) {
Log.d(
TAG,
"TvCastingApp.reportSleepingVideoPlayerCommissioners Skipping Player with hostName"
+ player.getHostName()
+ " but no MACAddress");
continue;
}
// do NOT surface this cached Player if it has not been discoverable recently (in
// CHIP_DEVICE_CONFIG_STR_CACHE_LAST_DISCOVERED_HOURS)
if (!WasRecentlyDiscoverable(player)) {
Log.d(
TAG,
"TvCastingApp.reportSleepingVideoPlayerCommissioners Skipping Player with hostName"
+ player.getHostName()
+ " that has not been discovered recently");
continue;
}
// do NOT surface this cached Player if it was just discovered right now (in this discovery
// call)
boolean justDiscovered =
discoveredPlayers
.stream()
.anyMatch(
new Predicate<DiscoveredNodeData>() {
@Override
public boolean test(DiscoveredNodeData discoveredNodeData) {
return player.getHostName().equals(discoveredNodeData.getHostName());
}
});
if (justDiscovered) {
Log.d(
TAG,
"TvCastingApp.reportSleepingVideoPlayerCommissioners Skipping Player with hostName"
+ player.getHostName()
+ " that was just discovered");
continue;
}
// DO surface this cached Player (as asleep)
Log.d(TAG, "Reporting sleeping player with hostName " + player.getHostName());
player.setIsAsleep(true);
discoverySuccessCallback.handle(new DiscoveredNodeData(player));
}
}
private native boolean WasRecentlyDiscoverable(VideoPlayer player);
public void stopVideoPlayerDiscovery() {
synchronized (discoveryLock) {
Log.d(TAG, "TvCastingApp trying to stop video player discovery");
if (this.discoveryStarted
&& nsdManager != null
&& multicastLock != null
&& nsdDiscoveryListener != null) {
Log.d(TAG, "TvCastingApp stopping Video Player commissioner discovery");
try {
nsdManager.stopServiceDiscovery(nsdDiscoveryListener);
} catch (IllegalArgumentException e) {
Log.w(
TAG,
"TvCastingApp received exception on calling nsdManager.stopServiceDiscovery() "
+ e.getMessage());
}
if (multicastLock.isHeld()) {
multicastLock.release();
}
if (reportSleepingVideoPlayerCommissionersFuture != null) {
reportSleepingVideoPlayerCommissionersFuture.cancel(false);
}
this.discoveryStarted = false;
}
}
}
void resetDiscoveryState() {
synchronized (discoveryLock) {
Log.d(TAG, "TvCastingApp resetting discovery state");
this.discoveryStarted = false;
this.nsdDiscoveryListener = null;
if (multicastLock != null && multicastLock.isHeld()) {
multicastLock.release();
}
}
}
public native boolean openBasicCommissioningWindow(
int duration,
CommissioningCallbacks commissioningCallbacks,
SuccessCallback<VideoPlayer> onConnectionSuccess,
FailureCallback onConnectionFailure,
SuccessCallback<ContentApp> onNewOrUpdatedEndpointCallback);
public native boolean sendCommissioningRequest(DiscoveredNodeData commissioner);
/** @Deprecated Use sendCommissioningRequest(DiscoveredNodeData) instead */
private native boolean sendUserDirectedCommissioningRequest(String address, int port);
public native List<VideoPlayer> readCachedVideoPlayers();
public native boolean verifyOrEstablishConnection(
VideoPlayer targetVideoPlayer,
SuccessCallback<VideoPlayer> onConnectionSuccess,
FailureCallback onConnectionFailure,
SuccessCallback<ContentApp> onNewOrUpdatedEndpointCallback);
public native void shutdownAllSubscriptions();
public native void disconnect();
public native List<VideoPlayer> getActiveTargetVideoPlayers();
public native boolean purgeCache();
/*
* CONTENT LAUNCHER CLUSTER
*
* TODO: Add API to subscribe to AcceptHeader
*/
public native boolean contentLauncherLaunchURL(
ContentApp contentApp, String contentUrl, String contentDisplayStr, Object launchURLHandler);
public native boolean contentLauncher_launchContent(
ContentApp contentApp,
ContentLauncherTypes.ContentSearch search,
boolean autoPlay,
String data,
Object responseHandler);
public native boolean contentLauncher_subscribeToSupportedStreamingProtocols(
ContentApp contentApp,
SuccessCallback<Integer> readSuccessHandler,
FailureCallback readFailureHandler,
int minInterval,
int maxInterval,
SubscriptionEstablishedCallback subscriptionEstablishedHandler);
/*
* LEVEL CONTROL CLUSTER
*/
public native boolean levelControl_step(
ContentApp contentApp,
byte stepMode,
byte stepSize,
short transitionTime,
byte optionMask,
byte optionOverridem,
Object responseHandler);
public native boolean levelControl_moveToLevel(
ContentApp contentApp,
byte level,
short transitionTime,
byte optionMask,
byte optionOverridem,
Object responseHandler);
public native boolean levelControl_subscribeToCurrentLevel(
ContentApp contentApp,
SuccessCallback<Byte> readSuccessHandler,
FailureCallback readFailureHandler,
int minInterval,
int maxInterval,
SubscriptionEstablishedCallback subscriptionEstablishedHandler);
public native boolean levelControl_subscribeToMinLevel(
ContentApp contentApp,
SuccessCallback<Byte> readSuccessHandler,
FailureCallback readFailureHandler,
int minInterval,
int maxInterval,
SubscriptionEstablishedCallback subscriptionEstablishedHandler);
public native boolean levelControl_subscribeToMaxLevel(
ContentApp contentApp,
SuccessCallback<Byte> readSuccessHandler,
FailureCallback readFailureHandler,
int minInterval,
int maxInterval,
SubscriptionEstablishedCallback subscriptionEstablishedHandler);
/*
* MEDIA PLAYBACK CLUSTER
*/
public native boolean mediaPlayback_play(ContentApp contentApp, Object responseHandler);
public native boolean mediaPlayback_pause(ContentApp contentApp, Object responseHandler);
public native boolean mediaPlayback_stopPlayback(ContentApp contentApp, Object responseHandler);
public native boolean mediaPlayback_next(ContentApp contentApp, Object responseHandler);
public native boolean mediaPlayback_previous(ContentApp contentApp, Object responseHandler);
public native boolean mediaPlayback_rewind(ContentApp contentApp, Object responseHandler);
public native boolean mediaPlayback_fastForward(ContentApp contentApp, Object responseHandler);
public native boolean mediaPlayback_startOver(ContentApp contentApp, Object responseHandler);
public native boolean mediaPlayback_seek(
ContentApp contentApp, long position, Object responseHandler);
public native boolean mediaPlayback_skipForward(
ContentApp contentApp, long deltaPositionMilliseconds, Object responseHandler);
public native boolean mediaPlayback_skipBackward(
ContentApp contentApp, long deltaPositionMilliseconds, Object responseHandler);
public native boolean mediaPlayback_subscribeToCurrentState(
ContentApp contentApp,
SuccessCallback<MediaPlaybackTypes.PlaybackStateEnum> readSuccessHandler,
FailureCallback readFailureHandler,
int minInterval,
int maxInterval,
SubscriptionEstablishedCallback subscriptionEstablishedHandler);
public native boolean mediaPlayback_subscribeToDuration(
ContentApp contentApp,
SuccessCallback<Long> readSuccessHandler,
FailureCallback readFailureHandler,
int minInterval,
int maxInterval,
SubscriptionEstablishedCallback subscriptionEstablishedHandler);
public native boolean mediaPlayback_subscribeToSampledPosition(
ContentApp contentApp,
SuccessCallback<MediaPlaybackTypes.PlaybackPosition> readSuccessHandler,
FailureCallback readFailureHandler,
int minInterval,
int maxInterval,
SubscriptionEstablishedCallback subscriptionEstablishedHandler);
public native boolean mediaPlayback_subscribeToPlaybackSpeed(
ContentApp contentApp,
SuccessCallback<Float> readSuccessHandler,
FailureCallback readFailureHandler,
int minInterval,
int maxInterval,
SubscriptionEstablishedCallback subscriptionEstablishedHandler);
public native boolean mediaPlayback_subscribeToSeekRangeEnd(
ContentApp contentApp,
SuccessCallback<Long> readSuccessHandler,
FailureCallback readFailureHandler,
int minInterval,
int maxInterval,
SubscriptionEstablishedCallback subscriptionEstablishedHandler);
public native boolean mediaPlayback_subscribeToSeekRangeStart(
ContentApp contentApp,
SuccessCallback<Long> readSuccessHandler,
FailureCallback readFailureHandler,
int minInterval,
int maxInterval,
SubscriptionEstablishedCallback subscriptionEstablishedHandler);
/*
* APPLICATION LAUNCHER CLUSTER
*/
public native boolean applicationLauncher_launchApp(
ContentApp contentApp,
short catalogVendorId,
String applicationId,
byte[] data,
Object responseHandler);
public native boolean applicationLauncher_stopApp(
ContentApp contentApp, short catalogVendorId, String applicationId, Object responseHandler);
public native boolean applicationLauncher_hideApp(
ContentApp contentApp, short catalogVendorId, String applicationId, Object responseHandler);
/*
* TARGET NAVIGATOR CLUSTER
*/
public native boolean targetNavigator_navigateTarget(
ContentApp contentApp, byte target, String data, Object responseHandler);
public native boolean targetNavigator_subscribeToCurrentTarget(
ContentApp contentApp,
SuccessCallback<Byte> readSuccessHandler,
FailureCallback readFailureHandler,
int minInterval,
int maxInterval,
SubscriptionEstablishedCallback subscriptionEstablishedHandler);
public native boolean targetNavigator_subscribeToTargetList(
ContentApp contentApp,
SuccessCallback<Object> readSuccessHandler,
FailureCallback readFailureHandler,
int minInterval,
int maxInterval,
SubscriptionEstablishedCallback subscriptionEstablishedHandler);
/*
* KEYPAD INPUT CLUSTER
*/
public native boolean keypadInput_sendKey(
ContentApp contentApp, byte keyCode, Object responseHandler);
/**
* APPLICATION BASIC
*
* <p>TODO: Add APIs to subscribe to & read Application, Status and AllowedVendorList
*/
public native boolean applicationBasic_subscribeToVendorName(
ContentApp contentApp,
SuccessCallback<String> readSuccessHandler,
FailureCallback readFailureHandler,
int minInterval,
int maxInterval,
SubscriptionEstablishedCallback subscriptionEstablishedHandler);
public native boolean applicationBasic_subscribeToVendorID(
ContentApp contentApp,
SuccessCallback<Integer> readSuccessHandler,
FailureCallback readFailureHandler,
int minInterval,
int maxInterval,
SubscriptionEstablishedCallback subscriptionEstablishedHandler);
public native boolean applicationBasic_subscribeToApplicationName(
ContentApp contentApp,
SuccessCallback<String> readSuccessHandler,
FailureCallback readFailureHandler,
int minInterval,
int maxInterval,
SubscriptionEstablishedCallback subscriptionEstablishedHandler);
public native boolean applicationBasic_subscribeToProductID(
ContentApp contentApp,
SuccessCallback<Integer> readSuccessHandler,
FailureCallback readFailureHandler,
int minInterval,
int maxInterval,
SubscriptionEstablishedCallback subscriptionEstablishedHandler);
public native boolean applicationBasic_subscribeToApplicationVersion(
ContentApp contentApp,
SuccessCallback<String> readSuccessHandler,
FailureCallback readFailureHandler,
int minInterval,
int maxInterval,
SubscriptionEstablishedCallback subscriptionEstablishedHandler);
public native boolean applicationBasic_readVendorName(
ContentApp contentApp,
SuccessCallback<String> readSuccessHandler,
FailureCallback readFailureHandler);
public native boolean applicationBasic_readVendorID(
ContentApp contentApp,
SuccessCallback<Integer> readSuccessHandler,
FailureCallback readFailureHandler);
public native boolean applicationBasic_readApplicationName(
ContentApp contentApp,
SuccessCallback<String> readSuccessHandler,
FailureCallback readFailureHandler);
public native boolean applicationBasic_readProductID(
ContentApp contentApp,
SuccessCallback<Integer> readSuccessHandler,
FailureCallback readFailureHandler);
public native boolean applicationBasic_readApplicationVersion(
ContentApp contentApp,
SuccessCallback<String> readSuccessHandler,
FailureCallback readFailureHandler);
public native boolean onOff_on(ContentApp contentApp, Object responseHandler);
public native boolean onOff_off(ContentApp contentApp, Object responseHandler);
public native boolean onOff_toggle(ContentApp contentApp, Object responseHandler);
public native boolean messages_presentMessages(
ContentApp contentApp, String messageText, Object responseHandler);
static {
System.loadLibrary("TvCastingApp");
}
}