├── .github └── workflows │ └── maven-publish.yml ├── README.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── kunal52 │ │ ├── AndroidRemoteContext.java │ │ ├── AndroidRemoteTv.java │ │ ├── AndroidTvListener.java │ │ ├── BaseAndroidRemoteTv.java │ │ ├── Main.java │ │ ├── exception │ │ └── PairingException.java │ │ ├── pairing │ │ ├── BufferedReaderSecretProvider.java │ │ ├── PairingChallengeResponse.java │ │ ├── PairingListener.java │ │ ├── PairingMessageManager.java │ │ ├── PairingPacketParser.java │ │ ├── PairingSession.java │ │ ├── Pairingmessage.java │ │ └── SecretProvider.java │ │ ├── remote │ │ ├── RemoteListener.java │ │ ├── RemoteMessageManager.java │ │ ├── RemotePacketParser.java │ │ ├── RemoteSession.java │ │ └── Remotemessage.java │ │ ├── ssl │ │ ├── DummySSLServerSocketFactory.java │ │ ├── DummySSLSocketFactory.java │ │ ├── DummyTrustManager.java │ │ ├── KeyStoreManager.java │ │ ├── SSLServerSocketFactoryWrapper.java │ │ ├── SSLSocketFactoryWrapper.java │ │ └── SslUtil.java │ │ ├── util │ │ └── Utils.java │ │ └── wire │ │ ├── MessageManager.java │ │ └── PacketParser.java └── proto │ ├── pairingmessage.proto │ └── remotemessage.proto └── test └── java └── com └── kunal52 └── AndroidRemoteTvTest.java /.github/workflows/maven-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a package using Maven and then publish it to GitHub packages when a release is created 2 | # For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#apache-maven-with-a-settings-path 3 | 4 | name: Maven Package 5 | 6 | on: 7 | # push: 8 | # branches: 9 | # - main 10 | release: 11 | types: [created] 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: read 19 | packages: write 20 | 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Set up JDK 11 24 | uses: actions/setup-java@v3 25 | with: 26 | java-version: '11' 27 | distribution: 'temurin' 28 | server-id: github # Value of the distributionManagement/repository/id field of the pom.xml 29 | settings-path: ${{ github.workspace }} # location for the settings.xml file 30 | 31 | - name: Build with Maven 32 | run: mvn -B package --file pom.xml 33 | 34 | - name: Publish to GitHub Packages Apache Maven 35 | run: mvn deploy -s $GITHUB_WORKSPACE/settings.xml 36 | env: 37 | GITHUB_TOKEN: ${{ github.token }} 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # AndroidTvRemote 3 | 4 | _Note: This is a prereleased version._ 5 | 6 | AndroidTvRemote is an open-source java library to control android tv. 7 | 8 | ## Usage 9 | 10 | ```java 11 | AndroidRemoteTv androidRemoteTv = new AndroidRemoteTv(); 12 | androidRemoteTv.connect("192.168.1.8", new AndroidRemoteTv.AndroidTvListener() { 13 | @Override 14 | public void onSessionCreated() { 15 | 16 | } 17 | 18 | @Override 19 | public void onSecretRequested() { 20 | BufferedReader reader = new BufferedReader( 21 | new InputStreamReader(System.in)); 22 | 23 | try { 24 | String name = reader.readLine(); 25 | androidRemoteTv.sendSecret(name); 26 | } catch (IOException e) { 27 | throw new RuntimeException(e); 28 | } 29 | } 30 | 31 | @Override 32 | public void onPaired() { 33 | 34 | } 35 | 36 | @Override 37 | public void onConnectingToRemote() { 38 | 39 | } 40 | 41 | @Override 42 | public void onConnected() { 43 | System.out.println("Connected"); 44 | 45 | androidRemoteTv.sendCommand(Remotemessage.RemoteKeyCode.KEYCODE_POWER, Remotemessage.RemoteDirection.SHORT); 46 | 47 | } 48 | 49 | @Override 50 | public void onDisconnect() { 51 | 52 | } 53 | 54 | @Override 55 | public void onError(String error) { 56 | 57 | } 58 | }); 59 | ``` 60 | 61 | Refer this Android Page for more commands - https://developer.android.com/reference/android/view/KeyEvent -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | com.github.kunal52 8 | androidsmarttvremote 9 | v1.0.1 10 | 11 | androidsmarttvremote 12 | 13 | http://www.example.com 14 | 15 | 16 | UTF-8 17 | 1.7 18 | 1.7 19 | 1.7.0-RC 20 | 21 | 22 | 23 | 24 | 25 | org.bouncycastle 26 | bcprov-jdk15on 27 | 1.70 28 | 29 | 30 | com.google.protobuf 31 | protobuf-java 32 | 3.11.4 33 | 34 | 35 | org.apache.logging.log4j 36 | log4j-api 37 | 2.7 38 | 39 | 40 | org.apache.logging.log4j 41 | log4j-core 42 | 2.7 43 | 44 | 45 | org.apache.logging.log4j 46 | log4j-slf4j-impl 47 | 2.7 48 | 49 | 50 | junit 51 | junit 52 | 4.11 53 | test 54 | 55 | 56 | org.jetbrains.kotlin 57 | kotlin-stdlib-jdk8 58 | ${kotlin.version} 59 | 60 | 61 | org.jetbrains.kotlin 62 | kotlin-test 63 | ${kotlin.version} 64 | test 65 | 66 | 67 | org.apache.maven.plugins 68 | maven-assembly-plugin 69 | 2.3 70 | maven-plugin 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | maven-clean-plugin 80 | 3.1.0 81 | 82 | 83 | 84 | maven-resources-plugin 85 | 3.0.2 86 | 87 | 88 | maven-compiler-plugin 89 | 3.8.0 90 | 91 | 92 | maven-surefire-plugin 93 | 2.22.1 94 | 95 | 96 | maven-install-plugin 97 | 2.5.2 98 | 99 | 100 | maven-deploy-plugin 101 | 2.8.2 102 | 103 | 104 | 105 | maven-site-plugin 106 | 3.7.1 107 | 108 | 109 | maven-project-info-reports-plugin 110 | 3.0.0 111 | 112 | 113 | org.apache.maven.plugins 114 | maven-compiler-plugin 115 | 3.8.0 116 | 117 | 1.8 118 | 1.8 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | org.jetbrains.kotlin 146 | kotlin-maven-plugin 147 | ${kotlin.version} 148 | 149 | 150 | compile 151 | compile 152 | 153 | compile 154 | 155 | 156 | 157 | test-compile 158 | test-compile 159 | 160 | test-compile 161 | 162 | 163 | 164 | 165 | 1.8 166 | 167 | 168 | 169 | org.apache.maven.plugins 170 | maven-compiler-plugin 171 | 172 | 173 | compile 174 | compile 175 | 176 | compile 177 | 178 | 179 | 180 | testCompile 181 | test-compile 182 | 183 | testCompile 184 | 185 | 186 | 187 | 188 | 189 | org.apache.maven.plugins 190 | maven-assembly-plugin 191 | 192 | 193 | package 194 | 195 | single 196 | 197 | 198 | 199 | 200 | 201 | jar-with-dependencies 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | github 211 | GitHub Packages 212 | https://maven.pkg.github.com/kunal52/AndroidTvRemote 213 | 214 | 215 | 216 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/AndroidRemoteContext.java: -------------------------------------------------------------------------------- 1 | package com.kunal52; 2 | 3 | import java.io.File; 4 | import java.nio.file.Paths; 5 | 6 | public class AndroidRemoteContext { 7 | 8 | private String serviceName = "com.github.kunal52/androidTvRemote"; 9 | 10 | private String clientName = "androidTvRemoteJava"; 11 | 12 | private File keyStoreFile = Paths.get("androidtv.keystore").toFile(); 13 | 14 | private char[] keyStorePass = "KeyStore_Password".toCharArray(); 15 | ; 16 | 17 | private static volatile AndroidRemoteContext instance; 18 | 19 | 20 | private AndroidRemoteContext() { 21 | 22 | } 23 | 24 | public static AndroidRemoteContext getInstance() { 25 | AndroidRemoteContext result = instance; 26 | if (result != null) { 27 | return result; 28 | } 29 | synchronized (AndroidRemoteContext.class) { 30 | if (instance == null) { 31 | instance = new AndroidRemoteContext(); 32 | } 33 | return instance; 34 | } 35 | } 36 | 37 | public String getServiceName() { 38 | return serviceName; 39 | } 40 | 41 | public void setServiceName(String serviceName) { 42 | this.serviceName = serviceName; 43 | } 44 | 45 | public String getClientName() { 46 | return clientName; 47 | } 48 | 49 | public void setClientName(String clientName) { 50 | this.clientName = clientName; 51 | } 52 | 53 | public File getKeyStoreFile() { 54 | return keyStoreFile; 55 | } 56 | 57 | public void setKeyStoreFile(File keyStoreFile) { 58 | this.keyStoreFile = keyStoreFile; 59 | } 60 | 61 | public char[] getKeyStorePass() { 62 | return keyStorePass; 63 | } 64 | 65 | public void setKeyStorePass(char[] keyStorePass) { 66 | this.keyStorePass = keyStorePass; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/AndroidRemoteTv.java: -------------------------------------------------------------------------------- 1 | package com.kunal52; 2 | 3 | import com.kunal52.exception.PairingException; 4 | import com.kunal52.pairing.PairingListener; 5 | import com.kunal52.pairing.PairingSession; 6 | import com.kunal52.remote.RemoteSession; 7 | import com.kunal52.remote.Remotemessage; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.io.IOException; 12 | import java.security.GeneralSecurityException; 13 | 14 | 15 | public class AndroidRemoteTv extends BaseAndroidRemoteTv { 16 | 17 | private final Logger logger = LoggerFactory.getLogger(AndroidRemoteTv.class); 18 | 19 | private PairingSession mPairingSession; 20 | 21 | private RemoteSession mRemoteSession; 22 | 23 | public void connect(String host, AndroidTvListener androidTvListener) throws GeneralSecurityException, IOException, InterruptedException, PairingException { 24 | mRemoteSession = new RemoteSession(host, 6466, new RemoteSession.RemoteSessionListener() { 25 | @Override 26 | public void onConnected() { 27 | androidTvListener.onConnected(); 28 | } 29 | 30 | @Override 31 | public void onSslError() { 32 | 33 | } 34 | 35 | @Override 36 | public void onDisconnected() { 37 | 38 | } 39 | 40 | @Override 41 | public void onError(String message) { 42 | 43 | } 44 | }); 45 | 46 | if (AndroidRemoteContext.getInstance().getKeyStoreFile().exists()) 47 | mRemoteSession.connect(); 48 | else { 49 | mPairingSession = new PairingSession(); 50 | mPairingSession.pair("192.168.1.8", 6467, new PairingListener() { 51 | @Override 52 | public void onSessionCreated() { 53 | 54 | } 55 | 56 | @Override 57 | public void onPerformInputDeviceRole() throws PairingException { 58 | 59 | } 60 | 61 | @Override 62 | public void onPerformOutputDeviceRole(byte[] gamma) throws PairingException { 63 | 64 | } 65 | 66 | @Override 67 | public void onSecretRequested() { 68 | androidTvListener.onSecretRequested(); 69 | } 70 | 71 | @Override 72 | public void onSessionEnded() { 73 | 74 | } 75 | 76 | @Override 77 | public void onError(String message) { 78 | 79 | } 80 | 81 | @Override 82 | public void onPaired() { 83 | try { 84 | mRemoteSession.connect(); 85 | } catch (GeneralSecurityException | IOException | InterruptedException | PairingException e) { 86 | throw new RuntimeException(e); 87 | } 88 | } 89 | 90 | @Override 91 | public void onLog(String message) { 92 | 93 | } 94 | }); 95 | } 96 | 97 | } 98 | 99 | public void sendCommand(Remotemessage.RemoteKeyCode remoteKeyCode, Remotemessage.RemoteDirection remoteDirection) { 100 | mRemoteSession.sendCommand(remoteKeyCode, remoteDirection); 101 | } 102 | 103 | public void sendSecret(String code) { 104 | mPairingSession.provideSecret(code); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/AndroidTvListener.java: -------------------------------------------------------------------------------- 1 | package com.kunal52; 2 | 3 | public interface AndroidTvListener { 4 | void onSessionCreated(); 5 | 6 | void onSecretRequested(); 7 | 8 | void onPaired(); 9 | 10 | void onConnectingToRemote(); 11 | 12 | void onConnected(); 13 | 14 | void onDisconnect(); 15 | 16 | void onError(String error); 17 | } -------------------------------------------------------------------------------- /src/main/java/com/kunal52/BaseAndroidRemoteTv.java: -------------------------------------------------------------------------------- 1 | package com.kunal52; 2 | 3 | import java.io.File; 4 | import java.nio.file.Paths; 5 | 6 | abstract class BaseAndroidRemoteTv { 7 | 8 | private final AndroidRemoteContext androidRemoteContext; 9 | 10 | BaseAndroidRemoteTv() { 11 | androidRemoteContext = AndroidRemoteContext.getInstance(); 12 | } 13 | 14 | 15 | public String getServiceName() { 16 | return androidRemoteContext.getServiceName(); 17 | } 18 | 19 | public void setServiceName(String serviceName) { 20 | androidRemoteContext.setServiceName(serviceName); 21 | } 22 | 23 | public String getClientName() { 24 | return androidRemoteContext.getClientName(); 25 | } 26 | 27 | public void setClientName(String clientName) { 28 | androidRemoteContext.setClientName(clientName); 29 | } 30 | 31 | public File getKeyStoreFile() { 32 | return androidRemoteContext.getKeyStoreFile(); 33 | } 34 | 35 | public void setKeyStoreFile(File keyStoreFile) { 36 | androidRemoteContext.setKeyStoreFile(keyStoreFile); 37 | } 38 | 39 | public char[] getKeyStorePass() { 40 | return androidRemoteContext.getKeyStorePass(); 41 | } 42 | 43 | public void setKeyStorePass(String keyStorePass) { 44 | androidRemoteContext.setKeyStorePass(keyStorePass.toCharArray()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/Main.java: -------------------------------------------------------------------------------- 1 | package com.kunal52; 2 | 3 | import com.kunal52.exception.PairingException; 4 | import com.kunal52.remote.Remotemessage; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.IOException; 8 | import java.io.InputStreamReader; 9 | import java.security.GeneralSecurityException; 10 | 11 | public class Main { 12 | public static void main(String[] args) throws GeneralSecurityException, IOException, InterruptedException, PairingException { 13 | AndroidRemoteTv androidRemoteTv = new AndroidRemoteTv(); 14 | androidRemoteTv.connect("192.168.1.8", new AndroidTvListener() { 15 | @Override 16 | public void onSessionCreated() { 17 | 18 | } 19 | 20 | @Override 21 | public void onSecretRequested() { 22 | BufferedReader reader = new BufferedReader( 23 | new InputStreamReader(System.in)); 24 | 25 | try { 26 | String name = reader.readLine(); 27 | androidRemoteTv.sendSecret(name); 28 | } catch (IOException e) { 29 | throw new RuntimeException(e); 30 | } 31 | } 32 | 33 | @Override 34 | public void onPaired() { 35 | 36 | } 37 | 38 | @Override 39 | public void onConnectingToRemote() { 40 | 41 | } 42 | 43 | @Override 44 | public void onConnected() { 45 | System.out.println("Connected"); 46 | 47 | androidRemoteTv.sendCommand(Remotemessage.RemoteKeyCode.KEYCODE_POWER, Remotemessage.RemoteDirection.SHORT); 48 | 49 | } 50 | 51 | @Override 52 | public void onDisconnect() { 53 | 54 | } 55 | 56 | @Override 57 | public void onError(String error) { 58 | 59 | } 60 | }); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/exception/PairingException.java: -------------------------------------------------------------------------------- 1 | package com.kunal52.exception; 2 | 3 | public class PairingException extends Exception { 4 | public PairingException(String message) { 5 | super(message); 6 | } 7 | 8 | public PairingException(String message, Exception e) { 9 | super(message, e); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/pairing/BufferedReaderSecretProvider.java: -------------------------------------------------------------------------------- 1 | package com.kunal52.pairing; 2 | 3 | public class BufferedReaderSecretProvider implements SecretProvider{ 4 | @Override 5 | public void requestSecret(PairingSession pairingSession) { 6 | 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/pairing/PairingChallengeResponse.java: -------------------------------------------------------------------------------- 1 | package com.kunal52.pairing; 2 | 3 | import com.kunal52.exception.PairingException; 4 | import com.kunal52.util.Utils; 5 | 6 | import java.security.MessageDigest; 7 | import java.security.NoSuchAlgorithmException; 8 | import java.security.PublicKey; 9 | import java.security.cert.Certificate; 10 | import java.security.interfaces.RSAPublicKey; 11 | import java.util.Arrays; 12 | 13 | /** 14 | * Class to represent the out-of-band secret transmitted during pairing. 15 | */ 16 | public class PairingChallengeResponse { 17 | 18 | /** 19 | * Hash algorithm to generate secret. 20 | */ 21 | private static final String HASH_ALGORITHM = "SHA-256"; 22 | 23 | 24 | /** 25 | * Certificate of the local peer in the protocol. 26 | */ 27 | private Certificate mClientCertificate; 28 | 29 | /** 30 | * Certificate of the remote peer in the protocol. 31 | */ 32 | private Certificate mServerCertificate; 33 | 34 | public PairingChallengeResponse(Certificate clientCert, Certificate serverCert) { 35 | mClientCertificate = clientCert; 36 | mServerCertificate = serverCert; 37 | } 38 | 39 | public byte[] getAlpha(byte[] nonce) throws PairingException { 40 | PublicKey clientPubKey = mClientCertificate.getPublicKey(); 41 | PublicKey serverPubKey = mServerCertificate.getPublicKey(); 42 | 43 | logDebug("getAlpha, nonce=" + Utils.bytesToHexString(nonce)); 44 | 45 | if (!(clientPubKey instanceof RSAPublicKey) || 46 | !(serverPubKey instanceof RSAPublicKey)) { 47 | throw new PairingException("Only supports RSA public keys"); 48 | } 49 | 50 | RSAPublicKey clientPubRsa = (RSAPublicKey) clientPubKey; 51 | RSAPublicKey serverPubRsa = (RSAPublicKey) serverPubKey; 52 | 53 | MessageDigest digest; 54 | try { 55 | digest = MessageDigest.getInstance(HASH_ALGORITHM); 56 | } catch (NoSuchAlgorithmException e) { 57 | throw new PairingException("Could not get digest algorithm", e); 58 | } 59 | 60 | byte[] digestBytes; 61 | byte[] clientModulus = clientPubRsa.getModulus().abs().toByteArray(); 62 | byte[] clientExponent = 63 | clientPubRsa.getPublicExponent().abs().toByteArray(); 64 | byte[] serverModulus = serverPubRsa.getModulus().abs().toByteArray(); 65 | byte[] serverExponent = 66 | serverPubRsa.getPublicExponent().abs().toByteArray(); 67 | 68 | // Per "Polo Implementation Overview", section 6.1, leading null bytes must 69 | // be removed prior to hashing the key material. 70 | clientModulus = removeLeadingNullBytes(clientModulus); 71 | clientExponent = removeLeadingNullBytes(clientExponent); 72 | serverModulus = removeLeadingNullBytes(serverModulus); 73 | serverExponent = removeLeadingNullBytes(serverExponent); 74 | 75 | logVerbose("Hash inputs, in order: "); 76 | logVerbose(" client modulus: " + Utils.bytesToHexString(clientModulus)); 77 | logVerbose(" client exponent: " + Utils.bytesToHexString(clientExponent)); 78 | logVerbose(" server modulus: " + Utils.bytesToHexString(serverModulus)); 79 | logVerbose(" server exponent: " + Utils.bytesToHexString(serverExponent)); 80 | logVerbose(" nonce: " + Utils.bytesToHexString(nonce)); 81 | 82 | // Per "Polo Implementation Overview", section 6.1, client key material is 83 | // hashed first, followed by the server key material, followed by the 84 | // nonce. 85 | digest.update(clientModulus); 86 | digest.update(clientExponent); 87 | digest.update(serverModulus); 88 | digest.update(serverExponent); 89 | digest.update(nonce); 90 | 91 | digestBytes = digest.digest(); 92 | logDebug("Generated hash: " + Utils.bytesToHexString(digestBytes)); 93 | return digestBytes; 94 | } 95 | 96 | public byte[] getGamma(byte[] nonce) throws PairingException { 97 | byte[] alphaBytes = getAlpha(nonce); 98 | assert(alphaBytes.length >= nonce.length); 99 | 100 | byte[] result = new byte[nonce.length * 2]; 101 | 102 | System.arraycopy(alphaBytes, 0, result, 0, nonce.length); 103 | System.arraycopy(nonce, 0, result, nonce.length, nonce.length); 104 | 105 | return result; 106 | } 107 | 108 | public byte[] extractNonce(byte[] gamma) { 109 | if ((gamma.length < 2) || (gamma.length % 2 != 0)) { 110 | throw new IllegalArgumentException(); 111 | } 112 | int nonceLength = gamma.length / 2; 113 | byte[] nonce = new byte[nonceLength]; 114 | System.arraycopy(gamma, nonceLength, nonce, 0, nonceLength); 115 | return nonce; 116 | } 117 | 118 | public boolean checkGamma(byte[] gamma) throws PairingException { 119 | 120 | byte[] nonce; 121 | try { 122 | nonce = extractNonce(gamma); 123 | } catch (IllegalArgumentException e) { 124 | logDebug("Illegal nonce value."); 125 | return false; 126 | } 127 | logDebug("Nonce is: " + Utils.bytesToHexString(nonce)); 128 | logDebug("User gamma is: " + Utils.bytesToHexString(gamma)); 129 | logDebug("Generated gamma is: " + Utils.bytesToHexString(getGamma(nonce))); 130 | return Arrays.equals(gamma, getGamma(nonce)); 131 | } 132 | 133 | /** 134 | * Strips leading null bytes from a byte array, returning a new copy. 135 | *

136 | * As a special case, if the input array consists entirely of null bytes, 137 | * then an array with a single null element will be returned. 138 | */ 139 | private byte[] removeLeadingNullBytes(byte[] inArray) { 140 | int offset = 0; 141 | while (offset < inArray.length & inArray[offset] == 0) { 142 | offset += 1; 143 | } 144 | byte[] result = new byte[inArray.length - offset]; 145 | for (int i=offset; i < inArray.length; i++) { 146 | result[i - offset] = inArray[i]; 147 | } 148 | return result; 149 | } 150 | 151 | private void logDebug(String message) { 152 | System.out.println(message); 153 | } 154 | 155 | private void logVerbose(String message) { 156 | System.out.println(message); 157 | } 158 | 159 | public static interface DebugLogger { 160 | /** 161 | * Logs debugging information from challenge-response generation. 162 | */ 163 | public void debug(String message); 164 | 165 | /** 166 | * Logs verbose debugging information from challenge-response generation. 167 | */ 168 | public void verbose(String message); 169 | 170 | } 171 | 172 | } 173 | 174 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/pairing/PairingListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.kunal52.pairing; 18 | 19 | 20 | import com.kunal52.exception.PairingException; 21 | 22 | /** 23 | * Listener interface for handling events within a pairing session. 24 | */ 25 | public interface PairingListener { 26 | 27 | 28 | void onSessionCreated(); 29 | 30 | 31 | void onPerformInputDeviceRole() throws PairingException; 32 | 33 | 34 | void onPerformOutputDeviceRole(byte[] gamma) 35 | throws PairingException; 36 | 37 | void onSecretRequested(); 38 | 39 | void onSessionEnded(); 40 | 41 | 42 | void onError(String message); 43 | 44 | void onPaired(); 45 | 46 | void onLog(String message); 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/pairing/PairingMessageManager.java: -------------------------------------------------------------------------------- 1 | package com.kunal52.pairing; 2 | 3 | import com.google.protobuf.ByteString; 4 | import com.kunal52.wire.MessageManager; 5 | 6 | import java.nio.ByteBuffer; 7 | import java.util.Collections; 8 | 9 | public class PairingMessageManager extends MessageManager { 10 | public byte[] createPairingMessage(String clientName, String serviceName) { 11 | Pairingmessage.PairingMessage.Builder pairingMessage = Pairingmessage.PairingMessage.newBuilder() 12 | .setPairingRequest(Pairingmessage.PairingRequest.newBuilder() 13 | .setClientName(clientName) 14 | .setServiceName(serviceName)) 15 | .setStatus(Pairingmessage.PairingMessage.Status.STATUS_OK) 16 | .setProtocolVersion(2); 17 | byte[] pairingMessageByteArray = pairingMessage.build().toByteArray(); 18 | return addLengthAndCreate(pairingMessageByteArray); 19 | } 20 | 21 | public byte[] createPairingOption() { 22 | Pairingmessage.PairingEncoding pairingEncoding = Pairingmessage.PairingEncoding.newBuilder() 23 | .setType(Pairingmessage.PairingEncoding.EncodingType.ENCODING_TYPE_HEXADECIMAL) 24 | .setSymbolLength(6).build(); 25 | 26 | Pairingmessage.PairingMessage.Builder pairingOption = Pairingmessage.PairingMessage.newBuilder() 27 | .setPairingOption(Pairingmessage.PairingOption.newBuilder() 28 | .setPreferredRole(Pairingmessage.RoleType.ROLE_TYPE_INPUT) 29 | .addInputEncodings(pairingEncoding)) 30 | .setStatus(Pairingmessage.PairingMessage.Status.STATUS_OK) 31 | .setProtocolVersion(2); 32 | 33 | byte[] pairingMessageByteArray = pairingOption.build().toByteArray(); 34 | return addLengthAndCreate(pairingMessageByteArray); 35 | } 36 | 37 | 38 | public byte[] createConfigMessage() { 39 | Pairingmessage.PairingEncoding pairingEncoding = Pairingmessage.PairingEncoding.newBuilder() 40 | .setType(Pairingmessage.PairingEncoding.EncodingType.ENCODING_TYPE_HEXADECIMAL) 41 | .setSymbolLength(6).build(); 42 | 43 | Pairingmessage.PairingMessage.Builder pairingConfig = Pairingmessage.PairingMessage.newBuilder() 44 | .setPairingConfiguration(Pairingmessage.PairingConfiguration.newBuilder() 45 | .setClientRole(Pairingmessage.RoleType.ROLE_TYPE_INPUT) 46 | .setEncoding(pairingEncoding).build()) 47 | .setStatus(Pairingmessage.PairingMessage.Status.STATUS_OK) 48 | .setProtocolVersion(2); 49 | 50 | byte[] pairingMessageByteArray = pairingConfig.build().toByteArray(); 51 | return addLengthAndCreate(pairingMessageByteArray); 52 | } 53 | 54 | public byte[] createSecretMessage(Pairingmessage.PairingMessage pairingSecretMessage) { 55 | byte[] pairingMessageByteArray = pairingSecretMessage.toByteArray(); 56 | return addLengthAndCreate(pairingMessageByteArray); 57 | } 58 | 59 | public Pairingmessage.PairingMessage createSecretMessageProto(byte[] secret) { 60 | 61 | return Pairingmessage.PairingMessage.newBuilder() 62 | .setPairingSecret(Pairingmessage.PairingSecret.newBuilder().setSecret(ByteString.copyFrom(secret)).build()) 63 | .setStatus(Pairingmessage.PairingMessage.Status.STATUS_OK) 64 | .setProtocolVersion(2).build(); 65 | 66 | } 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/pairing/PairingPacketParser.java: -------------------------------------------------------------------------------- 1 | package com.kunal52.pairing; 2 | 3 | import com.google.protobuf.InvalidProtocolBufferException; 4 | import com.kunal52.wire.PacketParser; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.util.Arrays; 9 | import java.util.concurrent.BlockingQueue; 10 | 11 | public class PairingPacketParser extends PacketParser { 12 | 13 | private BlockingQueue mMessagesQueue; 14 | 15 | public PairingPacketParser(InputStream inputStream, BlockingQueue messagesQueue) { 16 | super(inputStream); 17 | mMessagesQueue = messagesQueue; 18 | } 19 | 20 | @Override 21 | public void messageBufferReceived(byte[] buf) { 22 | try { 23 | Pairingmessage.PairingMessage pairingMessage = Pairingmessage.PairingMessage.parseFrom(buf); 24 | if (pairingMessage.getStatus() == Pairingmessage.PairingMessage.Status.STATUS_OK) { 25 | mMessagesQueue.put(pairingMessage); 26 | } 27 | } catch (InvalidProtocolBufferException | InterruptedException e) { 28 | throw new RuntimeException(e); 29 | } 30 | } 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/pairing/PairingSession.java: -------------------------------------------------------------------------------- 1 | package com.kunal52.pairing; 2 | 3 | import com.kunal52.AndroidRemoteContext; 4 | import com.kunal52.util.Utils; 5 | import com.kunal52.exception.PairingException; 6 | import com.kunal52.ssl.DummyTrustManager; 7 | import com.kunal52.ssl.KeyStoreManager; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import javax.net.ssl.SSLContext; 12 | import javax.net.ssl.SSLSocket; 13 | import javax.net.ssl.SSLSocketFactory; 14 | import javax.net.ssl.TrustManager; 15 | import java.io.IOException; 16 | import java.io.OutputStream; 17 | import java.security.GeneralSecurityException; 18 | import java.security.SecureRandom; 19 | import java.util.ArrayList; 20 | import java.util.Arrays; 21 | import java.util.List; 22 | import java.util.concurrent.BlockingQueue; 23 | import java.util.concurrent.LinkedBlockingDeque; 24 | 25 | public class PairingSession { 26 | 27 | private final Logger logger = LoggerFactory.getLogger(PairingSession.class); 28 | 29 | private final BlockingQueue mMessagesQueue; 30 | 31 | private final PairingMessageManager mPairingMessageManager; 32 | 33 | SecretProvider secretProvider; 34 | 35 | private SSLSocket mSslSocket; 36 | 37 | public PairingSession() { 38 | mMessagesQueue = new LinkedBlockingDeque<>(); 39 | mPairingMessageManager = new PairingMessageManager(); 40 | } 41 | 42 | public void pair(String host, int port, PairingListener pairingListener) throws GeneralSecurityException, IOException, InterruptedException, PairingException { 43 | 44 | SSLContext sSLContext = SSLContext.getInstance("TLS"); 45 | sSLContext.init(new KeyStoreManager().getKeyManagers(), new TrustManager[]{new DummyTrustManager()}, new SecureRandom()); 46 | SSLSocketFactory sslsocketfactory = sSLContext.getSocketFactory(); 47 | SSLSocket sSLSocket = (SSLSocket) sslsocketfactory.createSocket(host, port); 48 | mSslSocket = sSLSocket; 49 | // sSLSocket.setNeedClientAuth(true); 50 | // sSLSocket.setUseClientMode(true); 51 | // sSLSocket.setKeepAlive(true); 52 | // sSLSocket.setTcpNoDelay(true); 53 | // sSLSocket.startHandshake(); 54 | 55 | pairingListener.onSessionCreated(); 56 | PairingPacketParser pairingPacketParser = new PairingPacketParser(sSLSocket.getInputStream(), mMessagesQueue); 57 | pairingPacketParser.start(); 58 | 59 | final OutputStream outputStream = sSLSocket.getOutputStream(); 60 | 61 | byte[] pairingMessage = mPairingMessageManager.createPairingMessage(AndroidRemoteContext.getInstance().getClientName(), AndroidRemoteContext.getInstance().getServiceName()); 62 | outputStream.write(pairingMessage); 63 | Pairingmessage.PairingMessage pairingMessageResponse = waitForMessage(); 64 | logReceivedMessage(pairingMessageResponse.toString()); 65 | 66 | byte[] pairingOption = new PairingMessageManager().createPairingOption(); 67 | outputStream.write(pairingOption); 68 | Pairingmessage.PairingMessage pairingOptionAck = waitForMessage(); 69 | logReceivedMessage(pairingOptionAck.toString()); 70 | 71 | byte[] configMessage = new PairingMessageManager().createConfigMessage(); 72 | outputStream.write(configMessage); 73 | Pairingmessage.PairingMessage pairingConfigAck = waitForMessage(); 74 | logReceivedMessage(pairingConfigAck.toString()); 75 | 76 | if (secretProvider != null) 77 | secretProvider.requestSecret(this); 78 | pairingListener.onSecretRequested(); 79 | logger.info("Waiting for secret"); 80 | Pairingmessage.PairingMessage pairingSecretMessage = waitForMessage(); 81 | byte[] secretMessage = mPairingMessageManager.createSecretMessage(pairingSecretMessage); 82 | outputStream.write(secretMessage); 83 | Pairingmessage.PairingMessage pairingSecretAck = waitForMessage(); 84 | logReceivedMessage(pairingSecretAck.toString()); 85 | 86 | pairingListener.onPaired(); 87 | pairingListener.onSessionEnded(); 88 | 89 | } 90 | 91 | 92 | Pairingmessage.PairingMessage waitForMessage() throws InterruptedException, PairingException { 93 | Pairingmessage.PairingMessage pairingMessage = mMessagesQueue.take(); 94 | if (pairingMessage.getStatus() != Pairingmessage.PairingMessage.Status.STATUS_OK) { 95 | throw new PairingException(pairingMessage.toString()); 96 | } 97 | return pairingMessage; 98 | } 99 | 100 | 101 | public void provideSecret(String secret) { 102 | createCodeSecret(secret); 103 | } 104 | 105 | private void createCodeSecret(String code) { 106 | code = code.substring(2); 107 | PairingChallengeResponse pairingChallengeResponse = new PairingChallengeResponse(Utils.getLocalCert(mSslSocket.getSession()), Utils.getPeerCert(mSslSocket.getSession())); 108 | byte[] secret = Utils.hexStringToBytes(code); 109 | System.out.println(Arrays.toString(secret)); 110 | try { 111 | pairingChallengeResponse.checkGamma(secret); 112 | } catch (PairingException e) { 113 | throw new RuntimeException(e); 114 | } 115 | byte[] pairingChallengeResponseAlpha; 116 | try { 117 | pairingChallengeResponseAlpha = pairingChallengeResponse.getAlpha(secret); 118 | } catch (PairingException e) { 119 | throw new RuntimeException(e); 120 | } 121 | Pairingmessage.PairingMessage secretMessageProto = new PairingMessageManager().createSecretMessageProto(pairingChallengeResponseAlpha); 122 | try { 123 | mMessagesQueue.put(secretMessageProto); 124 | } catch (InterruptedException e) { 125 | throw new RuntimeException(e); 126 | } 127 | } 128 | 129 | void logSendMessage(String message) { 130 | logger.info("Send Message : {}", message); 131 | } 132 | 133 | void logReceivedMessage(String message) { 134 | logger.info("Received Message : {}", message); 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/pairing/SecretProvider.java: -------------------------------------------------------------------------------- 1 | package com.kunal52.pairing; 2 | 3 | interface SecretProvider { 4 | 5 | void requestSecret(PairingSession pairingSession); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/remote/RemoteListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.kunal52.remote; 18 | 19 | 20 | import com.kunal52.exception.PairingException; 21 | 22 | /** 23 | * Listener interface for handling events within a pairing session. 24 | */ 25 | public interface RemoteListener { 26 | 27 | 28 | void onConnected(); 29 | 30 | void onDisconnected(); 31 | 32 | void onVolume(); 33 | 34 | void onPerformInputDeviceRole() throws PairingException; 35 | 36 | void onPerformOutputDeviceRole(byte[] gamma) 37 | throws PairingException; 38 | 39 | 40 | void onSessionEnded(); 41 | 42 | 43 | void onError(String message); 44 | 45 | void onLog(String message); 46 | 47 | void sSLException(); 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/remote/RemoteMessageManager.java: -------------------------------------------------------------------------------- 1 | package com.kunal52.remote; 2 | 3 | import com.google.protobuf.ByteString; 4 | import com.kunal52.pairing.Pairingmessage; 5 | import com.kunal52.wire.MessageManager; 6 | 7 | import java.nio.ByteBuffer; 8 | 9 | public class RemoteMessageManager extends MessageManager { 10 | 11 | public byte[] createRemoteConfigure(int code, String model, String vendor, int unknown1, String unknown2) { 12 | Remotemessage.RemoteConfigure remoteConfigure = Remotemessage.RemoteConfigure.newBuilder() 13 | .setCode1(code) 14 | .setDeviceInfo(Remotemessage.RemoteDeviceInfo.newBuilder() 15 | .setModel(model) 16 | .setVendor(vendor) 17 | .setUnknown1(unknown1) 18 | .setUnknown2(unknown2) 19 | .setPackageName("androidtv-remote") 20 | .setAppVersion("1.0.0") 21 | .build()) 22 | .build(); 23 | 24 | return createRemoteConfigure(remoteConfigure); 25 | } 26 | 27 | public byte[] createRemoteConfigure(Remotemessage.RemoteConfigure remoteConfigure) { 28 | 29 | Remotemessage.RemoteMessage remoteMessage = Remotemessage.RemoteMessage.newBuilder() 30 | .setRemoteConfigure(remoteConfigure) 31 | .build(); 32 | byte[] pairingMessageByteArray = remoteMessage.toByteArray(); 33 | return addLengthAndCreate(pairingMessageByteArray); 34 | } 35 | 36 | public byte[] createRemoteActive(int code) { 37 | 38 | Remotemessage.RemoteMessage remoteMessage = Remotemessage.RemoteMessage.newBuilder() 39 | .setRemoteSetActive(Remotemessage.RemoteSetActive.newBuilder().setActive(code).build()) 40 | .build(); 41 | byte[] pairingMessageByteArray = remoteMessage.toByteArray(); 42 | return addLengthAndCreate(pairingMessageByteArray); 43 | } 44 | 45 | 46 | public byte[] createPingResponse(int val1) { 47 | Remotemessage.RemotePingResponse remotePingResponse = Remotemessage.RemotePingResponse.newBuilder().setVal1(val1).build(); 48 | Remotemessage.RemoteMessage remoteMessage = Remotemessage.RemoteMessage.newBuilder().setRemotePingResponse(remotePingResponse).build(); 49 | byte[] pairingMessageByteArray = remoteMessage.toByteArray(); 50 | return addLengthAndCreate(pairingMessageByteArray); 51 | } 52 | 53 | public byte[] createPower() { 54 | Remotemessage.RemoteKeyInject remoteKeyInject = Remotemessage.RemoteKeyInject.newBuilder().setDirection(Remotemessage.RemoteDirection.SHORT).setKeyCode(Remotemessage.RemoteKeyCode.KEYCODE_POWER).build(); 55 | Remotemessage.RemoteMessage remoteMessage = Remotemessage.RemoteMessage.newBuilder().setRemoteKeyInject(remoteKeyInject).build(); 56 | byte[] pairingMessageByteArray = remoteMessage.toByteArray(); 57 | int length = pairingMessageByteArray.length; 58 | mPacketBuffer.put((byte) length).put(pairingMessageByteArray); 59 | byte[] bArr = new byte[mPacketBuffer.position()]; 60 | System.arraycopy(mPacketBuffer.array(), mPacketBuffer.arrayOffset(), bArr, 0, mPacketBuffer.position()); 61 | mPacketBuffer.clear(); 62 | return bArr; 63 | } 64 | 65 | public byte[] createVolumeLevel(int volume) { 66 | // Remotemessage.RemoteKeyInject remoteKeyInject = Remotemessage.RemoteKeyInject.newBuilder().setDirection(Remotemessage.RemoteDirection.SHORT).setKeyCode(Remotemessage.RemoteKeyCode.KEYCODE_POWER).build(); 67 | Remotemessage.RemoteMessage remoteMessage = Remotemessage.RemoteMessage.newBuilder().setRemoteAdjustVolumeLevel(Remotemessage.RemoteAdjustVolumeLevel.newBuilder().build()).build(); 68 | byte[] pairingMessageByteArray = remoteMessage.toByteArray(); 69 | int length = pairingMessageByteArray.length; 70 | mPacketBuffer.put((byte) length).put(pairingMessageByteArray); 71 | byte[] bArr = new byte[mPacketBuffer.position()]; 72 | System.arraycopy(mPacketBuffer.array(), mPacketBuffer.arrayOffset(), bArr, 0, mPacketBuffer.position()); 73 | mPacketBuffer.clear(); 74 | return bArr; 75 | } 76 | 77 | public byte[] createKeyCommand(Remotemessage.RemoteKeyCode keyCode, Remotemessage.RemoteDirection remoteDirection) { 78 | Remotemessage.RemoteMessage remoteMessage = Remotemessage.RemoteMessage.newBuilder() 79 | .setRemoteKeyInject(Remotemessage.RemoteKeyInject.newBuilder() 80 | .setKeyCode(keyCode) 81 | .setDirection(remoteDirection) 82 | .build()) 83 | .build(); 84 | 85 | return addLengthAndCreate(remoteMessage.toByteArray()); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/remote/RemotePacketParser.java: -------------------------------------------------------------------------------- 1 | package com.kunal52.remote; 2 | 3 | import com.google.protobuf.InvalidProtocolBufferException; 4 | import com.kunal52.wire.PacketParser; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.OutputStream; 9 | import java.util.Arrays; 10 | import java.util.concurrent.BlockingQueue; 11 | 12 | public class RemotePacketParser extends PacketParser { 13 | 14 | BlockingQueue mMessageQueue; 15 | private final OutputStream mOutputStream; 16 | private final RemoteMessageManager remoteMessageManager; 17 | 18 | private final RemoteListener mRemoteListener; 19 | 20 | private boolean isConnected = false; 21 | 22 | public RemotePacketParser(InputStream inputStream, OutputStream outputStream, BlockingQueue messageQueue, RemoteListener remoteListener) { 23 | super(inputStream); 24 | mOutputStream = outputStream; 25 | remoteMessageManager = new RemoteMessageManager(); 26 | mRemoteListener = remoteListener; 27 | mMessageQueue = messageQueue; 28 | } 29 | 30 | @Override 31 | public void messageBufferReceived(byte[] buf) { 32 | System.out.println(Arrays.toString(buf)); 33 | Remotemessage.RemoteMessage remoteMessage; 34 | try { 35 | remoteMessage = Remotemessage.RemoteMessage.parseFrom(buf); 36 | } catch (InvalidProtocolBufferException e) { 37 | throw new RuntimeException(e); 38 | } 39 | //Send Ping Response 40 | if (remoteMessage.hasRemotePingRequest()) { 41 | try { 42 | mOutputStream.write(remoteMessageManager.createPingResponse(remoteMessage.getRemotePingRequest().getVal1())); 43 | } catch (IOException e) { 44 | throw new RuntimeException(e); 45 | } 46 | } else if (remoteMessage.hasRemoteStart()) { 47 | if (!isConnected) 48 | mRemoteListener.onConnected(); 49 | isConnected = true; 50 | } else { 51 | try { 52 | mMessageQueue.put(remoteMessage); 53 | } catch (InterruptedException e) { 54 | throw new RuntimeException(e); 55 | } 56 | } 57 | 58 | 59 | System.out.println(remoteMessage); 60 | 61 | } 62 | 63 | 64 | public interface RemotePacketParserListener { 65 | 66 | void onConnected(); 67 | 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/remote/RemoteSession.java: -------------------------------------------------------------------------------- 1 | package com.kunal52.remote; 2 | 3 | import com.kunal52.ssl.KeyStoreManager; 4 | import com.kunal52.exception.PairingException; 5 | import com.kunal52.ssl.DummyTrustManager; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import javax.net.ssl.*; 10 | import java.io.IOException; 11 | import java.io.OutputStream; 12 | import java.security.GeneralSecurityException; 13 | import java.security.SecureRandom; 14 | import java.util.concurrent.BlockingQueue; 15 | import java.util.concurrent.LinkedBlockingQueue; 16 | 17 | public class RemoteSession { 18 | 19 | private final Logger logger = LoggerFactory.getLogger(RemoteSession.class); 20 | 21 | private final BlockingQueue mMessageQueue; 22 | 23 | private static final int SECRET_POLL_TIMEOUT_MS = 500; 24 | 25 | private static RemoteMessageManager mMessageManager; 26 | 27 | private final String mHost; 28 | 29 | private final int mPort; 30 | 31 | private final RemoteSessionListener mRemoteSessionListener; 32 | 33 | int retry; 34 | 35 | OutputStream outputStream; 36 | 37 | public RemoteSession(String host, int port, RemoteSessionListener remoteSessionListener) { 38 | mMessageQueue = new LinkedBlockingQueue<>(); 39 | mMessageManager = new RemoteMessageManager(); 40 | mHost = host; 41 | mPort = port; 42 | mRemoteSessionListener = remoteSessionListener; 43 | } 44 | 45 | public void connect() throws GeneralSecurityException, IOException, InterruptedException, PairingException { 46 | 47 | try { 48 | SSLContext sSLContext = SSLContext.getInstance("TLS"); 49 | sSLContext.init(new KeyStoreManager().getKeyManagers(), new TrustManager[]{new DummyTrustManager()}, new SecureRandom()); 50 | SSLSocketFactory sslsocketfactory = sSLContext.getSocketFactory(); 51 | SSLSocket sSLSocket = (SSLSocket) sslsocketfactory.createSocket(mHost, mPort); 52 | sSLSocket.setNeedClientAuth(true); 53 | sSLSocket.setUseClientMode(true); 54 | sSLSocket.setKeepAlive(true); 55 | sSLSocket.setTcpNoDelay(true); 56 | sSLSocket.startHandshake(); 57 | 58 | outputStream = sSLSocket.getOutputStream(); 59 | new RemotePacketParser(sSLSocket.getInputStream(), outputStream, mMessageQueue, new RemoteListener() { 60 | @Override 61 | public void onConnected() { 62 | mRemoteSessionListener.onConnected(); 63 | } 64 | 65 | @Override 66 | public void onDisconnected() { 67 | 68 | } 69 | 70 | @Override 71 | public void onVolume() { 72 | 73 | } 74 | 75 | @Override 76 | public void onPerformInputDeviceRole() throws PairingException { 77 | 78 | } 79 | 80 | @Override 81 | public void onPerformOutputDeviceRole(byte[] gamma) throws PairingException { 82 | 83 | } 84 | 85 | @Override 86 | public void onSessionEnded() { 87 | 88 | } 89 | 90 | @Override 91 | public void onError(String message) { 92 | 93 | } 94 | 95 | @Override 96 | public void onLog(String message) { 97 | 98 | } 99 | 100 | @Override 101 | public void sSLException() { 102 | 103 | } 104 | }).start(); 105 | 106 | Remotemessage.RemoteMessage remoteMessage = waitForMessage(); 107 | logger.info(remoteMessage.toString()); 108 | 109 | byte[] remoteConfigure = mMessageManager.createRemoteConfigure(622, "ROG Strix G531GT_G531GT", "ASUSTeK COMPUTER INC.", 1, "1"); 110 | 111 | outputStream.write(remoteConfigure); 112 | 113 | waitForMessage(); 114 | 115 | byte[] remoteActive = mMessageManager.createRemoteActive(622); 116 | outputStream.write(remoteActive); 117 | } catch (SSLException sslException) { 118 | mRemoteSessionListener.onSslError(); 119 | } catch (Exception e) { 120 | e.printStackTrace(); 121 | mRemoteSessionListener.onError(e.getMessage()); 122 | } 123 | } 124 | 125 | Remotemessage.RemoteMessage waitForMessage() throws InterruptedException, PairingException { 126 | return mMessageQueue.take(); 127 | } 128 | 129 | public void attemptToReconnect() { 130 | retry++; 131 | try { 132 | connect(); 133 | } catch (GeneralSecurityException | IOException | InterruptedException | PairingException e) { 134 | mRemoteSessionListener.onError(e.getMessage()); 135 | throw new RuntimeException(e); 136 | } 137 | } 138 | 139 | 140 | public void sendCommand(Remotemessage.RemoteKeyCode remoteKeyCode, Remotemessage.RemoteDirection remoteDirection) { 141 | try { 142 | outputStream.write(mMessageManager.createKeyCommand(remoteKeyCode,remoteDirection)); 143 | } catch (IOException e) { 144 | throw new RuntimeException(e); 145 | } 146 | } 147 | 148 | public interface RemoteSessionListener { 149 | void onConnected(); 150 | 151 | void onSslError() throws GeneralSecurityException, IOException, InterruptedException, PairingException; 152 | 153 | void onDisconnected(); 154 | 155 | void onError(String message); 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/ssl/DummySSLServerSocketFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.kunal52.ssl; 18 | 19 | import java.security.KeyManagementException; 20 | import java.security.NoSuchAlgorithmException; 21 | 22 | import javax.net.ssl.KeyManager; 23 | import javax.net.ssl.SSLServerSocketFactory; 24 | import javax.net.ssl.TrustManager; 25 | 26 | 27 | /** 28 | * An {@link SSLServerSocketFactory} that performs no verification on client 29 | * certificates; ie, is all-trusting. 30 | * 31 | * @see DummyTrustManager 32 | */ 33 | public class DummySSLServerSocketFactory extends SSLServerSocketFactoryWrapper { 34 | 35 | DummySSLServerSocketFactory(KeyManager[] keyManagers, 36 | TrustManager[] trustManagers) throws KeyManagementException, 37 | NoSuchAlgorithmException { 38 | super(keyManagers, trustManagers); 39 | } 40 | 41 | public static DummySSLServerSocketFactory fromKeyManagers( 42 | KeyManager[] keyManagers) throws KeyManagementException, 43 | NoSuchAlgorithmException { 44 | TrustManager[] trustManagers = { new DummyTrustManager() }; 45 | return new DummySSLServerSocketFactory(keyManagers, trustManagers); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/ssl/DummySSLSocketFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.kunal52.ssl; 18 | 19 | import java.security.KeyManagementException; 20 | import java.security.NoSuchAlgorithmException; 21 | 22 | import javax.net.ssl.KeyManager; 23 | import javax.net.ssl.SSLSocketFactory; 24 | import javax.net.ssl.TrustManager; 25 | 26 | 27 | /** 28 | * An {@link SSLSocketFactory} that performs no verification on server 29 | * certificates; ie, is all-trusting. 30 | * 31 | * @see DummyTrustManager 32 | */ 33 | public class DummySSLSocketFactory extends SSLSocketFactoryWrapper { 34 | 35 | DummySSLSocketFactory(KeyManager[] keyManagers, 36 | TrustManager[] trustManagers) throws KeyManagementException, 37 | NoSuchAlgorithmException { 38 | super(keyManagers, trustManagers); 39 | } 40 | 41 | public static DummySSLSocketFactory fromKeyManagers(KeyManager[] keyManagers) 42 | throws KeyManagementException, NoSuchAlgorithmException { 43 | TrustManager[] trustManagers = { new DummyTrustManager() }; 44 | return new DummySSLSocketFactory(keyManagers, trustManagers); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/ssl/DummyTrustManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.kunal52.ssl; 18 | 19 | import java.security.cert.X509Certificate; 20 | 21 | import javax.net.ssl.X509TrustManager; 22 | 23 | /** 24 | * A trust manager that accepts all X509 client and server certificates. 25 | * 26 | * @see "http://java.sun.com/products/javamail/SSLNOTES.txt" 27 | */ 28 | public class DummyTrustManager implements X509TrustManager { 29 | 30 | public void checkClientTrusted(X509Certificate[] chain, String authType) { 31 | // Does not throw CertificateException: all chains trusted 32 | return; 33 | } 34 | 35 | public void checkServerTrusted(X509Certificate[] chain, String authType) { 36 | // Does not throw CertificateException: all chains trusted 37 | return; 38 | } 39 | 40 | public X509Certificate[] getAcceptedIssuers() { 41 | return new X509Certificate[0]; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/ssl/KeyStoreManager.java: -------------------------------------------------------------------------------- 1 | package com.kunal52.ssl; 2 | 3 | 4 | import com.kunal52.AndroidRemoteContext; 5 | import com.kunal52.ssl.SslUtil; 6 | 7 | import java.io.FileOutputStream; 8 | import java.io.IOException; 9 | import java.nio.file.Files; 10 | import java.nio.file.Paths; 11 | import java.security.GeneralSecurityException; 12 | import java.security.KeyPair; 13 | import java.security.KeyPairGenerator; 14 | import java.security.KeyStore; 15 | import java.security.KeyStoreException; 16 | import java.security.NoSuchAlgorithmException; 17 | import java.security.cert.Certificate; 18 | import java.security.cert.CertificateException; 19 | import java.security.cert.X509Certificate; 20 | import java.util.Enumeration; 21 | import java.util.Locale; 22 | import java.util.UUID; 23 | 24 | import javax.net.ssl.KeyManager; 25 | import javax.net.ssl.KeyManagerFactory; 26 | import javax.net.ssl.TrustManager; 27 | import javax.net.ssl.TrustManagerFactory; 28 | import javax.net.ssl.X509TrustManager; 29 | import javax.security.auth.x500.X500Principal; 30 | 31 | /* renamed from: sensustech.android.tv.remote.control.manager.keystore.KeyStoreManager */ 32 | /* loaded from: classes3.dex */ 33 | public final class KeyStoreManager { 34 | private static String ANDROID_KEYSTORE = "AndroidKeyStore"; 35 | private static final boolean DEBUG = false; 36 | // public static final String KEYSTORE_FILENAME = "androidtv.keystore"; 37 | // static final char[] KEYSTORE_PASSWORD = "KeyStore_Password".toCharArray(); 38 | private static final String LOCAL_IDENTITY_ALIAS = "androidtv-remote"; 39 | private static final String REMOTE_IDENTITY_ALIAS_PATTERN = "androidtv-remote-%s"; 40 | private static final String SERVER_IDENTITY_ALIAS = "androidtv-local"; 41 | private static final String TAG = "KeyStoreManager"; 42 | private DynamicTrustManager mDynamicTrustManager; 43 | private KeyStore mKeyStore; 44 | 45 | private AndroidRemoteContext androidRemoteContext = AndroidRemoteContext.getInstance(); 46 | 47 | /* renamed from: sensustech.android.tv.remote.control.manager.keystore.KeyStoreManager$DynamicTrustManager */ 48 | /* loaded from: classes3.dex */ 49 | public static class DynamicTrustManager implements X509TrustManager { 50 | private X509TrustManager trustManager; 51 | 52 | @Override // javax.net.ssl.X509TrustManager 53 | public X509Certificate[] getAcceptedIssuers() { 54 | return new X509Certificate[0]; 55 | } 56 | 57 | public DynamicTrustManager(KeyStore keyStore) { 58 | reloadTrustManager(keyStore); 59 | } 60 | 61 | @Override // javax.net.ssl.X509TrustManager 62 | public void checkClientTrusted(X509Certificate[] x509CertificateArr, String str) throws CertificateException { 63 | this.trustManager.checkClientTrusted(x509CertificateArr, str); 64 | } 65 | 66 | @Override // javax.net.ssl.X509TrustManager 67 | public void checkServerTrusted(X509Certificate[] x509CertificateArr, String str) throws CertificateException { 68 | this.trustManager.checkServerTrusted(x509CertificateArr, str); 69 | } 70 | 71 | public void reloadTrustManager(KeyStore keyStore) { 72 | try { 73 | TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 74 | trustManagerFactory.init(keyStore); 75 | TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); 76 | for (int i = 0; i < trustManagers.length; i++) { 77 | if (trustManagers[i] instanceof X509TrustManager) { 78 | this.trustManager = (X509TrustManager) trustManagers[i]; 79 | return; 80 | } 81 | } 82 | throw new IllegalStateException("No trust manager found"); 83 | } catch (KeyStoreException | NoSuchAlgorithmException unused) { 84 | } 85 | } 86 | } 87 | 88 | public KeyStoreManager() { 89 | KeyStore load = load(); 90 | this.mKeyStore = load; 91 | this.mDynamicTrustManager = new DynamicTrustManager(load); 92 | } 93 | 94 | private void clearKeyStore() { 95 | try { 96 | Enumeration aliases = this.mKeyStore.aliases(); 97 | while (aliases.hasMoreElements()) { 98 | this.mKeyStore.deleteEntry(aliases.nextElement()); 99 | } 100 | } catch (KeyStoreException unused) { 101 | } 102 | store(); 103 | } 104 | 105 | private static String createAlias(String str) { 106 | return String.format(REMOTE_IDENTITY_ALIAS_PATTERN, str); 107 | } 108 | 109 | private void createIdentity(KeyStore keyStore) throws GeneralSecurityException { 110 | createIdentity(keyStore, SERVER_IDENTITY_ALIAS); 111 | } 112 | 113 | private void createIdentity(KeyStore keyStore, String str) throws GeneralSecurityException { 114 | createIdentity(keyStore, str, getUniqueId()); 115 | } 116 | 117 | private void setLocale(Locale locale) { 118 | try { 119 | Locale.setDefault(locale); 120 | } catch (Exception unused) { 121 | } 122 | } 123 | 124 | private void createIdentity(KeyStore keyStore, String str, String str2) throws GeneralSecurityException { 125 | KeyPair generateKeyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); 126 | try { 127 | keyStore.setKeyEntry(str, generateKeyPair.getPrivate(), null, new Certificate[]{SslUtil.generateX509V3Certificate(generateKeyPair, getCertificateName(str2))}); 128 | } catch (IllegalArgumentException unused) { 129 | Locale locale = Locale.getDefault(); 130 | setLocale(Locale.ENGLISH); 131 | keyStore.setKeyEntry(str, generateKeyPair.getPrivate(), null, new Certificate[]{SslUtil.generateX509V3Certificate(generateKeyPair, getCertificateName(str2))}); 132 | setLocale(locale); 133 | } 134 | } 135 | 136 | private KeyStore createIdentityKeyStore() throws GeneralSecurityException { 137 | KeyStore keyStore; 138 | if (!useAndroidKeyStore()) { 139 | keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); 140 | try { 141 | keyStore.load(null, androidRemoteContext.getKeyStorePass()); 142 | } catch (IOException e) { 143 | throw new GeneralSecurityException("Unable to create empty keyStore", e); 144 | } 145 | } else { 146 | keyStore = KeyStore.getInstance(ANDROID_KEYSTORE); 147 | try { 148 | keyStore.load(null); 149 | } catch (IOException e2) { 150 | throw new GeneralSecurityException("Unable to create empty keyStore", e2); 151 | } 152 | } 153 | createIdentity(keyStore); 154 | return keyStore; 155 | } 156 | 157 | private static final String getCertificateName() { 158 | return getCertificateName(getUniqueId()); 159 | } 160 | 161 | private static final String getCertificateName(String str) { 162 | return "CN=androidtv/livingTV"; 163 | } 164 | 165 | private static String getSubjectDN(Certificate certificate) { 166 | X500Principal subjectX500Principal; 167 | if (!(certificate instanceof X509Certificate) || (subjectX500Principal = ((X509Certificate) certificate).getSubjectX500Principal()) == null) { 168 | return null; 169 | } 170 | return subjectX500Principal.getName(); 171 | } 172 | 173 | private static final String getUniqueId() { 174 | return UUID.randomUUID().toString(); 175 | } 176 | 177 | private boolean hasServerIdentityAlias(KeyStore keyStore) { 178 | try { 179 | return keyStore.containsAlias(SERVER_IDENTITY_ALIAS); 180 | } catch (KeyStoreException unused) { 181 | return false; 182 | } 183 | } 184 | 185 | private KeyStore load() { 186 | KeyStore keyStore; 187 | KeyStore keyStore2 = null; 188 | try { 189 | if (!useAndroidKeyStore()) { 190 | keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); 191 | keyStore.load(Files.newInputStream(androidRemoteContext.getKeyStoreFile().toPath()), androidRemoteContext.getKeyStorePass()); 192 | } else { 193 | keyStore = KeyStore.getInstance(ANDROID_KEYSTORE); 194 | keyStore.load(null); 195 | } 196 | keyStore2 = keyStore; 197 | } catch (KeyStoreException e) { 198 | throw new IllegalStateException("Unable to get default instance of KeyStore", e); 199 | } catch (IOException | GeneralSecurityException unused) { 200 | } 201 | if (keyStore2 == null || !hasServerIdentityAlias(keyStore2)) { 202 | try { 203 | KeyStore createIdentityKeyStore = createIdentityKeyStore(); 204 | store(createIdentityKeyStore); 205 | return createIdentityKeyStore; 206 | } catch (GeneralSecurityException e2) { 207 | throw new IllegalStateException("Unable to create identity KeyStore", e2); 208 | } 209 | } 210 | return keyStore2; 211 | } 212 | 213 | private void store(KeyStore keyStore) { 214 | if (!useAndroidKeyStore()) { 215 | try { 216 | FileOutputStream openFileOutput = new FileOutputStream(androidRemoteContext.getKeyStoreFile()); 217 | keyStore.store(openFileOutput, androidRemoteContext.getKeyStorePass()); 218 | openFileOutput.close(); 219 | } catch (IOException e) { 220 | throw new IllegalStateException("Unable to store keyStore", e); 221 | } catch (GeneralSecurityException e2) { 222 | throw new IllegalStateException("Unable to store keyStore", e2); 223 | } 224 | } 225 | } 226 | 227 | private boolean useAndroidKeyStore() { 228 | return false; 229 | } 230 | 231 | public void clear() { 232 | clearKeyStore(); 233 | try { 234 | createIdentity(this.mKeyStore); 235 | } catch (GeneralSecurityException unused) { 236 | } 237 | store(); 238 | } 239 | 240 | public KeyManager[] getKeyManagers() throws GeneralSecurityException { 241 | KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 242 | keyManagerFactory.init(this.mKeyStore, "".toCharArray()); 243 | return keyManagerFactory.getKeyManagers(); 244 | } 245 | 246 | public TrustManager[] getTrustManagers() throws GeneralSecurityException { 247 | try { 248 | return new DynamicTrustManager[]{this.mDynamicTrustManager}; 249 | } catch (Exception e) { 250 | throw new GeneralSecurityException(e); 251 | } 252 | } 253 | 254 | public boolean hasServerIdentityAlias() { 255 | return hasServerIdentityAlias(this.mKeyStore); 256 | } 257 | 258 | public void initializeKeyStore() { 259 | initializeKeyStore(getUniqueId()); 260 | } 261 | 262 | public void initializeKeyStore(String str) { 263 | try { 264 | createIdentity(this.mKeyStore, LOCAL_IDENTITY_ALIAS, str); 265 | store(); 266 | } catch (GeneralSecurityException e) { 267 | throw new IllegalStateException("Unable to create identity KeyStore", e); 268 | } 269 | } 270 | 271 | public Certificate removeCertificate(String str) { 272 | try { 273 | String createAlias = createAlias(str); 274 | if (!this.mKeyStore.containsAlias(createAlias)) { 275 | return null; 276 | } 277 | Certificate certificate = this.mKeyStore.getCertificate(createAlias); 278 | this.mKeyStore.deleteEntry(createAlias); 279 | store(); 280 | return certificate; 281 | } catch (KeyStoreException unused) { 282 | return null; 283 | } 284 | } 285 | 286 | public void store() { 287 | this.mDynamicTrustManager.reloadTrustManager(this.mKeyStore); 288 | store(this.mKeyStore); 289 | } 290 | 291 | public void storeCertificate(Certificate certificate) { 292 | storeCertificate(certificate, Integer.toString(certificate.hashCode())); 293 | } 294 | 295 | public void storeCertificate(Certificate certificate, String str) { 296 | try { 297 | String createAlias = createAlias(str); 298 | String subjectDN = getSubjectDN(certificate); 299 | if (this.mKeyStore.containsAlias(createAlias)) { 300 | this.mKeyStore.deleteEntry(createAlias); 301 | } 302 | if (subjectDN != null) { 303 | Enumeration aliases = this.mKeyStore.aliases(); 304 | while (aliases.hasMoreElements()) { 305 | String nextElement = aliases.nextElement(); 306 | String subjectDN2 = getSubjectDN(this.mKeyStore.getCertificate(nextElement)); 307 | if (subjectDN2 != null && subjectDN2.equals(subjectDN)) { 308 | this.mKeyStore.deleteEntry(nextElement); 309 | } 310 | } 311 | } 312 | this.mKeyStore.setCertificateEntry(createAlias, certificate); 313 | store(); 314 | } catch (KeyStoreException unused) { 315 | } 316 | } 317 | } -------------------------------------------------------------------------------- /src/main/java/com/kunal52/ssl/SSLServerSocketFactoryWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.kunal52.ssl; 18 | 19 | import java.io.IOException; 20 | import java.net.InetAddress; 21 | import java.net.ServerSocket; 22 | import java.security.KeyManagementException; 23 | import java.security.NoSuchAlgorithmException; 24 | 25 | import javax.net.ssl.KeyManager; 26 | import javax.net.ssl.SSLContext; 27 | import javax.net.ssl.SSLServerSocketFactory; 28 | import javax.net.ssl.TrustManager; 29 | 30 | 31 | /** 32 | * A convenience wrapper to generate an {@link SSLServerSocketFactory} that uses 33 | * the given {@link KeyManager} and {@link TrustManager} instances. 34 | */ 35 | public class SSLServerSocketFactoryWrapper extends SSLServerSocketFactory { 36 | 37 | /** 38 | * The internal SSLServerSocketFactory which will be wrapped. 39 | */ 40 | private SSLServerSocketFactory mFactory; 41 | 42 | public SSLServerSocketFactoryWrapper(KeyManager[] keyManagers, 43 | TrustManager[] trustManagers) 44 | throws NoSuchAlgorithmException, KeyManagementException { 45 | SSLContext sslcontext = SSLContext.getInstance("TLS"); 46 | sslcontext.init(keyManagers, trustManagers, null); 47 | mFactory = sslcontext.getServerSocketFactory(); 48 | } 49 | 50 | public static SSLServerSocketFactoryWrapper CreateWithDummyTrustManager( 51 | KeyManager[] keyManagers) throws KeyManagementException, 52 | NoSuchAlgorithmException { 53 | TrustManager[] trustManagers = { new DummyTrustManager() }; 54 | return new SSLServerSocketFactoryWrapper(keyManagers, trustManagers); 55 | } 56 | 57 | @Override 58 | public ServerSocket createServerSocket(int port) throws IOException { 59 | return mFactory.createServerSocket(port); 60 | } 61 | 62 | @Override 63 | public ServerSocket createServerSocket(int port, int backlog) 64 | throws IOException { 65 | return mFactory.createServerSocket(port, backlog); 66 | } 67 | 68 | @Override 69 | public ServerSocket createServerSocket(int port, int backlog, 70 | InetAddress ifAddress) throws IOException { 71 | return mFactory.createServerSocket(port, backlog, ifAddress); 72 | } 73 | 74 | @Override 75 | public String[] getDefaultCipherSuites() { 76 | return mFactory.getDefaultCipherSuites(); 77 | } 78 | 79 | @Override 80 | public String[] getSupportedCipherSuites() { 81 | return mFactory.getSupportedCipherSuites(); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/ssl/SSLSocketFactoryWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.kunal52.ssl; 18 | 19 | import java.io.IOException; 20 | import java.net.InetAddress; 21 | import java.net.Socket; 22 | import java.security.KeyManagementException; 23 | import java.security.NoSuchAlgorithmException; 24 | 25 | import javax.net.SocketFactory; 26 | import javax.net.ssl.KeyManager; 27 | import javax.net.ssl.SSLContext; 28 | import javax.net.ssl.SSLSocketFactory; 29 | import javax.net.ssl.TrustManager; 30 | 31 | 32 | /** 33 | * A convenience wrapper to generate an {@link SSLSocketFactory} that uses the 34 | * {@link KeyManager} and {@link TrustManager} instances that are passed in. 35 | */ 36 | public class SSLSocketFactoryWrapper extends SSLSocketFactory { 37 | 38 | /** 39 | * The internal SSLSocketFactory which will be wrapped. 40 | */ 41 | private SSLSocketFactory mFactory; 42 | 43 | public static SocketFactory getDefault() { 44 | throw new IllegalStateException("Not implemented."); 45 | } 46 | 47 | public SSLSocketFactoryWrapper() { 48 | throw new IllegalStateException("Not implemented."); 49 | } 50 | 51 | public SSLSocketFactoryWrapper(KeyManager[] keyManagers, 52 | TrustManager[] trustManagers) throws NoSuchAlgorithmException, 53 | KeyManagementException { 54 | java.security.Security.addProvider( 55 | new org.bouncycastle.jce.provider.BouncyCastleProvider()); 56 | 57 | SSLContext sslcontext = SSLContext.getInstance("TLS"); 58 | sslcontext.init(keyManagers, trustManagers, null); 59 | mFactory = sslcontext.getSocketFactory(); 60 | } 61 | 62 | public static SSLSocketFactoryWrapper CreateWithDummyTrustManager( 63 | KeyManager[] keyManagers) throws KeyManagementException, 64 | NoSuchAlgorithmException { 65 | TrustManager[] trustManagers = { new DummyTrustManager() }; 66 | return new SSLSocketFactoryWrapper(keyManagers, trustManagers); 67 | } 68 | 69 | @Override 70 | public Socket createSocket() throws IOException { 71 | return mFactory.createSocket(); 72 | } 73 | 74 | 75 | @Override 76 | public Socket createSocket(InetAddress inaddr, int i) 77 | throws IOException { 78 | return mFactory.createSocket(inaddr, i); 79 | } 80 | 81 | @Override 82 | public Socket createSocket(InetAddress inaddr, int i, 83 | InetAddress inaddr1, int j) throws IOException { 84 | return mFactory.createSocket(inaddr, i, inaddr1, j); 85 | } 86 | 87 | @Override 88 | public Socket createSocket(Socket socket, String s, int i, boolean flag) 89 | throws IOException { 90 | return mFactory.createSocket(socket, s, i, flag); 91 | } 92 | 93 | @Override 94 | public Socket createSocket(String s, int i) throws IOException { 95 | return mFactory.createSocket(s, i); 96 | } 97 | 98 | @Override 99 | public Socket createSocket(String s, int i, InetAddress inaddr, int j) 100 | throws IOException { 101 | return mFactory.createSocket(s, i, inaddr, j); 102 | } 103 | 104 | @Override 105 | public String[] getDefaultCipherSuites() { 106 | return mFactory.getDefaultCipherSuites(); 107 | } 108 | 109 | @Override 110 | public String[] getSupportedCipherSuites() { 111 | return mFactory.getSupportedCipherSuites(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/ssl/SslUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.kunal52.ssl; 18 | 19 | import org.bouncycastle.asn1.ASN1InputStream; 20 | import org.bouncycastle.asn1.ASN1Sequence; 21 | import org.bouncycastle.asn1.x500.X500Name; 22 | import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; 23 | import org.bouncycastle.asn1.x509.BasicConstraints; 24 | import org.bouncycastle.asn1.x509.ExtendedKeyUsage; 25 | import org.bouncycastle.asn1.x509.GeneralName; 26 | import org.bouncycastle.asn1.x509.GeneralNames; 27 | import org.bouncycastle.asn1.x509.KeyPurposeId; 28 | import org.bouncycastle.asn1.x509.KeyUsage; 29 | import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; 30 | import org.bouncycastle.asn1.x509.X509Extension; 31 | import org.bouncycastle.x509.X509V1CertificateGenerator; 32 | import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure; 33 | 34 | import java.io.FileInputStream; 35 | import java.io.IOException; 36 | import java.math.BigInteger; 37 | import java.security.GeneralSecurityException; 38 | import java.security.KeyPair; 39 | import java.security.KeyPairGenerator; 40 | import java.security.KeyStore; 41 | import java.security.NoSuchAlgorithmException; 42 | import java.security.PublicKey; 43 | import java.security.cert.Certificate; 44 | import java.security.cert.X509Certificate; 45 | import java.util.Calendar; 46 | import java.util.Date; 47 | 48 | import javax.net.ssl.KeyManager; 49 | import javax.net.ssl.KeyManagerFactory; 50 | import javax.net.ssl.SSLContext; 51 | import javax.net.ssl.TrustManager; 52 | import javax.security.auth.x500.X500Principal; 53 | 54 | /** 55 | * A collection of miscellaneous utility functions for use in Polo. 56 | */ 57 | public class SslUtil { 58 | 59 | /** 60 | * Generates a new RSA key pair. 61 | * 62 | * @return the new object 63 | * @throws NoSuchAlgorithmException 64 | * if the RSA generator could not be loaded 65 | */ 66 | public static KeyPair generateRsaKeyPair() throws NoSuchAlgorithmException { 67 | KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); 68 | KeyPair kp = kg.generateKeyPair(); 69 | return kp; 70 | } 71 | 72 | /** 73 | * Creates a new, empty {@link KeyStore} 74 | * 75 | * @return the new KeyStore 76 | * @throws GeneralSecurityException 77 | * on error creating the keystore 78 | * @throws IOException 79 | * on error loading the keystore 80 | */ 81 | public static KeyStore getEmptyKeyStore() throws GeneralSecurityException, 82 | IOException { 83 | KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); 84 | ks.load(null, null); 85 | return ks; 86 | } 87 | 88 | /** 89 | * Generates a new, self-signed X509 V1 certificate for a KeyPair. 90 | * 91 | * @param pair 92 | * the {@link KeyPair} to be used 93 | * @param name 94 | * X.500 distinguished name 95 | * @return the new certificate 96 | * @throws GeneralSecurityException 97 | * on error generating the certificate 98 | */ 99 | @SuppressWarnings("deprecation") 100 | public static X509Certificate generateX509V1Certificate(KeyPair pair, 101 | String name) throws GeneralSecurityException { 102 | java.security.Security 103 | .addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); 104 | 105 | Calendar calendar = Calendar.getInstance(); 106 | calendar.set(2009, 0, 1); 107 | Date startDate = new Date(calendar.getTimeInMillis()); 108 | calendar.set(2029, 0, 1); 109 | Date expiryDate = new Date(calendar.getTimeInMillis()); 110 | 111 | BigInteger serialNumber = BigInteger.valueOf(Math.abs(System 112 | .currentTimeMillis())); 113 | 114 | X509V1CertificateGenerator certGen = new X509V1CertificateGenerator(); 115 | X500Principal dnName = new X500Principal(name); 116 | certGen.setSerialNumber(serialNumber); 117 | certGen.setIssuerDN(dnName); 118 | certGen.setNotBefore(startDate); 119 | certGen.setNotAfter(expiryDate); 120 | certGen.setSubjectDN(dnName); // note: same as issuer 121 | certGen.setPublicKey(pair.getPublic()); 122 | certGen.setSignatureAlgorithm("SHA256WithRSAEncryption"); 123 | 124 | // This method is deprecated, but Android Eclair does not provide the 125 | // generate() methods. 126 | X509Certificate cert = certGen.generate(pair.getPrivate(), "BC"); 127 | return cert; 128 | } 129 | 130 | /** 131 | * Generates a new, self-signed X509 V3 certificate for a KeyPair. 132 | * 133 | * @param pair 134 | * the {@link KeyPair} to be used 135 | * @param name 136 | * X.500 distinguished name 137 | * @param notBefore 138 | * not valid before this date 139 | * @param notAfter 140 | * not valid after this date 141 | * @param serialNumber 142 | * serial number 143 | * @return the new certificate 144 | * @throws GeneralSecurityException 145 | * on error generating the certificate 146 | */ 147 | @SuppressWarnings("deprecation") 148 | public static X509Certificate generateX509V3Certificate(KeyPair pair, 149 | String name, Date notBefore, Date notAfter, BigInteger serialNumber) 150 | throws GeneralSecurityException { 151 | java.security.Security 152 | .addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); 153 | 154 | org.bouncycastle.x509.X509V3CertificateGenerator certGen = new org.bouncycastle.x509.X509V3CertificateGenerator(); 155 | X500Name dnName = new org.bouncycastle.asn1.x500.X500Name(name); 156 | X500Principal principal = new X500Principal(name); 157 | 158 | certGen.setSerialNumber(serialNumber); 159 | // certGen.setIssuerDN(dnName); 160 | // certGen.setSubjectDN(dnName); // note: same as issuer 161 | certGen.setIssuerDN(principal); 162 | certGen.setSubjectDN(principal); 163 | certGen.setNotBefore(notBefore); 164 | certGen.setNotAfter(notAfter); 165 | certGen.setPublicKey(pair.getPublic()); 166 | certGen.setSignatureAlgorithm("SHA256WithRSAEncryption"); 167 | 168 | // For self-signed certificates, OpenSSL 0.9.6 has specific requirements 169 | // about certificate and extension content. Quoting the `man verify`: 170 | // 171 | // In OpenSSL 0.9.6 and later all certificates whose subject name 172 | // matches 173 | // the issuer name of the current certificate are subject to further 174 | // tests. The relevant authority key identifier components of the 175 | // current 176 | // certificate (if present) must match the subject key identifier (if 177 | // present) and issuer and serial number of the candidate issuer, in 178 | // addition the keyUsage extension of the candidate issuer (if present) 179 | // must permit certificate signing. 180 | // 181 | // In the code that follows, 182 | // - the KeyUsage extension permits cert signing (KeyUsage.keyCertSign); 183 | // - the Authority Key Identifier extension is added, matching the 184 | // subject key identifier, and using the issuer, and serial number. 185 | 186 | certGen.addExtension(X509Extension.basicConstraints, true, 187 | new BasicConstraints(false)); 188 | 189 | certGen.addExtension(X509Extension.keyUsage, true, new KeyUsage( 190 | KeyUsage.digitalSignature | KeyUsage.keyEncipherment 191 | | KeyUsage.keyCertSign)); 192 | certGen.addExtension(X509Extension.extendedKeyUsage, true, 193 | new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth)); 194 | 195 | AuthorityKeyIdentifier authIdentifier = createAuthorityKeyIdentifier( 196 | pair.getPublic(), dnName, serialNumber); 197 | 198 | certGen.addExtension(X509Extension.authorityKeyIdentifier, true, 199 | authIdentifier); 200 | // certGen.addExtension(X509Extension.subjectKeyIdentifier, true, 201 | // new SubjectKeyIdentifier(pair.getPublic())); 202 | 203 | certGen.addExtension(X509Extension.subjectAlternativeName, false, 204 | new GeneralNames(new GeneralName(GeneralName.rfc822Name, 205 | "googletv@test.test"))); 206 | 207 | // This method is deprecated, but Android Eclair does not provide the 208 | // generate() methods. 209 | X509Certificate cert = certGen.generate(pair.getPrivate(), "BC"); 210 | return cert; 211 | } 212 | 213 | /** 214 | * Creates an AuthorityKeyIdentifier from a public key, name, and serial 215 | * number. 216 | *

217 | * {@link AuthorityKeyIdentifierStructure} is almost perfect for 218 | * this, but sadly it does not have a constructor suitable for us: 219 | * {@link AuthorityKeyIdentifierStructure#AuthorityKeyIdentifierStructure(PublicKey)} 220 | * does not set the serial number or name (which is important to us), while 221 | * {@link AuthorityKeyIdentifierStructure#AuthorityKeyIdentifierStructure(X509Certificate)} 222 | * sets those fields but needs a completed certificate to do so. 223 | *

224 | * This method addresses the gap in available {@link AuthorityKeyIdentifier} 225 | * constructors provided by BouncyCastle; its implementation is derived from 226 | * {@link AuthorityKeyIdentifierStructure#AuthorityKeyIdentifierStructure(X509Certificate)}. 227 | * 228 | * @param publicKey 229 | * the public key 230 | * @param dnName 231 | * the name 232 | * @param serialNumber 233 | * the serial number 234 | * @return a new {@link AuthorityKeyIdentifier} 235 | */ 236 | private static AuthorityKeyIdentifier createAuthorityKeyIdentifier( 237 | PublicKey publicKey, org.bouncycastle.asn1.x500.X500Name dnName, 238 | BigInteger serialNumber) { 239 | GeneralName genName = new GeneralName(dnName); 240 | SubjectPublicKeyInfo info; 241 | try { 242 | info = new SubjectPublicKeyInfo((ASN1Sequence) new ASN1InputStream( 243 | publicKey.getEncoded()).readObject()); 244 | } catch (IOException e) { 245 | throw new RuntimeException("Error encoding public key"); 246 | } 247 | return new AuthorityKeyIdentifier(info, new GeneralNames(genName), 248 | serialNumber); 249 | } 250 | 251 | /** 252 | * Wrapper for 253 | * {@link SslUtil#generateX509V3Certificate(KeyPair, String, Date, Date, BigInteger)} 254 | * which uses a default validity period and serial number. 255 | *

256 | * The validity period is Jan 1 2009 - Jan 1 2099. The serial number is the 257 | * current system time. 258 | */ 259 | public static X509Certificate generateX509V3Certificate(KeyPair pair, 260 | String name) throws GeneralSecurityException { 261 | Calendar calendar = Calendar.getInstance(); 262 | calendar.set(2009, 0, 1); 263 | Date notBefore = new Date(calendar.getTimeInMillis()); 264 | calendar.set(2099, 0, 1); 265 | Date notAfter = new Date(calendar.getTimeInMillis()); 266 | 267 | BigInteger serialNumber = BigInteger.valueOf(Math.abs(System 268 | .currentTimeMillis())); 269 | 270 | return generateX509V3Certificate(pair, name, notBefore, notAfter, 271 | serialNumber); 272 | } 273 | 274 | /** 275 | * Wrapper for 276 | * {@link SslUtil#generateX509V3Certificate(KeyPair, String, Date, Date, BigInteger)} 277 | * which uses a default validity period. 278 | *

279 | * The validity period is Jan 1 2009 - Jan 1 2099. 280 | */ 281 | public static X509Certificate generateX509V3Certificate(KeyPair pair, 282 | String name, BigInteger serialNumber) 283 | throws GeneralSecurityException { 284 | Calendar calendar = Calendar.getInstance(); 285 | calendar.set(2009, 0, 1); 286 | Date notBefore = new Date(calendar.getTimeInMillis()); 287 | calendar.set(2099, 0, 1); 288 | Date notAfter = new Date(calendar.getTimeInMillis()); 289 | 290 | return generateX509V3Certificate(pair, name, notBefore, notAfter, 291 | serialNumber); 292 | } 293 | 294 | /** 295 | * Generates a new {@code SSLContext} suitable for a test environment. 296 | *

297 | * A new {@link KeyPair}, {@link X509Certificate}, {@link DummyTrustManager} 298 | * , and an empty {@link KeyStore} are created and used to initialize the 299 | * context. 300 | * 301 | * @return the new context 302 | * @throws GeneralSecurityException 303 | * if an error occurred during initialization 304 | * @throws IOException 305 | * if an empty KeyStore could not be generated 306 | */ 307 | public SSLContext generateTestSslContext() throws GeneralSecurityException, 308 | IOException { 309 | SSLContext sslcontext = SSLContext.getInstance("SSLv3"); 310 | KeyManager[] keyManagers = SslUtil.generateTestServerKeyManager( 311 | "SunX509", "test"); 312 | sslcontext.init(keyManagers, 313 | new TrustManager[] { new DummyTrustManager() }, null); 314 | return sslcontext; 315 | } 316 | 317 | /** 318 | * Creates a new pain of {@link KeyManager}s, backed by a keystore file. 319 | * 320 | * @param keyManagerInstanceName 321 | * name of the {@link KeyManagerFactory} to request 322 | * @param fileName 323 | * the name of the keystore to load 324 | * @param password 325 | * the password for the keystore 326 | * @return the new object 327 | * @throws GeneralSecurityException 328 | * if an error occurred during initialization 329 | * @throws IOException 330 | * if the keystore could not be loaded 331 | */ 332 | public static KeyManager[] getFileBackedKeyManagers( 333 | String keyManagerInstanceName, String fileName, String password) 334 | throws GeneralSecurityException, IOException { 335 | KeyManagerFactory km = KeyManagerFactory 336 | .getInstance(keyManagerInstanceName); 337 | KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); 338 | ks.load(new FileInputStream(fileName), password.toCharArray()); 339 | km.init(ks, password.toCharArray()); 340 | return km.getKeyManagers(); 341 | } 342 | 343 | /** 344 | * Creates a pair of {@link KeyManager}s suitable for use in testing. 345 | *

346 | * A new {@link KeyPair} and {@link X509Certificate} are created and used to 347 | * initialize the KeyManager. 348 | * 349 | * @param keyManagerInstanceName 350 | * name of the {@link KeyManagerFactory} 351 | * @param password 352 | * password to apply to the new key store 353 | * @return the new key managers 354 | * @throws GeneralSecurityException 355 | * if an error occurred during initialization 356 | * @throws IOException 357 | * if the keystore could not be generated 358 | */ 359 | public static KeyManager[] generateTestServerKeyManager( 360 | String keyManagerInstanceName, String password) 361 | throws GeneralSecurityException, IOException { 362 | KeyManagerFactory km = KeyManagerFactory 363 | .getInstance(keyManagerInstanceName); 364 | KeyPair pair = SslUtil.generateRsaKeyPair(); 365 | X509Certificate cert = SslUtil.generateX509V1Certificate(pair, 366 | "CN=Test Server Cert"); 367 | Certificate[] chain = { cert }; 368 | 369 | KeyStore ks = SslUtil.getEmptyKeyStore(); 370 | ks.setKeyEntry("test-server", pair.getPrivate(), 371 | password.toCharArray(), chain); 372 | km.init(ks, password.toCharArray()); 373 | return km.getKeyManagers(); 374 | } 375 | 376 | } 377 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/util/Utils.java: -------------------------------------------------------------------------------- 1 | package com.kunal52.util; 2 | 3 | import javax.net.ssl.SSLPeerUnverifiedException; 4 | import javax.net.ssl.SSLSession; 5 | import java.math.BigInteger; 6 | import java.security.cert.Certificate; 7 | 8 | public class Utils { 9 | public static final long intBigEndianBytesToLong(byte[] input) { 10 | assert (input.length == 4); 11 | long ret = (long) (input[0]) & 0xff; 12 | ret <<= 8; 13 | ret |= (long) (input[1]) & 0xff; 14 | ret <<= 8; 15 | ret |= (long) (input[2]) & 0xff; 16 | ret <<= 8; 17 | ret |= (long) (input[3]) & 0xff; 18 | return ret; 19 | } 20 | 21 | public static final byte[] intToBigEndianIntBytes(int intVal) { 22 | byte[] outBuf = new byte[4]; 23 | outBuf[0] = (byte) ((intVal >> 24) & 0xff); 24 | outBuf[1] = (byte) ((intVal >> 16) & 0xff); 25 | outBuf[2] = (byte) ((intVal >> 8) & 0xff); 26 | outBuf[3] = (byte) (intVal & 0xff); 27 | return outBuf; 28 | } 29 | 30 | public static Certificate getPeerCert(SSLSession session) { 31 | try { 32 | // Peer certificate 33 | Certificate[] certs = session.getPeerCertificates(); 34 | if (certs == null || certs.length < 1) { 35 | System.out.println("No Certificate"); 36 | } 37 | return certs[0]; 38 | } catch (SSLPeerUnverifiedException e) { 39 | System.out.println("No Certificate"); 40 | } 41 | return null; 42 | } 43 | 44 | public static Certificate getLocalCert(SSLSession session) { 45 | Certificate[] certs = session.getLocalCertificates(); 46 | if (certs == null || certs.length < 1) { 47 | System.out.println("No Certificate"); 48 | return null; 49 | } 50 | return certs[0]; 51 | } 52 | 53 | public static String bytesToHexString(byte[] bytes) { 54 | if (bytes == null || bytes.length == 0) { 55 | return ""; 56 | } 57 | BigInteger bigint = new BigInteger(1, bytes); 58 | int formatLen = bytes.length * 2; 59 | return String.format("%0" + formatLen + "x", bigint); 60 | } 61 | 62 | /** 63 | * Converts a string of hex characters to a byte array. 64 | * 65 | * @param hexstr the string of hex characters 66 | * @return a byte array representation 67 | */ 68 | public static byte[] hexStringToBytes(String hexstr) { 69 | if (hexstr == null || hexstr.length() == 0 || (hexstr.length() % 2) != 0) { 70 | throw new IllegalArgumentException("Bad input string."); 71 | } 72 | 73 | byte[] result = new byte[hexstr.length() / 2]; 74 | for (int i=0; i < result.length; i++) { 75 | result[i] = (byte) Integer.parseInt(hexstr.substring(2 * i, 2 * (i + 1)), 76 | 16); 77 | } 78 | return result; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/wire/MessageManager.java: -------------------------------------------------------------------------------- 1 | package com.kunal52.wire; 2 | 3 | import com.kunal52.AndroidRemoteTv; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.nio.ByteBuffer; 8 | import java.util.Arrays; 9 | 10 | public abstract class MessageManager { 11 | 12 | private final Logger logger = LoggerFactory.getLogger(MessageManager.class); 13 | public ByteBuffer mPacketBuffer = ByteBuffer.allocate(65539); 14 | 15 | public byte[] addLengthAndCreate(byte[] message) { 16 | int length = message.length; 17 | mPacketBuffer.put((byte) length).put(message); 18 | byte[] buf = new byte[mPacketBuffer.position()]; 19 | System.arraycopy(mPacketBuffer.array(), mPacketBuffer.arrayOffset(), buf, 0, mPacketBuffer.position()); 20 | mPacketBuffer.clear(); 21 | logger.debug("Sending bytes {}", Arrays.toString(buf)); 22 | return buf; 23 | } 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/kunal52/wire/PacketParser.java: -------------------------------------------------------------------------------- 1 | package com.kunal52.wire; 2 | 3 | import com.google.protobuf.InvalidProtocolBufferException; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | 8 | public abstract class PacketParser extends Thread { 9 | private final InputStream mInputStream; 10 | 11 | private boolean isAbort = false; 12 | 13 | 14 | public PacketParser(InputStream inputStream) { 15 | mInputStream = inputStream; 16 | } 17 | 18 | @Override 19 | public void run() { 20 | int available; 21 | int bytesRead = 0; 22 | while (!isAbort) 23 | try { 24 | available = mInputStream.read(); 25 | byte[] buf = new byte[available]; 26 | while (bytesRead < available) { 27 | int read = mInputStream.read(buf, bytesRead, available - bytesRead); 28 | if (read < 0) { 29 | throw new IOException("Stream closed while reading."); 30 | } 31 | bytesRead += read; 32 | } 33 | 34 | bytesRead = 0; 35 | messageBufferReceived(buf); 36 | } catch (IOException e) { 37 | isAbort=true; 38 | throw new RuntimeException(e); 39 | } 40 | } 41 | 42 | 43 | public void abort() { 44 | isAbort = true; 45 | } 46 | 47 | public abstract void messageBufferReceived(byte[] buf); 48 | } 49 | -------------------------------------------------------------------------------- /src/main/proto/pairingmessage.proto: -------------------------------------------------------------------------------- 1 | // pairingmessage.proto 2 | syntax = "proto3"; 3 | package com.kunal52.pairing; 4 | 5 | enum RoleType { 6 | ROLE_TYPE_UNKNOWN = 0; 7 | ROLE_TYPE_INPUT = 1; 8 | ROLE_TYPE_OUTPUT = 2; 9 | UNRECOGNIZED = -1; 10 | } 11 | 12 | message PairingRequest { 13 | string client_name = 2; 14 | string service_name = 1; 15 | } 16 | 17 | message PairingRequestAck { 18 | string server_name = 1; 19 | } 20 | 21 | message PairingEncoding { 22 | enum EncodingType { 23 | ENCODING_TYPE_UNKNOWN = 0; 24 | ENCODING_TYPE_ALPHANUMERIC = 1; 25 | ENCODING_TYPE_NUMERIC = 2; 26 | ENCODING_TYPE_HEXADECIMAL = 3; 27 | ENCODING_TYPE_QRCODE = 4; 28 | UNRECOGNIZED = -1; 29 | } 30 | EncodingType type = 1; 31 | uint32 symbol_length = 2; 32 | } 33 | 34 | message PairingOption { 35 | repeated PairingEncoding input_encodings = 1; 36 | repeated PairingEncoding output_encodings = 2; 37 | RoleType preferred_role = 3; 38 | } 39 | 40 | message PairingConfiguration { 41 | PairingEncoding encoding = 1; 42 | RoleType client_role = 2; 43 | } 44 | 45 | message PairingConfigurationAck { 46 | 47 | } 48 | 49 | message PairingSecret { 50 | bytes secret = 1; 51 | } 52 | 53 | message PairingSecretAck { 54 | bytes secret = 1; 55 | } 56 | 57 | message PairingMessage { 58 | enum Status { 59 | UNKNOWN = 0; 60 | STATUS_OK = 200; 61 | STATUS_ERROR = 400; 62 | STATUS_BAD_CONFIGURATION = 401; 63 | STATUS_BAD_SECRET = 402; 64 | UNRECOGNIZED = -1; 65 | } 66 | int32 protocol_version = 1; 67 | Status status = 2; 68 | int32 request_case = 3; 69 | PairingRequest pairing_request = 10; 70 | PairingRequestAck pairing_request_ack = 11; 71 | PairingOption pairing_option = 20; 72 | PairingConfiguration pairing_configuration = 30; 73 | PairingConfigurationAck pairing_configuration_ack = 31; 74 | PairingSecret pairing_secret = 40; 75 | PairingSecretAck pairing_secret_ack = 41; 76 | } -------------------------------------------------------------------------------- /src/main/proto/remotemessage.proto: -------------------------------------------------------------------------------- 1 | // remotemessage.proto 2 | syntax = "proto3"; 3 | package com.kunal52.remote; 4 | 5 | message RemoteAppLinkLaunchRequest { 6 | string app_link = 1; 7 | } 8 | 9 | message RemoteResetPreferredAudioDevice { 10 | 11 | } 12 | 13 | message RemoteSetPreferredAudioDevice { 14 | 15 | } 16 | 17 | message RemoteAdjustVolumeLevel { 18 | 19 | } 20 | 21 | message RemoteSetVolumeLevel { 22 | uint32 unknown1 = 1; 23 | uint32 unknown2 = 2; 24 | string player_model = 3; 25 | uint32 unknown4 = 4; 26 | uint32 unknown5 = 5; 27 | uint32 volume_max = 6; 28 | uint32 volume_level = 7; 29 | bool volume_muted = 8; 30 | } 31 | 32 | message RemoteStart { 33 | bool started = 1; 34 | } 35 | 36 | message RemoteVoiceEnd { 37 | 38 | } 39 | 40 | message RemoteVoicePayload { 41 | 42 | } 43 | 44 | message RemoteVoiceBegin { 45 | 46 | } 47 | 48 | message RemoteTextFieldStatus { 49 | int32 counter_field = 1; 50 | string value = 2; 51 | int32 start = 3; 52 | int32 end = 4; 53 | int32 int5 = 5; 54 | string label = 6; 55 | } 56 | 57 | message RemoteImeShowRequest { 58 | RemoteTextFieldStatus remote_text_field_status = 2; 59 | } 60 | 61 | message RemoteEditInfo { 62 | int32 insert = 2; 63 | } 64 | 65 | message RemoteImeBatchEdit { 66 | int32 ime_counter = 1; 67 | int32 field_counter = 2; 68 | RemoteEditInfo edit_info = 3; 69 | } 70 | 71 | message RemoteAppInfo { 72 | int32 counter = 1; 73 | int32 int2 = 2; 74 | int32 int3 = 3; 75 | string int4 = 4; 76 | int32 int7 = 7; 77 | int32 int8 = 8; 78 | string label = 10; 79 | string app_package = 12; 80 | int32 int13 = 13; 81 | } 82 | 83 | message RemoteImeKeyInject { 84 | RemoteAppInfo app_info = 1; 85 | RemoteTextFieldStatus text_field_status = 2; 86 | } 87 | 88 | enum RemoteKeyCode { 89 | KEYCODE_UNKNOWN = 0; 90 | KEYCODE_SOFT_LEFT = 1; 91 | KEYCODE_SOFT_RIGHT = 2; 92 | KEYCODE_HOME = 3; 93 | KEYCODE_BACK = 4; 94 | KEYCODE_CALL = 5; 95 | KEYCODE_ENDCALL = 6; 96 | KEYCODE_0 = 7; 97 | KEYCODE_1 = 8; 98 | KEYCODE_2 = 9; 99 | KEYCODE_3 = 10; 100 | KEYCODE_4 = 11; 101 | KEYCODE_5 = 12; 102 | KEYCODE_6 = 13; 103 | KEYCODE_7 = 14; 104 | KEYCODE_8 = 15; 105 | KEYCODE_9 = 16; 106 | KEYCODE_STAR = 17; 107 | KEYCODE_POUND = 18; 108 | KEYCODE_DPAD_UP = 19; 109 | KEYCODE_DPAD_DOWN = 20; 110 | KEYCODE_DPAD_LEFT = 21; 111 | KEYCODE_DPAD_RIGHT = 22; 112 | KEYCODE_DPAD_CENTER = 23; 113 | KEYCODE_VOLUME_UP = 24; 114 | KEYCODE_VOLUME_DOWN = 25; 115 | KEYCODE_POWER = 26; 116 | KEYCODE_CAMERA = 27; 117 | KEYCODE_CLEAR = 28; 118 | KEYCODE_A = 29; 119 | KEYCODE_B = 30; 120 | KEYCODE_C = 31; 121 | KEYCODE_D = 32; 122 | KEYCODE_E = 33; 123 | KEYCODE_F = 34; 124 | KEYCODE_G = 35; 125 | KEYCODE_H = 36; 126 | KEYCODE_I = 37; 127 | KEYCODE_J = 38; 128 | KEYCODE_K = 39; 129 | KEYCODE_L = 40; 130 | KEYCODE_M = 41; 131 | KEYCODE_N = 42; 132 | KEYCODE_O = 43; 133 | KEYCODE_P = 44; 134 | KEYCODE_Q = 45; 135 | KEYCODE_R = 46; 136 | KEYCODE_S = 47; 137 | KEYCODE_T = 48; 138 | KEYCODE_U = 49; 139 | KEYCODE_V = 50; 140 | KEYCODE_W = 51; 141 | KEYCODE_X = 52; 142 | KEYCODE_Y = 53; 143 | KEYCODE_Z = 54; 144 | KEYCODE_COMMA = 55; 145 | KEYCODE_PERIOD = 56; 146 | KEYCODE_ALT_LEFT = 57; 147 | KEYCODE_ALT_RIGHT = 58; 148 | KEYCODE_SHIFT_LEFT = 59; 149 | KEYCODE_SHIFT_RIGHT = 60; 150 | KEYCODE_TAB = 61; 151 | KEYCODE_SPACE = 62; 152 | KEYCODE_SYM = 63; 153 | KEYCODE_EXPLORER = 64; 154 | KEYCODE_ENVELOPE = 65; 155 | KEYCODE_ENTER = 66; 156 | KEYCODE_DEL = 67; 157 | KEYCODE_GRAVE = 68; 158 | KEYCODE_MINUS = 69; 159 | KEYCODE_EQUALS = 70; 160 | KEYCODE_LEFT_BRACKET = 71; 161 | KEYCODE_RIGHT_BRACKET = 72; 162 | KEYCODE_BACKSLASH = 73; 163 | KEYCODE_SEMICOLON = 74; 164 | KEYCODE_APOSTROPHE = 75; 165 | KEYCODE_SLASH = 76; 166 | KEYCODE_AT = 77; 167 | KEYCODE_NUM = 78; 168 | KEYCODE_HEADSETHOOK = 79; 169 | KEYCODE_FOCUS = 80; 170 | KEYCODE_PLUS = 81; 171 | KEYCODE_MENU = 82; 172 | KEYCODE_NOTIFICATION = 83; 173 | KEYCODE_SEARCH = 84; 174 | KEYCODE_MEDIA_PLAY_PAUSE= 85; 175 | KEYCODE_MEDIA_STOP = 86; 176 | KEYCODE_MEDIA_NEXT = 87; 177 | KEYCODE_MEDIA_PREVIOUS = 88; 178 | KEYCODE_MEDIA_REWIND = 89; 179 | KEYCODE_MEDIA_FAST_FORWARD = 90; 180 | KEYCODE_MUTE = 91; 181 | KEYCODE_PAGE_UP = 92; 182 | KEYCODE_PAGE_DOWN = 93; 183 | KEYCODE_PICTSYMBOLS = 94; 184 | KEYCODE_SWITCH_CHARSET = 95; 185 | KEYCODE_BUTTON_A = 96; 186 | KEYCODE_BUTTON_B = 97; 187 | KEYCODE_BUTTON_C = 98; 188 | KEYCODE_BUTTON_X = 99; 189 | KEYCODE_BUTTON_Y = 100; 190 | KEYCODE_BUTTON_Z = 101; 191 | KEYCODE_BUTTON_L1 = 102; 192 | KEYCODE_BUTTON_R1 = 103; 193 | KEYCODE_BUTTON_L2 = 104; 194 | KEYCODE_BUTTON_R2 = 105; 195 | KEYCODE_BUTTON_THUMBL = 106; 196 | KEYCODE_BUTTON_THUMBR = 107; 197 | KEYCODE_BUTTON_START = 108; 198 | KEYCODE_BUTTON_SELECT = 109; 199 | KEYCODE_BUTTON_MODE = 110; 200 | KEYCODE_ESCAPE = 111; 201 | KEYCODE_FORWARD_DEL = 112; 202 | KEYCODE_CTRL_LEFT = 113; 203 | KEYCODE_CTRL_RIGHT = 114; 204 | KEYCODE_CAPS_LOCK = 115; 205 | KEYCODE_SCROLL_LOCK = 116; 206 | KEYCODE_META_LEFT = 117; 207 | KEYCODE_META_RIGHT = 118; 208 | KEYCODE_FUNCTION = 119; 209 | KEYCODE_SYSRQ = 120; 210 | KEYCODE_BREAK = 121; 211 | KEYCODE_MOVE_HOME = 122; 212 | KEYCODE_MOVE_END = 123; 213 | KEYCODE_INSERT = 124; 214 | KEYCODE_FORWARD = 125; 215 | KEYCODE_MEDIA_PLAY = 126; 216 | KEYCODE_MEDIA_PAUSE = 127; 217 | KEYCODE_MEDIA_CLOSE = 128; 218 | KEYCODE_MEDIA_EJECT = 129; 219 | KEYCODE_MEDIA_RECORD = 130; 220 | KEYCODE_F1 = 131; 221 | KEYCODE_F2 = 132; 222 | KEYCODE_F3 = 133; 223 | KEYCODE_F4 = 134; 224 | KEYCODE_F5 = 135; 225 | KEYCODE_F6 = 136; 226 | KEYCODE_F7 = 137; 227 | KEYCODE_F8 = 138; 228 | KEYCODE_F9 = 139; 229 | KEYCODE_F10 = 140; 230 | KEYCODE_F11 = 141; 231 | KEYCODE_F12 = 142; 232 | KEYCODE_NUM_LOCK = 143; 233 | KEYCODE_NUMPAD_0 = 144; 234 | KEYCODE_NUMPAD_1 = 145; 235 | KEYCODE_NUMPAD_2 = 146; 236 | KEYCODE_NUMPAD_3 = 147; 237 | KEYCODE_NUMPAD_4 = 148; 238 | KEYCODE_NUMPAD_5 = 149; 239 | KEYCODE_NUMPAD_6 = 150; 240 | KEYCODE_NUMPAD_7 = 151; 241 | KEYCODE_NUMPAD_8 = 152; 242 | KEYCODE_NUMPAD_9 = 153; 243 | KEYCODE_NUMPAD_DIVIDE = 154; 244 | KEYCODE_NUMPAD_MULTIPLY = 155; 245 | KEYCODE_NUMPAD_SUBTRACT = 156; 246 | KEYCODE_NUMPAD_ADD = 157; 247 | KEYCODE_NUMPAD_DOT = 158; 248 | KEYCODE_NUMPAD_COMMA = 159; 249 | KEYCODE_NUMPAD_ENTER = 160; 250 | KEYCODE_NUMPAD_EQUALS = 161; 251 | KEYCODE_NUMPAD_LEFT_PAREN = 162; 252 | KEYCODE_NUMPAD_RIGHT_PAREN = 163; 253 | KEYCODE_VOLUME_MUTE = 164; 254 | KEYCODE_INFO = 165; 255 | KEYCODE_CHANNEL_UP = 166; 256 | KEYCODE_CHANNEL_DOWN = 167; 257 | KEYCODE_ZOOM_IN = 168; 258 | KEYCODE_ZOOM_OUT = 169; 259 | KEYCODE_TV = 170; 260 | KEYCODE_WINDOW = 171; 261 | KEYCODE_GUIDE = 172; 262 | KEYCODE_DVR = 173; 263 | KEYCODE_BOOKMARK = 174; 264 | KEYCODE_CAPTIONS = 175; 265 | KEYCODE_SETTINGS = 176; 266 | KEYCODE_TV_POWER = 177; 267 | KEYCODE_TV_INPUT = 178; 268 | KEYCODE_STB_POWER = 179; 269 | KEYCODE_STB_INPUT = 180; 270 | KEYCODE_AVR_POWER = 181; 271 | KEYCODE_AVR_INPUT = 182; 272 | KEYCODE_PROG_RED = 183; 273 | KEYCODE_PROG_GREEN = 184; 274 | KEYCODE_PROG_YELLOW = 185; 275 | KEYCODE_PROG_BLUE = 186; 276 | KEYCODE_APP_SWITCH = 187; 277 | KEYCODE_BUTTON_1 = 188; 278 | KEYCODE_BUTTON_2 = 189; 279 | KEYCODE_BUTTON_3 = 190; 280 | KEYCODE_BUTTON_4 = 191; 281 | KEYCODE_BUTTON_5 = 192; 282 | KEYCODE_BUTTON_6 = 193; 283 | KEYCODE_BUTTON_7 = 194; 284 | KEYCODE_BUTTON_8 = 195; 285 | KEYCODE_BUTTON_9 = 196; 286 | KEYCODE_BUTTON_10 = 197; 287 | KEYCODE_BUTTON_11 = 198; 288 | KEYCODE_BUTTON_12 = 199; 289 | KEYCODE_BUTTON_13 = 200; 290 | KEYCODE_BUTTON_14 = 201; 291 | KEYCODE_BUTTON_15 = 202; 292 | KEYCODE_BUTTON_16 = 203; 293 | KEYCODE_LANGUAGE_SWITCH = 204; 294 | KEYCODE_MANNER_MODE = 205; 295 | KEYCODE_3D_MODE = 206; 296 | KEYCODE_CONTACTS = 207; 297 | KEYCODE_CALENDAR = 208; 298 | KEYCODE_MUSIC = 209; 299 | KEYCODE_CALCULATOR = 210; 300 | KEYCODE_ZENKAKU_HANKAKU = 211; 301 | KEYCODE_EISU = 212; 302 | KEYCODE_MUHENKAN = 213; 303 | KEYCODE_HENKAN = 214; 304 | KEYCODE_KATAKANA_HIRAGANA = 215; 305 | KEYCODE_YEN = 216; 306 | KEYCODE_RO = 217; 307 | KEYCODE_KANA = 218; 308 | KEYCODE_ASSIST = 219; 309 | KEYCODE_BRIGHTNESS_DOWN = 220; 310 | KEYCODE_BRIGHTNESS_UP = 221; 311 | KEYCODE_MEDIA_AUDIO_TRACK = 222; 312 | KEYCODE_SLEEP = 223; 313 | KEYCODE_WAKEUP = 224; 314 | KEYCODE_PAIRING = 225; 315 | KEYCODE_MEDIA_TOP_MENU = 226; 316 | KEYCODE_11 = 227; 317 | KEYCODE_12 = 228; 318 | KEYCODE_LAST_CHANNEL = 229; 319 | KEYCODE_TV_DATA_SERVICE = 230; 320 | KEYCODE_VOICE_ASSIST = 231; 321 | KEYCODE_TV_RADIO_SERVICE = 232; 322 | KEYCODE_TV_TELETEXT = 233; 323 | KEYCODE_TV_NUMBER_ENTRY = 234; 324 | KEYCODE_TV_TERRESTRIAL_ANALOG = 235; 325 | KEYCODE_TV_TERRESTRIAL_DIGITAL = 236; 326 | KEYCODE_TV_SATELLITE = 237; 327 | KEYCODE_TV_SATELLITE_BS = 238; 328 | KEYCODE_TV_SATELLITE_CS = 239; 329 | KEYCODE_TV_SATELLITE_SERVICE = 240; 330 | KEYCODE_TV_NETWORK = 241; 331 | KEYCODE_TV_ANTENNA_CABLE = 242; 332 | KEYCODE_TV_INPUT_HDMI_1 = 243; 333 | KEYCODE_TV_INPUT_HDMI_2 = 244; 334 | KEYCODE_TV_INPUT_HDMI_3 = 245; 335 | KEYCODE_TV_INPUT_HDMI_4 = 246; 336 | KEYCODE_TV_INPUT_COMPOSITE_1 = 247; 337 | KEYCODE_TV_INPUT_COMPOSITE_2 = 248; 338 | KEYCODE_TV_INPUT_COMPONENT_1 = 249; 339 | KEYCODE_TV_INPUT_COMPONENT_2 = 250; 340 | KEYCODE_TV_INPUT_VGA_1 = 251; 341 | KEYCODE_TV_AUDIO_DESCRIPTION = 252; 342 | KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP = 253; 343 | KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN = 254; 344 | KEYCODE_TV_ZOOM_MODE = 255; 345 | KEYCODE_TV_CONTENTS_MENU = 256; 346 | KEYCODE_TV_MEDIA_CONTEXT_MENU = 257; 347 | KEYCODE_TV_TIMER_PROGRAMMING = 258; 348 | KEYCODE_HELP = 259; 349 | KEYCODE_NAVIGATE_PREVIOUS = 260; 350 | KEYCODE_NAVIGATE_NEXT = 261; 351 | KEYCODE_NAVIGATE_IN = 262; 352 | KEYCODE_NAVIGATE_OUT = 263; 353 | KEYCODE_STEM_PRIMARY = 264; 354 | KEYCODE_STEM_1 = 265; 355 | KEYCODE_STEM_2 = 266; 356 | KEYCODE_STEM_3 = 267; 357 | KEYCODE_DPAD_UP_LEFT = 268; 358 | KEYCODE_DPAD_DOWN_LEFT = 269; 359 | KEYCODE_DPAD_UP_RIGHT = 270; 360 | KEYCODE_DPAD_DOWN_RIGHT = 271; 361 | KEYCODE_MEDIA_SKIP_FORWARD = 272; 362 | KEYCODE_MEDIA_SKIP_BACKWARD = 273; 363 | KEYCODE_MEDIA_STEP_FORWARD = 274; 364 | KEYCODE_MEDIA_STEP_BACKWARD = 275; 365 | KEYCODE_SOFT_SLEEP = 276; 366 | KEYCODE_CUT = 277; 367 | KEYCODE_COPY = 278; 368 | KEYCODE_PASTE = 279; 369 | KEYCODE_SYSTEM_NAVIGATION_UP = 280; 370 | KEYCODE_SYSTEM_NAVIGATION_DOWN = 281; 371 | KEYCODE_SYSTEM_NAVIGATION_LEFT = 282; 372 | KEYCODE_SYSTEM_NAVIGATION_RIGHT = 283; 373 | KEYCODE_ALL_APPS = 284; 374 | KEYCODE_REFRESH = 285; 375 | KEYCODE_THUMBS_UP = 286; 376 | KEYCODE_THUMBS_DOWN = 287; 377 | KEYCODE_PROFILE_SWITCH = 288; 378 | KEYCODE_VIDEO_APP_1 = 289; 379 | KEYCODE_VIDEO_APP_2 = 290; 380 | KEYCODE_VIDEO_APP_3 = 291; 381 | KEYCODE_VIDEO_APP_4 = 292; 382 | KEYCODE_VIDEO_APP_5 = 293; 383 | KEYCODE_VIDEO_APP_6 = 294; 384 | KEYCODE_VIDEO_APP_7 = 295; 385 | KEYCODE_VIDEO_APP_8 = 296; 386 | KEYCODE_FEATURED_APP_1 = 297; 387 | KEYCODE_FEATURED_APP_2 = 298; 388 | KEYCODE_FEATURED_APP_3 = 299; 389 | KEYCODE_FEATURED_APP_4 = 300; 390 | KEYCODE_DEMO_APP_1 = 301; 391 | KEYCODE_DEMO_APP_2 = 302; 392 | KEYCODE_DEMO_APP_3 = 303; 393 | KEYCODE_DEMO_APP_4 = 304; 394 | } 395 | 396 | enum RemoteDirection { 397 | UNKNOWN_DIRECTION = 0; 398 | START_LONG = 1; 399 | END_LONG = 2; 400 | SHORT = 3; 401 | } 402 | 403 | message RemoteKeyInject { 404 | RemoteKeyCode key_code = 1; 405 | RemoteDirection direction = 2; 406 | } 407 | 408 | message RemotePingResponse { 409 | int32 val1 = 1; 410 | } 411 | 412 | message RemotePingRequest { 413 | int32 val1 = 1; 414 | int32 val2 = 2; 415 | } 416 | 417 | message RemoteSetActive { 418 | int32 active = 1; 419 | } 420 | 421 | message RemoteDeviceInfo { 422 | string model = 1; 423 | string vendor = 2; 424 | int32 unknown1 = 3; 425 | string unknown2 = 4; 426 | string package_name = 5; 427 | string app_version = 6; 428 | } 429 | 430 | message RemoteConfigure { 431 | int32 code1 = 1; 432 | RemoteDeviceInfo device_info = 2; 433 | } 434 | 435 | message RemoteError{ 436 | bool value = 1; 437 | RemoteMessage message = 2; 438 | } 439 | 440 | message RemoteMessage { 441 | RemoteConfigure remote_configure = 1; 442 | RemoteSetActive remote_set_active = 2; 443 | RemoteError remote_error = 3; 444 | RemotePingRequest remote_ping_request = 8; 445 | RemotePingResponse remote_ping_response = 9; 446 | RemoteKeyInject remote_key_inject = 10; 447 | RemoteImeKeyInject remote_ime_key_inject = 20; 448 | RemoteImeBatchEdit remote_ime_batch_edit = 21; 449 | RemoteImeShowRequest remote_ime_show_request = 22; 450 | RemoteVoiceBegin remote_voice_begin = 30; 451 | RemoteVoicePayload remote_voice_payload = 31; 452 | RemoteVoiceEnd remote_voice_end = 32; 453 | RemoteStart remote_start = 40; 454 | RemoteSetVolumeLevel remote_set_volume_level = 50; 455 | RemoteAdjustVolumeLevel remote_adjust_volume_level = 51; 456 | RemoteSetPreferredAudioDevice remote_set_preferred_audio_device = 60; 457 | RemoteResetPreferredAudioDevice remote_reset_preferred_audio_device = 61; 458 | RemoteAppLinkLaunchRequest remote_app_link_launch_request = 90; 459 | } 460 | -------------------------------------------------------------------------------- /src/test/java/com/kunal52/AndroidRemoteTvTest.java: -------------------------------------------------------------------------------- 1 | package com.kunal52; 2 | 3 | import static org.junit.Assert.assertTrue; 4 | 5 | import org.junit.Test; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class AndroidRemoteTvTest 11 | { 12 | /** 13 | * Rigorous Test :-) 14 | */ 15 | @Test 16 | public void shouldAnswerWithTrue() 17 | { 18 | assertTrue( true ); 19 | } 20 | } 21 | --------------------------------------------------------------------------------