[CDM Transport] Exchange platform info to decide which Transport to
create

Bug: 253307662

Test: SystemDataTransferTest & SystemDataTransportTest
Change-Id: I26b03a8323d2870d0d772dbef34dd64ed8d4fe1b
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 9f27f72..3fffdbe 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -23,6 +23,7 @@
 import static android.content.ComponentName.createRelative;
 
 import static com.android.server.companion.Utils.prepareForIpc;
+import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PERMISSION_RESTORE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -91,7 +92,8 @@
         mAssociationStore = associationStore;
         mSystemDataTransferRequestStore = systemDataTransferRequestStore;
         mTransportManager = transportManager;
-        mTransportManager.setListener(this::onReceivePermissionRestore);
+        mTransportManager.addListener(MESSAGE_REQUEST_PERMISSION_RESTORE,
+                this::onReceivePermissionRestore);
         mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class);
         mExecutor = Executors.newSingleThreadExecutor();
     }
diff --git a/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java b/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java
index adaee75..1559a3f 100644
--- a/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java
+++ b/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java
@@ -35,7 +35,7 @@
 /**
  * Helper class to perform attestation verification synchronously.
  */
-class AttestationVerifier {
+public class AttestationVerifier {
     private static final long ATTESTATION_VERIFICATION_TIMEOUT_SECONDS = 10; // 10 seconds
     private static final String PARAM_OWNED_BY_SYSTEM = "android.key_owned_by_system";
 
diff --git a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
index 13dba84..05b6022 100644
--- a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
+++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
@@ -110,7 +110,7 @@
         this(in, out, callback, null, new AttestationVerifier(context));
     }
 
-    private SecureChannel(
+    public SecureChannel(
             final InputStream in,
             final OutputStream out,
             Callback callback,
@@ -381,9 +381,10 @@
 
     private void exchangeAuthentication()
             throws IOException, GeneralSecurityException, BadHandleException, CryptoException {
-        if (mVerifier == null) {
+        if (mPreSharedKey != null) {
             exchangePreSharedKey();
-        } else {
+        }
+        if (mVerifier != null) {
             exchangeAttestation();
         }
     }
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 6a53adf..2abdcb1 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -19,9 +19,9 @@
 import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
 
 import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PERMISSION_RESTORE;
+import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PLATFORM_INFO;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.ActivityManagerInternal;
 import android.content.Context;
@@ -30,12 +30,17 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.ParcelFileDescriptor;
+import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.LocalServices;
+import com.android.server.companion.transport.Transport.Listener;
 
 import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Future;
 
@@ -44,6 +49,8 @@
     private static final String TAG = "CDM_CompanionTransportManager";
     private static final boolean DEBUG = false;
 
+    private static final int NON_ANDROID = -1;
+
     private boolean mSecureTransportEnabled = true;
 
     private static boolean isRequest(int message) {
@@ -54,24 +61,29 @@
         return (message & 0xFF000000) == 0x33000000;
     }
 
-    public interface Listener {
-        void onRequestPermissionRestore(byte[] data);
-    }
-
     private final Context mContext;
 
     @GuardedBy("mTransports")
     private final SparseArray<Transport> mTransports = new SparseArray<>();
 
-    @Nullable
-    private Listener mListener;
+    @NonNull
+    private final Map<Integer, Listener> mListeners = new HashMap<>();
+
+    private Transport mTempTransport;
 
     public CompanionTransportManager(Context context) {
         mContext = context;
     }
 
-    public void setListener(@NonNull Listener listener) {
-        mListener = listener;
+    /**
+     * Add a message listener when a message is received for the message type
+     */
+    @GuardedBy("mTransports")
+    public void addListener(int message, @NonNull Listener listener) {
+        mListeners.put(message, listener);
+        for (int i = 0; i < mTransports.size(); i++) {
+            mTransports.valueAt(i).addListener(message, listener);
+        }
     }
 
     /**
@@ -105,15 +117,7 @@
                 detachSystemDataTransport(packageName, userId, associationId);
             }
 
-            final Transport transport;
-            if (isSecureTransportEnabled(associationId)) {
-                transport = new SecureTransport(associationId, fd, mContext, mListener);
-            } else {
-                transport = new RawTransport(associationId, fd, mContext, mListener);
-            }
-
-            transport.start();
-            mTransports.put(associationId, transport);
+            initializeTransport(associationId, fd);
         }
     }
 
@@ -128,13 +132,85 @@
         }
     }
 
+    @GuardedBy("mTransports")
+    private void initializeTransport(int associationId, ParcelFileDescriptor fd) {
+        if (!isSecureTransportEnabled()) {
+            Transport transport = new RawTransport(associationId, fd, mContext);
+            for (Map.Entry<Integer, Listener> entry : mListeners.entrySet()) {
+                transport.addListener(entry.getKey(), entry.getValue());
+            }
+            transport.start();
+            mTransports.put(associationId, transport);
+            Slog.i(TAG, "RawTransport is created");
+            return;
+        }
+
+        // Exchange platform info to decide which transport should be created
+        mTempTransport = new RawTransport(associationId, fd, mContext);
+        for (Map.Entry<Integer, Listener> entry : mListeners.entrySet()) {
+            mTempTransport.addListener(entry.getKey(), entry.getValue());
+        }
+        mTempTransport.addListener(MESSAGE_REQUEST_PLATFORM_INFO, this::onPlatformInfoReceived);
+        mTempTransport.start();
+
+        int sdk = Build.VERSION.SDK_INT;
+        String release = Build.VERSION.RELEASE;
+        // data format: | SDK_INT (int) | release length (int) | release |
+        final ByteBuffer data = ByteBuffer.allocate(4 + 4 + release.getBytes().length)
+                .putInt(sdk)
+                .putInt(release.getBytes().length)
+                .put(release.getBytes());
+
+        // TODO: it should check if preSharedKey is given
+        mTempTransport.requestForResponse(MESSAGE_REQUEST_PLATFORM_INFO, data.array());
+    }
+
+    /**
+     * Depending on the remote platform info to decide which transport should be created
+     */
+    @GuardedBy("mTransports")
+    private void onPlatformInfoReceived(byte[] data) {
+        // TODO: it should check if preSharedKey is given
+
+        ByteBuffer buffer = ByteBuffer.wrap(data);
+        int remoteSdk = buffer.getInt();
+        byte[] remoteRelease = new byte[buffer.getInt()];
+        buffer.get(remoteRelease);
+
+        Slog.i(TAG, "Remote device SDK: " + remoteSdk + ", release:" + new String(remoteRelease));
+
+        Transport transport = mTempTransport;
+        mTempTransport = null;
+
+        int sdk = Build.VERSION.SDK_INT;
+        String release = Build.VERSION.RELEASE;
+        if (remoteSdk == NON_ANDROID) {
+            // TODO: pass in a real preSharedKey
+            transport = new SecureTransport(transport.getAssociationId(), transport.getFd(),
+                    mContext, null, null);
+        } else if (sdk < Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+                || remoteSdk < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+            // TODO: depending on the release version, either
+            //       1) using a RawTransport for old T versions
+            //       2) or an Ukey2 handshaked transport for UKey2 backported T versions
+        } else {
+            Slog.i(TAG, "Creating a secure channel");
+            transport = new SecureTransport(transport.getAssociationId(), transport.getFd(),
+                    mContext);
+            for (Map.Entry<Integer, Listener> entry : mListeners.entrySet()) {
+                transport.addListener(entry.getKey(), entry.getValue());
+            }
+            transport.start();
+        }
+        mTransports.put(transport.getAssociationId(), transport);
+    }
+
     public Future<?> requestPermissionRestore(int associationId, byte[] data) {
         synchronized (mTransports) {
             final Transport transport = mTransports.get(associationId);
             if (transport == null) {
                 return CompletableFuture.failedFuture(new IOException("Missing transport"));
             }
-
             return transport.requestForResponse(MESSAGE_REQUEST_PERMISSION_RESTORE, data);
         }
     }
@@ -146,10 +222,9 @@
         this.mSecureTransportEnabled = enabled;
     }
 
-    private boolean isSecureTransportEnabled(int associationId) {
+    private boolean isSecureTransportEnabled() {
         boolean enabled = !Build.IS_DEBUGGABLE || mSecureTransportEnabled;
 
-        // TODO: version comparison logic
         return enabled;
     }
 }
diff --git a/services/companion/java/com/android/server/companion/transport/CryptoManager.java b/services/companion/java/com/android/server/companion/transport/CryptoManager.java
index b08354a..a15939e 100644
--- a/services/companion/java/com/android/server/companion/transport/CryptoManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CryptoManager.java
@@ -16,51 +16,51 @@
 
 package com.android.server.companion.transport;
 
-import android.security.keystore.KeyGenParameterSpec;
-import android.security.keystore.KeyProperties;
 import android.util.Slog;
 
-import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableEntryException;
-import java.security.cert.CertificateException;
+import java.util.Arrays;
 
 import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
 import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.KeyGenerator;
 import javax.crypto.NoSuchPaddingException;
 import javax.crypto.SecretKey;
 import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
 
 /**
- * This class can be used to encrypt and decrypt bytes using Android Cryptography
+ * This class uses Java Cryptography to encrypt and decrypt messages
  */
 public class CryptoManager {
 
     private static final String TAG = "CDM_CryptoManager";
+    private static final int SECRET_KEY_LENGTH = 32;
+    private static final String ALGORITHM = "AES";
+    private static final String TRANSFORMATION = "AES/CBC/PKCS7Padding";
 
-    private static final String KEY_STORE_ALIAS = "cdm_secret";
-    private static final String ALGORITHM = KeyProperties.KEY_ALGORITHM_AES;
-    private static final String BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC;
-    private static final String PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7;
-    private static final String TRANSFORMATION = ALGORITHM + "/" + BLOCK_MODE + "/" + PADDING;
+    private final byte[] mPreSharedKey;
+    private Cipher mEncryptCipher;
+    private Cipher mDecryptCipher;
 
-    private final KeyStore mKeyStore;
+    private SecretKey mSecretKey;
 
-    public CryptoManager() {
-        // Initialize KeyStore
+    public CryptoManager(byte[] preSharedKey) {
+        if (preSharedKey == null) {
+            mPreSharedKey = Arrays.copyOf(new byte[0], SECRET_KEY_LENGTH);
+        } else {
+            mPreSharedKey = Arrays.copyOf(preSharedKey, SECRET_KEY_LENGTH);
+        }
+        mSecretKey = new SecretKeySpec(mPreSharedKey, ALGORITHM);
         try {
-            mKeyStore = KeyStore.getInstance("AndroidKeyStore");
-            mKeyStore.load(null);
-        } catch (KeyStoreException | IOException | NoSuchAlgorithmException
-                 | CertificateException e) {
-            throw new RuntimeException(e);
+            mEncryptCipher = Cipher.getInstance(TRANSFORMATION);
+            mEncryptCipher.init(Cipher.ENCRYPT_MODE, mSecretKey);
+            mDecryptCipher = Cipher.getInstance(TRANSFORMATION);
+        } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
+            Slog.e(TAG, e.getMessage());
         }
     }
 
@@ -69,21 +69,19 @@
      */
     public byte[] encrypt(byte[] input) {
         try {
-            // Encrypt using Cipher
-            Cipher encryptCipher = Cipher.getInstance(TRANSFORMATION);
-            encryptCipher.init(Cipher.ENCRYPT_MODE, getKey());
-            byte[] encryptedBytes = encryptCipher.doFinal(input);
+            if (mEncryptCipher == null) {
+                return null;
+            }
 
-            // Write to bytes
+            byte[] encryptedBytes = mEncryptCipher.doFinal(input);
             ByteBuffer buffer = ByteBuffer.allocate(
-                            4 + encryptCipher.getIV().length + 4 + encryptedBytes.length)
-                    .putInt(encryptCipher.getIV().length)
-                    .put(encryptCipher.getIV())
+                            4 + mEncryptCipher.getIV().length + 4 + encryptedBytes.length)
+                    .putInt(mEncryptCipher.getIV().length)
+                    .put(mEncryptCipher.getIV())
                     .putInt(encryptedBytes.length)
                     .put(encryptedBytes);
             return buffer.array();
-        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
-                 | IllegalBlockSizeException | BadPaddingException e) {
+        } catch (IllegalBlockSizeException | BadPaddingException e) {
             Slog.e(TAG, e.getMessage());
             return null;
         }
@@ -99,45 +97,20 @@
         byte[] encryptedBytes = new byte[buffer.getInt()];
         buffer.get(encryptedBytes);
         try {
-            Cipher decryptCipher = Cipher.getInstance(TRANSFORMATION);
-            decryptCipher.init(Cipher.DECRYPT_MODE, getKey(), new IvParameterSpec(iv));
-            return decryptCipher.doFinal(encryptedBytes);
-        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
-                 | InvalidAlgorithmParameterException | IllegalBlockSizeException
-                 | BadPaddingException e) {
+            mDecryptCipher.init(Cipher.DECRYPT_MODE, getKey(), new IvParameterSpec(iv));
+            return mDecryptCipher.doFinal(encryptedBytes);
+        } catch (InvalidKeyException | InvalidAlgorithmParameterException
+                 | IllegalBlockSizeException | BadPaddingException e) {
             Slog.e(TAG, e.getMessage());
             return null;
         }
     }
 
     private SecretKey getKey() {
-        try {
-            KeyStore.Entry keyEntry = mKeyStore.getEntry(KEY_STORE_ALIAS, null);
-            if (keyEntry instanceof KeyStore.SecretKeyEntry
-                    && ((KeyStore.SecretKeyEntry) keyEntry).getSecretKey() != null) {
-                return ((KeyStore.SecretKeyEntry) keyEntry).getSecretKey();
-            } else {
-                return createKey();
-            }
-        } catch (NoSuchAlgorithmException | UnrecoverableEntryException | KeyStoreException e) {
-            throw new RuntimeException(e);
+        if (mSecretKey != null) {
+            return mSecretKey;
         }
-    }
-
-    private SecretKey createKey() {
-        try {
-            KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
-            keyGenerator.init(
-                    new KeyGenParameterSpec.Builder(KEY_STORE_ALIAS,
-                            KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
-                            .setBlockModes(BLOCK_MODE)
-                            .setEncryptionPaddings(PADDING)
-                            .setUserAuthenticationRequired(false)
-                            .setRandomizedEncryptionRequired(true)
-                            .build());
-            return keyGenerator.generateKey();
-        } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
-            throw new RuntimeException(e);
-        }
+        mSecretKey = new SecretKeySpec(mPreSharedKey, ALGORITHM);
+        return mSecretKey;
     }
 }
diff --git a/services/companion/java/com/android/server/companion/transport/RawTransport.java b/services/companion/java/com/android/server/companion/transport/RawTransport.java
index 7c0c7cf..4060f6e 100644
--- a/services/companion/java/com/android/server/companion/transport/RawTransport.java
+++ b/services/companion/java/com/android/server/companion/transport/RawTransport.java
@@ -21,8 +21,6 @@
 import android.os.ParcelFileDescriptor;
 import android.util.Slog;
 
-import com.android.server.companion.transport.CompanionTransportManager.Listener;
-
 import libcore.io.IoUtils;
 import libcore.io.Streams;
 
@@ -32,8 +30,8 @@
 class RawTransport extends Transport {
     private volatile boolean mStopped;
 
-    RawTransport(int associationId, ParcelFileDescriptor fd, Context context, Listener listener) {
-        super(associationId, fd, context, listener);
+    RawTransport(int associationId, ParcelFileDescriptor fd, Context context) {
+        super(associationId, fd, context);
     }
 
     @Override
@@ -64,7 +62,7 @@
     protected void sendMessage(int message, int sequence, @NonNull byte[] data)
             throws IOException {
         if (DEBUG) {
-            Slog.d(TAG, "Sending message 0x" + Integer.toHexString(message)
+            Slog.e(TAG, "Sending message 0x" + Integer.toHexString(message)
                     + " sequence " + sequence + " length " + data.length
                     + " to association " + mAssociationId);
         }
diff --git a/services/companion/java/com/android/server/companion/transport/SecureTransport.java b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
index 4194130..cca0843 100644
--- a/services/companion/java/com/android/server/companion/transport/SecureTransport.java
+++ b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
@@ -21,8 +21,8 @@
 import android.os.ParcelFileDescriptor;
 import android.util.Slog;
 
+import com.android.server.companion.securechannel.AttestationVerifier;
 import com.android.server.companion.securechannel.SecureChannel;
-import com.android.server.companion.transport.CompanionTransportManager.Listener;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -37,14 +37,17 @@
 
     private final BlockingQueue<byte[]> mRequestQueue = new ArrayBlockingQueue<>(100);
 
-    SecureTransport(int associationId,
-            ParcelFileDescriptor fd,
-            Context context,
-            Listener listener) {
-        super(associationId, fd, context, listener);
+    SecureTransport(int associationId, ParcelFileDescriptor fd, Context context) {
+        super(associationId, fd, context);
         mSecureChannel = new SecureChannel(mRemoteIn, mRemoteOut, this, context);
     }
 
+    SecureTransport(int associationId, ParcelFileDescriptor fd, Context context,
+            byte[] preSharedKey, AttestationVerifier verifier) {
+        super(associationId, fd, context);
+        mSecureChannel = new SecureChannel(mRemoteIn, mRemoteOut, this, preSharedKey, verifier);
+    }
+
     @Override
     public void start() {
         mSecureChannel.start();
diff --git a/services/companion/java/com/android/server/companion/transport/Transport.java b/services/companion/java/com/android/server/companion/transport/Transport.java
index 923d424..e984c63 100644
--- a/services/companion/java/com/android/server/companion/transport/Transport.java
+++ b/services/companion/java/com/android/server/companion/transport/Transport.java
@@ -25,23 +25,28 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.server.companion.transport.CompanionTransportManager.Listener;
 
 import libcore.util.EmptyArray;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Future;
 import java.util.concurrent.atomic.AtomicInteger;
 
-abstract class Transport {
+/**
+ * This class represents the channel established between two devices.
+ */
+public abstract class Transport {
     protected static final String TAG = "CDM_CompanionTransport";
     protected static final boolean DEBUG = Build.IS_DEBUGGABLE;
 
     static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
-    static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
+    public static final int MESSAGE_REQUEST_PLATFORM_INFO = 0x63807086; // ?PFV
+    public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
 
     static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC
     static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI
@@ -49,11 +54,24 @@
     protected static final int HEADER_LENGTH = 12;
 
     protected final int mAssociationId;
+    protected final ParcelFileDescriptor mFd;
     protected final InputStream mRemoteIn;
     protected final OutputStream mRemoteOut;
     protected final Context mContext;
 
-    private final Listener mListener;
+    /** Message type -> Listener */
+    private final Map<Integer, Listener> mListeners;
+
+    /**
+     * Message listener
+     */
+    public interface Listener {
+        /**
+         * Called when a message is received
+         * @param data data content in the message
+         */
+        void onDataReceived(byte[] data);
+    }
 
     private static boolean isRequest(int message) {
         return (message & 0xFF000000) == 0x63000000;
@@ -68,16 +86,36 @@
             new SparseArray<>();
     protected final AtomicInteger mNextSequence = new AtomicInteger();
 
-    Transport(int associationId, ParcelFileDescriptor fd, Context context, Listener listener) {
-        this.mAssociationId = associationId;
-        this.mRemoteIn = new ParcelFileDescriptor.AutoCloseInputStream(fd);
-        this.mRemoteOut = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
-        this.mContext = context;
-        this.mListener = listener;
+    Transport(int associationId, ParcelFileDescriptor fd, Context context) {
+        mAssociationId = associationId;
+        mFd = fd;
+        mRemoteIn = new ParcelFileDescriptor.AutoCloseInputStream(fd);
+        mRemoteOut = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+        mContext = context;
+        mListeners = new HashMap<>();
+    }
+
+    /**
+     * Add a listener when a message is received for the message type
+     * @param message Message type
+     * @param listener Execute when a message with the type is received
+     */
+    public void addListener(int message, Listener listener) {
+        mListeners.put(message, listener);
+    }
+
+    public int getAssociationId() {
+        return mAssociationId;
+    }
+
+    protected ParcelFileDescriptor getFd() {
+        return mFd;
     }
 
     public abstract void start();
     public abstract void stop();
+    protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data)
+            throws IOException;
 
     public Future<byte[]> requestForResponse(int message, byte[] data) {
         if (DEBUG) Slog.d(TAG, "Requesting for response");
@@ -99,9 +137,6 @@
         return pending;
     }
 
-    protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data)
-            throws IOException;
-
     protected final void handleMessage(int message, int sequence, @NonNull byte[] data)
             throws IOException {
         if (DEBUG) {
@@ -130,6 +165,11 @@
                 sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, data);
                 break;
             }
+            case MESSAGE_REQUEST_PLATFORM_INFO: {
+                callback(message, data);
+                sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE);
+                break;
+            }
             case MESSAGE_REQUEST_PERMISSION_RESTORE: {
                 if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
                         && !Build.isDebuggable()) {
@@ -138,7 +178,7 @@
                     break;
                 }
                 try {
-                    mListener.onRequestPermissionRestore(data);
+                    callback(message, data);
                     sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE);
                 } catch (Exception e) {
                     Slog.w(TAG, "Failed to restore permissions");
@@ -154,6 +194,12 @@
         }
     }
 
+    private void callback(int message, byte[] data) {
+        if (mListeners.containsKey(message)) {
+            mListeners.get(message).onDataReceived(data);
+        }
+    }
+
     private void processResponse(int message, int sequence, byte[] data) {
         final CompletableFuture<byte[]> future;
         synchronized (mPendingRequests) {