summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Tom Chan <tomchan@google.com> 2024-02-07 19:29:07 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-02-07 19:29:07 +0000
commit497253831d97507888df54119ca8f8737fc0db5a (patch)
tree7cc1761b676e1215fdf6cc1640e72fecf7e02d27
parenta68db6477d43cf9ffeab023fe0fb43d0da4945f7 (diff)
parentb44f7864371b5704653a5256120cc676095c7f0f (diff)
Merge "Add WSM#provideWearableConnection API" into main
-rw-r--r--core/api/system-current.txt3
-rw-r--r--core/java/android/app/wearable/IWearableSensingManager.aidl2
-rw-r--r--core/java/android/app/wearable/WearableSensingManager.java101
-rw-r--r--core/java/android/service/wearable/IWearableSensingService.aidl1
-rw-r--r--core/java/android/service/wearable/WearableSensingService.java124
-rw-r--r--services/core/java/com/android/server/wearable/RemoteWearableSensingService.java36
-rw-r--r--services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java71
-rw-r--r--services/core/java/com/android/server/wearable/WearableSensingManagerService.java22
-rw-r--r--services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java350
9 files changed, 646 insertions, 64 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index e998c2bc56bf..c46ca789b2b7 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3153,7 +3153,9 @@ package android.app.wearable {
public class WearableSensingManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideData(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideDataStream(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideWearableConnection(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
field public static final int STATUS_ACCESS_DENIED = 5; // 0x5
+ field @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") public static final int STATUS_CHANNEL_ERROR = 7; // 0x7
field public static final int STATUS_SERVICE_UNAVAILABLE = 3; // 0x3
field public static final int STATUS_SUCCESS = 1; // 0x1
field public static final int STATUS_UNKNOWN = 0; // 0x0
@@ -13429,6 +13431,7 @@ package android.service.wearable {
method @BinderThread public abstract void onDataProvided(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @BinderThread public abstract void onDataStreamProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @BinderThread public abstract void onQueryServiceStatus(@NonNull java.util.Set<java.lang.Integer>, @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>);
+ method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @BinderThread public void onSecureWearableConnectionProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @BinderThread public abstract void onStartDetection(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionResult>);
method public abstract void onStopDetection(@NonNull String);
field public static final String SERVICE_INTERFACE = "android.service.wearable.WearableSensingService";
diff --git a/core/java/android/app/wearable/IWearableSensingManager.aidl b/core/java/android/app/wearable/IWearableSensingManager.aidl
index ff37bd848d61..9d55ce28c84e 100644
--- a/core/java/android/app/wearable/IWearableSensingManager.aidl
+++ b/core/java/android/app/wearable/IWearableSensingManager.aidl
@@ -28,6 +28,8 @@ import android.os.SharedMemory;
*/
interface IWearableSensingManager {
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
+ void provideWearableConnection(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback);
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index eca0039c20f4..401d0b7b47fd 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -26,6 +26,7 @@ import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.ambientcontext.AmbientContextEvent;
+import android.companion.CompanionDeviceManager;
import android.content.Context;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
@@ -36,6 +37,8 @@ import android.os.SharedMemory;
import android.service.wearable.WearableSensingService;
import android.system.OsConstants;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.Executor;
@@ -107,6 +110,14 @@ public class WearableSensingManager {
@FlaggedApi(Flags.FLAG_ENABLE_UNSUPPORTED_OPERATION_STATUS_CODE)
public static final int STATUS_UNSUPPORTED_OPERATION = 6;
+ /**
+ * The value of the status code that indicates an error occurred in the encrypted channel backed
+ * by the provided connection. See {@link #provideWearableConnection(ParcelFileDescriptor,
+ * Executor, Consumer)}.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API)
+ public static final int STATUS_CHANNEL_ERROR = 7;
+
/** @hide */
@IntDef(prefix = { "STATUS_" }, value = {
STATUS_UNKNOWN,
@@ -115,7 +126,8 @@ public class WearableSensingManager {
STATUS_SERVICE_UNAVAILABLE,
STATUS_WEARABLE_UNAVAILABLE,
STATUS_ACCESS_DENIED,
- STATUS_UNSUPPORTED_OPERATION
+ STATUS_UNSUPPORTED_OPERATION,
+ STATUS_CHANNEL_ERROR
})
@Retention(RetentionPolicy.SOURCE)
public @interface StatusCode {}
@@ -132,6 +144,60 @@ public class WearableSensingManager {
}
/**
+ * Provides a remote wearable device connection to the WearableSensingService and sends the
+ * resulting status to the {@code statusConsumer} after the call.
+ *
+ * <p>This is used by applications that will also provide an implementation of the isolated
+ * WearableSensingService.
+ *
+ * <p>The provided {@code wearableConnection} is expected to be a connection to a remotely
+ * connected wearable device. This {@code wearableConnection} will be attached to
+ * CompanionDeviceManager via {@link CompanionDeviceManager#attachSystemDataTransport(int,
+ * InputStream, OutputStream)}, which will create an encrypted channel using {@code
+ * wearableConnection} as the raw underlying connection. The wearable device is expected to
+ * attach its side of the raw connection to its CompanionDeviceManager via the same method so
+ * that the two CompanionDeviceManagers on the two devices can perform attestation and set up
+ * the encrypted channel. Attestation requirements are listed in
+ * com.android.server.security.AttestationVerificationPeerDeviceVerifier
+ *
+ * <p>A proxy to the encrypted channel will be provided to the WearableSensingService, which is
+ * referred to as the secureWearableConnection in WearableSensingService. Any data written to
+ * secureWearableConnection will be encrypted by CompanionDeviceManager and sent over the raw
+ * {@code wearableConnection} to the remote wearable device, which is expected to use its
+ * CompanionDeviceManager to decrypt the data. Encrypted data arriving at the raw {@code
+ * wearableConnection} will be decrypted by CompanionDeviceManager and be readable as plain text
+ * from secureWearableConnection. The raw {@code wearableConnection} provided to this method
+ * will not be directly available to the WearableSensingService.
+ *
+ * <p>If an error occurred in the encrypted channel (such as the underlying stream closed), the
+ * system will send a status code of {@link STATUS_CHANNEL_ERROR} to the {@code statusConsumer}
+ * and kill the WearableSensingService process.
+ *
+ * <p>Before providing the secureWearableConnection, the system will restart the
+ * WearableSensingService process. Other method calls into WearableSensingService may be dropped
+ * during the restart. The caller is responsible for ensuring other method calls are queued
+ * until a success status is returned from the {@code statusConsumer}.
+ *
+ * @param wearableConnection The connection to provide
+ * @param executor Executor on which to run the consumer callback
+ * @param statusConsumer A consumer that handles the status codes for providing the connection
+ * and errors in the encrypted channel.
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
+ @FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API)
+ public void provideWearableConnection(
+ @NonNull ParcelFileDescriptor wearableConnection,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull @StatusCode Consumer<Integer> statusConsumer) {
+ try {
+ RemoteCallback callback = createStatusCallback(executor, statusConsumer);
+ mService.provideWearableConnection(wearableConnection, callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Provides a data stream to the WearableSensingService that's backed by the
* parcelFileDescriptor, and sends the result to the {@link Consumer} right after the call.
* This is used by applications that will also provide an implementation of
@@ -149,15 +215,7 @@ public class WearableSensingManager {
@NonNull @CallbackExecutor Executor executor,
@NonNull @StatusCode Consumer<Integer> statusConsumer) {
try {
- RemoteCallback callback = new RemoteCallback(result -> {
- int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
- final long identity = Binder.clearCallingIdentity();
- try {
- executor.execute(() -> statusConsumer.accept(status));
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- });
+ RemoteCallback callback = createStatusCallback(executor, statusConsumer);
mService.provideDataStream(parcelFileDescriptor, callback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -191,19 +249,24 @@ public class WearableSensingManager {
@NonNull @CallbackExecutor Executor executor,
@NonNull @StatusCode Consumer<Integer> statusConsumer) {
try {
- RemoteCallback callback = new RemoteCallback(result -> {
- int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
- final long identity = Binder.clearCallingIdentity();
- try {
- executor.execute(() -> statusConsumer.accept(status));
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- });
+ RemoteCallback callback = createStatusCallback(executor, statusConsumer);
mService.provideData(data, sharedMemory, callback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
+ private static RemoteCallback createStatusCallback(
+ Executor executor, Consumer<Integer> statusConsumer) {
+ return new RemoteCallback(
+ result -> {
+ int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> statusConsumer.accept(status));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ });
+ }
}
diff --git a/core/java/android/service/wearable/IWearableSensingService.aidl b/core/java/android/service/wearable/IWearableSensingService.aidl
index 44a13c4fb9e5..8fdb2c20e719 100644
--- a/core/java/android/service/wearable/IWearableSensingService.aidl
+++ b/core/java/android/service/wearable/IWearableSensingService.aidl
@@ -28,6 +28,7 @@ import android.os.SharedMemory;
* @hide
*/
oneway interface IWearableSensingService {
+ void provideSecureWearableConnection(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback);
void startDetection(in AmbientContextEventRequest request, in String packageName,
diff --git a/core/java/android/service/wearable/WearableSensingService.java b/core/java/android/service/wearable/WearableSensingService.java
index e7e44a54cbae..d21115b5d622 100644
--- a/core/java/android/service/wearable/WearableSensingService.java
+++ b/core/java/android/service/wearable/WearableSensingService.java
@@ -17,12 +17,14 @@
package android.service.wearable;
import android.annotation.BinderThread;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.Service;
import android.app.ambientcontext.AmbientContextEvent;
import android.app.ambientcontext.AmbientContextEventRequest;
+import android.app.wearable.Flags;
import android.app.wearable.WearableSensingManager;
import android.content.Intent;
import android.os.Bundle;
@@ -39,6 +41,7 @@ import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -94,17 +97,20 @@ public abstract class WearableSensingService extends Service {
return new IWearableSensingService.Stub() {
/** {@inheritDoc} */
@Override
+ public void provideSecureWearableConnection(
+ ParcelFileDescriptor secureWearableConnection, RemoteCallback callback) {
+ Objects.requireNonNull(secureWearableConnection);
+ Consumer<Integer> consumer = createWearableStatusConsumer(callback);
+ WearableSensingService.this.onSecureWearableConnectionProvided(
+ secureWearableConnection, consumer);
+ }
+
+ /** {@inheritDoc} */
+ @Override
public void provideDataStream(
- ParcelFileDescriptor parcelFileDescriptor,
- RemoteCallback callback) {
+ ParcelFileDescriptor parcelFileDescriptor, RemoteCallback callback) {
Objects.requireNonNull(parcelFileDescriptor);
- Consumer<Integer> consumer = response -> {
- Bundle bundle = new Bundle();
- bundle.putInt(
- STATUS_RESPONSE_BUNDLE_KEY,
- response);
- callback.sendResult(bundle);
- };
+ Consumer<Integer> consumer = createWearableStatusConsumer(callback);
WearableSensingService.this.onDataStreamProvided(
parcelFileDescriptor, consumer);
}
@@ -116,38 +122,38 @@ public abstract class WearableSensingService extends Service {
SharedMemory sharedMemory,
RemoteCallback callback) {
Objects.requireNonNull(data);
- Consumer<Integer> consumer = response -> {
- Bundle bundle = new Bundle();
- bundle.putInt(
- STATUS_RESPONSE_BUNDLE_KEY,
- response);
- callback.sendResult(bundle);
- };
+ Consumer<Integer> consumer = createWearableStatusConsumer(callback);
WearableSensingService.this.onDataProvided(data, sharedMemory, consumer);
}
/** {@inheritDoc} */
@Override
- public void startDetection(@NonNull AmbientContextEventRequest request,
- String packageName, RemoteCallback detectionResultCallback,
+ public void startDetection(
+ @NonNull AmbientContextEventRequest request,
+ String packageName,
+ RemoteCallback detectionResultCallback,
RemoteCallback statusCallback) {
Objects.requireNonNull(request);
Objects.requireNonNull(packageName);
Objects.requireNonNull(detectionResultCallback);
Objects.requireNonNull(statusCallback);
- Consumer<AmbientContextDetectionResult> detectionResultConsumer = result -> {
- Bundle bundle = new Bundle();
- bundle.putParcelable(
- AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY, result);
- detectionResultCallback.sendResult(bundle);
- };
- Consumer<AmbientContextDetectionServiceStatus> statusConsumer = status -> {
- Bundle bundle = new Bundle();
- bundle.putParcelable(
- AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY,
- status);
- statusCallback.sendResult(bundle);
- };
+ Consumer<AmbientContextDetectionResult> detectionResultConsumer =
+ result -> {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(
+ AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY,
+ result);
+ detectionResultCallback.sendResult(bundle);
+ };
+ Consumer<AmbientContextDetectionServiceStatus> statusConsumer =
+ status -> {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(
+ AmbientContextDetectionServiceStatus
+ .STATUS_RESPONSE_BUNDLE_KEY,
+ status);
+ statusCallback.sendResult(bundle);
+ };
WearableSensingService.this.onStartDetection(
request, packageName, statusConsumer, detectionResultConsumer);
Slog.d(TAG, "startDetection " + request);
@@ -162,23 +168,26 @@ public abstract class WearableSensingService extends Service {
/** {@inheritDoc} */
@Override
- public void queryServiceStatus(@AmbientContextEvent.EventCode int[] eventTypes,
- String packageName, RemoteCallback callback) {
+ public void queryServiceStatus(
+ @AmbientContextEvent.EventCode int[] eventTypes,
+ String packageName,
+ RemoteCallback callback) {
Objects.requireNonNull(eventTypes);
Objects.requireNonNull(packageName);
Objects.requireNonNull(callback);
- Consumer<AmbientContextDetectionServiceStatus> consumer = response -> {
- Bundle bundle = new Bundle();
- bundle.putParcelable(
- AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY,
- response);
- callback.sendResult(bundle);
- };
+ Consumer<AmbientContextDetectionServiceStatus> consumer =
+ response -> {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(
+ AmbientContextDetectionServiceStatus
+ .STATUS_RESPONSE_BUNDLE_KEY,
+ response);
+ callback.sendResult(bundle);
+ };
Integer[] events = intArrayToIntegerArray(eventTypes);
WearableSensingService.this.onQueryServiceStatus(
new HashSet<>(Arrays.asList(events)), packageName, consumer);
}
-
};
}
Slog.w(TAG, "Incorrect service interface, returning null.");
@@ -186,6 +195,30 @@ public abstract class WearableSensingService extends Service {
}
/**
+ * Called when a secure connection to the wearable is available. See {@link
+ * WearableSensingManager#provideWearableConnection(ParcelFileDescriptor, Executor, Consumer)}
+ * for details about the secure connection.
+ *
+ * <p>When the {@code secureWearableConnection} is closed, the system will send a {@link
+ * WearableSensingManager#STATUS_CHANNEL_ERROR} status code to the status consumer provided by
+ * the caller of {@link WearableSensingManager#provideWearableConnection(ParcelFileDescriptor,
+ * Executor, Consumer)}.
+ *
+ * <p>The implementing class should override this method. It should return an appropriate status
+ * code via {@code statusConsumer} after receiving the {@code secureWearableConnection}.
+ *
+ * @param secureWearableConnection The secure connection to the wearable.
+ * @param statusConsumer The consumer for the service status.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API)
+ @BinderThread
+ public void onSecureWearableConnectionProvided(
+ @NonNull ParcelFileDescriptor secureWearableConnection,
+ @NonNull Consumer<Integer> statusConsumer) {
+ statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION);
+ }
+
+ /**
* Called when a data stream to the wearable is provided. This data stream can be used to obtain
* data from a wearable device. It is up to the implementation to maintain the data stream and
* close the data stream when it is finished.
@@ -275,4 +308,13 @@ public abstract class WearableSensingService extends Service {
}
return intArray;
}
+
+ @NonNull
+ private static Consumer<Integer> createWearableStatusConsumer(RemoteCallback statusCallback) {
+ return response -> {
+ Bundle bundle = new Bundle();
+ bundle.putInt(STATUS_RESPONSE_BUNDLE_KEY, response);
+ statusCallback.sendResult(bundle);
+ };
+ }
}
diff --git a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
index b2bbcda862ec..e1abae808f10 100644
--- a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
+++ b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
@@ -32,6 +32,8 @@ import android.util.Slog;
import com.android.internal.infra.ServiceConnector;
+import java.io.IOException;
+
/** Manages the connection to the remote wearable sensing service. */
final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearableSensingService> {
private static final String TAG =
@@ -56,6 +58,29 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable
}
/**
+ * Provides a secure connection to the wearable.
+ *
+ * @param secureWearableConnection The secure connection to the wearable
+ * @param callback The callback for service status
+ */
+ public void provideSecureWearableConnection(
+ ParcelFileDescriptor secureWearableConnection, RemoteCallback callback) {
+ if (DEBUG) {
+ Slog.i(TAG, "Providing secure wearable connection.");
+ }
+ var unused = post(
+ service -> {
+ service.provideSecureWearableConnection(secureWearableConnection, callback);
+ try {
+ // close the local fd after it has been sent to the WSS process
+ secureWearableConnection.close();
+ } catch (IOException ex) {
+ Slog.w(TAG, "Unable to close the local parcelFileDescriptor.", ex);
+ }
+ });
+ }
+
+ /**
* Provides the implementation a data stream to the wearable.
*
* @param parcelFileDescriptor The data stream to the wearable
@@ -66,7 +91,16 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable
if (DEBUG) {
Slog.i(TAG, "Providing data stream.");
}
- post(service -> service.provideDataStream(parcelFileDescriptor, callback));
+ var unused = post(
+ service -> {
+ service.provideDataStream(parcelFileDescriptor, callback);
+ try {
+ // close the local fd after it has been sent to the WSS process
+ parcelFileDescriptor.close();
+ } catch (IOException ex) {
+ Slog.w(TAG, "Unable to close the local parcelFileDescriptor.", ex);
+ }
+ });
}
/**
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
index e73fd0f400ac..a8d63228775f 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
@@ -22,17 +22,19 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.app.ambientcontext.AmbientContextEvent;
+import android.app.wearable.Flags;
import android.app.wearable.WearableSensingManager;
+import android.companion.CompanionDeviceManager;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.Bundle;
-import android.system.OsConstants;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.SharedMemory;
+import android.system.OsConstants;
import android.util.IndentingPrintWriter;
import android.util.Slog;
@@ -40,6 +42,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.infra.AbstractPerUserSystemService;
+import java.io.IOException;
import java.io.PrintWriter;
/**
@@ -55,6 +58,10 @@ final class WearableSensingManagerPerUserService extends
RemoteWearableSensingService mRemoteService;
private ComponentName mComponentName;
+ private final Object mSecureChannelLock = new Object();
+
+ @GuardedBy("mSecureChannelLock")
+ private WearableSensingSecureChannel mSecureChannel;
WearableSensingManagerPerUserService(
@NonNull WearableSensingManagerService master, Object lock, @UserIdInt int userId) {
@@ -76,6 +83,11 @@ final class WearableSensingManagerPerUserService extends
mRemoteService = null;
}
}
+ synchronized (mSecureChannelLock) {
+ if (mSecureChannel != null) {
+ mSecureChannel.close();
+ }
+ }
}
@GuardedBy("mLock")
@@ -156,6 +168,63 @@ final class WearableSensingManagerPerUserService extends
}
/**
+ * Creates a CompanionDeviceManager secure channel and sends a proxy to the wearable sensing
+ * service.
+ */
+ public void onProvideWearableConnection(
+ ParcelFileDescriptor wearableConnection, RemoteCallback callback) {
+ Slog.i(TAG, "onProvideWearableConnection in per user service.");
+ synchronized (mLock) {
+ if (!setUpServiceIfNeeded()) {
+ Slog.w(TAG, "Detection service is not available at this moment.");
+ notifyStatusCallback(callback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ return;
+ }
+ }
+ synchronized (mSecureChannelLock) {
+ if (mSecureChannel != null) {
+ // TODO(b/321012559): Kill the WearableSensingService process if it has not been
+ // killed from onError
+ mSecureChannel.close();
+ }
+ try {
+ mSecureChannel =
+ WearableSensingSecureChannel.create(
+ getContext().getSystemService(CompanionDeviceManager.class),
+ wearableConnection,
+ new WearableSensingSecureChannel.SecureTransportListener() {
+ @Override
+ public void onSecureTransportAvailable(
+ ParcelFileDescriptor secureTransport) {
+ Slog.i(TAG, "calling over to remote service.");
+ synchronized (mLock) {
+ ensureRemoteServiceInitiated();
+ mRemoteService.provideSecureWearableConnection(
+ secureTransport, callback);
+ }
+ }
+
+ @Override
+ public void onError() {
+ // TODO(b/321012559): Kill the WearableSensingService
+ // process if mSecureChannel has not been reassigned
+ if (Flags.enableProvideWearableConnectionApi()) {
+ notifyStatusCallback(
+ callback,
+ WearableSensingManager.STATUS_CHANNEL_ERROR);
+ }
+ }
+ });
+ } catch (IOException ex) {
+ Slog.e(TAG, "Unable to create the secure channel.", ex);
+ if (Flags.enableProvideWearableConnectionApi()) {
+ notifyStatusCallback(callback, WearableSensingManager.STATUS_CHANNEL_ERROR);
+ }
+ }
+ }
+ }
+
+ /**
* Handles sending the provided data stream for the wearable to the wearable sensing service.
*/
public void onProvideDataStream(
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index 4cc2c025575e..28c8f8769e15 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -211,9 +211,27 @@ public class WearableSensingManagerService extends
private final class WearableSensingManagerInternal extends IWearableSensingManager.Stub {
@Override
+ public void provideWearableConnection(
+ ParcelFileDescriptor wearableConnection, RemoteCallback callback) {
+ Slog.i(TAG, "WearableSensingManagerInternal provideWearableConnection.");
+ Objects.requireNonNull(wearableConnection);
+ Objects.requireNonNull(callback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available.");
+ WearableSensingManagerPerUserService.notifyStatusCallback(
+ callback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ return;
+ }
+ callPerUserServiceIfExist(
+ service -> service.onProvideWearableConnection(wearableConnection, callback),
+ callback);
+ }
+
+ @Override
public void provideDataStream(
- ParcelFileDescriptor parcelFileDescriptor,
- RemoteCallback callback) {
+ ParcelFileDescriptor parcelFileDescriptor, RemoteCallback callback) {
Slog.i(TAG, "WearableSensingManagerInternal provideDataStream.");
Objects.requireNonNull(parcelFileDescriptor);
Objects.requireNonNull(callback);
diff --git a/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java b/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java
new file mode 100644
index 000000000000..a16ff51e2d20
--- /dev/null
+++ b/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2024 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.wearable;
+
+import android.annotation.NonNull;
+import android.companion.AssociationInfo;
+import android.companion.AssociationRequest;
+import android.companion.CompanionDeviceManager;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.AutoCloseInputStream;
+import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+/**
+ * A wrapper that manages a CompanionDeviceManager secure channel for wearable sensing.
+ *
+ * <p>This wrapper accepts a connection to a wearable from the caller. It then attaches the
+ * connection to the CompanionDeviceManager via {@link
+ * CompanionDeviceManager#attachSystemDataTransport(int, InputStream, OutputStream)}, which will
+ * create an encrypted channel using the provided connection as the raw underlying connection. The
+ * wearable device is expected to attach its side of the raw connection to its
+ * CompanionDeviceManager via the same method so that the two CompanionDeviceManagers on the two
+ * devices can perform attestation and set up the encrypted channel. Attestation requirements are
+ * listed in {@link com.android.server.security.AttestationVerificationPeerDeviceVerifier}.
+ *
+ * <p>When the encrypted channel is available, it will be provided to the caller via the
+ * SecureTransportListener.
+ */
+final class WearableSensingSecureChannel {
+
+ /** A listener for secure transport and its error signal. */
+ interface SecureTransportListener {
+
+ /** Called when the secure transport is available. */
+ void onSecureTransportAvailable(ParcelFileDescriptor secureTransport);
+
+ /**
+ * Called when there is a non-recoverable error. The secure channel will be automatically
+ * closed.
+ */
+ void onError();
+ }
+
+ private static final String TAG = WearableSensingSecureChannel.class.getSimpleName();
+ private static final String CDM_ASSOCIATION_DISPLAY_NAME = "PlaceholderDisplayNameFromWSM";
+ // The batch size of reading from the ParcelFileDescriptor returned to mSecureTransportListener
+ private static final int READ_BUFFER_SIZE = 8192;
+
+ private final Object mLock = new Object();
+ // CompanionDeviceManager (CDM) can continue to call these ExecutorServices even after the
+ // corresponding cleanup methods in CDM have been called (e.g.
+ // removeOnTransportsChangedListener). Since we shut down these ExecutorServices after
+ // clean up, we use SoftShutdownExecutor to suppress RejectedExecutionExceptions.
+ private final SoftShutdownExecutor mMessageFromWearableExecutor =
+ new SoftShutdownExecutor(Executors.newSingleThreadExecutor());
+ private final SoftShutdownExecutor mMessageToWearableExecutor =
+ new SoftShutdownExecutor(Executors.newSingleThreadExecutor());
+ private final SoftShutdownExecutor mLightWeightExecutor =
+ new SoftShutdownExecutor(Executors.newSingleThreadExecutor());
+ private final CompanionDeviceManager mCompanionDeviceManager;
+ private final ParcelFileDescriptor mUnderlyingTransport;
+ private final SecureTransportListener mSecureTransportListener;
+ private final AtomicBoolean mTransportAvailable = new AtomicBoolean(false);
+ private final Consumer<List<AssociationInfo>> mOnTransportsChangedListener =
+ this::onTransportsChanged;
+ private final BiConsumer<Integer, byte[]> mOnMessageReceivedListener = this::onMessageReceived;
+ private final ParcelFileDescriptor mRemoteFd; // To be returned to mSecureTransportListener
+ // read input received from the ParcelFileDescriptor returned to mSecureTransportListener
+ private final InputStream mLocalIn;
+ // send output to the ParcelFileDescriptor returned to mSecureTransportListener
+ private final OutputStream mLocalOut;
+
+ @GuardedBy("mLock")
+ private boolean mClosed = false;
+
+ private Integer mAssociationId = null;
+
+ /**
+ * Creates a WearableSensingSecureChannel. When the secure transport is ready,
+ * secureTransportListener will be notified.
+ *
+ * @param companionDeviceManager The CompanionDeviceManager system service.
+ * @param underlyingTransport The underlying transport to create the secure channel on.
+ * @param secureTransportListener The listener to receive the secure transport when it is ready.
+ * @throws IOException if it cannot create a {@link ParcelFileDescriptor} socket pair.
+ */
+ static WearableSensingSecureChannel create(
+ @NonNull CompanionDeviceManager companionDeviceManager,
+ @NonNull ParcelFileDescriptor underlyingTransport,
+ @NonNull SecureTransportListener secureTransportListener)
+ throws IOException {
+ Objects.requireNonNull(companionDeviceManager);
+ Objects.requireNonNull(underlyingTransport);
+ Objects.requireNonNull(secureTransportListener);
+ ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair();
+ WearableSensingSecureChannel channel =
+ new WearableSensingSecureChannel(
+ companionDeviceManager,
+ underlyingTransport,
+ secureTransportListener,
+ pair[0],
+ pair[1]);
+ channel.initialize();
+ return channel;
+ }
+
+ private WearableSensingSecureChannel(
+ CompanionDeviceManager companionDeviceManager,
+ ParcelFileDescriptor underlyingTransport,
+ SecureTransportListener secureTransportListener,
+ ParcelFileDescriptor remoteFd,
+ ParcelFileDescriptor localFd) {
+ mCompanionDeviceManager = companionDeviceManager;
+ mUnderlyingTransport = underlyingTransport;
+ mSecureTransportListener = secureTransportListener;
+ mRemoteFd = remoteFd;
+ mLocalIn = new AutoCloseInputStream(localFd);
+ mLocalOut = new AutoCloseOutputStream(localFd);
+ }
+
+ private void initialize() {
+ final long originalCallingIdentity = Binder.clearCallingIdentity();
+ try {
+ Slog.d(TAG, "Requesting CDM association.");
+ mCompanionDeviceManager.associate(
+ new AssociationRequest.Builder()
+ .setDisplayName(CDM_ASSOCIATION_DISPLAY_NAME)
+ .setSelfManaged(true)
+ .build(),
+ mLightWeightExecutor,
+ new CompanionDeviceManager.Callback() {
+ @Override
+ public void onAssociationCreated(AssociationInfo associationInfo) {
+ WearableSensingSecureChannel.this.onAssociationCreated(
+ associationInfo.getId());
+ }
+
+ @Override
+ public void onFailure(CharSequence error) {
+ Slog.e(
+ TAG,
+ "Failed to create CompanionDeviceManager association: "
+ + error);
+ onError();
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(originalCallingIdentity);
+ }
+ }
+
+ private void onAssociationCreated(int associationId) {
+ Slog.i(TAG, "CDM association created.");
+ synchronized (mLock) {
+ if (mClosed) {
+ return;
+ }
+ mAssociationId = associationId;
+ mCompanionDeviceManager.addOnMessageReceivedListener(
+ mMessageFromWearableExecutor,
+ CompanionDeviceManager.MESSAGE_ONEWAY_FROM_WEARABLE,
+ mOnMessageReceivedListener);
+ mCompanionDeviceManager.addOnTransportsChangedListener(
+ mLightWeightExecutor, mOnTransportsChangedListener);
+ mCompanionDeviceManager.attachSystemDataTransport(
+ associationId,
+ new AutoCloseInputStream(mUnderlyingTransport),
+ new AutoCloseOutputStream(mUnderlyingTransport));
+ }
+ }
+
+ private void onTransportsChanged(List<AssociationInfo> associationInfos) {
+ synchronized (mLock) {
+ if (mClosed) {
+ return;
+ }
+ if (mAssociationId == null) {
+ Slog.e(TAG, "mAssociationId is null when transport changed");
+ return;
+ }
+ }
+ // Do not call onTransportAvailable() or onError() when holding the lock because it can
+ // cause a deadlock if the callback holds another lock.
+ boolean transportAvailable =
+ associationInfos.stream().anyMatch(info -> info.getId() == mAssociationId);
+ if (transportAvailable && mTransportAvailable.compareAndSet(false, true)) {
+ onTransportAvailable();
+ } else if (!transportAvailable && mTransportAvailable.compareAndSet(true, false)) {
+ Slog.i(TAG, "CDM transport is detached. This is not recoverable.");
+ onError();
+ }
+ }
+
+ private void onTransportAvailable() {
+ // Start sending data received from the remote stream to the wearable.
+ Slog.i(TAG, "Transport available");
+ mMessageToWearableExecutor.execute(
+ () -> {
+ int[] associationIdsToSendMessageTo = new int[] {mAssociationId};
+ byte[] buffer = new byte[READ_BUFFER_SIZE];
+ int readLen;
+ try {
+ while ((readLen = mLocalIn.read(buffer)) != -1) {
+ byte[] data = new byte[readLen];
+ System.arraycopy(buffer, 0, data, 0, readLen);
+ Slog.v(TAG, "Sending message to wearable");
+ mCompanionDeviceManager.sendMessage(
+ CompanionDeviceManager.MESSAGE_ONEWAY_TO_WEARABLE,
+ data,
+ associationIdsToSendMessageTo);
+ }
+ } catch (IOException e) {
+ Slog.i(TAG, "IOException while reading from remote stream.");
+ onError();
+ return;
+ }
+ Slog.i(
+ TAG,
+ "Reached EOF when reading from remote stream. Reporting this as an"
+ + " error.");
+ onError();
+ });
+ mSecureTransportListener.onSecureTransportAvailable(mRemoteFd);
+ }
+
+ private void onMessageReceived(int associationIdForMessage, byte[] data) {
+ if (associationIdForMessage == mAssociationId) {
+ Slog.v(TAG, "Received message from wearable.");
+ try {
+ mLocalOut.write(data);
+ mLocalOut.flush();
+ } catch (IOException e) {
+ Slog.i(
+ TAG,
+ "IOException when writing to remote stream. Closing the secure channel.");
+ onError();
+ }
+ } else {
+ Slog.v(
+ TAG,
+ "Received CDM message of type MESSAGE_ONEWAY_FROM_WEARABLE, but it is for"
+ + " another association. Ignoring the message.");
+ }
+ }
+
+ private void onError() {
+ synchronized (mLock) {
+ if (mClosed) {
+ return;
+ }
+ }
+ mSecureTransportListener.onError();
+ close();
+ }
+
+ /** Closes this secure channel and releases all resources. */
+ void close() {
+ synchronized (mLock) {
+ if (mClosed) {
+ return;
+ }
+ Slog.i(TAG, "Closing WearableSensingSecureChannel.");
+ mClosed = true;
+ if (mAssociationId != null) {
+ final long originalCallingIdentity = Binder.clearCallingIdentity();
+ try {
+ mCompanionDeviceManager.removeOnTransportsChangedListener(
+ mOnTransportsChangedListener);
+ mCompanionDeviceManager.removeOnMessageReceivedListener(
+ CompanionDeviceManager.MESSAGE_ONEWAY_FROM_WEARABLE,
+ mOnMessageReceivedListener);
+ mCompanionDeviceManager.detachSystemDataTransport(mAssociationId);
+ mCompanionDeviceManager.disassociate(mAssociationId);
+ } finally {
+ Binder.restoreCallingIdentity(originalCallingIdentity);
+ }
+ }
+ try {
+ mLocalIn.close();
+ } catch (IOException ex) {
+ Slog.e(TAG, "Encountered IOException when closing local input stream.", ex);
+ }
+ try {
+ mLocalOut.close();
+ } catch (IOException ex) {
+ Slog.e(TAG, "Encountered IOException when closing local output stream.", ex);
+ }
+ mMessageFromWearableExecutor.shutdown();
+ mMessageToWearableExecutor.shutdown();
+ mLightWeightExecutor.shutdown();
+ }
+ }
+
+ /**
+ * An executor that can be shutdown. Unlike an ExecutorService, it will not throw a
+ * RejectedExecutionException if {@link #execute(Runnable)} is called after shutdown.
+ */
+ private static class SoftShutdownExecutor implements Executor {
+
+ private final ExecutorService mExecutorService;
+
+ SoftShutdownExecutor(ExecutorService executorService) {
+ mExecutorService = executorService;
+ }
+
+ @Override
+ public void execute(Runnable runnable) {
+ try {
+ mExecutorService.execute(runnable);
+ } catch (RejectedExecutionException ex) {
+ Slog.d(TAG, "Received new runnable after shutdown. Ignoring.");
+ }
+ }
+
+ /** Shutdown the underlying ExecutorService. */
+ void shutdown() {
+ mExecutorService.shutdown();
+ }
+ }
+}