summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/system-current.txt41
-rw-r--r--core/java/android/app/wearable/IWearableSensingManager.aidl10
-rw-r--r--core/java/android/app/wearable/WearableConnection.java64
-rw-r--r--core/java/android/app/wearable/WearableSensingDataRequest.java3
-rw-r--r--core/java/android/app/wearable/WearableSensingManager.java342
-rw-r--r--core/java/android/app/wearable/flags.aconfig10
-rw-r--r--core/java/android/service/wearable/IWearableSensingService.aidl3
-rw-r--r--core/java/android/service/wearable/WearableSensingDataRequester.java3
-rw-r--r--core/java/android/service/wearable/WearableSensingService.java157
-rw-r--r--core/res/res/values/config.xml5
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--services/core/java/com/android/server/wearable/RemoteWearableSensingService.java88
-rw-r--r--services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java297
-rw-r--r--services/core/java/com/android/server/wearable/WearableSensingManagerService.java179
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;
}
}
}