diff options
14 files changed, 938 insertions, 265 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 491bca2c3a5f..f9cd31651623 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3355,7 +3355,14 @@ package android.app.wallpapereffectsgeneration { package android.app.wearable { - @FlaggedApi("android.app.wearable.enable_data_request_observer_api") public final class WearableSensingDataRequest implements android.os.Parcelable { + @FlaggedApi("android.app.wearable.enable_concurrent_wearable_connections") public interface WearableConnection { + method @NonNull public android.os.ParcelFileDescriptor getConnection(); + method @NonNull public android.os.PersistableBundle getMetadata(); + method public void onConnectionAccepted(); + method public void onError(int); + } + + public final class WearableSensingDataRequest implements android.os.Parcelable { method public int describeContents(); method public int getDataSize(); method public int getDataType(); @@ -3367,29 +3374,35 @@ package android.app.wearable { field @NonNull public static final android.os.Parcelable.Creator<android.app.wearable.WearableSensingDataRequest> CREATOR; } - @FlaggedApi("android.app.wearable.enable_data_request_observer_api") public static final class WearableSensingDataRequest.Builder { + public static final class WearableSensingDataRequest.Builder { ctor public WearableSensingDataRequest.Builder(int); method @NonNull public android.app.wearable.WearableSensingDataRequest build(); method @NonNull public android.app.wearable.WearableSensingDataRequest.Builder setRequestDetails(@NonNull android.os.PersistableBundle); } public class WearableSensingManager { - method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @Nullable public static android.app.wearable.WearableSensingDataRequest getDataRequestFromIntent(@NonNull android.content.Intent); - method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideConnection(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("android.app.wearable.enable_concurrent_wearable_connections") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public int getAvailableConnectionCount(); + method @Nullable public static android.app.wearable.WearableSensingDataRequest getDataRequestFromIntent(@NonNull android.content.Intent); + method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideConnection(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("android.app.wearable.enable_concurrent_wearable_connections") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideConnection(@NonNull android.app.wearable.WearableConnection, @NonNull java.util.concurrent.Executor); 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 @Deprecated @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_data_request_observer_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void registerDataRequestObserver(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("android.app.wearable.enable_provide_read_only_pfd") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideReadOnlyParcelFileDescriptor(@NonNull android.os.ParcelFileDescriptor, @NonNull android.os.PersistableBundle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void registerDataRequestObserver(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("android.app.wearable.enable_concurrent_wearable_connections") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void removeAllConnections(); + method @FlaggedApi("android.app.wearable.enable_concurrent_wearable_connections") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public boolean removeConnection(@NonNull android.app.wearable.WearableConnection); method @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void startHotwordRecognition(@Nullable android.content.ComponentName, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void stopHotwordRecognition(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void unregisterDataRequestObserver(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void unregisterDataRequestObserver(int, @NonNull android.app.PendingIntent, @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_CHANNEL_ERROR = 7; // 0x7 + field @FlaggedApi("android.app.wearable.enable_concurrent_wearable_connections") public static final int STATUS_MAX_CONCURRENT_CONNECTIONS_EXCEEDED = 9; // 0x9 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 field @Deprecated public static final int STATUS_UNSUPPORTED = 2; // 0x2 - field @FlaggedApi("android.app.wearable.enable_data_request_observer_api") public static final int STATUS_UNSUPPORTED_DATA_TYPE = 8; // 0x8 - field @FlaggedApi("android.app.wearable.enable_unsupported_operation_status_code") public static final int STATUS_UNSUPPORTED_OPERATION = 6; // 0x6 + field public static final int STATUS_UNSUPPORTED_DATA_TYPE = 8; // 0x8 + field public static final int STATUS_UNSUPPORTED_OPERATION = 6; // 0x6 field public static final int STATUS_WEARABLE_UNAVAILABLE = 4; // 0x4 } @@ -14088,7 +14101,7 @@ package android.service.wallpapereffectsgeneration { package android.service.wearable { - @FlaggedApi("android.app.wearable.enable_data_request_observer_api") public interface WearableSensingDataRequester { + public interface WearableSensingDataRequester { method public void requestData(@NonNull android.app.wearable.WearableSensingDataRequest, @NonNull java.util.function.Consumer<java.lang.Integer>); field public static final int STATUS_OBSERVER_CANCELLED = 2; // 0x2 field public static final int STATUS_SUCCESS = 1; // 0x1 @@ -14101,11 +14114,13 @@ package android.service.wearable { ctor public WearableSensingService(); method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); method @BinderThread public abstract void onDataProvided(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @BinderThread public void onDataRequestObserverRegistered(int, @NonNull String, @NonNull android.service.wearable.WearableSensingDataRequester, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @BinderThread public void onDataRequestObserverUnregistered(int, @NonNull String, @NonNull android.service.wearable.WearableSensingDataRequester, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @BinderThread public void onDataRequestObserverRegistered(int, @NonNull String, @NonNull android.service.wearable.WearableSensingDataRequester, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @BinderThread public void onDataRequestObserverUnregistered(int, @NonNull String, @NonNull android.service.wearable.WearableSensingDataRequester, @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 onSecureConnectionProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("android.app.wearable.enable_provide_read_only_pfd") @BinderThread public void onReadOnlyParcelFileDescriptorProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull android.os.PersistableBundle, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @BinderThread public void onSecureConnectionProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("android.app.wearable.enable_concurrent_wearable_connections") @BinderThread public void onSecureConnectionProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull android.os.PersistableBundle, @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 @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") @BinderThread public void onStartHotwordRecognition(@NonNull java.util.function.Consumer<android.service.voice.HotwordAudioStream>, @NonNull java.util.function.Consumer<java.lang.Integer>); method public abstract void onStopDetection(@NonNull String); diff --git a/core/java/android/app/wearable/IWearableSensingManager.aidl b/core/java/android/app/wearable/IWearableSensingManager.aidl index c0d06ea88388..39a727f47208 100644 --- a/core/java/android/app/wearable/IWearableSensingManager.aidl +++ b/core/java/android/app/wearable/IWearableSensingManager.aidl @@ -31,8 +31,18 @@ import android.os.SharedMemory; */ interface IWearableSensingManager { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)") + int getAvailableConnectionCount(); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)") void provideConnection(in ParcelFileDescriptor parcelFileDescriptor, in IWearableSensingCallback wearableSensingCallback, in RemoteCallback statusCallback); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)") + int provideConcurrentConnection(in ParcelFileDescriptor parcelFileDescriptor, in PersistableBundle metadata, in IWearableSensingCallback wearableSensingCallback, in RemoteCallback statusCallback); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)") + boolean removeConnection(int connectionId); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)") + void removeAllConnections(); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)") + void provideReadOnlyParcelFileDescriptor(in ParcelFileDescriptor parcelFileDescriptor, in PersistableBundle metadata, in RemoteCallback statusCallback); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)") void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in @nullable IWearableSensingCallback wearableSensingCallback, in RemoteCallback statusCallback); @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/WearableConnection.java b/core/java/android/app/wearable/WearableConnection.java new file mode 100644 index 000000000000..f4935b2c220a --- /dev/null +++ b/core/java/android/app/wearable/WearableConnection.java @@ -0,0 +1,64 @@ +/* + * 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 android.app.wearable; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; + +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + * A connection to a remote wearable device. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_ENABLE_CONCURRENT_WEARABLE_CONNECTIONS) +public interface WearableConnection { + + /** Returns the connection to provide. */ + @NonNull + ParcelFileDescriptor getConnection(); + + /** Returns the metadata related to this connection. */ + @NonNull + PersistableBundle getMetadata(); + + /** + * Callback method called when the connection is accepted by the WearableSensingService. + * + * <p>See {@link WearableSensingManager#provideConnection(ParcelFileDescriptor, Executor, + * Consumer)} for details about the relationship between the connection provided via {@link + * #getConnection()} and the connection accepted by the WearableSensingService. + * + * <p>There will be no new invocation of this callback method after the connection is removed. + * Ongoing invocation will continue to run. + */ + void onConnectionAccepted(); + + /** + * Callback method called when an error occurred during secure connection setup. + * + * <p>There will be no new invocation of this callback method after the connection is removed. + * Ongoing invocation will continue to run. + */ + void onError(@WearableSensingManager.StatusCode int errorCode); +} diff --git a/core/java/android/app/wearable/WearableSensingDataRequest.java b/core/java/android/app/wearable/WearableSensingDataRequest.java index 9329b37be078..adbc4ec1f39c 100644 --- a/core/java/android/app/wearable/WearableSensingDataRequest.java +++ b/core/java/android/app/wearable/WearableSensingDataRequest.java @@ -16,7 +16,6 @@ package android.app.wearable; -import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; @@ -30,7 +29,6 @@ import java.time.Duration; * * @hide */ -@FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) @SystemApi public final class WearableSensingDataRequest implements Parcelable { private static final int MAX_REQUEST_SIZE = 200; @@ -164,7 +162,6 @@ public final class WearableSensingDataRequest implements Parcelable { } /** A builder for WearableSensingDataRequest. */ - @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) public static final class Builder { private int mDataType; private PersistableBundle mRequestDetails; diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java index d28969d0b3c1..f0763752e054 100644 --- a/core/java/android/app/wearable/WearableSensingManager.java +++ b/core/java/android/app/wearable/WearableSensingManager.java @@ -54,20 +54,21 @@ import java.io.InputStream; import java.io.OutputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.function.Consumer; /** - * Allows granted apps to manage the WearableSensingService. - * Applications are responsible for managing the connection to Wearables. Applications can choose - * to provide a data stream to the WearableSensingService to use for - * computing {@link AmbientContextEvent}s. Applications can also optionally provide their own - * defined data to power the detection of {@link AmbientContextEvent}s. - * Methods on this class requires the caller to hold and be granted the - * {@link Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE}. + * Allows granted apps to manage the WearableSensingService. Applications are responsible for + * managing the connection to Wearables. Applications can choose to provide a data stream to the + * WearableSensingService to use for computing {@link AmbientContextEvent}s. Applications can also + * optionally provide their own defined data to power the detection of {@link AmbientContextEvent}s. + * Methods on this class requires the caller to hold and be granted the {@link + * Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE}. * * <p>The use of "Wearable" here is not the same as the Android Wear platform and should be treated - * separately. </p> + * separately. * * @hide */ @@ -75,8 +76,8 @@ import java.util.function.Consumer; @SystemService(Context.WEARABLE_SENSING_SERVICE) public class WearableSensingManager { /** - * The bundle key for the service status query result, used in - * {@code RemoteCallback#sendResult}. + * The bundle key for the service status query result, used in {@code + * RemoteCallback#sendResult}. * * @hide */ @@ -93,13 +94,20 @@ public class WearableSensingManager { "android.app.wearable.extra.WEARABLE_SENSING_DATA_REQUEST"; /** - * An unknown status. + * An invalid connection ID returned by the system_server when it encounters an error providing + * a connection to WearableSensingService. + * + * @hide */ + public static final int CONNECTION_ID_INVALID = -1; + + /** A placeholder connection ID used in an implementation detail. */ + private static final int CONNECTION_ID_PLACEHOLDER = -2; + + /** An unknown status. */ public static final int STATUS_UNKNOWN = 0; - /** - * The value of the status code that indicates success. - */ + /** The value of the status code that indicates success. */ public static final int STATUS_SUCCESS = 1; /** @@ -107,46 +115,45 @@ public class WearableSensingManager { * supported. * * @deprecated WearableSensingManager does not deal with events. Use {@link - * STATUS_UNSUPPORTED_OPERATION} instead for operations not supported by the implementation of - * {@link WearableSensingService}. + * STATUS_UNSUPPORTED_OPERATION} instead for operations not supported by the implementation + * of {@link WearableSensingService}. */ - @Deprecated - public static final int STATUS_UNSUPPORTED = 2; + @Deprecated public static final int STATUS_UNSUPPORTED = 2; - /** - * The value of the status code that indicates service not available. - */ + /** The value of the status code that indicates service not available. */ public static final int STATUS_SERVICE_UNAVAILABLE = 3; - /** - * The value of the status code that there's no connection to the wearable. - */ + /** The value of the status code that there's no connection to the wearable. */ public static final int STATUS_WEARABLE_UNAVAILABLE = 4; - /** - * The value of the status code that the app is not granted access. - */ + /** The value of the status code that the app is not granted access. */ public static final int STATUS_ACCESS_DENIED = 5; /** * The value of the status code that indicates the method called is not supported by the * implementation of {@link WearableSensingService}. */ - @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 #provideConnection(ParcelFileDescriptor, - * Executor, Consumer)}. + * by the provided connection. See {@link #provideConnection(ParcelFileDescriptor, Executor, + * Consumer)}. */ - @FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API) public static final int STATUS_CHANNEL_ERROR = 7; /** The value of the status code that indicates the provided data type is not supported. */ - @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) public static final int STATUS_UNSUPPORTED_DATA_TYPE = 8; + /** + * The value of the status code that indicates the provided connection is rejected because the + * maximum number of concurrent connections have already been provided. Use {@link + * #removeConnection(WearableConnection)} or {@link #removeAllConnections()} to remove a + * connection before providing a new one. + */ + @FlaggedApi(Flags.FLAG_ENABLE_CONCURRENT_WEARABLE_CONNECTIONS) + public static final int STATUS_MAX_CONCURRENT_CONNECTIONS_EXCEEDED = 9; + /** @hide */ @IntDef( prefix = {"STATUS_"}, @@ -159,7 +166,8 @@ public class WearableSensingManager { STATUS_ACCESS_DENIED, STATUS_UNSUPPORTED_OPERATION, STATUS_CHANNEL_ERROR, - STATUS_UNSUPPORTED_DATA_TYPE + STATUS_UNSUPPORTED_DATA_TYPE, + STATUS_MAX_CONCURRENT_CONNECTIONS_EXCEEDED }) @Retention(RetentionPolicy.SOURCE) public @interface StatusCode {} @@ -183,7 +191,6 @@ public class WearableSensingManager { * @return The WearableSensingDataRequest in the provided Intent, or null if the Intent does not * contain a WearableSensingDataRequest. */ - @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) @Nullable public static WearableSensingDataRequest getDataRequestFromIntent(@NonNull Intent intent) { return intent.getParcelableExtra( @@ -194,8 +201,13 @@ public class WearableSensingManager { private final Context mContext; private final IWearableSensingManager mService; + private final Map<WearableConnection, Integer> mWearableConnectionIdMap = + new ConcurrentHashMap<>(); + /** - * {@hide} + * Creates a WearableSensingManager. + * + * @hide */ public WearableSensingManager(Context context, IWearableSensingManager service) { mContext = context; @@ -203,9 +215,61 @@ public class WearableSensingManager { } /** + * Returns the remaining number of concurrent connections allowed by {@link + * #provideConnection(WearableConnection, Executor)}. + */ + @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) + @FlaggedApi(Flags.FLAG_ENABLE_CONCURRENT_WEARABLE_CONNECTIONS) + public int getAvailableConnectionCount() { + try { + return mService.getAvailableConnectionCount(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Provides a remote wearable device connection to the WearableSensingService and sends the * resulting status to the {@code statusConsumer} after the call. * + * <p>This method has the same behavior as {@link #provideConnection(WearableConnection, + * Executor)} except that concurrent connections are not allowed. Before providing the + * secureWearableConnection, the system will restart the WearableSensingService process if it + * has not been restarted since the last secureWearableConnection was provided via this method. + * 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}. + * + * <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. + * + * @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) + public void provideConnection( + @NonNull ParcelFileDescriptor wearableConnection, + @NonNull @CallbackExecutor Executor executor, + @NonNull @StatusCode Consumer<Integer> statusConsumer) { + RemoteCallback statusCallback = createStatusCallback(executor, statusConsumer); + try { + // The wearableSensingCallback is included in this method call even though it is not + // semantically related to the connection because we want to avoid race conditions + // during the process restart triggered by this method call. See + // com.android.server.wearable.RemoteWearableSensingService for details. + mService.provideConnection( + wearableConnection, createWearableSensingCallback(executor), statusCallback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Provides a remote wearable device connection to the WearableSensingService. + * * <p>This is used by applications that will also provide an implementation of the isolated * WearableSensingService. * @@ -228,15 +292,18 @@ public class WearableSensingManager { * 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>There is a limit on the number of concurrent connections allowed. Call {@link + * #getAvailableConnectionCount()} to check the remaining quota. If more connections are + * provided than allowed, the new connection will be rejected with {@value + * #STATUS_MAX_CONCURRENT_CONNECTIONS_EXCEEDED}. To reclaim the quota of a previously provided + * connection that is no longer needed, call {@link #removeConnection(WearableConnection)} with + * the same WearableConnection instance. Connections provided via {@link + * #provideConnection(ParcelFileDescriptor, Executor, Consumer)} will not contribute towards the + * concurrent connection limit. * - * <p>Before providing the secureWearableConnection, the system will restart the - * WearableSensingService process if it has not been restarted since the last - * secureWearableConnection was provided. 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}. + * <p>If the {@code wearableConnection} receives an error, either the connection will not be + * sent to the WearableSensingService, or the connection will be closed by the system. Either + * way, the concurrent connection quota for this connection will be automatically released. * * <p>If the WearableSensingService implementation belongs to the same APK as the caller, * calling this method will allow WearableSensingService to read from the caller's file @@ -244,24 +311,144 @@ public class WearableSensingManager { * caller's process and executed by the {@code executor} provided to this method. * * @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. + * @param executor Executor on which to run the callback methods on {@code wearableConnection} */ @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) - @FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API) + @FlaggedApi(Flags.FLAG_ENABLE_CONCURRENT_WEARABLE_CONNECTIONS) public void provideConnection( - @NonNull ParcelFileDescriptor wearableConnection, + @NonNull WearableConnection wearableConnection, @NonNull Executor executor) { + RemoteCallback statusCallback = + createStatusCallback( + executor, + statusCode -> { + if (!mWearableConnectionIdMap.containsKey(wearableConnection)) { + Slog.i( + TAG, + "Surpassed status callback for removed connection " + + wearableConnection); + return; + } + if (statusCode == STATUS_SUCCESS) { + wearableConnection.onConnectionAccepted(); + } else { + mWearableConnectionIdMap.remove(wearableConnection); + wearableConnection.onError(statusCode); + } + }); + try { + // The statusCallback should not invoke callback on a removed connection. To implement + // this behavior, statusCallback will only invoke the callback if the connection is + // present in mWearableConnectionIdMap. We need to add the connection to the map before + // statusCallback is sent to mService in case the system triggers the statusCallback + // before the connectionId is returned. + mWearableConnectionIdMap.put(wearableConnection, CONNECTION_ID_PLACEHOLDER); + int connectionId = + mService.provideConcurrentConnection( + wearableConnection.getConnection(), + wearableConnection.getMetadata(), + createWearableSensingCallback(executor), + statusCallback); + if (connectionId != CONNECTION_ID_INVALID) { + mWearableConnectionIdMap.put(wearableConnection, connectionId); + } + // For invalid connection IDs, the status callback will remove the connection from + // mWearableConnectionIdMap + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes a connection previously provided via {@link #provideConnection(WearableConnection, + * Executor)}. + * + * <p>The WearableSensingService will no longer be able to use this connection. + * + * <p>After this method returns, there will be no new invocation to callback methods in the + * removed {@link WearableConnection}. Ongoing invocations will continue to run. + * + * <p>This method does nothing if the provided {@code wearableConnection} does not match any + * open connection. + * + * <p>This method should not be called before the corresponding {@link + * #provideConnection(WearableConnection, Executor)} invocation returns. Otherwise, the + * connection may not be removed. + * + * @param wearableConnection The WearableConnection instance previously provided to {@link + * #provideConnection(WearableConnection, Executor)}. + * @return true if a concurrent connection quota has been freed due to this method invocation. + * Returns false otherwise. + */ + @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) + @FlaggedApi(Flags.FLAG_ENABLE_CONCURRENT_WEARABLE_CONNECTIONS) + public boolean removeConnection(@NonNull WearableConnection wearableConnection) { + int connectionId = + mWearableConnectionIdMap.getOrDefault(wearableConnection, CONNECTION_ID_INVALID); + if (connectionId == CONNECTION_ID_INVALID) { + return false; + } + if (connectionId == CONNECTION_ID_PLACEHOLDER) { + Slog.w( + TAG, + "Attempt to remove connection before provideConnection returns. The connection" + + " will not be removed."); + return false; + } + mWearableConnectionIdMap.remove(wearableConnection); + try { + return mService.removeConnection(connectionId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes all connections previously provided via {@link #provideConnection(WearableConnection, + * Executor)}. + * + * <p>The connection provided via {@link #provideConnection(ParcelFileDescriptor, Executor, + * Consumer)}, if exists, will not be affected by this method. + * + * <p>The WearableSensingService will no longer be able to use any of the removed connections. + * + * <p>After this method returns, there will be no new invocation to callback methods in the + * removed {@link WearableConnection}s. Ongoing invocations will continue to run. + */ + @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) + @FlaggedApi(Flags.FLAG_ENABLE_CONCURRENT_WEARABLE_CONNECTIONS) + public void removeAllConnections() { + mWearableConnectionIdMap.clear(); + try { + mService.removeAllConnections(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Provides a read-only {@link ParcelFileDescriptor} to the WearableSensingService. + * + * <p>This is used by the application that will also provide an implementation of the isolated + * WearableSensingService. If the {@link ParcelFileDescriptor} was provided successfully, {@link + * WearableSensingManager#STATUS_SUCCESS} will be sent to the {@code statusConsumer}. + * + * @param parcelFileDescriptor The read-only {@link ParcelFileDescriptor} to provide + * @param metadata Metadata used to identify the {@code parcelFileDescriptor} + * @param executor Executor on which to run the {@code statusConsumer} + * @param statusConsumer A consumer that handles the status codes + * @throws IllegalArgumentException when the {@code parcelFileDescriptor} is not read-only + */ + @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) + @FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_READ_ONLY_PFD) + public void provideReadOnlyParcelFileDescriptor( + @NonNull ParcelFileDescriptor parcelFileDescriptor, + @NonNull PersistableBundle metadata, @NonNull @CallbackExecutor Executor executor, @NonNull @StatusCode Consumer<Integer> statusConsumer) { RemoteCallback statusCallback = createStatusCallback(executor, statusConsumer); try { - // The wearableSensingCallback is included in this method call even though it is not - // semantically related to the connection because we want to avoid race conditions - // during the process restart triggered by this method call. See - // com.android.server.wearable.RemoteWearableSensingService for details. - mService.provideConnection( - wearableConnection, createWearableSensingCallback(executor), statusCallback); + mService.provideReadOnlyParcelFileDescriptor( + parcelFileDescriptor, metadata, statusCallback); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -284,8 +471,8 @@ public class WearableSensingManager { * @param executor Executor on which to run the consumer callback * @param statusConsumer A consumer that handles the status codes, which is returned right after * the call. - * @deprecated Use {@link #provideConnection(ParcelFileDescriptor, Executor, Consumer)} instead - * to provide a remote wearable device connection to the WearableSensingService + * @deprecated Use {@link #provideConnection(WearableConnection, Executor)} instead to provide a + * remote wearable device connection to the WearableSensingService */ @Deprecated @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) @@ -308,28 +495,27 @@ public class WearableSensingManager { /** * Sets configuration and provides read-only data in a {@link PersistableBundle} that may be - * used by the WearableSensingService, and sends the result to the {@link Consumer} - * right after the call. It is dependent on the application to - * define the type of data to provide. This is used by applications that will also - * provide an implementation of an isolated WearableSensingService. If the data was - * provided successfully {@link WearableSensingManager#STATUS_SUCCESS} will be povided. + * used by the WearableSensingService, and sends the result to the {@link Consumer} right after + * the call. It is dependent on the application to define the type of data to provide. This is + * used by applications that will also provide an implementation of an isolated + * WearableSensingService. If the data was provided successfully {@link + * WearableSensingManager#STATUS_SUCCESS} will be povided. * * @param data Application configuration data to provide to the {@link WearableSensingService}. - * PersistableBundle does not allow any remotable objects or other contents - * that can be used to communicate with other processes. - * @param sharedMemory The unrestricted data blob to - * provide to the {@link WearableSensingService}. Use this to provide the - * sensing models data or other such data to the trusted process. - * The sharedMemory must be read only and protected with - * {@link OsConstants.PROT_READ}. - * Other operations will be removed by the system. + * PersistableBundle does not allow any remotable objects or other contents that can be used + * to communicate with other processes. + * @param sharedMemory The unrestricted data blob to provide to the {@link + * WearableSensingService}. Use this to provide the sensing models data or other such data + * to the trusted process. The sharedMemory must be read only and protected with {@link + * OsConstants.PROT_READ}. Other operations will be removed by the system. * @param executor Executor on which to run the consumer callback - * @param statusConsumer A consumer that handles the status codes, which is returned - * right after the call + * @param statusConsumer A consumer that handles the status codes, which is returned right after + * the call */ @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideData( - @NonNull PersistableBundle data, @Nullable SharedMemory sharedMemory, + @NonNull PersistableBundle data, + @Nullable SharedMemory sharedMemory, @NonNull @CallbackExecutor Executor executor, @NonNull @StatusCode Consumer<Integer> statusConsumer) { try { @@ -390,7 +576,6 @@ public class WearableSensingManager { * @param executor Executor on which to run the consumer callback. * @param statusConsumer A consumer that handles the status code for the observer registration. */ - @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void registerDataRequestObserver( int dataType, @@ -417,7 +602,6 @@ public class WearableSensingManager { * @param statusConsumer A consumer that handles the status code for the observer * unregistration. */ - @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void unregisterDataRequestObserver( int dataType, @@ -449,8 +633,8 @@ public class WearableSensingManager { * Consumer<android.service.voice.HotwordAudioStream>}, the system will check whether the {@link * android.service.voice.VoiceInteractionService} at that time is {@code * targetVisComponentName}. If not, the system will call {@link - * WearableSensingService#onStopHotwordAudioStream()} and will not forward the audio - * data to the current {@link android.service.voice.HotwordDetectionService} nor {@link + * WearableSensingService#onStopHotwordAudioStream()} and will not forward the audio data to the + * current {@link android.service.voice.HotwordDetectionService} nor {@link * android.service.voice.VoiceInteractionService}. The system will not send a status code to * {@code statusConsumer} regarding the {@code targetVisComponentName} check. The caller is * responsible for determining whether the system's {@link @@ -466,9 +650,9 @@ public class WearableSensingManager { * <p>If the {@code statusConsumer} returns {@link STATUS_SUCCESS}, the caller should call * {@link #stopHotwordRecognition(Executor, Consumer)} when it wants the wearable to stop * listening for hotword. If the {@code statusConsumer} returns any other status code, a failure - * has occurred and calling {@link #stopHotwordRecognition(Executor, Consumer)} is not - * required. The system will not retry listening automatically. The caller should call this - * method again if they want to retry. + * has occurred and calling {@link #stopHotwordRecognition(Executor, Consumer)} is not required. + * The system will not retry listening automatically. The caller should call this method again + * if they want to retry. * * <p>If a failure occurred after the {@link statusConsumer} returns {@link STATUS_SUCCESS}, * {@link statusConsumer} will be invoked again with a status code other than {@link diff --git a/core/java/android/app/wearable/flags.aconfig b/core/java/android/app/wearable/flags.aconfig index 534f46172fc1..acb3ff88223d 100644 --- a/core/java/android/app/wearable/flags.aconfig +++ b/core/java/android/app/wearable/flags.aconfig @@ -46,4 +46,12 @@ flag { namespace: "machine_learning" description: "This flag enables the APIs for providing multiple concurrent connections to the WearableSensingService." bug: "358133158" -}
\ No newline at end of file +} + +flag { + name: "enable_provide_read_only_pfd" + is_exported: true + namespace: "machine_learning" + description: "This flag enables the APIs for providing read-only ParcelFileDescriptors to the WearableSensingService." + bug: "358130861" +} diff --git a/core/java/android/service/wearable/IWearableSensingService.aidl b/core/java/android/service/wearable/IWearableSensingService.aidl index 9d9cacff120b..e6624ca7d5d6 100644 --- a/core/java/android/service/wearable/IWearableSensingService.aidl +++ b/core/java/android/service/wearable/IWearableSensingService.aidl @@ -18,6 +18,7 @@ package android.service.wearable; import android.app.ambientcontext.AmbientContextEventRequest; import android.app.wearable.IWearableSensingCallback; +import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.SharedMemory; @@ -30,6 +31,8 @@ import android.os.SharedMemory; */ oneway interface IWearableSensingService { void provideSecureConnection(in ParcelFileDescriptor parcelFileDescriptor, in IWearableSensingCallback wearableSensingCallback, in RemoteCallback statusCallback); + void provideConcurrentSecureConnection(in ParcelFileDescriptor parcelFileDescriptor, in PersistableBundle metadata, in IWearableSensingCallback wearableSensingCallback, in RemoteCallback statusCallback); + void provideReadOnlyParcelFileDescriptor(in ParcelFileDescriptor parcelFileDescriptor, in PersistableBundle metadata, in RemoteCallback statusCallback); void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in IWearableSensingCallback wearableSensingCallback, in RemoteCallback statusCallback); void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback); void registerDataRequestObserver(int dataType, in RemoteCallback dataRequestCallback, int dataRequestObserverId, in String packageName, in RemoteCallback statusCallback); diff --git a/core/java/android/service/wearable/WearableSensingDataRequester.java b/core/java/android/service/wearable/WearableSensingDataRequester.java index 5a8104f7e0cc..d6133f227b94 100644 --- a/core/java/android/service/wearable/WearableSensingDataRequester.java +++ b/core/java/android/service/wearable/WearableSensingDataRequester.java @@ -16,11 +16,9 @@ package android.service.wearable; -import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; -import android.app.wearable.Flags; import android.app.wearable.WearableSensingDataRequest; import java.lang.annotation.Retention; @@ -32,7 +30,6 @@ import java.util.function.Consumer; * * @hide */ -@FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) @SystemApi public interface WearableSensingDataRequester { diff --git a/core/java/android/service/wearable/WearableSensingService.java b/core/java/android/service/wearable/WearableSensingService.java index 3735c4383031..47ec0801f551 100644 --- a/core/java/android/service/wearable/WearableSensingService.java +++ b/core/java/android/service/wearable/WearableSensingService.java @@ -63,16 +63,15 @@ import java.util.function.Consumer; /** * Abstract base class for sensing with wearable devices. An example of this is {@link - *AmbientContextEvent} detection. + * AmbientContextEvent} detection. * - * <p> A service that provides requested sensing events to the system, such as a {@link - *AmbientContextEvent}. The system's default WearableSensingService implementation is configured in - * {@code config_defaultWearableSensingService}. If this config has no value, a stub is - * returned. + * <p>A service that provides requested sensing events to the system, such as a {@link + * AmbientContextEvent}. The system's default WearableSensingService implementation is configured in + * {@code config_defaultWearableSensingService}. If this config has no value, a stub is returned. + * + * <p>An implementation of a WearableSensingService should be an isolated service. Using the + * "isolatedProcess=true" attribute in the service's configurations. * - * <p> An implementation of a WearableSensingService should be an isolated service. Using the - * "isolatedProcess=true" attribute in the service's configurations. </p> - ** * <pre> * {@literal * <service android:name=".YourWearableSensingService" @@ -82,7 +81,7 @@ import java.util.function.Consumer; * </pre> * * <p>The use of "Wearable" here is not the same as the Android Wear platform and should be treated - * separately. </p> + * separately. * * @hide */ @@ -108,9 +107,9 @@ public abstract class WearableSensingService extends Service { /** * The {@link Intent} that must be declared as handled by the service. To be supported, the - * service must also require the - * {@link android.Manifest.permission#BIND_WEARABLE_SENSING_SERVICE} - * permission so that other applications can not abuse it. + * service must also require the {@link + * android.Manifest.permission#BIND_WEARABLE_SENSING_SERVICE} permission so that other + * applications can not abuse it. */ public static final String SERVICE_INTERFACE = "android.service.wearable.WearableSensingService"; @@ -145,6 +144,35 @@ public abstract class WearableSensingService extends Service { /** {@inheritDoc} */ @Override + public void provideConcurrentSecureConnection( + ParcelFileDescriptor secureWearableConnection, + PersistableBundle metadata, + IWearableSensingCallback wearableSensingCallback, + RemoteCallback callback) { + Objects.requireNonNull(secureWearableConnection); + Objects.requireNonNull(metadata); + if (wearableSensingCallback != null) { + mWearableSensingCallback = wearableSensingCallback; + } + Consumer<Integer> consumer = createWearableStatusConsumer(callback); + WearableSensingService.this.onSecureConnectionProvided( + secureWearableConnection, metadata, consumer); + } + + /** {@inheritDoc} */ + @Override + public void provideReadOnlyParcelFileDescriptor( + ParcelFileDescriptor parcelFileDescriptor, + PersistableBundle metadata, + RemoteCallback callback) { + Objects.requireNonNull(parcelFileDescriptor); + Consumer<Integer> consumer = createWearableStatusConsumer(callback); + WearableSensingService.this.onReadOnlyParcelFileDescriptorProvided( + parcelFileDescriptor, metadata, consumer); + } + + /** {@inheritDoc} */ + @Override public void provideDataStream( ParcelFileDescriptor parcelFileDescriptor, IWearableSensingCallback wearableSensingCallback, @@ -339,13 +367,13 @@ public abstract class WearableSensingService extends Service { /** * Called when a secure connection to the wearable is available. See {@link - * WearableSensingManager#provideConnection(ParcelFileDescriptor, Executor, Consumer)} - * for details about the secure connection. + * WearableSensingManager#provideConnection(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#provideConnection(ParcelFileDescriptor, - * Executor, Consumer)}. + * the caller of {@link WearableSensingManager#provideConnection(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}. @@ -353,7 +381,6 @@ public abstract class WearableSensingService extends Service { * @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 onSecureConnectionProvided( @NonNull ParcelFileDescriptor secureWearableConnection, @@ -362,6 +389,46 @@ public abstract class WearableSensingService extends Service { } /** + * Called when a secure connection to the wearable is available. + * + * @param secureWearableConnection The secure connection to the wearable. + * @param metadata Metadata related to the provided connection. + * @param statusConsumer The consumer for the service status. + * @see #onSecureConnectionProvided(ParcelFileDescriptor, Consumer) + */ + @FlaggedApi(Flags.FLAG_ENABLE_CONCURRENT_WEARABLE_CONNECTIONS) + @BinderThread + public void onSecureConnectionProvided( + @NonNull ParcelFileDescriptor secureWearableConnection, + @NonNull PersistableBundle metadata, + @NonNull Consumer<Integer> statusConsumer) { + statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION); + } + + /** + * Called when a read-only {@link ParcelFileDescriptor} is provided. + * + * <p>It is up to the implementation to close the {@link ParcelFileDescriptor} when it is + * finished. + * + * <p>The implementation should return one of the status code defined in the {@link + * WearableSensingManager} via the {@code statusConsumer}. + * + * @param parcelFileDescriptor The provided read-only {@link ParcelFileDescriptor} + * @param metadata The metadata provided along with the {@code parcelFileDescriptor} + * @param statusConsumer the consumer for the status code + */ + @FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_READ_ONLY_PFD) + @BinderThread + public void onReadOnlyParcelFileDescriptorProvided( + @NonNull ParcelFileDescriptor parcelFileDescriptor, + @NonNull PersistableBundle metadata, + @NonNull Consumer<Integer> statusConsumer) { + // placeholder implementation + 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. @@ -370,7 +437,8 @@ public abstract class WearableSensingService extends Service { * @param statusConsumer the consumer for the service status. */ @BinderThread - public abstract void onDataStreamProvided(@NonNull ParcelFileDescriptor parcelFileDescriptor, + public abstract void onDataStreamProvided( + @NonNull ParcelFileDescriptor parcelFileDescriptor, @NonNull Consumer<Integer> statusConsumer); /** @@ -419,7 +487,6 @@ public abstract class WearableSensingService extends Service { * @param statusConsumer the consumer for the status of the data request observer registration. * This is different from the status for each data request. */ - @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) @BinderThread public void onDataRequestObserverRegistered( int dataType, @@ -447,7 +514,6 @@ public abstract class WearableSensingService extends Service { * @param statusConsumer the consumer for the status of the data request observer * unregistration. This is different from the status for each data request. */ - @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) @BinderThread public void onDataRequestObserverUnregistered( int dataType, @@ -514,16 +580,16 @@ public abstract class WearableSensingService extends Service { /** * Called when hotword audio data sent to the {@code hotwordAudioConsumer} in {@link - * #onStartHotwordRecognition(Consumer, Consumer)} is accepted by the - * {@link android.service.voice.HotwordDetectionService} as valid hotword. + * #onStartHotwordRecognition(Consumer, Consumer)} is accepted by the {@link + * android.service.voice.HotwordDetectionService} as valid hotword. * * <p>After the implementation of this class sends the hotword audio data to the {@code - * hotwordAudioConsumer} in {@link #onStartHotwordRecognition(Consumer, - * Consumer)}, the system will forward the data into {@link - * android.service.voice.HotwordDetectionService} (which runs in an isolated process) for - * second-stage hotword detection. If accepted as valid hotword there, this method will be - * called, and then the system will send the data to the currently active {@link - * android.service.voice.AlwaysOnHotwordDetector} (which may not run in an isolated process). + * hotwordAudioConsumer} in {@link #onStartHotwordRecognition(Consumer, Consumer)}, the system + * will forward the data into {@link android.service.voice.HotwordDetectionService} (which runs + * in an isolated process) for second-stage hotword detection. If accepted as valid hotword + * there, this method will be called, and then the system will send the data to the currently + * active {@link android.service.voice.AlwaysOnHotwordDetector} (which may not run in an + * isolated process). * * <p>This method is expected to be overridden by a derived class. The implementation must * request the wearable to turn on the microphone indicator to notify the user that audio data @@ -554,17 +620,16 @@ public abstract class WearableSensingService extends Service { /** * Called when a client app requests starting detection of the events in the request. The * implementation should keep track of whether the user has explicitly consented to detecting - * the events using on-going ambient sensor (e.g. microphone), and agreed to share the - * detection results with this client app. If the user has not consented, the detection - * should not start, and the statusConsumer should get a response with STATUS_ACCESS_DENIED. - * If the user has made the consent and the underlying services are available, the - * implementation should start detection and provide detected events to the - * detectionResultConsumer. If the type of event needs immediate attention, the implementation - * should send result as soon as detected. Otherwise, the implementation can batch response. - * The ongoing detection will keep running, until onStopDetection is called. If there were - * previously requested detections from the same package, regardless of the type of events in - * the request, the previous request will be replaced with the new request and pending events - * are discarded. + * the events using on-going ambient sensor (e.g. microphone), and agreed to share the detection + * results with this client app. If the user has not consented, the detection should not start, + * and the statusConsumer should get a response with STATUS_ACCESS_DENIED. If the user has made + * the consent and the underlying services are available, the implementation should start + * detection and provide detected events to the detectionResultConsumer. If the type of event + * needs immediate attention, the implementation should send result as soon as detected. + * Otherwise, the implementation can batch response. The ongoing detection will keep running, + * until onStopDetection is called. If there were previously requested detections from the same + * package, regardless of the type of events in the request, the previous request will be + * replaced with the new request and pending events are discarded. * * @param request The request with events to detect. * @param packageName the requesting app's package name @@ -572,7 +637,8 @@ public abstract class WearableSensingService extends Service { * @param detectionResultConsumer the consumer for the detected event */ @BinderThread - public abstract void onStartDetection(@NonNull AmbientContextEventRequest request, + public abstract void onStartDetection( + @NonNull AmbientContextEventRequest request, @NonNull String packageName, @NonNull Consumer<AmbientContextDetectionServiceStatus> statusConsumer, @NonNull Consumer<AmbientContextDetectionResult> detectionResultConsumer); @@ -585,16 +651,17 @@ public abstract class WearableSensingService extends Service { public abstract void onStopDetection(@NonNull String packageName); /** - * Called when a query for the detection status occurs. The implementation should check - * the detection status of the requested events for the package, and provide results in a - * {@link AmbientContextDetectionServiceStatus} for the consumer. + * Called when a query for the detection status occurs. The implementation should check the + * detection status of the requested events for the package, and provide results in a {@link + * AmbientContextDetectionServiceStatus} for the consumer. * * @param eventTypes The events to check for status. * @param packageName the requesting app's package name * @param consumer the consumer for the query results */ @BinderThread - public abstract void onQueryServiceStatus(@NonNull Set<Integer> eventTypes, + public abstract void onQueryServiceStatus( + @NonNull Set<Integer> eventTypes, @NonNull String packageName, @NonNull Consumer<AmbientContextDetectionServiceStatus> consumer); @@ -693,6 +760,4 @@ public abstract class WearableSensingService extends Service { statusCallback.sendResult(bundle); }; } - - } diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b55e5f08f7c7..4111cfd17515 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4734,6 +4734,11 @@ --> <string name="config_defaultWearableSensingService" translatable="false"></string> + <!-- The maximum number of concurrent connections allowed between the WearableSensingService and + wearable devices. + --> + <integer name="config_maxWearableSensingServiceConcurrentConnections">5</integer> + <!-- The component name for the default system on-device intelligence service, --> <string name="config_defaultOnDeviceIntelligenceService" translatable="false"></string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 73244eb774df..d7cbc161f0c0 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3989,6 +3989,7 @@ <java-symbol type="string" name="config_ambientContextPackageNameExtraKey" /> <java-symbol type="string" name="config_ambientContextEventArrayExtraKey" /> <java-symbol type="string" name="config_defaultWearableSensingService" /> + <java-symbol type="integer" name="config_maxWearableSensingServiceConcurrentConnections" /> <java-symbol type="string" name="config_defaultOnDeviceIntelligenceService" /> <java-symbol type="string" name="config_defaultOnDeviceSandboxedInferenceService" /> <java-symbol type="string" name="config_onDeviceIntelligenceModelLoadedBroadcastKey" /> diff --git a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java index 6776f268e743..e697d1509b1e 100644 --- a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java +++ b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java @@ -19,7 +19,6 @@ package com.android.server.wearable; import static android.content.Context.BIND_FOREGROUND_SERVICE; import static android.content.Context.BIND_INCLUDE_CAPABILITIES; -import android.app.wearable.Flags; import android.app.wearable.IWearableSensingCallback; import android.app.wearable.WearableSensingManager; import android.content.ComponentName; @@ -42,7 +41,7 @@ import java.io.IOException; final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearableSensingService> { private static final String TAG = com.android.server.wearable.RemoteWearableSensingService.class.getSimpleName(); - private final static boolean DEBUG = false; + private static final boolean DEBUG = false; private final Object mSecureConnectionLock = new Object(); @@ -55,11 +54,12 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable @GuardedBy("mSecureConnectionLock") private boolean mSecureConnectionProvided = false; - RemoteWearableSensingService(Context context, ComponentName serviceName, - int userId) { - super(context, new Intent( - WearableSensingService.SERVICE_INTERFACE).setComponent(serviceName), - BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId, + RemoteWearableSensingService(Context context, ComponentName serviceName, int userId) { + super( + context, + new Intent(WearableSensingService.SERVICE_INTERFACE).setComponent(serviceName), + BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, + userId, IWearableSensingService.Stub::asInterface); // Bind right away @@ -87,15 +87,6 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable if (DEBUG) { Slog.i(TAG, "#provideSecureConnection"); } - if (!Flags.enableRestartWssProcess()) { - Slog.d( - TAG, - "FLAG_ENABLE_RESTART_WSS_PROCESS is disabled. Do not attempt to restart the" - + " WearableSensingService process"); - provideSecureConnectionInternal( - secureWearableConnection, wearableSensingCallback, statusCallback); - return; - } synchronized (mSecureConnectionLock) { if (mNextSecureConnectionContext != null) { // A process restart is in progress, #binderDied is about to be called. Replace @@ -103,13 +94,11 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable Slog.i( TAG, "A new wearable connection is provided before the process restart triggered" - + " by the previous connection is complete. Discarding the previous" - + " connection."); - if (Flags.enableProvideWearableConnectionApi()) { - WearableSensingManagerPerUserService.notifyStatusCallback( - mNextSecureConnectionContext.mStatusCallback, - WearableSensingManager.STATUS_CHANNEL_ERROR); - } + + " by the previous connection is complete. Discarding the previous" + + " connection."); + WearableSensingManagerPerUserService.notifyStatusCallback( + mNextSecureConnectionContext.mStatusCallback, + WearableSensingManager.STATUS_CHANNEL_ERROR); mNextSecureConnectionContext = new SecureWearableConnectionContext( secureWearableConnection, wearableSensingCallback, statusCallback); @@ -130,6 +119,32 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable } } + public void provideConcurrentSecureConnection( + ParcelFileDescriptor secureWearableConnection, + PersistableBundle metadata, + IWearableSensingCallback wearableSensingCallback, + RemoteCallback statusCallback) { + if (DEBUG) { + Slog.i(TAG, "#provideConcurrentSecureConnection"); + } + var unused = + post( + service -> { + service.provideConcurrentSecureConnection( + secureWearableConnection, + metadata, + wearableSensingCallback, + statusCallback); + try { + // close the local fd after it has been sent to the + // WearableSensingService process + secureWearableConnection.close(); + } catch (IOException ex) { + Slog.w(TAG, "Unable to close the local parcelFileDescriptor.", ex); + } + }); + } + private void provideSecureConnectionInternal( ParcelFileDescriptor secureWearableConnection, IWearableSensingCallback wearableSensingCallback, @@ -174,6 +189,28 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable var unused = post(service -> service.killProcess()); } + /** Provides a read-only {@link ParcelFileDescriptor} to the WearableSensingService. */ + public void provideReadOnlyParcelFileDescriptor( + ParcelFileDescriptor parcelFileDescriptor, + PersistableBundle metadata, + RemoteCallback callback) { + if (DEBUG) { + Slog.i(TAG, "Providing read-only ParcelFileDescriptor."); + } + var unused = + post( + service -> { + service.provideReadOnlyParcelFileDescriptor( + parcelFileDescriptor, metadata, 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); + } + }); + } + /** * Provides the implementation a data stream to the wearable. * @@ -210,9 +247,8 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable * @param sharedMemory The unrestricted data blob to provide to the implementation. * @param callback The callback for service status */ - public void provideData(PersistableBundle data, - SharedMemory sharedMemory, - RemoteCallback callback) { + public void provideData( + PersistableBundle data, SharedMemory sharedMemory, RemoteCallback callback) { if (DEBUG) { Slog.i(TAG, "Providing data."); } diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java index 36e52008f223..395816902592 100644 --- a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java +++ b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java @@ -51,6 +51,7 @@ import android.system.OsConstants; import android.util.IndentingPrintWriter; import android.util.Slog; +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.infra.AndroidFuture; @@ -59,21 +60,22 @@ import com.android.server.infra.AbstractPerUserSystemService; import java.io.IOException; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -/** - * Per-user manager service for managing sensing {@link AmbientContextEvent}s on Wearables. - */ -final class WearableSensingManagerPerUserService extends - AbstractPerUserSystemService<WearableSensingManagerPerUserService, - WearableSensingManagerService> { +/** Per-user manager service for managing sensing {@link AmbientContextEvent}s on Wearables. */ +final class WearableSensingManagerPerUserService + extends AbstractPerUserSystemService< + WearableSensingManagerPerUserService, WearableSensingManagerService> { private static final String TAG = WearableSensingManagerPerUserService.class.getSimpleName(); private final PackageManagerInternal mPackageManagerInternal; - @Nullable - @VisibleForTesting - RemoteWearableSensingService mRemoteService; + @Nullable @VisibleForTesting RemoteWearableSensingService mRemoteService; @Nullable private VoiceInteractionManagerInternal mVoiceInteractionManagerInternal; @@ -85,16 +87,32 @@ final class WearableSensingManagerPerUserService extends @GuardedBy("mSecureChannelLock") private WearableSensingSecureChannel mSecureChannel; + // mSecureChannelMap is used by the WearableSensingManager#provideConnection( + // WearableConnection, Executor) API, which allows up to mMaxNumberOfConcurrentConnections + // concurrent connections, while the mSecureChannel above is used by the deprecated + // #provideConnection(ParcelFileDescriptor, Executor, Consumer) API, which does not allow + // concurrent connections. + @GuardedBy("mSecureChannelMap") + private final Map<Integer, WearableSensingSecureChannel> mSecureChannelMap = new HashMap<>(); + + private final AtomicInteger mNextConnectionId = new AtomicInteger(1); + + private final int mMaxNumberOfConcurrentConnections; + WearableSensingManagerPerUserService( @NonNull WearableSensingManagerService master, Object lock, @UserIdInt int userId) { super(master, lock, userId); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); + mMaxNumberOfConcurrentConnections = + getContext() + .getResources() + .getInteger( + R.integer.config_maxWearableSensingServiceConcurrentConnections); } public static void notifyStatusCallback(RemoteCallback statusCallback, int statusCode) { Bundle bundle = new Bundle(); - bundle.putInt( - WearableSensingManager.STATUS_RESPONSE_BUNDLE_KEY, statusCode); + bundle.putInt(WearableSensingManager.STATUS_RESPONSE_BUNDLE_KEY, statusCode); statusCallback.sendResult(bundle); } @@ -116,8 +134,8 @@ final class WearableSensingManagerPerUserService extends @GuardedBy("mLock") private void ensureRemoteServiceInitiated() { if (mRemoteService == null) { - mRemoteService = new RemoteWearableSensingService( - getContext(), mComponentName, getUserId()); + mRemoteService = + new RemoteWearableSensingService(getContext(), mComponentName, getUserId()); } } @@ -130,18 +148,15 @@ final class WearableSensingManagerPerUserService extends return mVoiceInteractionManagerInternal != null; } - /** - * get the currently bound component name. - */ + /** get the currently bound component name. */ @VisibleForTesting ComponentName getComponentName() { return mComponentName; } - /** - * Resolves and sets up the service if it had not been done yet. Returns true if the service - * is available. + * Resolves and sets up the service if it had not been done yet. Returns true if the service is + * available. */ @GuardedBy("mLock") @VisibleForTesting @@ -155,8 +170,7 @@ final class WearableSensingManagerPerUserService extends ServiceInfo serviceInfo; try { - serviceInfo = AppGlobals.getPackageManager().getServiceInfo( - mComponentName, 0, mUserId); + serviceInfo = AppGlobals.getPackageManager().getServiceInfo(mComponentName, 0, mUserId); } catch (RemoteException e) { Slog.w(TAG, "RemoteException while setting up service"); return false; @@ -169,17 +183,17 @@ final class WearableSensingManagerPerUserService extends throws PackageManager.NameNotFoundException { ServiceInfo serviceInfo; try { - serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, - 0, mUserId); + serviceInfo = + AppGlobals.getPackageManager().getServiceInfo(serviceComponent, 0, mUserId); if (serviceInfo != null) { final String permission = serviceInfo.permission; - if (!Manifest.permission.BIND_WEARABLE_SENSING_SERVICE.equals( - permission)) { - throw new SecurityException(String.format( - "Service %s requires %s permission. Found %s permission", - serviceInfo.getComponentName(), - Manifest.permission.BIND_WEARABLE_SENSING_SERVICE, - serviceInfo.permission)); + if (!Manifest.permission.BIND_WEARABLE_SENSING_SERVICE.equals(permission)) { + throw new SecurityException( + String.format( + "Service %s requires %s permission. Found %s permission", + serviceInfo.getComponentName(), + Manifest.permission.BIND_WEARABLE_SENSING_SERVICE, + serviceInfo.permission)); } } } catch (RemoteException e) { @@ -199,6 +213,20 @@ final class WearableSensingManagerPerUserService extends } } + /** Returns the number of available concurrent connection quota. */ + public int getAvailableConnectionCount() { + synchronized (mSecureChannelMap) { + if (mSecureChannelMap.size() > mMaxNumberOfConcurrentConnections) { + Slog.e( + TAG, + "mMaxNumberOfConcurrentConnections exceeded. This should not be" + + " possible!"); + return 0; + } + return mMaxNumberOfConcurrentConnections - mSecureChannelMap.size(); + } + } + /** * Creates a CompanionDeviceManager secure channel and sends a proxy to the wearable sensing * service. @@ -245,36 +273,198 @@ final class WearableSensingManagerPerUserService extends @Override public void onError() { - if (Flags.enableRestartWssProcess()) { - synchronized (mSecureChannelLock) { - if (mSecureChannel != null - && mSecureChannel - == currentSecureChannelRef.get()) { - mRemoteService - .killWearableSensingServiceProcess(); - mSecureChannel = null; - } + synchronized (mSecureChannelLock) { + if (mSecureChannel != null + && mSecureChannel + == currentSecureChannelRef.get()) { + mRemoteService.killWearableSensingServiceProcess(); + mSecureChannel = null; } } - if (Flags.enableProvideWearableConnectionApi()) { - notifyStatusCallback( - statusCallback, - WearableSensingManager.STATUS_CHANNEL_ERROR); - } + notifyStatusCallback( + statusCallback, + WearableSensingManager.STATUS_CHANNEL_ERROR); } }); currentSecureChannelRef.set(mSecureChannel); } catch (IOException ex) { Slog.e(TAG, "Unable to create the secure channel.", ex); - if (Flags.enableProvideWearableConnectionApi()) { - notifyStatusCallback( - statusCallback, WearableSensingManager.STATUS_CHANNEL_ERROR); - } + notifyStatusCallback(statusCallback, WearableSensingManager.STATUS_CHANNEL_ERROR); } } } /** + * Creates a CompanionDeviceManager secure channel and sends a proxy to the wearable sensing + * service. + */ + public int onProvideConcurrentConnection( + ParcelFileDescriptor wearableConnection, + PersistableBundle metadata, + IWearableSensingCallback wearableSensingCallback, + RemoteCallback statusCallback) { + Slog.i(TAG, "onProvideConcurrentConnection in per user service."); + synchronized (mLock) { + if (!setUpServiceIfNeeded()) { + Slog.w(TAG, "Detection service is not available at this moment."); + notifyStatusCallback( + statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); + return WearableSensingManager.CONNECTION_ID_INVALID; + } + } + boolean isConcurrentConnectionLimitReached = false; + synchronized (mSecureChannelMap) { + // Do a pre-check on concurrent connection count. We need another check right before we + // add the connection into the map to prevent race conditions, but that can only happen + // after the WearableSensingSecureChannel is created. This check here allows us to + // reject before creating a new secure channel. + if (mSecureChannelMap.size() >= mMaxNumberOfConcurrentConnections) { + isConcurrentConnectionLimitReached = true; + } + } + if (isConcurrentConnectionLimitReached) { + Slog.i( + TAG, + "Rejecting connection because max concurrent connections limit has been" + + " reached."); + if (Flags.enableConcurrentWearableConnections()) { + notifyStatusCallback( + statusCallback, + WearableSensingManager.STATUS_MAX_CONCURRENT_CONNECTIONS_EXCEEDED); + } + return WearableSensingManager.CONNECTION_ID_INVALID; + } + int connectionId = mNextConnectionId.getAndIncrement(); + RemoteCallback wrappedStatusCallback = + wrapCallbackWithSecureChannelMapCleanUp(statusCallback, connectionId); + WearableSensingSecureChannel secureChannel; + try { + secureChannel = + 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.provideConcurrentSecureConnection( + secureTransport, + metadata, + wearableSensingCallback, + wrappedStatusCallback); + } + } + + @Override + public void onError() { + synchronized (mSecureChannelMap) { + mSecureChannelMap.remove(connectionId); + } + notifyStatusCallback( + wrappedStatusCallback, + WearableSensingManager.STATUS_CHANNEL_ERROR); + } + }); + } catch (IOException ex) { + Slog.e(TAG, "Unable to create the secure channel.", ex); + notifyStatusCallback(statusCallback, WearableSensingManager.STATUS_CHANNEL_ERROR); + return WearableSensingManager.CONNECTION_ID_INVALID; + } + synchronized (mSecureChannelMap) { + if (mSecureChannelMap.size() >= mMaxNumberOfConcurrentConnections) { + isConcurrentConnectionLimitReached = true; + } else { + mSecureChannelMap.put(connectionId, secureChannel); + } + } + if (isConcurrentConnectionLimitReached) { + Slog.i( + TAG, + "Rejecting connection because max concurrent connections limit has been" + + " reached."); + if (Flags.enableConcurrentWearableConnections()) { + notifyStatusCallback( + statusCallback, + WearableSensingManager.STATUS_MAX_CONCURRENT_CONNECTIONS_EXCEEDED); + } + secureChannel.close(); + return WearableSensingManager.CONNECTION_ID_INVALID; + } + return connectionId; + } + + private RemoteCallback wrapCallbackWithSecureChannelMapCleanUp( + RemoteCallback statusCallback, int connectionId) { + return new RemoteCallback( + result -> { + int status = result.getInt(WearableSensingManager.STATUS_RESPONSE_BUNDLE_KEY); + if (status != WearableSensingManager.STATUS_SUCCESS) { + removeConnection(connectionId); + } + statusCallback.sendResult(result); + }); + } + + /** + * Removes a connection by ID. + * + * @param connectionId The ID of the connection to remove. + * @return true if the {@code connectionId} corresponds to a stored connection. Returns false + * otherwise. + */ + public boolean removeConnection(int connectionId) { + WearableSensingSecureChannel removedChannel; + synchronized (mSecureChannelMap) { + removedChannel = mSecureChannelMap.remove(connectionId); + } + if (removedChannel != null) { + removedChannel.close(); + return true; + } + return false; + } + + /** Removes all stored connections. */ + public void removeAllConnections() { + List<WearableSensingSecureChannel> allChannels; + synchronized (mSecureChannelMap) { + allChannels = new ArrayList<>(mSecureChannelMap.values()); + mSecureChannelMap.clear(); + } + for (WearableSensingSecureChannel channel : allChannels) { + channel.close(); + } + } + + /** + * Handles sending the provided read-only {@link ParcelFileDescriptor} to the wearable sensing + * service. + */ + public void onProvideReadOnlyParcelFileDescriptor( + ParcelFileDescriptor parcelFileDescriptor, + PersistableBundle metadata, + RemoteCallback statusCallback) { + if (!isReadOnly(parcelFileDescriptor)) { + throw new IllegalArgumentException("Provided ParcelFileDescriptor is not read-only."); + } + synchronized (mLock) { + if (!setUpServiceIfNeeded()) { + Slog.w(TAG, "Detection service is not available at this moment."); + notifyStatusCallback( + statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); + return; + } + Slog.i(TAG, "calling over to remote servvice."); + ensureRemoteServiceInitiated(); + mRemoteService.provideReadOnlyParcelFileDescriptor( + parcelFileDescriptor, metadata, statusCallback); + } + } + + /** * Handles sending the provided data stream for the wearable to the wearable sensing service. */ public void onProvideDataStream( @@ -301,12 +491,9 @@ final class WearableSensingManagerPerUserService extends } } - /** - * Handles sending the provided data to the wearable sensing service. - */ - public void onProvidedData(PersistableBundle data, - SharedMemory sharedMemory, - RemoteCallback callback) { + /** Handles sending the provided data to the wearable sensing service. */ + public void onProvidedData( + PersistableBundle data, SharedMemory sharedMemory, RemoteCallback callback) { synchronized (mLock) { if (!setUpServiceIfNeeded()) { Slog.w(TAG, "Detection service is not available at this moment."); @@ -557,7 +744,7 @@ final class WearableSensingManagerPerUserService extends Slog.w( TAG, "Error encountered when trying to determine if the parcelFileDescriptor is" - + " read-only. Treating it as not read-only", + + " read-only. Treating it as not read-only", ex); } return false; diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java index ac419a541ccd..7297a23c1707 100644 --- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java +++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java @@ -66,11 +66,11 @@ import java.util.function.Consumer; * System service for managing sensing {@link AmbientContextEvent}s on Wearables. * * <p>The use of "Wearable" here is not the same as the Android Wear platform and should be treated - * separately. </p> + * separately. */ -public class WearableSensingManagerService extends - AbstractMasterSystemService<WearableSensingManagerService, - WearableSensingManagerPerUserService> { +public class WearableSensingManagerService + extends AbstractMasterSystemService< + WearableSensingManagerService, WearableSensingManagerPerUserService> { private static final String TAG = WearableSensingManagerService.class.getSimpleName(); private static final String KEY_SERVICE_ENABLED = "service_enabled"; @@ -111,11 +111,11 @@ public class WearableSensingManagerService extends volatile boolean mIsServiceEnabled; public WearableSensingManagerService(Context context) { - super(context, + super( + context, new FrameworkResourcesServiceNameResolver( - context, - R.string.config_defaultWearableSensingService), - /*disallowProperty=*/null, + context, R.string.config_defaultWearableSensingService), + /* disallowProperty= */ null, PACKAGE_UPDATE_POLICY_REFRESH_EAGER | /*To avoid high latency*/ PACKAGE_RESTART_POLICY_REFRESH_EAGER); mContext = context; @@ -141,24 +141,27 @@ public class WearableSensingManagerService extends getContext().getMainExecutor(), (properties) -> onDeviceConfigChange(properties.getKeyset())); - mIsServiceEnabled = DeviceConfig.getBoolean( - NAMESPACE_WEARABLE_SENSING, - KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED); + mIsServiceEnabled = + DeviceConfig.getBoolean( + NAMESPACE_WEARABLE_SENSING, + KEY_SERVICE_ENABLED, + DEFAULT_SERVICE_ENABLED); } } - private void onDeviceConfigChange(@NonNull Set<String> keys) { if (keys.contains(KEY_SERVICE_ENABLED)) { - mIsServiceEnabled = DeviceConfig.getBoolean( - NAMESPACE_WEARABLE_SENSING, - KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED); + mIsServiceEnabled = + DeviceConfig.getBoolean( + NAMESPACE_WEARABLE_SENSING, + KEY_SERVICE_ENABLED, + DEFAULT_SERVICE_ENABLED); } } @Override - protected WearableSensingManagerPerUserService newServiceLocked(int resolvedUserId, - boolean disabled) { + protected WearableSensingManagerPerUserService newServiceLocked( + int resolvedUserId, boolean disabled) { return new WearableSensingManagerPerUserService(this, mLock, resolvedUserId); } @@ -181,8 +184,8 @@ public class WearableSensingManagerService extends @Override protected void enforceCallingPermissionForManagement() { - getContext().enforceCallingPermission( - Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); + getContext() + .enforceCallingPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); } @Override @@ -193,9 +196,7 @@ public class WearableSensingManagerService extends return MAX_TEMPORARY_SERVICE_DURATION_MS; } - /** - * Returns the AmbientContextManagerPerUserService component for this user. - */ + /** Returns the AmbientContextManagerPerUserService component for this user. */ public ComponentName getComponentName(@UserIdInt int userId) { synchronized (mLock) { final WearableSensingManagerPerUserService service = getServiceForUserLocked(userId); @@ -260,8 +261,8 @@ public class WearableSensingManagerService extends * * <p>The current rate limit will also be reset. * - * <p>This method is only used for testing and must not be called in production code because - * it effectively bypasses the rate limiting introduced to enhance privacy protection. + * <p>This method is only used for testing and must not be called in production code because it + * effectively bypasses the rate limiting introduced to enhance privacy protection. */ @VisibleForTesting void setDataRequestRateLimitWindowSize(@NonNull Duration windowSize) { @@ -269,7 +270,7 @@ public class WearableSensingManagerService extends TAG, TextUtils.formatSimple( "Setting the data request rate limit window size to %s. This also resets" - + " the current limit and should only be callable from a test.", + + " the current limit and should only be callable from a test.", windowSize)); mDataRequestRateLimiter = new MultiRateLimiter.Builder(mContext) @@ -282,8 +283,8 @@ public class WearableSensingManagerService extends * * <p>The current rate limit will also be reset. * - * <p>This method is only used for testing and must not be called in production code because - * it effectively bypasses the rate limiting introduced to enhance privacy protection. + * <p>This method is only used for testing and must not be called in production code because it + * effectively bypasses the rate limiting introduced to enhance privacy protection. */ @VisibleForTesting void resetDataRequestRateLimitWindowSize() { @@ -391,14 +392,16 @@ public class WearableSensingManagerService extends private void callPerUserServiceIfExist( Consumer<WearableSensingManagerPerUserService> serviceConsumer, - RemoteCallback statusCallback) { + @Nullable RemoteCallback statusCallback) { int userId = UserHandle.getCallingUserId(); synchronized (mLock) { WearableSensingManagerPerUserService service = getServiceForUserLocked(userId); if (service == null) { Slog.w(TAG, "Service not available for userId " + userId); - WearableSensingManagerPerUserService.notifyStatusCallback(statusCallback, - WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); + if (statusCallback != null) { + WearableSensingManagerPerUserService.notifyStatusCallback( + statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); + } return; } serviceConsumer.accept(service); @@ -408,6 +411,16 @@ public class WearableSensingManagerService extends private final class WearableSensingManagerInternal extends IWearableSensingManager.Stub { @Override + public int getAvailableConnectionCount() { + WearableSensingManagerPerUserService service = + validateAndGetPerUserService(/* statusCallback= */ null); + if (service == null) { + return 0; + } + return service.getAvailableConnectionCount(); + } + + @Override public void provideConnection( ParcelFileDescriptor wearableConnection, IWearableSensingCallback wearableSensingCallback, @@ -431,6 +444,63 @@ public class WearableSensingManagerService extends } @Override + public int provideConcurrentConnection( + ParcelFileDescriptor wearableConnection, + PersistableBundle metadata, + IWearableSensingCallback wearableSensingCallback, + RemoteCallback statusCallback) { + Slog.i(TAG, "WearableSensingManagerInternal provideConcurrentConnection."); + Objects.requireNonNull(wearableConnection); + Objects.requireNonNull(metadata); + Objects.requireNonNull(wearableSensingCallback); + Objects.requireNonNull(statusCallback); + WearableSensingManagerPerUserService service = + validateAndGetPerUserService(statusCallback); + if (service == null) { + return WearableSensingManager.CONNECTION_ID_INVALID; + } + return service.onProvideConcurrentConnection( + wearableConnection, metadata, wearableSensingCallback, statusCallback); + } + + @Override + public boolean removeConnection(int connectionId) { + Slog.i(TAG, "WearableSensingManagerInternal removeConnection."); + WearableSensingManagerPerUserService service = + validateAndGetPerUserService(/* statusCallback= */ null); + if (service == null) { + return false; + } + return service.removeConnection(connectionId); + } + + @Override + public void removeAllConnections() { + Slog.i(TAG, "WearableSensingManagerInternal removeAllConnections."); + WearableSensingManagerPerUserService service = + validateAndGetPerUserService(/* statusCallback= */ null); + if (service == null) { + return; + } + service.removeAllConnections(); + } + + @Override + public void provideReadOnlyParcelFileDescriptor( + ParcelFileDescriptor parcelFileDescriptor, + PersistableBundle metadata, + RemoteCallback statusCallback) { + Slog.i(TAG, "WearableSensingManagerInternal provideReadOnlyParcelFileDescriptor."); + WearableSensingManagerPerUserService service = + validateAndGetPerUserService(statusCallback); + if (service == null) { + return; + } + service.onProvideReadOnlyParcelFileDescriptor( + parcelFileDescriptor, metadata, statusCallback); + } + + @Override public void provideDataStream( ParcelFileDescriptor parcelFileDescriptor, @Nullable IWearableSensingCallback wearableSensingCallback, @@ -455,9 +525,7 @@ public class WearableSensingManagerService extends @Override public void provideData( - PersistableBundle data, - SharedMemory sharedMemory, - RemoteCallback callback) { + PersistableBundle data, SharedMemory sharedMemory, RemoteCallback callback) { Slog.d(TAG, "WearableSensingManagerInternal provideData."); Objects.requireNonNull(data); Objects.requireNonNull(callback); @@ -470,8 +538,7 @@ public class WearableSensingManagerService extends return; } callPerUserServiceIfExist( - service -> service.onProvidedData(data, sharedMemory, callback), - callback); + service -> service.onProvidedData(data, sharedMemory, callback), callback); } @Override @@ -601,10 +668,44 @@ public class WearableSensingManagerService extends } @Override - public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, - String[] args, ShellCallback callback, ResultReceiver resultReceiver) { - new WearableSensingShellCommand(WearableSensingManagerService.this).exec( - this, in, out, err, args, callback, resultReceiver); + public void onShellCommand( + FileDescriptor in, + FileDescriptor out, + FileDescriptor err, + String[] args, + ShellCallback callback, + ResultReceiver resultReceiver) { + new WearableSensingShellCommand(WearableSensingManagerService.this) + .exec(this, in, out, err, args, callback, resultReceiver); + } + + @Nullable + private WearableSensingManagerPerUserService validateAndGetPerUserService( + @Nullable RemoteCallback statusCallback) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available."); + if (statusCallback != null) { + WearableSensingManagerPerUserService.notifyStatusCallback( + statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); + } + return null; + } + int userId = UserHandle.getCallingUserId(); + WearableSensingManagerPerUserService service; + synchronized (mLock) { + service = getServiceForUserLocked(userId); + } + if (service == null) { + Slog.w(TAG, "Service not available for userId " + userId); + if (statusCallback != null) { + WearableSensingManagerPerUserService.notifyStatusCallback( + statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); + } + return null; + } + return service; } } } |