Progress on permissions sync transport migration.
An earlier change introduced a socketpair() based CDM transport,
which is robust "streaming" interface between two devices.
This change updates the existing permissions sync logic to use this
new transport for its payloads. It also begins returning success or
failure messages so the requesting side knows when the permissions
have been applied.
We also rename the package and class hosting the transport logic,
and migrate our socketpair() based transport logic there. Since
the initial version of this protocol won't be using protobufs,
and doesn't need to implement manual pagination, this change also
removes that logic to reduce the size of our backports.
Finally, since this transport isn't ready to be used by third-party
apps, we add security checks to only allow built-in apps.
Bug: 237030169
Test: atest CompanionTests
Change-Id: Ia0dab24d44f1973d5af6ad8a2abb198408a681df
diff --git a/core/api/current.txt b/core/api/current.txt
index 10957ff..15aa188 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9010,8 +9010,6 @@
method @Deprecated public boolean hasNotificationAccess(android.content.ComponentName);
method public void requestNotificationAccess(android.content.ComponentName);
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
- method @Deprecated public void startSystemDataTransfer(int) throws android.companion.DeviceNotAssociatedException;
- method public void startSystemDataTransfer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.companion.CompanionException>) throws android.companion.DeviceNotAssociatedException;
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
field public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION";
field @Deprecated public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
@@ -9027,19 +9025,14 @@
public abstract class CompanionDeviceService extends android.app.Service {
ctor public CompanionDeviceService();
- method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void dispatchMessageToSystem(int, int, @NonNull byte[]) throws android.companion.DeviceNotAssociatedException;
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method @Deprecated @MainThread public void onDeviceAppeared(@NonNull String);
method @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo);
method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String);
method @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo);
- method public void onMessageDispatchedFromSystem(int, int, @NonNull byte[]);
field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
}
- public class CompanionException extends java.lang.RuntimeException {
- }
-
public interface DeviceFilter<D extends android.os.Parcelable> extends android.os.Parcelable {
}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 1b51faf3..633b734 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -800,18 +800,15 @@
*
* @hide
*/
+ @Deprecated
@RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
public void dispatchMessage(int messageId, int associationId, @NonNull byte[] message)
throws DeviceNotAssociatedException {
- try {
- mService.dispatchMessage(messageId, associationId, message);
- } catch (RemoteException e) {
- ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class);
- throw e.rethrowFromSystemServer();
- }
+ Log.w(LOG_TAG, "dispatchMessage replaced by attachSystemDataTransport");
}
/** {@hide} */
+ @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
public final void attachSystemDataTransport(int associationId, @NonNull InputStream in,
@NonNull OutputStream out) throws DeviceNotAssociatedException {
synchronized (mTransports) {
@@ -830,6 +827,7 @@
}
/** {@hide} */
+ @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
public final void detachSystemDataTransport(int associationId)
throws DeviceNotAssociatedException {
synchronized (mTransports) {
@@ -927,7 +925,7 @@
*
* <p>The permission transfer doesn't happen immediately after the call or user consented.
* The app needs to trigger the system data transfer manually by calling
- * {@link #startSystemDataTransfer(int)}, when it confirms the communication channel between
+ * {@code #startSystemDataTransfer(int)}, when it confirms the communication channel between
* the two devices is established.</p>
*
* @param associationId The unique {@link AssociationInfo#getId ID} assigned to the association
@@ -965,8 +963,8 @@
* @param associationId The unique {@link AssociationInfo#getId ID} assigned to the Association
* of the companion device recorded by CompanionDeviceManager
* @throws DeviceNotAssociatedException Exception if the companion device is not associated
- *
* @deprecated Use {@link #startSystemDataTransfer(int, Executor, OutcomeReceiver)} instead.
+ * @hide
*/
@Deprecated
@UserHandleAware
@@ -993,6 +991,7 @@
* @param executor The executor which will be used to invoke the result callback.
* @param result The callback to notify the app of the result of the system data transfer.
* @throws DeviceNotAssociatedException Exception if the companion device is not associated
+ * @hide
*/
@UserHandleAware
public void startSystemDataTransfer(
@@ -1125,12 +1124,14 @@
public void start() throws IOException {
final ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair();
- mLocalIn = new ParcelFileDescriptor.AutoCloseInputStream(pair[0]);
- mLocalOut = new ParcelFileDescriptor.AutoCloseOutputStream(pair[0]);
+ final ParcelFileDescriptor localFd = pair[0];
+ final ParcelFileDescriptor remoteFd = pair[1];
+ mLocalIn = new ParcelFileDescriptor.AutoCloseInputStream(localFd);
+ mLocalOut = new ParcelFileDescriptor.AutoCloseOutputStream(localFd);
try {
mService.attachSystemDataTransport(mContext.getPackageName(),
- mContext.getUserId(), mAssociationId, pair[1]);
+ mContext.getUserId(), mAssociationId, remoteFd);
} catch (RemoteException e) {
throw new IOException("Failed to configure transport", e);
}
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index 83d2713..a79983a 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -156,9 +156,12 @@
* @param messageId system assigned id of the message to be sent
* @param associationId association id of the associated device
* @param message message to be sent
+ * @hide
*/
+ @Deprecated
public void onMessageDispatchedFromSystem(int messageId, int associationId,
@NonNull byte[] message) {
+ Log.w(LOG_TAG, "Replaced by attachSystemDataTransport");
// do nothing. Companion apps can override this function for system to send messages.
}
@@ -185,22 +188,14 @@
* @param messageId id of the message
* @param associationId id of the associated device
* @param message message received from the associated device
+ * @hide
*/
+ @Deprecated
@RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
public final void dispatchMessageToSystem(int messageId, int associationId,
@NonNull byte[] message)
throws DeviceNotAssociatedException {
- if (getBaseContext() == null) {
- Log.e(LOG_TAG, "Dispatch failed. Start your service before calling this method.");
- return;
- }
- CompanionDeviceManager companionDeviceManager =
- getSystemService(CompanionDeviceManager.class);
- if (companionDeviceManager != null) {
- companionDeviceManager.dispatchMessage(messageId, associationId, message);
- } else {
- Log.e(LOG_TAG, "CompanionDeviceManager is null. Can't dispatch messages.");
- }
+ Log.w(LOG_TAG, "Replaced by attachSystemDataTransport");
}
/**
@@ -223,10 +218,13 @@
* device
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
public final void attachSystemDataTransport(int associationId, @NonNull InputStream in,
@NonNull OutputStream out) throws DeviceNotAssociatedException {
getSystemService(CompanionDeviceManager.class)
- .attachSystemDataTransport(associationId, in, out);
+ .attachSystemDataTransport(associationId,
+ Objects.requireNonNull(in),
+ Objects.requireNonNull(out));
}
/**
@@ -236,6 +234,7 @@
* @param associationId id of the associated device
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
public final void detachSystemDataTransport(int associationId)
throws DeviceNotAssociatedException {
getSystemService(CompanionDeviceManager.class)
@@ -299,13 +298,5 @@
public void onDeviceDisappeared(AssociationInfo associationInfo) {
mMainHandler.postAtFrontOfQueue(() -> mService.onDeviceDisappeared(associationInfo));
}
-
- @Override
- public void onMessageDispatchedFromSystem(int messageId, int associationId,
- @NonNull byte[] message) {
- mMainHandler.postAtFrontOfQueue(
- () -> mService.onMessageDispatchedFromSystem(messageId, associationId,
- message));
- }
}
}
diff --git a/core/java/android/companion/CompanionException.java b/core/java/android/companion/CompanionException.java
index fb78e8d..9a92401 100644
--- a/core/java/android/companion/CompanionException.java
+++ b/core/java/android/companion/CompanionException.java
@@ -19,7 +19,10 @@
import android.annotation.NonNull;
/**
- * {@code CompanionException} can be thrown during the companion system data transfer process.
+ * {@code CompanionException} can be thrown during the companion system data
+ * transfer process.
+ *
+ * @hide
*/
public class CompanionException extends RuntimeException {
/** @hide */
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 42f9083..6e6e187 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -63,8 +63,6 @@
void createAssociation(in String packageName, in String macAddress, int userId,
in byte[] certificate);
- void dispatchMessage(int messageId, int associationId, in byte[] message);
-
void addOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId);
void removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId);
diff --git a/core/java/android/companion/ICompanionDeviceService.aidl b/core/java/android/companion/ICompanionDeviceService.aidl
index 3c90b86..fa68508 100644
--- a/core/java/android/companion/ICompanionDeviceService.aidl
+++ b/core/java/android/companion/ICompanionDeviceService.aidl
@@ -22,5 +22,4 @@
oneway interface ICompanionDeviceService {
void onDeviceAppeared(in AssociationInfo associationInfo);
void onDeviceDisappeared(in AssociationInfo associationInfo);
- void onMessageDispatchedFromSystem(in int messageId, in int associationId, in byte[] message);
}
diff --git a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
index be04b6c..6bd498c 100644
--- a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
+++ b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
@@ -16,12 +16,15 @@
package android.companion;
+import android.content.Context;
import android.os.SystemClock;
import android.test.InstrumentationTestCase;
import android.util.Log;
import com.android.internal.util.HexDump;
+import libcore.util.EmptyArray;
+
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
@@ -31,69 +34,81 @@
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
+import java.util.List;
import java.util.Random;
public class SystemDataTransportTest extends InstrumentationTestCase {
private static final String TAG = "SystemDataTransportTest";
- private static final int COMMAND_INVALID = 0xF00DCAFE;
- private static final int COMMAND_PING_V0 = 0x50490000;
- private static final int COMMAND_PONG_V0 = 0x504F0000;
+ private static final int MESSAGE_INVALID = 0xF00DCAFE;
+ 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
+
+ private Context mContext;
private CompanionDeviceManager mCdm;
+ private int mAssociationId;
@Override
protected void setUp() throws Exception {
super.setUp();
- mCdm = getInstrumentation().getTargetContext()
- .getSystemService(CompanionDeviceManager.class);
+ mContext = getInstrumentation().getTargetContext();
+ mCdm = mContext.getSystemService(CompanionDeviceManager.class);
+ mAssociationId = createAssociation();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ mCdm.disassociate(mAssociationId);
}
public void testPingHandRolled() {
// NOTE: These packets are explicitly hand-rolled to verify wire format;
// the remainder of the tests are fine using generated packets
- // PING v0 with payload "HELLO WORLD!"
+ // MESSAGE_REQUEST_PING with payload "HELLO WORLD!"
final byte[] input = new byte[] {
- 0x50, 0x49, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x0C,
+ 0x63, (byte) 0x80, 0x73, 0x78, // message: MESSAGE_REQUEST_PING
+ 0x00, 0x00, 0x00, 0x2A, // sequence: 42
+ 0x00, 0x00, 0x00, 0x0C, // length: 12
0x48, 0x45, 0x4C, 0x4C, 0x4F, 0x20, 0x57, 0x4F, 0x52, 0x4C, 0x44, 0x21,
};
- // PONG v0 with payload "HELLO WORLD!"
+ // MESSAGE_RESPONSE_SUCCESS with payload "HELLO WORLD!"
final byte[] expected = new byte[] {
- 0x50, 0x4F, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x0C,
+ 0x33, (byte) 0x83, (byte) 0x85, 0x67, // message: MESSAGE_RESPONSE_SUCCESS
+ 0x00, 0x00, 0x00, 0x2A, // sequence: 42
+ 0x00, 0x00, 0x00, 0x0C, // length: 12
0x48, 0x45, 0x4C, 0x4C, 0x4F, 0x20, 0x57, 0x4F, 0x52, 0x4C, 0x44, 0x21,
};
-
- final ByteArrayInputStream in = new ByteArrayInputStream(input);
- final ByteArrayOutputStream out = new ByteArrayOutputStream();
- mCdm.attachSystemDataTransport(42, in, out);
-
- final byte[] actual = waitForByteArray(out, expected.length);
- assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
+ assertTransportBehavior(input, expected);
}
public void testPingTrickle() {
- final byte[] input = generatePacket(COMMAND_PING_V0, TAG);
- final byte[] expected = generatePacket(COMMAND_PONG_V0, TAG);
+ final byte[] input = generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, TAG);
+ final byte[] expected = generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 1, TAG);
final ByteArrayInputStream in = new ByteArrayInputStream(input);
final ByteArrayOutputStream out = new ByteArrayOutputStream();
- mCdm.attachSystemDataTransport(42, new TrickleInputStream(in), out);
+ mCdm.attachSystemDataTransport(mAssociationId, new TrickleInputStream(in), out);
final byte[] actual = waitForByteArray(out, expected.length);
assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
}
public void testPingDelay() {
- final byte[] input = generatePacket(COMMAND_PING_V0, TAG);
- final byte[] expected = generatePacket(COMMAND_PONG_V0, TAG);
+ final byte[] input = generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, TAG);
+ final byte[] expected = generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 1, TAG);
final ByteArrayInputStream in = new ByteArrayInputStream(input);
final ByteArrayOutputStream out = new ByteArrayOutputStream();
- mCdm.attachSystemDataTransport(42, new DelayingInputStream(in, 1000),
+ mCdm.attachSystemDataTransport(mAssociationId, new DelayingInputStream(in, 1000),
new DelayingOutputStream(out, 1000));
final byte[] actual = waitForByteArray(out, expected.length);
@@ -104,49 +119,54 @@
final byte[] blob = new byte[500_000];
new Random().nextBytes(blob);
- final byte[] input = generatePacket(COMMAND_PING_V0, blob);
- final byte[] expected = generatePacket(COMMAND_PONG_V0, blob);
-
- final ByteArrayInputStream in = new ByteArrayInputStream(input);
- final ByteArrayOutputStream out = new ByteArrayOutputStream();
- mCdm.attachSystemDataTransport(42, in, out);
-
- final byte[] actual = waitForByteArray(out, expected.length);
- assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
+ 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 testMutiplePingPing() {
- final byte[] input = concat(generatePacket(COMMAND_PING_V0, "red"),
- generatePacket(COMMAND_PING_V0, "green"));
- final byte[] expected = concat(generatePacket(COMMAND_PONG_V0, "red"),
- generatePacket(COMMAND_PONG_V0, "green"));
-
- final ByteArrayInputStream in = new ByteArrayInputStream(input);
- final ByteArrayOutputStream out = new ByteArrayOutputStream();
- mCdm.attachSystemDataTransport(42, in, out);
-
- final byte[] actual = waitForByteArray(out, expected.length);
- assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
+ final byte[] input = concat(
+ generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, "red"),
+ generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 2, "green"));
+ final byte[] expected = concat(
+ generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 1, "red"),
+ generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 2, "green"));
+ assertTransportBehavior(input, expected);
}
public void testMultipleInvalidPing() {
- final byte[] input = concat(generatePacket(COMMAND_INVALID, "red"),
- generatePacket(COMMAND_PING_V0, "green"));
- final byte[] expected = generatePacket(COMMAND_PONG_V0, "green");
+ final byte[] input = concat(
+ generatePacket(MESSAGE_INVALID, /* sequence */ 1, "red"),
+ generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 2, "green"));
+ final byte[] expected =
+ generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 2, "green");
+ assertTransportBehavior(input, expected);
+ }
- final ByteArrayInputStream in = new ByteArrayInputStream(input);
- final ByteArrayOutputStream out = new ByteArrayOutputStream();
- mCdm.attachSystemDataTransport(42, in, out);
+ public void testMultipleInvalidRequestPing() {
+ final byte[] input = concat(
+ generatePacket(MESSAGE_REQUEST_INVALID, /* sequence */ 1, "red"),
+ generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 2, "green"));
+ final byte[] expected = concat(
+ generatePacket(MESSAGE_RESPONSE_FAILURE, /* sequence */ 1),
+ generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 2, "green"));
+ assertTransportBehavior(input, expected);
+ }
- final byte[] actual = waitForByteArray(out, expected.length);
- assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
+ public void testMultipleInvalidResponsePing() {
+ final byte[] input = concat(
+ generatePacket(MESSAGE_RESPONSE_INVALID, /* sequence */ 1, "red"),
+ generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 2, "green"));
+ final byte[] expected =
+ generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 2, "green");
+ assertTransportBehavior(input, expected);
}
public void testDoubleAttach() {
// Connect an empty connection that is stalled out
final InputStream in = new EmptyInputStream();
final OutputStream out = new ByteArrayOutputStream();
- mCdm.attachSystemDataTransport(42, in, out);
+ mCdm.attachSystemDataTransport(mAssociationId, in, out);
SystemClock.sleep(1000);
// Attach a second transport that has some packets; it should disconnect
@@ -154,17 +174,57 @@
testPingHandRolled();
}
- public static byte[] concat(byte[] a, byte[] b) {
- return ByteBuffer.allocate(a.length + b.length).put(a).put(b).array();
+ public static byte[] concat(byte[]... blobs) {
+ int length = 0;
+ for (byte[] blob : blobs) {
+ length += blob.length;
+ }
+ final ByteBuffer buf = ByteBuffer.allocate(length);
+ for (byte[] blob : blobs) {
+ buf.put(blob);
+ }
+ return buf.array();
}
- public static byte[] generatePacket(int command, String data) {
- return generatePacket(command, data.getBytes(StandardCharsets.UTF_8));
+ public static byte[] generatePacket(int message, int sequence) {
+ return generatePacket(message, sequence, EmptyArray.BYTE);
}
- public static byte[] generatePacket(int command, byte[] data) {
- return ByteBuffer.allocate(data.length + 8)
- .putInt(command).putInt(data.length).put(data).array();
+ public static byte[] generatePacket(int message, int sequence, String data) {
+ return generatePacket(message, sequence, data.getBytes(StandardCharsets.UTF_8));
+ }
+
+ public static byte[] generatePacket(int message, int sequence, byte[] data) {
+ return ByteBuffer.allocate(data.length + 12)
+ .putInt(message)
+ .putInt(sequence)
+ .putInt(data.length)
+ .put(data)
+ .array();
+ }
+
+ private int createAssociation() {
+ getInstrumentation().getUiAutomation().executeShellCommand("cmd companiondevice associate "
+ + mContext.getUserId() + " " + mContext.getPackageName() + " AA:BB:CC:DD:EE:FF");
+ List<AssociationInfo> infos;
+ for (int i = 0; i < 100; i++) {
+ infos = mCdm.getMyAssociations();
+ if (!infos.isEmpty()) {
+ return infos.get(0).getId();
+ } else {
+ SystemClock.sleep(100);
+ }
+ }
+ throw new AssertionError();
+ }
+
+ private void assertTransportBehavior(byte[] input, byte[] expected) {
+ final ByteArrayInputStream in = new ByteArrayInputStream(input);
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ mCdm.attachSystemDataTransport(mAssociationId, in, out);
+
+ final byte[] actual = waitForByteArray(out, expected.length);
+ assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
}
private static byte[] waitForByteArray(ByteArrayOutputStream out, int size) {
diff --git a/services/companion/Android.bp b/services/companion/Android.bp
index d3ef6dc..cdeb2dc 100644
--- a/services/companion/Android.bp
+++ b/services/companion/Android.bp
@@ -11,7 +11,6 @@
name: "services.companion-sources",
srcs: [
"java/**/*.java",
- "java/**/*.proto",
],
path: "java",
visibility: ["//frameworks/base/services"],
@@ -20,9 +19,6 @@
java_library_static {
name: "services.companion",
defaults: ["platform_service_defaults"],
- proto: {
- type: "stream",
- },
srcs: [":services.companion-sources"],
libs: [
"app-compat-annotations",
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
index 2ab1aa8..2acb65e 100644
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -244,29 +244,6 @@
primaryServiceConnector.postOnDeviceDisappeared(association);
}
- /** Pass an encrypted secure message to the companion application for transporting. */
- public void dispatchMessage(@UserIdInt int userId, @NonNull String packageName,
- int associationId, int messageId, @NonNull byte[] message) {
- if (DEBUG) {
- Log.i(TAG, "dispatchMessage() u" + userId + "/" + packageName
- + " associationId=" + associationId);
- }
-
- final CompanionDeviceServiceConnector primaryServiceConnector =
- getPrimaryServiceConnector(userId, packageName);
- if (primaryServiceConnector == null) {
- if (DEBUG) {
- Log.e(TAG, "dispatchMessage(): "
- + "u" + userId + "/" + packageName + " is NOT bound.");
- Log.d(TAG, "Stacktrace", new Throwable());
- }
- return;
- }
-
- primaryServiceConnector.postOnMessageDispatchedFromSystem(associationId, messageId,
- message);
- }
-
void dump(@NonNull PrintWriter out) {
out.append("Companion Device Application Controller: \n");
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index c354561..fa043f8 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -106,11 +106,10 @@
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
-import com.android.server.companion.datatransfer.CompanionMessageProcessor;
import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
-import com.android.server.companion.securechannel.CompanionSecureCommunicationsManager;
+import com.android.server.companion.transport.CompanionTransportManager;
import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -151,10 +150,9 @@
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
private AssociationRequestsProcessor mAssociationRequestsProcessor;
private SystemDataTransferProcessor mSystemDataTransferProcessor;
- private CompanionMessageProcessor mCompanionMessageProcessor;
private CompanionDevicePresenceMonitor mDevicePresenceMonitor;
private CompanionApplicationController mCompanionAppController;
- private CompanionSecureCommunicationsManager mSecureCommsManager;
+ private CompanionTransportManager mTransportManager;
private final ActivityTaskManagerInternal mAtmInternal;
private final ActivityManagerInternal mAmInternal;
@@ -238,11 +236,9 @@
/* cdmService */this, mAssociationStore);
mCompanionAppController = new CompanionApplicationController(
context, mApplicationControllerCallback);
- mSecureCommsManager = new CompanionSecureCommunicationsManager(
- mAssociationStore, mCompanionAppController);
- mCompanionMessageProcessor = new CompanionMessageProcessor(mSecureCommsManager);
+ mTransportManager = new CompanionTransportManager(context);
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this, mAssociationStore,
- mSystemDataTransferRequestStore, mCompanionMessageProcessor);
+ mSystemDataTransferRequestStore, mTransportManager);
// Publish "binder" service.
final CompanionDeviceManagerImpl impl = new CompanionDeviceManagerImpl();
@@ -319,18 +315,32 @@
MINUTES.toMillis(10));
}
- @Nullable
+ @NonNull
AssociationInfo getAssociationWithCallerChecks(
@UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
- final AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress(
+ AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress(
userId, packageName, macAddress);
- return sanitizeWithCallerChecks(getContext(), association);
+ association = sanitizeWithCallerChecks(getContext(), association);
+ if (association != null) {
+ return association;
+ } else {
+ throw new IllegalArgumentException("Association does not exist "
+ + "or the caller does not have permissions to manage it "
+ + "(ie. it belongs to a different package or a different user).");
+ }
}
- @Nullable
+ @NonNull
AssociationInfo getAssociationWithCallerChecks(int associationId) {
- final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
- return sanitizeWithCallerChecks(getContext(), association);
+ AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ association = sanitizeWithCallerChecks(getContext(), association);
+ if (association != null) {
+ return association;
+ } else {
+ throw new IllegalArgumentException("Association does not exist "
+ + "or the caller does not have permissions to manage it "
+ + "(ie. it belongs to a different package or a different user).");
+ }
}
private void onDeviceAppearedInternal(int associationId) {
@@ -609,12 +619,6 @@
final AssociationInfo association =
getAssociationWithCallerChecks(userId, packageName, deviceMacAddress);
- if (association == null) {
- throw new IllegalArgumentException("Association does not exist "
- + "or the caller does not have permissions to manage it "
- + "(ie. it belongs to a different package or a different user).");
- }
-
disassociateInternal(association.getId());
}
@@ -622,15 +626,9 @@
public void disassociate(int associationId) {
if (DEBUG) Log.i(TAG, "disassociate() associationId=" + associationId);
- final AssociationInfo association = getAssociationWithCallerChecks(associationId);
- if (association == null) {
- throw new IllegalArgumentException("Association with ID " + associationId + " "
- + "does not exist "
- + "or belongs to a different package "
- + "or belongs to a different user");
- }
-
- disassociateInternal(associationId);
+ final AssociationInfo association =
+ getAssociationWithCallerChecks(associationId);
+ disassociateInternal(association.getId());
}
@Override
@@ -698,27 +696,6 @@
}
@Override
- public void dispatchMessage(int messageId, int associationId, @NonNull byte[] message) {
- if (DEBUG) {
- Log.i(TAG, "dispatchMessage() associationId=" + associationId + "\n"
- + " message(Base64)=" + Base64.encodeToString(message, 0));
- }
-
- getContext().enforceCallingOrSelfPermission(DELIVER_COMPANION_MESSAGES,
- "dispatchMessage");
-
- AssociationInfo association = getAssociationWithCallerChecks(associationId);
- if (association == null) {
- throw new IllegalArgumentException("Association with ID " + associationId + " "
- + "does not exist "
- + "or belongs to a different package "
- + "or belongs to a different user");
- }
-
- mSecureCommsManager.receiveSecureMessage(messageId, associationId, message);
- }
-
- @Override
public PendingIntent buildPermissionTransferUserConsentIntent(String packageName,
int userId, int associationId) {
return mSystemDataTransferProcessor.buildPermissionTransferUserConsentIntent(
@@ -735,14 +712,14 @@
@Override
public void attachSystemDataTransport(String packageName, int userId, int associationId,
ParcelFileDescriptor fd) {
- mSystemDataTransferProcessor.attachSystemDataTransport(packageName, userId,
- associationId, fd);
+ getAssociationWithCallerChecks(associationId);
+ mTransportManager.attachSystemDataTransport(packageName, userId, associationId, fd);
}
@Override
public void detachSystemDataTransport(String packageName, int userId, int associationId) {
- mSystemDataTransferProcessor.detachSystemDataTransport(packageName, userId,
- associationId);
+ getAssociationWithCallerChecks(associationId);
+ mTransportManager.detachSystemDataTransport(packageName, userId, associationId);
}
@Override
@@ -750,13 +727,6 @@
if (DEBUG) Log.i(TAG, "notifyDevice_Appeared() id=" + associationId);
AssociationInfo association = getAssociationWithCallerChecks(associationId);
- if (association == null) {
- throw new IllegalArgumentException("Association with ID " + associationId + " "
- + "does not exist "
- + "or belongs to a different package "
- + "or belongs to a different user");
- }
-
if (!association.isSelfManaged()) {
throw new IllegalArgumentException("Association with ID " + associationId
+ " is not self-managed. notifyDeviceAppeared(int) can only be called for"
@@ -777,13 +747,6 @@
if (DEBUG) Log.i(TAG, "notifyDevice_Disappeared() id=" + associationId);
final AssociationInfo association = getAssociationWithCallerChecks(associationId);
- if (association == null) {
- throw new IllegalArgumentException("Association with ID " + associationId + " "
- + "does not exist "
- + "or belongs to a different package "
- + "or belongs to a different user");
- }
-
if (!association.isSelfManaged()) {
throw new IllegalArgumentException("Association with ID " + associationId
+ " is not self-managed. notifyDeviceAppeared(int) can only be called for"
@@ -892,7 +855,6 @@
final CompanionDeviceShellCommand cmd = new CompanionDeviceShellCommand(
CompanionDeviceManagerService.this,
mAssociationStore,
- mSecureCommsManager,
mDevicePresenceMonitor);
cmd.exec(this, in, out, err, args, callback, resultReceiver);
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
index 7360f08..a288f7b 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
@@ -99,12 +99,6 @@
post(companionService -> companionService.onDeviceDisappeared(associationInfo));
}
- void postOnMessageDispatchedFromSystem(int associationId, int messageId,
- @NonNull byte[] message) {
- post(companionService ->
- companionService.onMessageDispatchedFromSystem(messageId, associationId, message));
- }
-
/**
* Post "unbind" job, which will run *after* all previously posted jobs complete.
*
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 0b7bc03..322ad46 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -16,14 +16,9 @@
package com.android.server.companion;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
import android.companion.AssociationInfo;
import android.os.Binder;
import android.os.ShellCommand;
-import android.util.Base64;
-
-import com.android.server.companion.securechannel.CompanionSecureCommunicationsManager;
import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
@@ -35,16 +30,13 @@
private final CompanionDeviceManagerService mService;
private final AssociationStore mAssociationStore;
- private final CompanionSecureCommunicationsManager mSecureCommsManager;
private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
CompanionDeviceShellCommand(CompanionDeviceManagerService service,
AssociationStore associationStore,
- CompanionSecureCommunicationsManager secureCommsManager,
CompanionDevicePresenceMonitor devicePresenceMonitor) {
mService = service;
mAssociationStore = associationStore;
- mSecureCommsManager = secureCommsManager;
mDevicePresenceMonitor = devicePresenceMonitor;
}
@@ -92,32 +84,6 @@
mService.loadAssociationsFromDisk();
break;
- case "send-secure-message":
- associationId = getNextIntArgRequired();
- final byte[] message;
-
- // The message should be either a UTF-8 String or Base64-encoded data.
- final boolean isBase64 = "--base64".equals(getNextOption());
- if (isBase64) {
- final String base64encodedMessage = getNextArgRequired();
- message = Base64.decode(base64encodedMessage, 0);
- } else {
- // We treat the rest of the command as the message, which should contain at
- // least one word (hence getNextArg_Required() below), but there may be
- // more.
- final StringBuilder sb = new StringBuilder(getNextArgRequired());
- // Pick up the rest.
- for (String word : peekRemainingArgs()) {
- sb.append(" ").append(word);
- }
- // And now convert to byte[]...
- message = sb.toString().getBytes(UTF_8);
- }
-
- mSecureCommsManager.sendSecureMessage(associationId, /* messageId */ 0,
- message);
- break;
-
case "simulate-device-appeared":
associationId = getNextIntArgRequired();
mDevicePresenceMonitor.simulateDeviceAppeared(associationId);
@@ -167,8 +133,6 @@
pw.println(" Create a new Association.");
pw.println(" disassociate USER_ID PACKAGE MAC_ADDRESS");
pw.println(" Remove an existing Association.");
- pw.println(" send-secure-message ASSOCIATION_ID [--base64] MESSAGE");
- pw.println(" Send a secure message to an associated companion device.");
pw.println(" clear-association-memory-cache");
pw.println(" Clear the in-memory association cache and reload all association ");
pw.println(" information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
diff --git a/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageInfo.java b/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageInfo.java
deleted file mode 100644
index 91ad93c..0000000
--- a/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageInfo.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.datatransfer;
-
-class CompanionMessageInfo {
-
- private final long mId;
- private final int mPage;
- private final int mTotal;
- private final int mType;
- private final byte[] mData;
-
- CompanionMessageInfo(long id, int page, int total, int type, byte[] data) {
- mId = id;
- mPage = page;
- mTotal = total;
- mType = type;
- mData = data;
- }
-
- public long getId() {
- return mId;
- }
-
- public int getPage() {
- return mPage;
- }
-
- public int getType() {
- return mType;
- }
-
- public byte[] getData() {
- return mData;
- }
-}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageProcessor.java
deleted file mode 100644
index 98a00aa6..0000000
--- a/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageProcessor.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.datatransfer;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.util.Slog;
-import android.util.proto.ProtoInputStream;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.server.companion.proto.CompanionMessage;
-import com.android.server.companion.securechannel.CompanionSecureCommunicationsManager;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * This class builds and reads CompanionMessage. And also paginate and combine messages.
- */
-public class CompanionMessageProcessor {
-
- private static final String LOG_TAG = CompanionMessageProcessor.class.getSimpleName();
-
- /** Listener for incoming complete messages. */
- interface Listener {
- /** When a complete message is received from the companion app. */
- void onCompleteMessageReceived(@NonNull CompanionMessageInfo message);
- }
-
- // Rough size for each CompanionMessage, each message can exceed 50K for a little, but not
- // too much. Hard limit is 100K, WCS data processing limit. Closer to 100K, less stable at
- // the WCS data processing layer. Refer to
- // https://developers.google.com/android/reference/com/google/android/gms/wearable/MessageClient
- // #public-abstract-taskinteger-sendmessage-string-nodeid,-string-path,-byte[]-data
- private static final int MESSAGE_SIZE_IN_BYTES = 50000;
-
- private final CompanionSecureCommunicationsManager mSecureCommsManager;
-
- @Nullable
- private Listener mListener;
-
- // Association id -> (parent id -> received messages)
- private final Map<Integer, Map<Integer, List<CompanionMessageInfo>>> mAssociationsMessagesMap =
- new HashMap<>();
- // Association id -> next parent id
- private final Map<Integer, Integer> mNextParentId = new HashMap<>();
-
- public CompanionMessageProcessor(CompanionSecureCommunicationsManager secureCommsManager) {
- mSecureCommsManager = secureCommsManager;
- mSecureCommsManager.setListener(this::onDecryptedMessageReceived);
- }
-
- public void setListener(@NonNull Listener listener) {
- mListener = listener;
- }
-
- /**
- * Paginate the data into multiple messages with size limit. And dispatch the messages to the
- * companion app.
- */
- public void paginateAndDispatchMessagesToApp(byte[] data, int messageType,
- String packageName, int userId, int associationId) {
- Slog.i(LOG_TAG, "Paginating " + data.length + " bytes.");
-
- final int totalMessageCount = (data.length / MESSAGE_SIZE_IN_BYTES)
- + ((data.length % MESSAGE_SIZE_IN_BYTES == 0) ? 0 : 1);
- int parentMessageId = findNextParentId(associationId, totalMessageCount);
-
- for (int i = 0; i < totalMessageCount; i++) {
- ProtoOutputStream proto = new ProtoOutputStream();
- int messageId = parentMessageId + i + 1;
- proto.write(CompanionMessage.ID, messageId);
-
- long paginationInfoToken = proto.start(CompanionMessage.PAGINATION_INFO);
- proto.write(CompanionMessage.PaginationInfo.PARENT_ID, parentMessageId);
- proto.write(CompanionMessage.PaginationInfo.PAGE, i + 1);
- proto.write(CompanionMessage.PaginationInfo.TOTAL, totalMessageCount);
- proto.end(paginationInfoToken);
-
- proto.write(CompanionMessage.TYPE, messageType);
- byte[] currentData = Arrays.copyOfRange(data, i * MESSAGE_SIZE_IN_BYTES,
- Math.min((i + 1) * MESSAGE_SIZE_IN_BYTES, data.length));
- proto.write(CompanionMessage.DATA, currentData);
-
- byte[] message = proto.getBytes();
-
- Slog.i(LOG_TAG, "Sending [" + message.length + "] bytes to " + packageName);
-
- mSecureCommsManager.sendSecureMessage(associationId, messageId, message);
- }
- }
-
- /**
- * Process the message and store it. If all the messages with the same parent id have been
- * received, return the message with combined message data. Otherwise, return null if there's
- * still data parts missing.
- */
- public CompanionMessageInfo onDecryptedMessageReceived(int messageId, int associationId,
- byte[] message) {
- Slog.i(LOG_TAG, "Partial message received, size [" + message.length
- + "], reading from protobuf.");
-
- ProtoInputStream proto = new ProtoInputStream(message);
- try {
- int id = 0;
- int parentId = 0;
- int page = 0;
- int total = 0;
- int type = CompanionMessage.UNKNOWN;
- byte[] data = null;
-
- // Read proto data
- while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- switch (proto.getFieldNumber()) {
- case (int) CompanionMessage.ID:
- id = proto.readInt(CompanionMessage.ID);
- break;
- case (int) CompanionMessage.PAGINATION_INFO:
- long paginationToken = proto.start(CompanionMessage.PAGINATION_INFO);
- while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- switch (proto.getFieldNumber()) {
- case (int) CompanionMessage.PaginationInfo.PARENT_ID:
- parentId = proto.readInt(
- CompanionMessage.PaginationInfo.PARENT_ID);
- break;
- case (int) CompanionMessage.PaginationInfo.PAGE:
- page = proto.readInt(CompanionMessage.PaginationInfo.PAGE);
- break;
- case (int) CompanionMessage.PaginationInfo.TOTAL:
- total = proto.readInt(CompanionMessage.PaginationInfo.TOTAL);
- break;
- default:
- Slog.e(LOG_TAG, "Unexpected field id "
- + proto.getFieldNumber() + " for PaginationInfo.");
- break;
- }
- }
- proto.end(paginationToken);
- break;
- case (int) CompanionMessage.TYPE:
- type = proto.readInt(CompanionMessage.TYPE);
- break;
- case (int) CompanionMessage.DATA:
- data = proto.readBytes(CompanionMessage.DATA);
- break;
- default:
- Slog.e(LOG_TAG, "Unexpected field id " + proto.getFieldNumber()
- + " for CompanionMessage.");
- break;
- }
- }
-
- if (id == messageId) {
- CompanionMessageInfo messageInfo = new CompanionMessageInfo(id, page, total, type,
- data);
- // Add the message into mAssociationsMessagesMap
- Map<Integer, List<CompanionMessageInfo>> associationMessages =
- mAssociationsMessagesMap.getOrDefault(associationId, new HashMap<>());
- List<CompanionMessageInfo> childMessages = associationMessages.getOrDefault(
- parentId, new ArrayList<>());
- childMessages.add(messageInfo);
- associationMessages.put(parentId, childMessages);
- mAssociationsMessagesMap.put(associationId, associationMessages);
- // Check if all the messages with the same parentId are received.
- if (childMessages.size() == total) {
- Slog.i(LOG_TAG, "All [" + total + "] messages are received for parentId ["
- + parentId + "]. Processing.");
-
- childMessages.sort(Comparator.comparing(CompanionMessageInfo::getPage));
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- for (int i = 0; i < childMessages.size(); i++) {
- stream.write(childMessages.get(i).getData());
- }
- mAssociationsMessagesMap.remove(parentId);
- mListener.onCompleteMessageReceived(
- new CompanionMessageInfo(parentId, 0, total, type,
- stream.toByteArray()));
- } else {
- Slog.i(LOG_TAG, "[" + childMessages.size() + "/" + total
- + "] messages are received for parentId [" + parentId + "]");
- }
- } else {
- Slog.e(LOG_TAG, "Message id mismatch.");
- return null;
- }
- } catch (IOException e) {
- Slog.e(LOG_TAG, "Can't read proto from the message.");
- return null;
- }
- return null;
- }
-
- /**
- * Find the next parent id from [1, Integer.MAX_VALUE].
- * The parent and child ids are incremental.
- */
- private int findNextParentId(int associationId, int totalMessageCount) {
- int nextParentId = mNextParentId.getOrDefault(associationId, 1);
-
- // If the last child message id exceeds the Integer range, start from 1 again.
- if (nextParentId > Integer.MAX_VALUE - totalMessageCount - 1) {
- nextParentId = 1;
- }
-
- mNextParentId.put(associationId, nextParentId + totalMessageCount + 1);
-
- return nextParentId;
- }
-}
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 7eede55..f0a0492 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -38,34 +38,27 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
-import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.UserHandle;
import android.permission.PermissionControllerManager;
import android.util.Slog;
-import android.util.SparseArray;
-import com.android.internal.annotations.GuardedBy;
import com.android.server.companion.AssociationStore;
import com.android.server.companion.CompanionDeviceManagerService;
import com.android.server.companion.PermissionsUtils;
-import com.android.server.companion.proto.CompanionMessage;
+import com.android.server.companion.transport.CompanionTransportManager;
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
/**
- * This processor builds user consent intent for a given SystemDataTransferRequest and processes the
- * request when the system is ready (a secure channel is established between the handhold and the
- * companion device).
+ * This processor builds user consent intent for a
+ * {@link SystemDataTransferRequest} and processes the request once a channel is
+ * established between the local and remote companion device.
*/
public class SystemDataTransferProcessor {
@@ -85,21 +78,19 @@
private final Context mContext;
private final AssociationStore mAssociationStore;
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
- private final CompanionMessageProcessor mCompanionMessageProcessor;
+ private final CompanionTransportManager mTransportManager;
private final PermissionControllerManager mPermissionControllerManager;
private final ExecutorService mExecutor;
- @GuardedBy("mTransports")
- private final SparseArray<Transport> mTransports = new SparseArray<>();
public SystemDataTransferProcessor(CompanionDeviceManagerService service,
AssociationStore associationStore,
SystemDataTransferRequestStore systemDataTransferRequestStore,
- CompanionMessageProcessor companionMessageProcessor) {
+ CompanionTransportManager transportManager) {
mContext = service.getContext();
mAssociationStore = associationStore;
mSystemDataTransferRequestStore = systemDataTransferRequestStore;
- mCompanionMessageProcessor = companionMessageProcessor;
- mCompanionMessageProcessor.setListener(this::onCompleteMessageReceived);
+ mTransportManager = transportManager;
+ mTransportManager.setListener(this::onReceivePermissionRestore);
mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class);
mExecutor = Executors.newSingleThreadExecutor();
}
@@ -194,73 +185,42 @@
return;
}
- // TODO: Establish a secure channel
-
// Start permission sync
final long callingIdentityToken = Binder.clearCallingIdentity();
try {
+ // TODO: refactor to work with streams of data
mPermissionControllerManager.getRuntimePermissionBackup(UserHandle.of(userId),
- mExecutor,
- backup -> mCompanionMessageProcessor.paginateAndDispatchMessagesToApp(backup,
- CompanionMessage.PERMISSION_SYNC, packageName, userId, associationId));
+ mExecutor, backup -> {
+ Future<?> result = mTransportManager
+ .requestPermissionRestore(associationId, backup);
+ try {
+ result.get(15, TimeUnit.SECONDS);
+ try {
+ callback.onResult();
+ } catch (RemoteException ignored) {
+ }
+ } catch (Exception e) {
+ try {
+ callback.onError(e.getMessage());
+ } catch (RemoteException ignored) {
+ }
+ }
+ });
} finally {
Binder.restoreCallingIdentity(callingIdentityToken);
}
}
- public void attachSystemDataTransport(String packageName, int userId, int associationId,
- ParcelFileDescriptor fd) {
- synchronized (mTransports) {
- // TODO: restore once testing has evolved
- // resolveAssociation(packageName, userId, associationId);
-
- if (mTransports.contains(associationId)) {
- detachSystemDataTransport(packageName, userId, associationId);
- }
-
- final Transport transport = new Transport(fd);
- transport.start();
- mTransports.put(associationId, transport);
- }
- }
-
- public void detachSystemDataTransport(String packageName, int userId, int associationId) {
- synchronized (mTransports) {
- // TODO: restore once testing has evolved
- // resolveAssociation(packageName, userId, associationId);
-
- final Transport transport = mTransports.get(associationId);
- if (transport != null) {
- mTransports.delete(associationId);
- transport.stop();
- }
- }
- }
-
- /**
- * Process a complete decrypted message reported by the companion app.
- */
- public void onCompleteMessageReceived(@NonNull CompanionMessageInfo completeMessage) {
- switch (completeMessage.getType()) {
- case CompanionMessage.PERMISSION_SYNC:
- processPermissionSyncMessage(completeMessage);
- break;
- default:
- Slog.e(LOG_TAG, "Unknown message type [" + completeMessage.getType()
- + "]. Unable to process.");
- }
- }
-
- private void processPermissionSyncMessage(CompanionMessageInfo messageInfo) {
+ private void onReceivePermissionRestore(byte[] message) {
Slog.i(LOG_TAG, "Applying permissions.");
// Start applying permissions
UserHandle user = mContext.getUser();
final long callingIdentityToken = Binder.clearCallingIdentity();
try {
+ // TODO: refactor to work with streams of data
mPermissionControllerManager.stageAndApplyRuntimePermissionsBackup(
- messageInfo.getData(), user);
+ message, user);
} finally {
- Slog.i(LOG_TAG, "Permissions applied.");
Binder.restoreCallingIdentity(callingIdentityToken);
}
}
@@ -291,74 +251,4 @@
Slog.e(LOG_TAG, "Unknown result code:" + resultCode);
}
};
-
- private class Transport {
- private final InputStream mRemoteIn;
- private final OutputStream mRemoteOut;
-
- private volatile boolean mStopped;
-
- public Transport(ParcelFileDescriptor fd) {
- mRemoteIn = new ParcelFileDescriptor.AutoCloseInputStream(fd);
- mRemoteOut = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
- }
-
- public void start() {
- new Thread(() -> {
- try {
- while (!mStopped) {
- processNextCommand();
- }
- } catch (IOException e) {
- if (!mStopped) {
- Slog.w(LOG_TAG, "Trouble during transport", e);
- stop();
- }
- }
- }).start();
- }
-
- public void stop() {
- mStopped = true;
-
- IoUtils.closeQuietly(mRemoteIn);
- IoUtils.closeQuietly(mRemoteOut);
- }
-
- private void processNextCommand() throws IOException {
- Slog.d(LOG_TAG, "Waiting for next command...");
-
- // Read message header
- final byte[] headerBytes = new byte[8];
- Streams.readFully(mRemoteIn, headerBytes);
- final ByteBuffer header = ByteBuffer.wrap(headerBytes);
- final int command = header.getInt();
- final int length = header.getInt();
-
- Slog.d(LOG_TAG, "Received command 0x" + Integer.toHexString(command)
- + " length " + length);
- switch (command) {
- case 0x50490000: // PI(NG) version 0
- // Repeat back the given payload, within reason
- final int target = Math.min(length, 1_000_000);
- final byte[] payload = new byte[target];
- Streams.readFully(mRemoteIn, payload);
- Streams.skipByReading(mRemoteIn, length - target);
-
- // Respond with PO(NG) version 0
- header.rewind();
- header.putInt(0x504F0000);
- header.putInt(target);
- mRemoteOut.write(header.array());
- mRemoteOut.write(payload);
- break;
-
- default:
- // Emit local warning, and skip message to
- // handle next one
- Slog.w(LOG_TAG, "Unknown command 0x" + Integer.toHexString(command));
- mRemoteIn.skip(length);
- }
- }
- }
}
diff --git a/services/companion/java/com/android/server/companion/proto/companion_message.proto b/services/companion/java/com/android/server/companion/proto/companion_message.proto
deleted file mode 100644
index 893a0db..0000000
--- a/services/companion/java/com/android/server/companion/proto/companion_message.proto
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-syntax = "proto3";
-
-option java_multiple_files = true;
-
-package com.android.server.companion.proto;
-
-/* Represents a message between companion devices */
-message CompanionMessage {
- int32 id = 1;
-
- PaginationInfo paginationInfo = 2;
-
- Type type = 3;
-
- // message body data
- bytes data = 4;
-
- /* Message pagination info */
- message PaginationInfo {
- // id of the parent message, which should be the same for all the paginated messages
- int32 parentId = 1;
-
- // page number of the current message in [1, total]
- int32 page = 2;
-
- // total number of messages
- int32 total = 3;
- }
-
- /* Message type */
- enum Type {
- // default value for proto3
- UNKNOWN = 0;
-
- // handshake message to establish secure channel
- SECURE_CHANNEL_HANDSHAKE = 1;
-
- // permission sync
- PERMISSION_SYNC = 2;
- }
-}
diff --git a/services/companion/java/com/android/server/companion/securechannel/CompanionSecureCommunicationsManager.java b/services/companion/java/com/android/server/companion/securechannel/CompanionSecureCommunicationsManager.java
deleted file mode 100644
index 16a74ec..0000000
--- a/services/companion/java/com/android/server/companion/securechannel/CompanionSecureCommunicationsManager.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.securechannel;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.companion.AssociationInfo;
-import android.util.Base64;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.server.companion.AssociationStore;
-import com.android.server.companion.CompanionApplicationController;
-
-/** Secure Comms Manager */
-@SuppressLint("LongLogTag")
-public class CompanionSecureCommunicationsManager {
- static final String TAG = "CompanionDevice_SecureComms";
- static final boolean DEBUG = false;
-
- /** Listener for incoming decrypted messages. */
- public interface Listener {
- /** When an incoming message is decrypted. */
- void onDecryptedMessageReceived(int messageId, int associationId, byte[] message);
- }
-
- private final AssociationStore mAssociationStore;
- private final CompanionApplicationController mCompanionAppController;
-
- @Nullable
- private Listener mListener;
-
- /** Constructor */
- public CompanionSecureCommunicationsManager(AssociationStore associationStore,
- CompanionApplicationController companionApplicationController) {
- mAssociationStore = associationStore;
- mCompanionAppController = companionApplicationController;
- }
-
- public void setListener(@NonNull Listener listener) {
- mListener = listener;
- }
-
- /**
- * Send a data to the associated companion device via secure channel (establishing one if
- * needed).
- * @param associationId associationId of the "recipient" companion device.
- * @param messageId id of the message
- * @param message data to be sent securely.
- */
- public void sendSecureMessage(int associationId, int messageId, @NonNull byte[] message) {
- if (DEBUG) {
- Log.d(TAG, "sendSecureMessage() associationId=" + associationId + "\n"
- + " message (Base64)=\"" + Base64.encodeToString(message, 0) + "\"");
- }
-
- final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
- if (association == null) {
- throw new IllegalArgumentException(
- "Association with ID " + associationId + " does not exist");
- }
- if (DEBUG) Log.d(TAG, " association=" + association);
-
- final int userId = association.getUserId();
- final String packageName = association.getPackageName();
-
- // Bind to the app if it hasn't been bound.
- if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
- Slog.d(TAG, "userId [" + userId + "] packageName [" + packageName
- + "] is not bound. Binding it now to send a secure message.");
- mCompanionAppController.bindCompanionApplication(userId, packageName,
- association.isSelfManaged());
-
- // TODO(b/202926196): implement: encrypt and pass on the companion application for
- // transporting
- mCompanionAppController.dispatchMessage(userId, packageName, associationId, messageId,
- message);
-
- Slog.d(TAG, "Unbinding userId [" + userId + "] packageName [" + packageName
- + "]");
- mCompanionAppController.unbindCompanionApplication(userId, packageName);
- }
-
- // TODO(b/202926196): implement: encrypt and pass on the companion application for
- // transporting
- mCompanionAppController.dispatchMessage(userId, packageName, associationId, messageId,
- message);
- }
-
- /**
- * Decrypt and dispatch message received from an associated companion device.
- * @param associationId associationId of the "sender" companion device.
- * @param encryptedMessage data.
- */
- public void receiveSecureMessage(int messageId, int associationId,
- @NonNull byte[] encryptedMessage) {
- if (DEBUG) {
- Log.d(TAG, "sendSecureMessage() associationId=" + associationId + "\n"
- + " message (Base64)=\"" + Base64.encodeToString(encryptedMessage, 0) + "\"");
- }
-
- // TODO(b/202926196): implement: decrypt and dispatch
-
- mListener.onDecryptedMessageReceived(messageId, associationId, encryptedMessage);
- }
-}
diff --git a/services/companion/java/com/android/server/companion/securechannel/OWNERS b/services/companion/java/com/android/server/companion/securechannel/OWNERS
deleted file mode 100644
index ecb97f4..0000000
--- a/services/companion/java/com/android/server/companion/securechannel/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-set noparent
-
-sergeynv@google.com
-ewol@google.com
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
new file mode 100644
index 0000000..f01bdf2
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.transport;
+
+import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.ActivityManagerInternal;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+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 libcore.io.IoUtils;
+import libcore.io.Streams;
+import libcore.util.EmptyArray;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@SuppressLint("LongLogTag")
+public class CompanionTransportManager {
+ private static final String TAG = "CompanionTransportManager";
+ // TODO: flip to false
+ private static final boolean DEBUG = true;
+
+ private static final int HEADER_LENGTH = 12;
+ // TODO: refactor message processing to use streams to remove this limit
+ private static final int MAX_PAYLOAD_LENGTH = 1_000_000;
+
+ private static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
+ private static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
+
+ private static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC
+ private static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI
+
+ private static boolean isRequest(int message) {
+ return (message & 0xFF000000) == 0x63000000;
+ }
+
+ private static boolean isResponse(int message) {
+ 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;
+
+ public CompanionTransportManager(Context context) {
+ mContext = context;
+ }
+
+ public void setListener(@NonNull Listener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * For the moment, we only offer transporting of system data to built-in
+ * companion apps; future work will improve the security model to support
+ * third-party companion apps.
+ */
+ private void enforceCallerCanTransportSystemData(String packageName, int userId) {
+ mContext.enforceCallingOrSelfPermission(DELIVER_COMPANION_MESSAGES, TAG);
+
+ try {
+ final ApplicationInfo info = mContext.getPackageManager().getApplicationInfoAsUser(
+ packageName, 0, userId);
+ final int instrumentationUid = LocalServices.getService(ActivityManagerInternal.class)
+ .getInstrumentationSourceUid(Binder.getCallingUid());
+ if (!Build.isDebuggable() && !info.isSystemApp()
+ && instrumentationUid == android.os.Process.INVALID_UID) {
+ throw new SecurityException("Transporting of system data currently only available "
+ + "to built-in companion apps or tests");
+ }
+ } catch (NameNotFoundException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public void attachSystemDataTransport(String packageName, int userId, int associationId,
+ ParcelFileDescriptor fd) {
+ enforceCallerCanTransportSystemData(packageName, userId);
+ synchronized (mTransports) {
+ if (mTransports.contains(associationId)) {
+ detachSystemDataTransport(packageName, userId, associationId);
+ }
+
+ final Transport transport = new Transport(associationId, fd);
+ transport.start();
+ mTransports.put(associationId, transport);
+ }
+ }
+
+ public void detachSystemDataTransport(String packageName, int userId, int associationId) {
+ enforceCallerCanTransportSystemData(packageName, userId);
+ synchronized (mTransports) {
+ final Transport transport = mTransports.get(associationId);
+ if (transport != null) {
+ mTransports.delete(associationId);
+ transport.stop();
+ }
+ }
+ }
+
+ public Future<?> requestPermissionRestore(int associationId, byte[] data) {
+ synchronized (mTransports) {
+ final Transport transport = mTransports.get(associationId);
+ if (transport != null) {
+ return transport.requestForResponse(MESSAGE_REQUEST_PERMISSION_RESTORE, data);
+ } else {
+ return CompletableFuture.failedFuture(new IOException("Missing transport"));
+ }
+ }
+ }
+
+ private class Transport {
+ private final int mAssociationId;
+
+ private final InputStream mRemoteIn;
+ private final OutputStream mRemoteOut;
+
+ private final AtomicInteger mNextSequence = new AtomicInteger();
+
+ @GuardedBy("mPendingRequests")
+ private final SparseArray<CompletableFuture<byte[]>> mPendingRequests = new SparseArray<>();
+
+ private volatile boolean mStopped;
+
+ public Transport(int associationId, ParcelFileDescriptor fd) {
+ mAssociationId = associationId;
+ mRemoteIn = new ParcelFileDescriptor.AutoCloseInputStream(fd);
+ mRemoteOut = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+ }
+
+ public void start() {
+ new Thread(() -> {
+ try {
+ while (!mStopped) {
+ receiveMessage();
+ }
+ } catch (IOException e) {
+ if (!mStopped) {
+ Slog.w(TAG, "Trouble during transport", e);
+ stop();
+ }
+ }
+ }).start();
+ }
+
+ public void stop() {
+ mStopped = true;
+
+ IoUtils.closeQuietly(mRemoteIn);
+ IoUtils.closeQuietly(mRemoteOut);
+ }
+
+ public Future<byte[]> requestForResponse(int message, byte[] data) {
+ final int sequence = mNextSequence.incrementAndGet();
+ final CompletableFuture<byte[]> pending = new CompletableFuture<>();
+ synchronized (mPendingRequests) {
+ mPendingRequests.put(sequence, pending);
+ }
+ try {
+ sendMessage(message, sequence, data);
+ } catch (IOException e) {
+ pending.completeExceptionally(e);
+ }
+ return pending;
+ }
+
+ private void sendMessage(int message, int sequence, @NonNull byte[] data)
+ throws IOException {
+ if (DEBUG) {
+ Slog.d(TAG, "Sending message 0x" + Integer.toHexString(message)
+ + " sequence " + sequence + " length " + data.length
+ + " to association " + mAssociationId);
+ }
+
+ synchronized (mRemoteOut) {
+ final ByteBuffer header = ByteBuffer.allocate(HEADER_LENGTH)
+ .putInt(message)
+ .putInt(sequence)
+ .putInt(data.length);
+ mRemoteOut.write(header.array());
+ mRemoteOut.write(data);
+ mRemoteOut.flush();
+ }
+ }
+
+ private void receiveMessage() throws IOException {
+ if (DEBUG) {
+ Slog.d(TAG, "Waiting for next message...");
+ }
+
+ final byte[] headerBytes = new byte[HEADER_LENGTH];
+ Streams.readFully(mRemoteIn, headerBytes);
+ final ByteBuffer header = ByteBuffer.wrap(headerBytes);
+ final int message = header.getInt();
+ final int sequence = header.getInt();
+ final int length = header.getInt();
+
+ if (DEBUG) {
+ Slog.d(TAG, "Received message 0x" + Integer.toHexString(message)
+ + " sequence " + sequence + " length " + length
+ + " from association " + mAssociationId);
+ }
+ if (length > MAX_PAYLOAD_LENGTH) {
+ Slog.w(TAG, "Ignoring message 0x" + Integer.toHexString(message)
+ + " sequence " + sequence + " length " + length
+ + " from association " + mAssociationId + " beyond maximum length");
+ Streams.skipByReading(mRemoteIn, length);
+ return;
+ }
+
+ final byte[] data = new byte[length];
+ Streams.readFully(mRemoteIn, data);
+
+ if (isRequest(message)) {
+ processRequest(message, sequence, data);
+ } else if (isResponse(message)) {
+ processResponse(message, sequence, data);
+ } else {
+ Slog.w(TAG, "Unknown message " + Integer.toHexString(message));
+ }
+ }
+
+ private void processRequest(int message, int sequence, byte[] data)
+ throws IOException {
+ switch (message) {
+ case MESSAGE_REQUEST_PING: {
+ sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, data);
+ break;
+ }
+ case MESSAGE_REQUEST_PERMISSION_RESTORE: {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+ && !Build.isDebuggable()) {
+ Slog.w(TAG, "Restoring permissions only supported on watches");
+ sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE);
+ break;
+ }
+ try {
+ mListener.onRequestPermissionRestore(data);
+ sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE);
+ } catch (Exception e) {
+ Slog.w(TAG, "Failed to restore permissions");
+ sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE);
+ }
+ break;
+ }
+ default: {
+ Slog.w(TAG, "Unknown request " + Integer.toHexString(message));
+ sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE);
+ break;
+ }
+ }
+ }
+
+ private void processResponse(int message, int sequence, byte[] data) {
+ final CompletableFuture<byte[]> future;
+ synchronized (mPendingRequests) {
+ future = mPendingRequests.removeReturnOld(sequence);
+ }
+ if (future == null) {
+ Slog.w(TAG, "Ignoring unknown sequence " + sequence);
+ return;
+ }
+
+ switch (message) {
+ case MESSAGE_RESPONSE_SUCCESS: {
+ future.complete(data);
+ }
+ case MESSAGE_RESPONSE_FAILURE: {
+ future.completeExceptionally(new RuntimeException("Remote failure"));
+ }
+ default: {
+ Slog.w(TAG, "Ignoring unknown response " + Integer.toHexString(message));
+ }
+ }
+ }
+ }
+}