Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9c40fb3a authored by Tom Chan's avatar Tom Chan Committed by Android (Google) Code Review
Browse files

Merge changes from topics "wsm-data-request-observer-no-rate-limiting",...

Merge changes from topics "wsm-data-request-observer-no-rate-limiting", "wsm-rate-limiting" into main

* changes:
  Add size- and rate-limiting to data requests.
  Add (un)registerDataRequestObserver API.
parents 4c1bdc28 e07894eb
Loading
Loading
Loading
Loading
+33 −0
Original line number Diff line number Diff line
@@ -3151,16 +3151,38 @@ 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 {
    method public int describeContents();
    method public int getDataSize();
    method public int getDataType();
    method public static int getMaxRequestSize();
    method public static int getRateLimit();
    method @NonNull public static java.time.Duration getRateLimitWindowSize();
    method @NonNull public android.os.PersistableBundle getRequestDetails();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    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 {
    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 @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideData(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
    method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideDataStream(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
    method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideWearableConnection(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
    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_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>);
    field public static final int STATUS_ACCESS_DENIED = 5; // 0x5
    field @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") public static final int STATUS_CHANNEL_ERROR = 7; // 0x7
    field public static final int STATUS_SERVICE_UNAVAILABLE = 3; // 0x3
    field public static final int STATUS_SUCCESS = 1; // 0x1
    field public static final int STATUS_UNKNOWN = 0; // 0x0
    field 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_WEARABLE_UNAVAILABLE = 4; // 0x4
  }
@@ -13429,10 +13451,21 @@ package android.service.watchdog {
package android.service.wearable {
  @FlaggedApi("android.app.wearable.enable_data_request_observer_api") 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
    field public static final int STATUS_TOO_FREQUENT = 4; // 0x4
    field public static final int STATUS_TOO_LARGE = 3; // 0x3
    field public static final int STATUS_UNKNOWN = 0; // 0x0
  }
  public abstract class WearableSensingService extends android.app.Service {
    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 abstract void onDataStreamProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.function.Consumer<java.lang.Integer>);
    method @BinderThread public abstract void onQueryServiceStatus(@NonNull java.util.Set<java.lang.Integer>, @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>);
    method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @BinderThread public void onSecureWearableConnectionProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+5 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.app.wearable;

import android.app.PendingIntent;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
@@ -33,4 +34,8 @@ interface IWearableSensingManager {
     void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
     void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback);
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
     void registerDataRequestObserver(int dataType, in PendingIntent dataRequestPendingIntent, in RemoteCallback statusCallback);
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
     void unregisterDataRequestObserver(int dataType, in PendingIntent dataRequestPendingIntent, in RemoteCallback statusCallback);
}
 No newline at end of file
+190 −0
Original line number Diff line number Diff line
/*
 * 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.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;

import java.time.Duration;

/**
 * Data class for a data request for wearable sensing.
 *
 * @hide
 */
@FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
@SystemApi
public final class WearableSensingDataRequest implements Parcelable {
    private static final int MAX_REQUEST_SIZE = 200;
    private static final Duration RATE_LIMIT_WINDOW_SIZE = Duration.ofMinutes(1);
    private static final int RATE_LIMIT = 30;

    private final int mDataType;
    @NonNull private final PersistableBundle mRequestDetails;

    private WearableSensingDataRequest(int dataType, @NonNull PersistableBundle requestDetails) {
        mDataType = dataType;
        mRequestDetails = requestDetails;
    }

    /** Returns the data type this request is for. */
    public int getDataType() {
        return mDataType;
    }

    /** Returns the details for this request. */
    @NonNull
    public PersistableBundle getRequestDetails() {
        return mRequestDetails;
    }

    /** Returns the data size of this object when it is parcelled. */
    public int getDataSize() {
        Parcel parcel = Parcel.obtain();
        try {
            writeToParcel(parcel, describeContents());
            return parcel.dataSize();
        } finally {
            parcel.recycle();
        }
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeInt(mDataType);
        dest.writeTypedObject(mRequestDetails, flags);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public String toString() {
        return "WearableSensingDataRequest { "
                + "dataType = "
                + mDataType
                + ", "
                + "requestDetails = "
                + mRequestDetails
                + " }";
    }

    /**
     * Returns a String representation of this data request that shows its contents.
     *
     * @hide
     */
    public String toExpandedString() {
        if (mRequestDetails != null) {
            // Trigger unparcelling so that its individual fields will be listed in toString
            boolean unused =
                    mRequestDetails.getBoolean(
                            "PlaceholderForWearableSensingDataRequest#toExpandedString()");
        }
        return toString();
    }

    /**
     * The bundle key for this class of object, used in {@code RemoteCallback#sendResult}.
     *
     * @hide
     */
    public static final String REQUEST_BUNDLE_KEY =
            "android.app.wearable.WearableSensingDataRequestBundleKey";

    /**
     * The bundle key for the status callback for a data request, used in {@code
     * RemoteCallback#sendResult}.
     *
     * @hide
     */
    public static final String REQUEST_STATUS_CALLBACK_BUNDLE_KEY =
            "android.app.wearable.WearableSensingDataRequestStatusCallbackBundleKey";

    public static final @NonNull Parcelable.Creator<WearableSensingDataRequest> CREATOR =
            new Parcelable.Creator<WearableSensingDataRequest>() {
                @Override
                public WearableSensingDataRequest[] newArray(int size) {
                    return new WearableSensingDataRequest[size];
                }

                @Override
                public WearableSensingDataRequest createFromParcel(@NonNull Parcel in) {
                    int dataType = in.readInt();
                    PersistableBundle requestDetails =
                            in.readTypedObject(PersistableBundle.CREATOR);
                    return new WearableSensingDataRequest(dataType, requestDetails);
                }
            };

    /**
     * Returns the maximum allowed size of a WearableSensingDataRequest when it is parcelled.
     * Instances that exceed this size can be constructed, but will be rejected by the system when
     * they leave the isolated WearableSensingService.
     */
    public static int getMaxRequestSize() {
        return MAX_REQUEST_SIZE;
    }

    /**
     * Returns the rolling time window used to perform rate limiting on data requests leaving the
     * WearableSensingService.
     */
    @NonNull
    public static Duration getRateLimitWindowSize() {
        return RATE_LIMIT_WINDOW_SIZE;
    }

    /**
     * Returns the number of data requests allowed to leave the WearableSensingService in each
     * {@link #getRateLimitWindowSize()}.
     */
    public static int getRateLimit() {
        return RATE_LIMIT;
    }

    /** A builder for WearableSensingDataRequest. */
    @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
    public static final class Builder {
        private int mDataType;
        private PersistableBundle mRequestDetails;

        public Builder(int dataType) {
            mDataType = dataType;
        }

        /** Sets the request details. */
        public @NonNull Builder setRequestDetails(@NonNull PersistableBundle requestDetails) {
            mRequestDetails = requestDetails;
            return this;
        }

        /** Builds the WearableSensingDataRequest. */
        public @NonNull WearableSensingDataRequest build() {
            if (mRequestDetails == null) {
                mRequestDetails = PersistableBundle.EMPTY;
            }
            return new WearableSensingDataRequest(mDataType, mRequestDetails);
        }
    }
}
+136 −11
Original line number Diff line number Diff line
@@ -25,9 +25,11 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.PendingIntent;
import android.app.ambientcontext.AmbientContextEvent;
import android.companion.CompanionDeviceManager;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
@@ -58,7 +60,6 @@ import java.util.function.Consumer;
 *
 * @hide
 */

@SystemApi
@SystemService(Context.WEARABLE_SENSING_SERVICE)
public class WearableSensingManager {
@@ -71,6 +72,14 @@ public class WearableSensingManager {
    public static final String STATUS_RESPONSE_BUNDLE_KEY =
            "android.app.wearable.WearableSensingStatusBundleKey";

    /**
     * The Intent extra key for the data request in the Intent sent to the PendingIntent registered
     * with {@link #registerDataRequestObserver(int, PendingIntent, Executor, Consumer)}.
     *
     * @hide
     */
    public static final String EXTRA_WEARABLE_SENSING_DATA_REQUEST =
            "android.app.wearable.extra.WEARABLE_SENSING_DATA_REQUEST";

    /**
     * An unknown status.
@@ -107,6 +116,7 @@ public class WearableSensingManager {
     * 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;

@@ -118,8 +128,14 @@ public class WearableSensingManager {
    @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;

    /** @hide */
    @IntDef(prefix = { "STATUS_" }, value = {
    @IntDef(
            prefix = {"STATUS_"},
            value = {
                STATUS_UNKNOWN,
                STATUS_SUCCESS,
                STATUS_UNSUPPORTED,
@@ -127,11 +143,27 @@ public class WearableSensingManager {
                STATUS_WEARABLE_UNAVAILABLE,
                STATUS_ACCESS_DENIED,
                STATUS_UNSUPPORTED_OPERATION,
            STATUS_CHANNEL_ERROR
                STATUS_CHANNEL_ERROR,
                STATUS_UNSUPPORTED_DATA_TYPE
            })
    @Retention(RetentionPolicy.SOURCE)
    public @interface StatusCode {}

    /**
     * Retrieves a {@link WearableSensingDataRequest} from the Intent sent to the PendingIntent
     * provided to {@link #registerDataRequestObserver(int, PendingIntent, Executor, Consumer)}.
     *
     * @param intent The Intent received from the PendingIntent.
     * @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(
                EXTRA_WEARABLE_SENSING_DATA_REQUEST, WearableSensingDataRequest.class);
    }

    private final Context mContext;
    private final IWearableSensingManager mService;

@@ -256,6 +288,99 @@ public class WearableSensingManager {
        }
    }

    /**
     * Registers a data request observer for the provided data type.
     *
     * <p>When data is requested, the provided {@code dataRequestPendingIntent} will be invoked. A
     * {@link WearableSensingDataRequest} can be extracted from the Intent sent to {@code
     * dataRequestPendingIntent} by calling {@link #getDataRequestFromIntent(Intent)}. The observer
     * can then provide the requested data via {@link #provideData(PersistableBundle, SharedMemory,
     * Executor, Consumer)}.
     *
     * <p>There is no limit to the number of observers registered for a data type. How they are
     * handled depends on the implementation of WearableSensingService.
     *
     * <p>When the observer is no longer needed, {@link #unregisterDataRequestObserver(int,
     * PendingIntent, Executor, Consumer)} should be called with the same {@code
     * dataRequestPendingIntent}. It should be done regardless of the status code returned from
     * {@code statusConsumer} in order to clean up housekeeping data for the {@code
     * dataRequestPendingIntent} maintained by the system.
     *
     * <p>Example:
     *
     * <pre>{@code
     * // Create a PendingIntent for MyDataRequestBroadcastReceiver
     * Intent intent =
     *         new Intent(actionString).setClass(context, MyDataRequestBroadcastReceiver.class);
     * PendingIntent pendingIntent = PendingIntent.getBroadcast(
     *         context, 0, intent, PendingIntent.FLAG_MUTABLE);
     *
     * // Register the PendingIntent as a data request observer
     * wearableSensingManager.registerDataRequestObserver(
     *         dataType, pendingIntent, executor, statusConsumer);
     *
     * // Within MyDataRequestBroadcastReceiver, receive the broadcast Intent and extract the
     * // WearableSensingDataRequest
     * {@literal @}Override
     * public void onReceive(Context context, Intent intent) {
     *     WearableSensingDataRequest dataRequest =
     *             WearableSensingManager.getDataRequestFromIntent(intent);
     *     // After parsing the dataRequest, provide the data
     *     wearableSensingManager.provideData(data, sharedMemory, executor, statusConsumer);
     * }
     * }</pre>
     *
     * @param dataType The data type to listen to. Values are defined by the application that
     *     implements {@link WearableSensingService}.
     * @param dataRequestPendingIntent A mutable {@link PendingIntent} that will be invoked when
     *     data is requested. See {@link #getDataRequestFromIntent(Intent)}. Activities are not
     *     allowed to be launched using this PendingIntent.
     * @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,
            @NonNull PendingIntent dataRequestPendingIntent,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull @StatusCode Consumer<Integer> statusConsumer) {
        try {
            RemoteCallback statusCallback = createStatusCallback(executor, statusConsumer);
            mService.registerDataRequestObserver(
                    dataType, dataRequestPendingIntent, statusCallback);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Unregisters a previously registered data request observer. If the provided {@link
     * PendingIntent} was not registered, or is already unregistered, the {@link
     * WearableSensingService} will not be notified.
     *
     * @param dataType The data type the observer is for.
     * @param dataRequestPendingIntent The observer to unregister.
     * @param executor Executor on which to run the consumer callback.
     * @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,
            @NonNull PendingIntent dataRequestPendingIntent,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull @StatusCode Consumer<Integer> statusConsumer) {
        try {
            RemoteCallback statusCallback = createStatusCallback(executor, statusConsumer);
            mService.unregisterDataRequestObserver(
                    dataType, dataRequestPendingIntent, statusCallback);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    private static RemoteCallback createStatusCallback(
            Executor executor, Consumer<Integer> statusConsumer) {
        return new RemoteCallback(
+2 −0
Original line number Diff line number Diff line
@@ -31,6 +31,8 @@ oneway interface IWearableSensingService {
    void provideSecureWearableConnection(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
    void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
    void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback);
    void registerDataRequestObserver(int dataType, in RemoteCallback dataRequestCallback, int dataRequestObserverId, in String packageName, in RemoteCallback statusCallback);
    void unregisterDataRequestObserver(int dataType, int dataRequestObserverId, in String packageName, in RemoteCallback statusCallback);
    void startDetection(in AmbientContextEventRequest request, in String packageName,
            in RemoteCallback detectionResultCallback, in RemoteCallback statusCallback);
    void stopDetection(in String packageName);
Loading