diff options
11 files changed, 739 insertions, 208 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index d86893b07a3d..d4cdb0085787 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3353,6 +3353,13 @@ package android.app.wallpapereffectsgeneration { package android.app.wearable { + @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(); @@ -3372,16 +3379,21 @@ package android.app.wearable { } public class WearableSensingManager { + 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 @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 @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 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 @@ -14039,6 +14051,7 @@ package android.service.wearable { 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 @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..c36c8050579b 100644 --- a/core/java/android/app/wearable/IWearableSensingManager.aidl +++ b/core/java/android/app/wearable/IWearableSensingManager.aidl @@ -31,8 +31,16 @@ 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 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/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java index 9fb90d4437c9..cebac077dc70 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,25 +115,18 @@ 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; /** @@ -136,14 +137,23 @@ public class WearableSensingManager { /** * 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)}. */ public static final int STATUS_CHANNEL_ERROR = 7; /** The value of the status code that indicates the provided data type is not supported. */ 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_"}, @@ -156,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 {} @@ -190,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; @@ -199,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. * @@ -224,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 @@ -240,23 +311,115 @@ 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_CONCURRENT_WEARABLE_CONNECTIONS) public void provideConnection( - @NonNull ParcelFileDescriptor wearableConnection, - @NonNull @CallbackExecutor Executor executor, - @NonNull @StatusCode Consumer<Integer> statusConsumer) { - RemoteCallback statusCallback = createStatusCallback(executor, statusConsumer); + @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 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); + // 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); + } else { + mWearableConnectionIdMap.remove(wearableConnection); + } + } 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(); } @@ -279,8 +442,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) @@ -303,28 +466,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 { @@ -442,8 +604,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 @@ -459,9 +621,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/service/wearable/IWearableSensingService.aidl b/core/java/android/service/wearable/IWearableSensingService.aidl index 9d9cacff120b..6abfa7054ab3 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,7 @@ 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 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/WearableSensingService.java b/core/java/android/service/wearable/WearableSensingService.java index a8cc9bcac47e..e282bbf723f7 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,23 @@ 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 provideDataStream( ParcelFileDescriptor parcelFileDescriptor, IWearableSensingCallback wearableSensingCallback, @@ -339,13 +355,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}. @@ -361,6 +377,23 @@ 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 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. @@ -369,7 +402,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); /** @@ -511,16 +545,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 @@ -551,17 +585,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 @@ -569,7 +602,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); @@ -582,16 +616,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); @@ -690,6 +725,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 ecc3afe7dbe2..e2eab311d424 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3988,6 +3988,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 4ee7bbb6a17d..dbf5c5083a65 100644 --- a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java +++ b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java @@ -41,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(); @@ -54,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 @@ -93,8 +94,8 @@ 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."); + + " by the previous connection is complete. Discarding the previous" + + " connection."); WearableSensingManagerPerUserService.notifyStatusCallback( mNextSecureConnectionContext.mStatusCallback, WearableSensingManager.STATUS_CHANNEL_ERROR); @@ -118,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, @@ -198,9 +225,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 70dd76e4232c..6697b1e1299c 100644 --- a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java +++ b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java @@ -50,6 +50,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; @@ -58,21 +59,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; @@ -84,16 +86,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); } @@ -115,8 +133,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()); } } @@ -129,18 +147,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 @@ -154,8 +169,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; @@ -168,17 +182,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) { @@ -198,6 +212,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. @@ -248,8 +276,7 @@ final class WearableSensingManagerPerUserService extends if (mSecureChannel != null && mSecureChannel == currentSecureChannelRef.get()) { - mRemoteService - .killWearableSensingServiceProcess(); + mRemoteService.killWearableSensingServiceProcess(); mSecureChannel = null; } } @@ -261,10 +288,117 @@ final class WearableSensingManagerPerUserService extends currentSecureChannelRef.set(mSecureChannel); } catch (IOException ex) { Slog.e(TAG, "Unable to create the secure channel.", ex); + 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_CHANNEL_ERROR); + statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); + return WearableSensingManager.CONNECTION_ID_INVALID; } } + int connectionId = mNextConnectionId.getAndIncrement(); + RemoteCallback wrappedStatusCallback = + wrapCallbackWithSecureChannelMapCleanUp(statusCallback, connectionId); + + // TODO(b/358133158): enforce mMaxNumberOfConcurrentConnections + 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) { + mSecureChannelMap.put(connectionId, secureChannel); + } + 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(); + } } /** @@ -294,12 +428,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."); @@ -550,7 +681,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..6ee2184ac716 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,48 @@ 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 provideDataStream( ParcelFileDescriptor parcelFileDescriptor, @Nullable IWearableSensingCallback wearableSensingCallback, @@ -455,9 +510,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 +523,7 @@ public class WearableSensingManagerService extends return; } callPerUserServiceIfExist( - service -> service.onProvidedData(data, sharedMemory, callback), - callback); + service -> service.onProvidedData(data, sharedMemory, callback), callback); } @Override @@ -601,10 +653,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; } } } |