diff options
| author | 2024-02-07 19:29:07 +0000 | |
|---|---|---|
| committer | 2024-02-07 19:29:07 +0000 | |
| commit | 497253831d97507888df54119ca8f8737fc0db5a (patch) | |
| tree | 7cc1761b676e1215fdf6cc1640e72fecf7e02d27 | |
| parent | a68db6477d43cf9ffeab023fe0fb43d0da4945f7 (diff) | |
| parent | b44f7864371b5704653a5256120cc676095c7f0f (diff) | |
Merge "Add WSM#provideWearableConnection API" into main
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(); + } + } +} |