Loading core/java/android/companion/CompanionDeviceManager.java +18 −0 Original line number Diff line number Diff line Loading @@ -219,6 +219,12 @@ public final class CompanionDeviceManager { * @hide */ public static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN /** * Test message type without a response. * * @hide */ public static final int MESSAGE_ONEWAY_PING = 0x43807378; // +PIN /** * Message header assigned to the remote authentication handshakes. * Loading @@ -237,6 +243,18 @@ public final class CompanionDeviceManager { * @hide */ public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES /** * Message header assigned to the one-way message sent from the wearable device. * * @hide */ public static final int MESSAGE_ONEWAY_FROM_WEARABLE = 0x43708287; // +FRW /** * Message header assigned to the one-way message sent to the wearable device. * * @hide */ public static final int MESSAGE_ONEWAY_TO_WEARABLE = 0x43847987; // +TOW /** * The length limit of Association tag. Loading core/tests/companiontests/src/android/companion/SystemDataTransportTest.java +49 −5 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package android.companion; import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_PING; import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PING; import android.content.Context; import android.os.SystemClock; import android.test.InstrumentationTestCase; Loading @@ -36,16 +39,22 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Tests that CDM can intake incoming messages in the system data transport and output results. * * Build/Install/Run: atest CompanionTests:SystemDataTransportTest */ public class SystemDataTransportTest extends InstrumentationTestCase { private static final String TAG = "SystemDataTransportTest"; private static final int MESSAGE_INVALID = 0xF00DCAFE; private static final int MESSAGE_ONEWAY_INVALID = 0x43434343; // ++++ private static final int MESSAGE_RESPONSE_INVALID = 0x33333333; // !!!! private static final int MESSAGE_REQUEST_INVALID = 0x63636363; // ???? private static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN private static final int MESSAGE_RESPONSE_INVALID = 0x33333333; // !!!! private static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC private static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI Loading Loading @@ -122,8 +131,6 @@ public class SystemDataTransportTest extends InstrumentationTestCase { new Random().nextBytes(blob); final byte[] input = generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, blob); final byte[] expected = generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 1, blob); assertTransportBehavior(input, expected); } public void testMultiplePingPing() { Loading Loading @@ -176,6 +183,43 @@ public class SystemDataTransportTest extends InstrumentationTestCase { testPingHandRolled(); } public void testInvalidOnewayMessages() throws InterruptedException { // Add a callback final CountDownLatch received = new CountDownLatch(1); mCdm.addOnMessageReceivedListener(Runnable::run, MESSAGE_ONEWAY_INVALID, (id, data) -> received.countDown()); final byte[] input = generatePacket(MESSAGE_ONEWAY_INVALID, /* sequence */ 1); final ByteArrayInputStream in = new ByteArrayInputStream(input); final ByteArrayOutputStream out = new ByteArrayOutputStream(); mCdm.attachSystemDataTransport(mAssociationId, in, out); // Assert that a one-way message was ignored (does not trigger a callback) assertFalse(received.await(5, TimeUnit.SECONDS)); // There should not be a response to one-way messages assertEquals(0, out.toByteArray().length); } public void testOnewayMessages() throws InterruptedException { // Add a callback final CountDownLatch received = new CountDownLatch(1); mCdm.addOnMessageReceivedListener(Runnable::run, MESSAGE_ONEWAY_PING, (id, data) -> received.countDown()); final byte[] input = generatePacket(MESSAGE_ONEWAY_PING, /* sequence */ 1); final ByteArrayInputStream in = new ByteArrayInputStream(input); final ByteArrayOutputStream out = new ByteArrayOutputStream(); mCdm.attachSystemDataTransport(mAssociationId, in, out); // Assert that a one-way message was received assertTrue(received.await(1, TimeUnit.SECONDS)); // There should not be a response to one-way messages assertEquals(0, out.toByteArray().length); } public static byte[] concat(byte[]... blobs) { int length = 0; for (byte[] blob : blobs) { Loading services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java +2 −2 Original line number Diff line number Diff line Loading @@ -130,7 +130,7 @@ public class CompanionTransportManager { synchronized (mTransports) { for (int i = 0; i < associationIds.length; i++) { if (mTransports.contains(associationIds[i])) { mTransports.get(associationIds[i]).requestForResponse(message, data); mTransports.get(associationIds[i]).sendMessage(message, data); } } } Loading Loading @@ -220,7 +220,7 @@ public class CompanionTransportManager { if (transport == null) { return CompletableFuture.failedFuture(new IOException("Missing transport")); } return transport.requestForResponse(MESSAGE_REQUEST_PERMISSION_RESTORE, data); return transport.sendMessage(MESSAGE_REQUEST_PERMISSION_RESTORE, data); } } Loading services/companion/java/com/android/server/companion/transport/Transport.java +75 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package com.android.server.companion.transport; import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_FROM_WEARABLE; import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_PING; import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_TO_WEARABLE; import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC; import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PERMISSION_RESTORE; import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PING; Loading Loading @@ -80,6 +83,10 @@ public abstract class Transport { return (message & 0xFF000000) == 0x33000000; } private static boolean isOneway(int message) { return (message & 0xFF000000) == 0x43000000; } @GuardedBy("mPendingRequests") protected final SparseArray<CompletableFuture<byte[]>> mPendingRequests = new SparseArray<>(); Loading Loading @@ -134,6 +141,42 @@ public abstract class Transport { protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data) throws IOException; /** * Send a message using this transport. If the message was a request, then the returned Future * object will complete successfully only if the remote device both received and processed it * as expected. If the message was a send-and-forget type message, then the Future object will * resolve successfully immediately (with null) upon sending the message. * * @param message the message type * @param data the message payload * @return Future object containing the result of the sent message. */ public Future<byte[]> sendMessage(int message, byte[] data) { final CompletableFuture<byte[]> pending = new CompletableFuture<>(); if (isOneway(message)) { return sendAndForget(message, data); } else if (isRequest(message)) { return requestForResponse(message, data); } else { Slog.w(TAG, "Failed to send message 0x" + Integer.toHexString(message)); pending.completeExceptionally(new IllegalArgumentException( "The message being sent must be either a one-way or a request." )); } return pending; } /** * @deprecated Method was renamed to sendMessage(int, byte[]) to support both * send-and-forget type messages as well as wait-for-response type messages. * * @param message request message type * @param data the message payload * @return future object containing the result of the request. * * @see #sendMessage(int, byte[]) */ @Deprecated public Future<byte[]> requestForResponse(int message, byte[] data) { if (DEBUG) Slog.d(TAG, "Requesting for response"); final int sequence = mNextSequence.incrementAndGet(); Loading @@ -154,6 +197,20 @@ public abstract class Transport { return pending; } private Future<byte[]> sendAndForget(int message, byte[]data) { if (DEBUG) Slog.d(TAG, "Sending a one-way message"); final CompletableFuture<byte[]> pending = new CompletableFuture<>(); try { sendMessage(message, -1, data); pending.complete(null); } catch (IOException e) { pending.completeExceptionally(e); } return pending; } protected final void handleMessage(int message, int sequence, @NonNull byte[] data) throws IOException { if (DEBUG) { Loading @@ -162,7 +219,9 @@ public abstract class Transport { + " from association " + mAssociationId); } if (isRequest(message)) { if (isOneway(message)) { processOneway(message, data); } else if (isRequest(message)) { try { processRequest(message, sequence, data); } catch (IOException e) { Loading @@ -175,6 +234,21 @@ public abstract class Transport { } } private void processOneway(int message, byte[] data) { switch (message) { case MESSAGE_ONEWAY_PING: case MESSAGE_ONEWAY_FROM_WEARABLE: case MESSAGE_ONEWAY_TO_WEARABLE: { callback(message, data); break; } default: { Slog.w(TAG, "Ignoring unknown message 0x" + Integer.toHexString(message)); break; } } } private void processRequest(int message, int sequence, byte[] data) throws IOException { switch (message) { Loading Loading
core/java/android/companion/CompanionDeviceManager.java +18 −0 Original line number Diff line number Diff line Loading @@ -219,6 +219,12 @@ public final class CompanionDeviceManager { * @hide */ public static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN /** * Test message type without a response. * * @hide */ public static final int MESSAGE_ONEWAY_PING = 0x43807378; // +PIN /** * Message header assigned to the remote authentication handshakes. * Loading @@ -237,6 +243,18 @@ public final class CompanionDeviceManager { * @hide */ public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES /** * Message header assigned to the one-way message sent from the wearable device. * * @hide */ public static final int MESSAGE_ONEWAY_FROM_WEARABLE = 0x43708287; // +FRW /** * Message header assigned to the one-way message sent to the wearable device. * * @hide */ public static final int MESSAGE_ONEWAY_TO_WEARABLE = 0x43847987; // +TOW /** * The length limit of Association tag. Loading
core/tests/companiontests/src/android/companion/SystemDataTransportTest.java +49 −5 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package android.companion; import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_PING; import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PING; import android.content.Context; import android.os.SystemClock; import android.test.InstrumentationTestCase; Loading @@ -36,16 +39,22 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Tests that CDM can intake incoming messages in the system data transport and output results. * * Build/Install/Run: atest CompanionTests:SystemDataTransportTest */ public class SystemDataTransportTest extends InstrumentationTestCase { private static final String TAG = "SystemDataTransportTest"; private static final int MESSAGE_INVALID = 0xF00DCAFE; private static final int MESSAGE_ONEWAY_INVALID = 0x43434343; // ++++ private static final int MESSAGE_RESPONSE_INVALID = 0x33333333; // !!!! private static final int MESSAGE_REQUEST_INVALID = 0x63636363; // ???? private static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN private static final int MESSAGE_RESPONSE_INVALID = 0x33333333; // !!!! private static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC private static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI Loading Loading @@ -122,8 +131,6 @@ public class SystemDataTransportTest extends InstrumentationTestCase { new Random().nextBytes(blob); final byte[] input = generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, blob); final byte[] expected = generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 1, blob); assertTransportBehavior(input, expected); } public void testMultiplePingPing() { Loading Loading @@ -176,6 +183,43 @@ public class SystemDataTransportTest extends InstrumentationTestCase { testPingHandRolled(); } public void testInvalidOnewayMessages() throws InterruptedException { // Add a callback final CountDownLatch received = new CountDownLatch(1); mCdm.addOnMessageReceivedListener(Runnable::run, MESSAGE_ONEWAY_INVALID, (id, data) -> received.countDown()); final byte[] input = generatePacket(MESSAGE_ONEWAY_INVALID, /* sequence */ 1); final ByteArrayInputStream in = new ByteArrayInputStream(input); final ByteArrayOutputStream out = new ByteArrayOutputStream(); mCdm.attachSystemDataTransport(mAssociationId, in, out); // Assert that a one-way message was ignored (does not trigger a callback) assertFalse(received.await(5, TimeUnit.SECONDS)); // There should not be a response to one-way messages assertEquals(0, out.toByteArray().length); } public void testOnewayMessages() throws InterruptedException { // Add a callback final CountDownLatch received = new CountDownLatch(1); mCdm.addOnMessageReceivedListener(Runnable::run, MESSAGE_ONEWAY_PING, (id, data) -> received.countDown()); final byte[] input = generatePacket(MESSAGE_ONEWAY_PING, /* sequence */ 1); final ByteArrayInputStream in = new ByteArrayInputStream(input); final ByteArrayOutputStream out = new ByteArrayOutputStream(); mCdm.attachSystemDataTransport(mAssociationId, in, out); // Assert that a one-way message was received assertTrue(received.await(1, TimeUnit.SECONDS)); // There should not be a response to one-way messages assertEquals(0, out.toByteArray().length); } public static byte[] concat(byte[]... blobs) { int length = 0; for (byte[] blob : blobs) { Loading
services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java +2 −2 Original line number Diff line number Diff line Loading @@ -130,7 +130,7 @@ public class CompanionTransportManager { synchronized (mTransports) { for (int i = 0; i < associationIds.length; i++) { if (mTransports.contains(associationIds[i])) { mTransports.get(associationIds[i]).requestForResponse(message, data); mTransports.get(associationIds[i]).sendMessage(message, data); } } } Loading Loading @@ -220,7 +220,7 @@ public class CompanionTransportManager { if (transport == null) { return CompletableFuture.failedFuture(new IOException("Missing transport")); } return transport.requestForResponse(MESSAGE_REQUEST_PERMISSION_RESTORE, data); return transport.sendMessage(MESSAGE_REQUEST_PERMISSION_RESTORE, data); } } Loading
services/companion/java/com/android/server/companion/transport/Transport.java +75 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package com.android.server.companion.transport; import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_FROM_WEARABLE; import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_PING; import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_TO_WEARABLE; import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC; import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PERMISSION_RESTORE; import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PING; Loading Loading @@ -80,6 +83,10 @@ public abstract class Transport { return (message & 0xFF000000) == 0x33000000; } private static boolean isOneway(int message) { return (message & 0xFF000000) == 0x43000000; } @GuardedBy("mPendingRequests") protected final SparseArray<CompletableFuture<byte[]>> mPendingRequests = new SparseArray<>(); Loading Loading @@ -134,6 +141,42 @@ public abstract class Transport { protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data) throws IOException; /** * Send a message using this transport. If the message was a request, then the returned Future * object will complete successfully only if the remote device both received and processed it * as expected. If the message was a send-and-forget type message, then the Future object will * resolve successfully immediately (with null) upon sending the message. * * @param message the message type * @param data the message payload * @return Future object containing the result of the sent message. */ public Future<byte[]> sendMessage(int message, byte[] data) { final CompletableFuture<byte[]> pending = new CompletableFuture<>(); if (isOneway(message)) { return sendAndForget(message, data); } else if (isRequest(message)) { return requestForResponse(message, data); } else { Slog.w(TAG, "Failed to send message 0x" + Integer.toHexString(message)); pending.completeExceptionally(new IllegalArgumentException( "The message being sent must be either a one-way or a request." )); } return pending; } /** * @deprecated Method was renamed to sendMessage(int, byte[]) to support both * send-and-forget type messages as well as wait-for-response type messages. * * @param message request message type * @param data the message payload * @return future object containing the result of the request. * * @see #sendMessage(int, byte[]) */ @Deprecated public Future<byte[]> requestForResponse(int message, byte[] data) { if (DEBUG) Slog.d(TAG, "Requesting for response"); final int sequence = mNextSequence.incrementAndGet(); Loading @@ -154,6 +197,20 @@ public abstract class Transport { return pending; } private Future<byte[]> sendAndForget(int message, byte[]data) { if (DEBUG) Slog.d(TAG, "Sending a one-way message"); final CompletableFuture<byte[]> pending = new CompletableFuture<>(); try { sendMessage(message, -1, data); pending.complete(null); } catch (IOException e) { pending.completeExceptionally(e); } return pending; } protected final void handleMessage(int message, int sequence, @NonNull byte[] data) throws IOException { if (DEBUG) { Loading @@ -162,7 +219,9 @@ public abstract class Transport { + " from association " + mAssociationId); } if (isRequest(message)) { if (isOneway(message)) { processOneway(message, data); } else if (isRequest(message)) { try { processRequest(message, sequence, data); } catch (IOException e) { Loading @@ -175,6 +234,21 @@ public abstract class Transport { } } private void processOneway(int message, byte[] data) { switch (message) { case MESSAGE_ONEWAY_PING: case MESSAGE_ONEWAY_FROM_WEARABLE: case MESSAGE_ONEWAY_TO_WEARABLE: { callback(message, data); break; } default: { Slog.w(TAG, "Ignoring unknown message 0x" + Integer.toHexString(message)); break; } } } private void processRequest(int message, int sequence, byte[] data) throws IOException { switch (message) { Loading