[androidtv] Fixes Bugs and Prepares for PhilipsTV (#16191)

Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com>
Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
morph166955 2024-01-12 11:53:35 -06:00 committed by Ciprian Pascu
parent deece051e8
commit 65d9768cb6
15 changed files with 119 additions and 77 deletions

View File

@ -35,6 +35,8 @@ There are three required fields to connect successfully to a ShieldTV.
| Name | Type | Description | Default | Required | Advanced | | Name | Type | Description | Default | Required | Advanced |
|------------------|---------|---------------------------------------|---------|----------|----------| |------------------|---------|---------------------------------------|---------|----------|----------|
| ipAddress | text | IP address of the device | N/A | yes | no | | ipAddress | text | IP address of the device | N/A | yes | no |
| googletvPort | text | TCP Port for GoogleTV | 6466 | no | no |
| shieldtvPort | text | TCP Port for ShieldTV | 8987 | no | no |
| keystore | text | Location of the Java Keystore | N/A | no | no | | keystore | text | Location of the Java Keystore | N/A | no | no |
| keystorePassword | text | Password of the Java Keystore | N/A | no | no | | keystorePassword | text | Password of the Java Keystore | N/A | no | no |
| gtvEnabled | boolean | Enable/Disable the GoogleTV protocol | true | no | no | | gtvEnabled | boolean | Enable/Disable the GoogleTV protocol | true | no | no |

View File

@ -49,8 +49,10 @@ public class AndroidTVBindingConstants {
public static final String CHANNEL_PLAYER = "player"; public static final String CHANNEL_PLAYER = "player";
// List of all config properties // List of all config properties
public static final String PROPERTY_IP_ADDRESS = "ipAddress"; public static final String PARAMETER_IP_ADDRESS = "ipAddress";
public static final String PROPERTY_GTV_ENABLED = "gtvEnabled"; public static final String PARAMETER_GOOGLETV_PORT = "googletvPort";
public static final String PARAMETER_SHIELDTV_PORT = "shieldtvPort";
public static final String PARAMETER_GTV_ENABLED = "gtvEnabled";
// List of all static String literals // List of all static String literals
public static final String PIN_REQUEST = "REQUEST"; public static final String PIN_REQUEST = "REQUEST";

View File

@ -128,6 +128,10 @@ public class AndroidTVHandler extends BaseThingHandler {
failed = true; failed = true;
} }
statusMessage = "GoogleTV: " + googletvConnectionManager.getStatusMessage(); statusMessage = "GoogleTV: " + googletvConnectionManager.getStatusMessage();
if (!THING_TYPE_GOOGLETV.equals(thingTypeUID)) {
statusMessage = statusMessage + " | ";
}
} }
if (THING_TYPE_SHIELDTV.equals(thingTypeUID)) { if (THING_TYPE_SHIELDTV.equals(thingTypeUID)) {
@ -135,7 +139,7 @@ public class AndroidTVHandler extends BaseThingHandler {
if (!shieldtvConnectionManager.getLoggedIn()) { if (!shieldtvConnectionManager.getLoggedIn()) {
failed = true; failed = true;
} }
statusMessage = statusMessage + " | ShieldTV: " + shieldtvConnectionManager.getStatusMessage(); statusMessage = statusMessage + "ShieldTV: " + shieldtvConnectionManager.getStatusMessage();
} }
} }
@ -159,13 +163,13 @@ public class AndroidTVHandler extends BaseThingHandler {
String ipAddress = googletvConfig.ipAddress; String ipAddress = googletvConfig.ipAddress;
boolean gtvEnabled = googletvConfig.gtvEnabled; boolean gtvEnabled = googletvConfig.gtvEnabled;
if (ipAddress.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.googletv-address-not-specified");
return;
}
if (THING_TYPE_GOOGLETV.equals(thingTypeUID) || gtvEnabled) { if (THING_TYPE_GOOGLETV.equals(thingTypeUID) || gtvEnabled) {
if (ipAddress.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.googletv-address-not-specified");
return;
}
googletvConnectionManager = new GoogleTVConnectionManager(this, googletvConfig); googletvConnectionManager = new GoogleTVConnectionManager(this, googletvConfig);
} }
@ -186,6 +190,13 @@ public class AndroidTVHandler extends BaseThingHandler {
TimeUnit.MILLISECONDS); TimeUnit.MILLISECONDS);
} }
public void sendCommandToProtocol(ChannelUID channelUID, Command command) {
ShieldTVConnectionManager shieldtvConnectionManager = this.shieldtvConnectionManager;
if (THING_TYPE_SHIELDTV.equals(thingTypeUID) && (shieldtvConnectionManager != null)) {
shieldtvConnectionManager.handleCommand(channelUID, command);
}
}
@Override @Override
public void handleCommand(ChannelUID channelUID, Command command) { public void handleCommand(ChannelUID channelUID, Command command) {
logger.trace("{} - Command received at handler: {} {}", this.thingID, channelUID.getId(), command); logger.trace("{} - Command received at handler: {} {}", this.thingID, channelUID.getId(), command);

View File

@ -79,7 +79,7 @@ public class GoogleTVDiscoveryParticipant implements MDNSDiscoveryParticipant {
if (uid != null) { if (uid != null) {
final String id = uid.getId(); final String id = uid.getId();
final String label = service.getName() + " (" + id + ")"; final String label = service.getName() + " (" + id + ")";
final Map<String, Object> properties = Map.of(PROPERTY_IP_ADDRESS, ipAddress); final Map<String, Object> properties = Map.of(PARAMETER_IP_ADDRESS, ipAddress);
return DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel(label).build(); return DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel(label).build();
} else { } else {

View File

@ -81,7 +81,7 @@ public class ShieldTVDiscoveryParticipant implements MDNSDiscoveryParticipant {
if (uid != null) { if (uid != null) {
final String id = uid.getId(); final String id = uid.getId();
final String label = service.getName() + " (" + id + ")"; final String label = service.getName() + " (" + id + ")";
final Map<String, Object> properties = Map.of(PROPERTY_IP_ADDRESS, ipAddress); final Map<String, Object> properties = Map.of(PARAMETER_IP_ADDRESS, ipAddress);
return DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel(label).build(); return DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel(label).build();
} else { } else {

View File

@ -23,7 +23,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
public class GoogleTVConfiguration { public class GoogleTVConfiguration {
public String ipAddress = ""; public String ipAddress = "";
public int port = 6466; public int googletvPort = 6466;
public int reconnect; public int reconnect;
public int heartbeat; public int heartbeat;
public String keystoreFileName = ""; public String keystoreFileName = "";

View File

@ -80,8 +80,6 @@ import org.slf4j.LoggerFactory;
*/ */
@NonNullByDefault @NonNullByDefault
public class GoogleTVConnectionManager { public class GoogleTVConnectionManager {
private static final int DEFAULT_RECONNECT_SECONDS = 60;
private static final int DEFAULT_HEARTBEAT_SECONDS = 5;
private static final long KEEPALIVE_TIMEOUT_SECONDS = 30; private static final long KEEPALIVE_TIMEOUT_SECONDS = 30;
private static final String DEFAULT_KEYSTORE_PASSWORD = "secret"; private static final String DEFAULT_KEYSTORE_PASSWORD = "secret";
private static final String DEFAULT_MODE = "NORMAL"; private static final String DEFAULT_MODE = "NORMAL";
@ -335,7 +333,7 @@ public class GoogleTVConnectionManager {
private boolean servicePing() { private boolean servicePing() {
int timeout = 500; int timeout = 500;
SocketAddress socketAddress = new InetSocketAddress(config.ipAddress, config.port); SocketAddress socketAddress = new InetSocketAddress(config.ipAddress, config.googletvPort);
try (Socket socket = new Socket()) { try (Socket socket = new Socket()) {
socket.connect(socketAddress, timeout); socket.connect(socketAddress, timeout);
return true; return true;
@ -369,7 +367,7 @@ public class GoogleTVConnectionManager {
private void setShimX509ClientChain(X509Certificate @Nullable [] shimX509ClientChain) { private void setShimX509ClientChain(X509Certificate @Nullable [] shimX509ClientChain) {
try { try {
this.shimX509ClientChain = shimX509ClientChain; this.shimX509ClientChain = shimX509ClientChain;
logger.trace("Setting shimX509ClientChain {}", config.port); logger.trace("Setting shimX509ClientChain {}", config.googletvPort);
if (shimX509ClientChain != null) { if (shimX509ClientChain != null) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Subject DN: {}", shimX509ClientChain[0].getSubjectX500Principal()); logger.trace("Subject DN: {}", shimX509ClientChain[0].getSubjectX500Principal());
@ -389,7 +387,7 @@ public class GoogleTVConnectionManager {
private void startChildConnectionManager(int port, String mode) { private void startChildConnectionManager(int port, String mode) {
GoogleTVConfiguration childConfig = new GoogleTVConfiguration(); GoogleTVConfiguration childConfig = new GoogleTVConfiguration();
childConfig.ipAddress = config.ipAddress; childConfig.ipAddress = config.ipAddress;
childConfig.port = port; childConfig.googletvPort = port;
childConfig.reconnect = config.reconnect; childConfig.reconnect = config.reconnect;
childConfig.heartbeat = config.heartbeat; childConfig.heartbeat = config.heartbeat;
childConfig.keystoreFileName = config.keystoreFileName; childConfig.keystoreFileName = config.keystoreFileName;
@ -397,10 +395,10 @@ public class GoogleTVConnectionManager {
childConfig.delay = config.delay; childConfig.delay = config.delay;
childConfig.shim = config.shim; childConfig.shim = config.shim;
childConfig.mode = mode; childConfig.mode = mode;
logger.debug("{} - startChildConnectionManager parent config: {} {} {}", handler.getThingID(), config.port, logger.debug("{} - startChildConnectionManager parent config: {} {} {}", handler.getThingID(),
config.mode, config.shim); config.googletvPort, config.mode, config.shim);
logger.debug("{} - startChildConnectionManager child config: {} {} {}", handler.getThingID(), childConfig.port, logger.debug("{} - startChildConnectionManager child config: {} {} {}", handler.getThingID(),
childConfig.mode, childConfig.shim); childConfig.googletvPort, childConfig.mode, childConfig.shim);
childConnectionManager = new GoogleTVConnectionManager(this.handler, childConfig, this); childConnectionManager = new GoogleTVConnectionManager(this.handler, childConfig, this);
} }
@ -460,7 +458,7 @@ public class GoogleTVConnectionManager {
folder.mkdirs(); folder.mkdirs();
} }
config.port = (config.port > 0) ? config.port : DEFAULT_PORT; config.googletvPort = (config.googletvPort > 0) ? config.googletvPort : DEFAULT_PORT;
config.mode = (!config.mode.equals("")) ? config.mode : DEFAULT_MODE; config.mode = (!config.mode.equals("")) ? config.mode : DEFAULT_MODE;
config.keystoreFileName = (!config.keystoreFileName.equals("")) ? config.keystoreFileName config.keystoreFileName = (!config.keystoreFileName.equals("")) ? config.keystoreFileName
@ -520,8 +518,9 @@ public class GoogleTVConnectionManager {
if (isOnline || config.mode.equals(PIN_MODE)) { if (isOnline || config.mode.equals(PIN_MODE)) {
try { try {
logger.debug("{} - Opening GoogleTV SSL connection to {}:{} {}", handler.getThingID(), logger.debug("{} - Opening GoogleTV SSL connection to {}:{} {}", handler.getThingID(),
config.ipAddress, config.port, config.mode); config.ipAddress, config.googletvPort, config.mode);
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(config.ipAddress, config.port); SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(config.ipAddress,
config.googletvPort);
sslSocket.startHandshake(); sslSocket.startHandshake();
this.shimServerChain = ((SSLSocket) sslSocket).getSession().getPeerCertificates(); this.shimServerChain = ((SSLSocket) sslSocket).getSession().getPeerCertificates();
writer = new BufferedWriter( writer = new BufferedWriter(
@ -531,7 +530,7 @@ public class GoogleTVConnectionManager {
this.sslSocket = sslSocket; this.sslSocket = sslSocket;
this.sendQueue.clear(); this.sendQueue.clear();
logger.debug("{} - Connection to {}:{} {} successful", handler.getThingID(), config.ipAddress, logger.debug("{} - Connection to {}:{} {} successful", handler.getThingID(), config.ipAddress,
config.port, config.mode); config.googletvPort, config.mode);
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
setStatus(false, "offline.unknown-host"); setStatus(false, "offline.unknown-host");
logger.debug("{} - Unknown host {}", handler.getThingID(), config.ipAddress); logger.debug("{} - Unknown host {}", handler.getThingID(), config.ipAddress);
@ -539,7 +538,8 @@ public class GoogleTVConnectionManager {
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// port out of valid range // port out of valid range
setStatus(false, "offline.invalid-port-number"); setStatus(false, "offline.invalid-port-number");
logger.debug("{} - Invalid port number {}:{}", handler.getThingID(), config.ipAddress, config.port); logger.debug("{} - Invalid port number {}:{}", handler.getThingID(), config.ipAddress,
config.googletvPort);
return; return;
} catch (InterruptedIOException e) { } catch (InterruptedIOException e) {
logger.debug("{} - Interrupted while establishing GoogleTV connection", handler.getThingID()); logger.debug("{} - Interrupted while establishing GoogleTV connection", handler.getThingID());
@ -552,7 +552,7 @@ public class GoogleTVConnectionManager {
setStatus(false, "offline.pin-process-incomplete"); setStatus(false, "offline.pin-process-incomplete");
logger.debug("{} - GoogleTV PIN Process Incomplete", handler.getThingID()); logger.debug("{} - GoogleTV PIN Process Incomplete", handler.getThingID());
reconnectTaskCancel(true); reconnectTaskCancel(true);
startChildConnectionManager(this.config.port + 1, PIN_MODE); startChildConnectionManager(this.config.googletvPort + 1, PIN_MODE);
} else if ((message != null) && (message.contains("certificate_unknown")) && (config.shim)) { } else if ((message != null) && (message.contains("certificate_unknown")) && (config.shim)) {
logger.debug("Shim cert_unknown I/O error while connecting: {}", e.getMessage()); logger.debug("Shim cert_unknown I/O error while connecting: {}", e.getMessage());
Socket shimServerSocket = this.shimServerSocket; Socket shimServerSocket = this.shimServerSocket;
@ -567,7 +567,7 @@ public class GoogleTVConnectionManager {
} else { } else {
setStatus(false, "offline.error-opening-ssl-connection-check-log"); setStatus(false, "offline.error-opening-ssl-connection-check-log");
logger.info("{} - Error opening SSL connection to {}:{} {}", handler.getThingID(), logger.info("{} - Error opening SSL connection to {}:{} {}", handler.getThingID(),
config.ipAddress, config.port, e.getMessage()); config.ipAddress, config.googletvPort, e.getMessage());
disconnect(false); disconnect(false);
scheduleConnectRetry(config.reconnect); // Possibly a temporary problem. Try again later. scheduleConnectRetry(config.reconnect); // Possibly a temporary problem. Try again later.
} }
@ -577,7 +577,7 @@ public class GoogleTVConnectionManager {
setStatus(false, "offline.initializing"); setStatus(false, "offline.initializing");
logger.trace("{} - Starting Reader Thread for {}:{}", handler.getThingID(), config.ipAddress, logger.trace("{} - Starting Reader Thread for {}:{}", handler.getThingID(), config.ipAddress,
config.port); config.googletvPort);
Thread readerThread = new Thread(this::readerThreadJob, "GoogleTV reader " + handler.getThingID()); Thread readerThread = new Thread(this::readerThreadJob, "GoogleTV reader " + handler.getThingID());
readerThread.setDaemon(true); readerThread.setDaemon(true);
@ -585,7 +585,7 @@ public class GoogleTVConnectionManager {
this.readerThread = readerThread; this.readerThread = readerThread;
logger.trace("{} - Starting Sender Thread for {}:{}", handler.getThingID(), config.ipAddress, logger.trace("{} - Starting Sender Thread for {}:{}", handler.getThingID(), config.ipAddress,
config.port); config.googletvPort);
Thread senderThread = new Thread(this::senderThreadJob, "GoogleTV sender " + handler.getThingID()); Thread senderThread = new Thread(this::senderThreadJob, "GoogleTV sender " + handler.getThingID());
senderThread.setDaemon(true); senderThread.setDaemon(true);
@ -593,19 +593,19 @@ public class GoogleTVConnectionManager {
this.senderThread = senderThread; this.senderThread = senderThread;
logger.trace("{} - Checking for PIN MODE for {}:{} {}", handler.getThingID(), config.ipAddress, logger.trace("{} - Checking for PIN MODE for {}:{} {}", handler.getThingID(), config.ipAddress,
config.port, config.mode); config.googletvPort, config.mode);
if (config.mode.equals(PIN_MODE)) { if (config.mode.equals(PIN_MODE)) {
logger.trace("{} - Sending PIN Login to {}:{}", handler.getThingID(), config.ipAddress, logger.trace("{} - Sending PIN Login to {}:{}", handler.getThingID(), config.ipAddress,
config.port); config.googletvPort);
// Send app name and device name // Send app name and device name
sendCommand(new GoogleTVCommand(GoogleTVRequest.encodeMessage(GoogleTVRequest.loginRequest(1)))); sendCommand(new GoogleTVCommand(GoogleTVRequest.encodeMessage(GoogleTVRequest.loginRequest(1))));
// Unknown but required // Unknown but required
sendCommand(new GoogleTVCommand(GoogleTVRequest.encodeMessage(GoogleTVRequest.loginRequest(2)))); sendCommand(new GoogleTVCommand(GoogleTVRequest.encodeMessage(GoogleTVRequest.loginRequest(2))));
// Don't send pin request yet, let user send REQUEST via PINCODE channel // Don't send pin request yet, let user send REQUEST via PINCODE channel
} else { } else {
logger.trace("{} - Not PIN Mode {}:{} {}", handler.getThingID(), config.ipAddress, config.port, logger.trace("{} - Not PIN Mode {}:{} {}", handler.getThingID(), config.ipAddress,
config.mode); config.googletvPort, config.mode);
} }
} else { } else {
scheduleConnectRetry(config.reconnect); // Possibly a temporary problem. Try again later. scheduleConnectRetry(config.reconnect); // Possibly a temporary problem. Try again later.
@ -628,9 +628,9 @@ public class GoogleTVConnectionManager {
sslContext.init(kmf.getKeyManagers(), trustManagers, null); sslContext.init(kmf.getKeyManagers(), trustManagers, null);
this.sslServerSocketFactory = sslContext.getServerSocketFactory(); this.sslServerSocketFactory = sslContext.getServerSocketFactory();
logger.trace("Opening GoogleTV shim on port {}", config.port); logger.trace("Opening GoogleTV shim on port {}", config.googletvPort);
SSLServerSocket sslServerSocket = (SSLServerSocket) this.sslServerSocketFactory SSLServerSocket sslServerSocket = (SSLServerSocket) this.sslServerSocketFactory
.createServerSocket(config.port); .createServerSocket(config.googletvPort);
if (this.config.mode.equals(DEFAULT_MODE)) { if (this.config.mode.equals(DEFAULT_MODE)) {
sslServerSocket.setNeedClientAuth(true); sslServerSocket.setNeedClientAuth(true);
} else { } else {
@ -638,18 +638,18 @@ public class GoogleTVConnectionManager {
} }
while (true) { while (true) {
logger.trace("Waiting for shim connection... {}", config.port); logger.trace("Waiting for shim connection... {}", config.googletvPort);
if (this.config.mode.equals(DEFAULT_MODE) && (childConnectionManager == null)) { if (this.config.mode.equals(DEFAULT_MODE) && (childConnectionManager == null)) {
logger.trace("Starting childConnectionManager {}", config.port); logger.trace("Starting childConnectionManager {}", config.googletvPort);
startChildConnectionManager(this.config.port + 1, PIN_MODE); startChildConnectionManager(this.config.googletvPort + 1, PIN_MODE);
} }
SSLSocket serverSocket = (SSLSocket) sslServerSocket.accept(); SSLSocket serverSocket = (SSLSocket) sslServerSocket.accept();
logger.trace("shimInitialize accepted {}", config.port); logger.trace("shimInitialize accepted {}", config.googletvPort);
try { try {
serverSocket.startHandshake(); serverSocket.startHandshake();
logger.trace("shimInitialize startHandshake {}", config.port); logger.trace("shimInitialize startHandshake {}", config.googletvPort);
connect(); connect();
logger.trace("shimInitialize connected {}", config.port); logger.trace("shimInitialize connected {}", config.googletvPort);
SSLSession session = serverSocket.getSession(); SSLSession session = serverSocket.getSession();
Certificate[] cchain2 = session.getPeerCertificates(); Certificate[] cchain2 = session.getPeerCertificates();
@ -708,12 +708,12 @@ public class GoogleTVConnectionManager {
senderThread.start(); senderThread.start();
this.shimSenderThread = senderThread; this.shimSenderThread = senderThread;
} catch (Exception e) { } catch (Exception e) {
logger.trace("Shim initalization exception {}", config.port); logger.trace("Shim initalization exception {}", config.googletvPort);
logger.trace("Shim initalization exception", e); logger.trace("Shim initalization exception", e);
} }
} }
} catch (Exception e) { } catch (Exception e) {
logger.trace("Shim initalization exception {}", config.port); logger.trace("Shim initalization exception {}", config.googletvPort);
logger.trace("Shim initalization exception", e); logger.trace("Shim initalization exception", e);
return; return;
@ -838,7 +838,7 @@ public class GoogleTVConnectionManager {
* Method executed by the message sender thread (senderThread) * Method executed by the message sender thread (senderThread)
*/ */
private void senderThreadJob() { private void senderThreadJob() {
logger.debug("{} - Command sender thread started {}", handler.getThingID(), config.port); logger.debug("{} - Command sender thread started {}", handler.getThingID(), config.googletvPort);
try { try {
while (!Thread.currentThread().isInterrupted() && writer != null) { while (!Thread.currentThread().isInterrupted() && writer != null) {
GoogleTVCommand command = sendQueue.take(); GoogleTVCommand command = sendQueue.take();
@ -871,7 +871,7 @@ public class GoogleTVConnectionManager {
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} finally { } finally {
logger.debug("{} - Command sender thread exiting {}", handler.getThingID(), config.port); logger.debug("{} - Command sender thread exiting {}", handler.getThingID(), config.googletvPort);
} }
} }
@ -908,7 +908,7 @@ public class GoogleTVConnectionManager {
* Method executed by the message reader thread (readerThread) * Method executed by the message reader thread (readerThread)
*/ */
private void readerThreadJob() { private void readerThreadJob() {
logger.debug("{} - Message reader thread started {}", handler.getThingID(), config.port); logger.debug("{} - Message reader thread started {}", handler.getThingID(), config.googletvPort);
try { try {
BufferedReader reader = this.reader; BufferedReader reader = this.reader;
int length = 0; int length = 0;
@ -953,7 +953,7 @@ public class GoogleTVConnectionManager {
setStatus(false, "offline.pin-process-incomplete"); setStatus(false, "offline.pin-process-incomplete");
logger.debug("{} - GoogleTV PIN Process Incomplete", handler.getThingID()); logger.debug("{} - GoogleTV PIN Process Incomplete", handler.getThingID());
reconnectTaskCancel(true); reconnectTaskCancel(true);
startChildConnectionManager(this.config.port + 1, PIN_MODE); startChildConnectionManager(this.config.googletvPort + 1, PIN_MODE);
} else if ((message != null) && (message.contains("certificate_unknown")) && (config.shim)) { } else if ((message != null) && (message.contains("certificate_unknown")) && (config.shim)) {
logger.debug("Shim cert_unknown I/O error while reading from stream: {}", e.getMessage()); logger.debug("Shim cert_unknown I/O error while reading from stream: {}", e.getMessage());
Socket shimServerSocket = this.shimServerSocket; Socket shimServerSocket = this.shimServerSocket;
@ -973,7 +973,7 @@ public class GoogleTVConnectionManager {
logger.warn("Runtime exception in reader thread", e); logger.warn("Runtime exception in reader thread", e);
setStatus(false, "offline.runtime-exception"); setStatus(false, "offline.runtime-exception");
} finally { } finally {
logger.debug("{} - Message reader thread exiting {}", handler.getThingID(), config.port); logger.debug("{} - Message reader thread exiting {}", handler.getThingID(), config.googletvPort);
} }
} }
@ -1007,7 +1007,7 @@ public class GoogleTVConnectionManager {
} }
private void shimReaderThreadJob() { private void shimReaderThreadJob() {
logger.debug("Shim reader thread started {}", config.port); logger.debug("Shim reader thread started {}", config.googletvPort);
try { try {
BufferedReader reader = this.shimReader; BufferedReader reader = this.shimReader;
String thisShimMsg = ""; String thisShimMsg = "";
@ -1047,7 +1047,7 @@ public class GoogleTVConnectionManager {
logger.warn("Runtime exception in reader thread", e); logger.warn("Runtime exception in reader thread", e);
setStatus(false, "offline.runtime-exception"); setStatus(false, "offline.runtime-exception");
} finally { } finally {
logger.debug("Shim message reader thread exiting {}", config.port); logger.debug("Shim message reader thread exiting {}", config.googletvPort);
} }
} }
@ -1215,7 +1215,8 @@ public class GoogleTVConnectionManager {
message = "5204085b" + suffix; message = "5204085b" + suffix;
break; break;
default: default:
logger.debug("Unknown Key {}", command); logger.debug("Unknown Key {} - sending to vendor protocol", command);
handler.sendCommandToProtocol(channelUID, command);
return; return;
} }
sendCommand(new GoogleTVCommand(GoogleTVRequest.encodeMessage(message))); sendCommand(new GoogleTVCommand(GoogleTVRequest.encodeMessage(message)));
@ -1267,7 +1268,7 @@ public class GoogleTVConnectionManager {
setStatus(false, "offline.user-forced-pin-process"); setStatus(false, "offline.user-forced-pin-process");
logger.debug("{} - User Forced PIN Process", handler.getThingID()); logger.debug("{} - User Forced PIN Process", handler.getThingID());
disconnect(true); disconnect(true);
startChildConnectionManager(config.port + 1, PIN_MODE); startChildConnectionManager(config.googletvPort + 1, PIN_MODE);
try { try {
Thread.sleep(PIN_DELAY); Thread.sleep(PIN_DELAY);
} catch (InterruptedException e) { } catch (InterruptedException e) {

View File

@ -41,4 +41,5 @@ public class GoogleTVConstants {
public static final String MESSAGE_POWERON = "c202020801"; public static final String MESSAGE_POWERON = "c202020801";
public static final String MESSAGE_PINSUCCESS = "080210c801ca02"; public static final String MESSAGE_PINSUCCESS = "080210c801ca02";
public static final String HARD_DROP = "ffffffff"; public static final String HARD_DROP = "ffffffff";
public static final String VERSION_01 = "7b2270726f746f636f6c5f76657273696f6e223a312c22737461747573223a3430307d";
} }

View File

@ -57,6 +57,8 @@ public class GoogleTVMessageParser {
if (msg.startsWith(DELIMITER_1A)) { if (msg.startsWith(DELIMITER_1A)) {
logger.warn("{} - GoogleTV Error Message: {}", thingId, msg); logger.warn("{} - GoogleTV Error Message: {}", thingId, msg);
callback.getHandler().dispose(); callback.getHandler().dispose();
} else if (msg.equals(VERSION_01)) {
logger.warn("{} - GoogleTV version on device needs to be updated", thingId);
} else if (msg.startsWith(DELIMITER_0A)) { } else if (msg.startsWith(DELIMITER_0A)) {
// First message on connection from GTV // First message on connection from GTV
// //

View File

@ -23,7 +23,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
public class ShieldTVConfiguration { public class ShieldTVConfiguration {
public String ipAddress = ""; public String ipAddress = "";
public int port = 8987; public int shieldtvPort = 8987;
public int reconnect; public int reconnect;
public int heartbeat; public int heartbeat;
public String keystoreFileName = ""; public String keystoreFileName = "";

View File

@ -76,8 +76,6 @@ import org.slf4j.LoggerFactory;
*/ */
@NonNullByDefault @NonNullByDefault
public class ShieldTVConnectionManager { public class ShieldTVConnectionManager {
private static final int DEFAULT_RECONNECT_SECONDS = 60;
private static final int DEFAULT_HEARTBEAT_SECONDS = 5;
private static final long KEEPALIVE_TIMEOUT_SECONDS = 30; private static final long KEEPALIVE_TIMEOUT_SECONDS = 30;
private static final String DEFAULT_KEYSTORE_PASSWORD = "secret"; private static final String DEFAULT_KEYSTORE_PASSWORD = "secret";
private static final int DEFAULT_PORT = 8987; private static final int DEFAULT_PORT = 8987;
@ -261,7 +259,7 @@ public class ShieldTVConnectionManager {
private boolean servicePing() { private boolean servicePing() {
int timeout = 500; int timeout = 500;
SocketAddress socketAddress = new InetSocketAddress(config.ipAddress, config.port); SocketAddress socketAddress = new InetSocketAddress(config.ipAddress, config.shieldtvPort);
try (Socket socket = new Socket()) { try (Socket socket = new Socket()) {
socket.connect(socketAddress, timeout); socket.connect(socketAddress, timeout);
return true; return true;
@ -357,7 +355,7 @@ public class ShieldTVConnectionManager {
folder.mkdirs(); folder.mkdirs();
} }
config.port = (config.port > 0) ? config.port : DEFAULT_PORT; config.shieldtvPort = (config.shieldtvPort > 0) ? config.shieldtvPort : DEFAULT_PORT;
config.keystoreFileName = (!config.keystoreFileName.equals("")) ? config.keystoreFileName config.keystoreFileName = (!config.keystoreFileName.equals("")) ? config.keystoreFileName
: folderName + "/shieldtv." + ((config.shim) ? "shim." : "") + handler.getThing().getUID().getId() : folderName + "/shieldtv." + ((config.shim) ? "shim." : "") + handler.getThing().getUID().getId()
@ -414,8 +412,9 @@ public class ShieldTVConnectionManager {
if (isOnline) { if (isOnline) {
try { try {
logger.debug("{} - Opening ShieldTV SSL connection to {}:{}", handler.getThingID(), logger.debug("{} - Opening ShieldTV SSL connection to {}:{}", handler.getThingID(),
config.ipAddress, config.port); config.ipAddress, config.shieldtvPort);
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(config.ipAddress, config.port); SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(config.ipAddress,
config.shieldtvPort);
sslSocket.startHandshake(); sslSocket.startHandshake();
writer = new BufferedWriter( writer = new BufferedWriter(
new OutputStreamWriter(sslSocket.getOutputStream(), StandardCharsets.ISO_8859_1)); new OutputStreamWriter(sslSocket.getOutputStream(), StandardCharsets.ISO_8859_1));
@ -436,7 +435,7 @@ public class ShieldTVConnectionManager {
} catch (IOException e) { } catch (IOException e) {
setStatus(false, "offline.error-opening-ssl-connection-check-log"); setStatus(false, "offline.error-opening-ssl-connection-check-log");
logger.info("{} - Error opening SSL connection to {}:{} {}", handler.getThingID(), config.ipAddress, logger.info("{} - Error opening SSL connection to {}:{} {}", handler.getThingID(), config.ipAddress,
config.port, e.getMessage()); config.shieldtvPort, e.getMessage());
disconnect(false); disconnect(false);
scheduleConnectRetry(config.reconnect); // Possibly a temporary problem. Try again later. scheduleConnectRetry(config.reconnect); // Possibly a temporary problem. Try again later.
return; return;
@ -486,8 +485,8 @@ public class ShieldTVConnectionManager {
sslContext.init(kmf.getKeyManagers(), trustManagers, null); sslContext.init(kmf.getKeyManagers(), trustManagers, null);
this.sslServerSocketFactory = sslContext.getServerSocketFactory(); this.sslServerSocketFactory = sslContext.getServerSocketFactory();
logger.debug("{} - Opening ShieldTV shim on port {}", handler.getThingID(), config.port); logger.debug("{} - Opening ShieldTV shim on port {}", handler.getThingID(), config.shieldtvPort);
ServerSocket sslServerSocket = this.sslServerSocketFactory.createServerSocket(config.port); ServerSocket sslServerSocket = this.sslServerSocketFactory.createServerSocket(config.shieldtvPort);
while (true) { while (true) {
logger.debug("{} - Waiting for shim connection...", handler.getThingID()); logger.debug("{} - Waiting for shim connection...", handler.getThingID());

View File

@ -48,6 +48,7 @@ public class ShieldTVConstants {
public static final String MESSAGE_LOWPRIV = "080a12"; public static final String MESSAGE_LOWPRIV = "080a12";
public static final String MESSAGE_HOSTNAME = "080b12"; public static final String MESSAGE_HOSTNAME = "080b12";
public static final String MESSAGE_APPDB = "08f10712"; public static final String MESSAGE_APPDB = "08f10712";
public static final String MESSAGE_APPDB_FULL = "080112";
public static final String MESSAGE_GOOD_COMMAND = "08f30712"; public static final String MESSAGE_GOOD_COMMAND = "08f30712";
public static final String MESSAGE_PINSTART = "0308cf08"; public static final String MESSAGE_PINSTART = "0308cf08";
public static final String MESSAGE_CERT_COMING = "20"; public static final String MESSAGE_CERT_COMING = "20";

View File

@ -204,13 +204,10 @@ public class ShieldTVMessageParser {
} else if (APP_START_FAILED.equals(msg)) { } else if (APP_START_FAILED.equals(msg)) {
// App failed to start // App failed to start
logger.debug("{} - App failed to start", thingId); logger.debug("{} - App failed to start", thingId);
} else if (msg.startsWith(MESSAGE_APPDB) && msg.startsWith(DELIMITER_0A, 18)) { } else if (msg.startsWith(MESSAGE_APPDB) && msg.startsWith(MESSAGE_APPDB_FULL, 12)) {
// Individual update?
// 08f10712 5808061254 0a LEN app.name 12 LEN app.real.name 22 LEN URL 2801 300118f107
logger.info("{} - Individual App Update - Please Report This: {}", thingId, msg);
} else if (msg.startsWith(MESSAGE_APPDB) && (msg.length() > 30)) {
// Massive dump of currently installed apps // Massive dump of currently installed apps
// 08f10712 d81f080112 d31f0a540a LEN app.name 12 LEN app.real.name 22 LEN URL 2801 30010a650a LEN // 08f10712 d81f 080112 d31f0a540a LEN app.name 12 LEN app.real.name 22 LEN URL 2801 30010a650a LEN
// --------------08XX12 where XX is not 01 are individual updates and should be ignored
Map<String, String> appNameDB = new HashMap<>(); Map<String, String> appNameDB = new HashMap<>();
Map<String, String> appURLDB = new HashMap<>(); Map<String, String> appURLDB = new HashMap<>();
int appCount = 0; int appCount = 0;
@ -353,6 +350,8 @@ public class ShieldTVMessageParser {
logger.warn("{} - MP empty msg: {} appDB appNameDB: {} appURLDB: {}", thingId, msg, logger.warn("{} - MP empty msg: {} appDB appNameDB: {} appURLDB: {}", thingId, msg,
appNameDB.toString(), appURLDB.toString()); appNameDB.toString(), appURLDB.toString());
} }
} else if (msg.startsWith(MESSAGE_APPDB)) {
logger.debug("{} - Individual app update ignored {}", thingId, msg);
} else if (msg.startsWith(MESSAGE_GOOD_COMMAND)) { } else if (msg.startsWith(MESSAGE_GOOD_COMMAND)) {
// This has something to do with successful command response, maybe. // This has something to do with successful command response, maybe.
logger.trace("{} - Good Command Response", thingId); logger.trace("{} - Good Command Response", thingId);

View File

@ -24,8 +24,8 @@ thing-type.config.androidtv.googletv.keystoreFileName.label = Keystore File Name
thing-type.config.androidtv.googletv.keystoreFileName.description = Java keystore containing key and certs thing-type.config.androidtv.googletv.keystoreFileName.description = Java keystore containing key and certs
thing-type.config.androidtv.googletv.keystorePassword.label = Keystore Password thing-type.config.androidtv.googletv.keystorePassword.label = Keystore Password
thing-type.config.androidtv.googletv.keystorePassword.description = Password for the keystore file thing-type.config.androidtv.googletv.keystorePassword.description = Password for the keystore file
thing-type.config.androidtv.googletv.port.label = Port thing-type.config.androidtv.googletv.googletvPort.label = GoogleTV Port
thing-type.config.androidtv.googletv.port.description = Port to connect to thing-type.config.androidtv.googletv.googletvPort.description = Port to connect to
thing-type.config.androidtv.googletv.reconnect.label = Reconnect Delay thing-type.config.androidtv.googletv.reconnect.label = Reconnect Delay
thing-type.config.androidtv.googletv.reconnect.description = Delay between reconnection attempts thing-type.config.androidtv.googletv.reconnect.description = Delay between reconnection attempts
thing-type.config.androidtv.shieldtv.delay.label = Delay thing-type.config.androidtv.shieldtv.delay.label = Delay
@ -40,8 +40,10 @@ thing-type.config.androidtv.shieldtv.keystoreFileName.label = Keystore File Name
thing-type.config.androidtv.shieldtv.keystoreFileName.description = Java keystore containing key and certs thing-type.config.androidtv.shieldtv.keystoreFileName.description = Java keystore containing key and certs
thing-type.config.androidtv.shieldtv.keystorePassword.label = Keystore Password thing-type.config.androidtv.shieldtv.keystorePassword.label = Keystore Password
thing-type.config.androidtv.shieldtv.keystorePassword.description = Password for the keystore file thing-type.config.androidtv.shieldtv.keystorePassword.description = Password for the keystore file
thing-type.config.androidtv.shieldtv.port.label = Port thing-type.config.androidtv.shieldtv.googletvPort.label = GoogleTV Port
thing-type.config.androidtv.shieldtv.port.description = Port to connect to thing-type.config.androidtv.shieldtv.googletvPort.description = Port to connect to
thing-type.config.androidtv.shieldtv.shieldtvPort.label = ShieldTV Port
thing-type.config.androidtv.shieldtv.shieldtvPort.description = Port to connect to
thing-type.config.androidtv.shieldtv.reconnect.label = Reconnect Delay thing-type.config.androidtv.shieldtv.reconnect.label = Reconnect Delay
thing-type.config.androidtv.shieldtv.reconnect.description = Delay between reconnection attempts thing-type.config.androidtv.shieldtv.reconnect.description = Delay between reconnection attempts

View File

@ -43,38 +43,52 @@
<label>Hostname</label> <label>Hostname</label>
<description>Hostname or IP address of the device</description> <description>Hostname or IP address of the device</description>
</parameter> </parameter>
<parameter name="port" type="integer"> <parameter name="googletvPort" type="integer">
<label>Port</label> <label>GoogleTV Port</label>
<description>Port to connect to</description> <description>Port to connect to</description>
<default>6466</default>
<advanced>true</advanced>
</parameter>
<parameter name="shieldtvPort" type="integer">
<label>ShieldTV Port</label>
<description>Port to connect to</description>
<default>8987</default>
<advanced>true</advanced>
</parameter> </parameter>
<parameter name="keystoreFileName" type="text"> <parameter name="keystoreFileName" type="text">
<label>Keystore File Name</label> <label>Keystore File Name</label>
<description>Java keystore containing key and certs</description> <description>Java keystore containing key and certs</description>
<advanced>true</advanced>
</parameter> </parameter>
<parameter name="keystorePassword" type="text"> <parameter name="keystorePassword" type="text">
<context>password</context> <context>password</context>
<label>Keystore Password</label> <label>Keystore Password</label>
<description>Password for the keystore file</description> <description>Password for the keystore file</description>
<advanced>true</advanced>
</parameter> </parameter>
<parameter name="reconnect" type="integer" min="0"> <parameter name="reconnect" type="integer" min="0">
<label>Reconnect Delay</label> <label>Reconnect Delay</label>
<description>Delay between reconnection attempts</description> <description>Delay between reconnection attempts</description>
<default>60</default> <default>60</default>
<advanced>true</advanced>
</parameter> </parameter>
<parameter name="heartbeat" type="integer" min="0"> <parameter name="heartbeat" type="integer" min="0">
<label>Heartbeat Frequency</label> <label>Heartbeat Frequency</label>
<description>Frequency of heartbeats</description> <description>Frequency of heartbeats</description>
<default>5</default> <default>5</default>
<advanced>true</advanced>
</parameter> </parameter>
<parameter name="delay" type="integer" min="0"> <parameter name="delay" type="integer" min="0">
<label>Delay</label> <label>Delay</label>
<description>Delay between messages</description> <description>Delay between messages</description>
<default>0</default> <default>0</default>
<advanced>true</advanced>
</parameter> </parameter>
<parameter name="gtvEnabled" type="boolean"> <parameter name="gtvEnabled" type="boolean">
<label>Enable GoogleTV</label> <label>Enable GoogleTV</label>
<description>Enable the GoogleTV Protocol</description> <description>Enable the GoogleTV Protocol</description>
<default>true</default> <default>true</default>
<advanced>true</advanced>
</parameter> </parameter>
</config-description> </config-description>
@ -114,38 +128,46 @@
<label>Hostname</label> <label>Hostname</label>
<description>Hostname or IP address of the device</description> <description>Hostname or IP address of the device</description>
</parameter> </parameter>
<parameter name="port" type="integer"> <parameter name="googletvPort" type="integer">
<label>Port</label> <label>GoogleTV Port</label>
<description>Port to connect to</description> <description>Port to connect to</description>
<default>6466</default>
<advanced>true</advanced>
</parameter> </parameter>
<parameter name="keystoreFileName" type="text"> <parameter name="keystoreFileName" type="text">
<label>Keystore File Name</label> <label>Keystore File Name</label>
<description>Java keystore containing key and certs</description> <description>Java keystore containing key and certs</description>
<advanced>true</advanced>
</parameter> </parameter>
<parameter name="keystorePassword" type="text"> <parameter name="keystorePassword" type="text">
<context>password</context> <context>password</context>
<label>Keystore Password</label> <label>Keystore Password</label>
<description>Password for the keystore file</description> <description>Password for the keystore file</description>
<advanced>true</advanced>
</parameter> </parameter>
<parameter name="reconnect" type="integer" min="0"> <parameter name="reconnect" type="integer" min="0">
<label>Reconnect Delay</label> <label>Reconnect Delay</label>
<description>Delay between reconnection attempts</description> <description>Delay between reconnection attempts</description>
<default>60</default> <default>60</default>
<advanced>true</advanced>
</parameter> </parameter>
<parameter name="heartbeat" type="integer" min="0"> <parameter name="heartbeat" type="integer" min="0">
<label>Heartbeat Frequency</label> <label>Heartbeat Frequency</label>
<description>Frequency of heartbeats</description> <description>Frequency of heartbeats</description>
<default>5</default> <default>5</default>
<advanced>true</advanced>
</parameter> </parameter>
<parameter name="delay" type="integer" min="0"> <parameter name="delay" type="integer" min="0">
<label>Delay</label> <label>Delay</label>
<description>Delay between messages</description> <description>Delay between messages</description>
<default>0</default> <default>0</default>
<advanced>true</advanced>
</parameter> </parameter>
<parameter name="gtvEnabled" type="boolean"> <parameter name="gtvEnabled" type="boolean">
<label>Enable GoogleTV</label> <label>Enable GoogleTV</label>
<description>Enable the GoogleTV Protocol</description> <description>Enable the GoogleTV Protocol</description>
<default>true</default> <default>true</default>
<advanced>true</advanced>
</parameter> </parameter>
</config-description> </config-description>