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

Commit e07894eb authored by Tom Chan's avatar Tom Chan
Browse files

Add size- and rate-limiting to data requests.

Test: atest CtsWearableSensingServiceTestCases
Bug: 301427767

Change-Id: Ib72cd5785a0e1f242d6d96f22de3047181f9f57f
parent dcdc208f
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -3152,7 +3152,11 @@ 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;
@@ -13447,6 +13451,8 @@ package android.service.wearable {
    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
  }
+43 −0
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ 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.
 *
@@ -31,6 +33,10 @@ import android.os.PersistableBundle;
@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;

@@ -50,6 +56,17 @@ public final class WearableSensingDataRequest implements Parcelable {
        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);
@@ -120,6 +137,32 @@ public final class WearableSensingDataRequest implements Parcelable {
                }
            };

    /**
     * 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 {
+22 −2
Original line number Diff line number Diff line
@@ -48,15 +48,35 @@ public interface WearableSensingDataRequester {
     */
    int STATUS_OBSERVER_CANCELLED = 2;

    /**
     * The value of the status code that indicates the request is rejected because it is larger than
     * {@link WearableSensingDataRequest#getMaxRequestSize()}.
     */
    int STATUS_TOO_LARGE = 3;

    /**
     * The value of the status code that indicates the request is rejected because it exceeds the
     * rate limit. See {@link WearableSensingDataRequest#getRateLimit()}.
     */
    int STATUS_TOO_FREQUENT = 4;

    /** @hide */
    @IntDef(
            prefix = {"STATUS_"},
            value = {STATUS_UNKNOWN, STATUS_SUCCESS, STATUS_OBSERVER_CANCELLED})
            value = {
                STATUS_UNKNOWN,
                STATUS_SUCCESS,
                STATUS_OBSERVER_CANCELLED,
                STATUS_TOO_LARGE,
                STATUS_TOO_FREQUENT
            })
    @Retention(RetentionPolicy.SOURCE)
    @interface StatusCode {}

    /**
     * Sends a data request.
     * Sends a data request. See {@link WearableSensingService#onDataRequestObserverRegistered(int,
     * String, WearableSensingDataRequester, Consumer)} for size and rate restrictions on data
     * requests.
     *
     * @param dataRequest The data request to send.
     * @param statusConsumer A consumer that handles the status code for the data request.
+7 −1
Original line number Diff line number Diff line
@@ -307,7 +307,13 @@ public abstract class WearableSensingService extends Service {
            @NonNull Consumer<Integer> statusConsumer);

    /**
     * Called when a data request observer is registered.
     * Called when a data request observer is registered. Each request must not be larger than
     * {@link WearableSensingDataRequest#getMaxRequestSize()}. In addition, at most {@link
     * WearableSensingDataRequester#getRateLimit()} requests can be sent every rolling {@link
     * WearableSensingDataRequester#getRateLimitWindowSize()}. Requests that are too large or too
     * frequent will be dropped by the system. See {@link
     * WearableSensingDataRequester#requestData(WearableSensingDataRequest, Consumer)} for details
     * about the status code returned for each request.
     *
     * <p>The implementing class should override this method. After the data requester is received,
     * it should send a {@link WearableSensingManager#STATUS_SUCCESS} status code to the {@code
+39 −2
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@ import com.android.server.SystemService;
import com.android.server.infra.AbstractMasterSystemService;
import com.android.server.infra.FrameworkResourcesServiceNameResolver;
import com.android.server.pm.KnownPackages;
import com.android.server.utils.quota.MultiRateLimiter;

import java.io.FileDescriptor;
import java.util.HashSet;
@@ -77,6 +78,10 @@ public class WearableSensingManagerService extends

    public static final int MAX_TEMPORARY_SERVICE_DURATION_MS = 30000;

    private static final String RATE_LIMITER_PACKAGE_NAME = "android";
    private static final String RATE_LIMITER_TAG =
            WearableSensingManagerService.class.getSimpleName();

    private static final class DataRequestObserverContext {
        final int mDataType;
        final int mUserId;
@@ -101,6 +106,7 @@ public class WearableSensingManagerService extends
    private final Context mContext;
    private final AtomicInteger mNextDataRequestObserverId = new AtomicInteger(1);
    private final Set<DataRequestObserverContext> mDataRequestObserverContexts = new HashSet<>();
    private final MultiRateLimiter mDataRequestRateLimiter;
    volatile boolean mIsServiceEnabled;

    public WearableSensingManagerService(Context context) {
@@ -112,6 +118,12 @@ public class WearableSensingManagerService extends
                PACKAGE_UPDATE_POLICY_REFRESH_EAGER
                        | /*To avoid high latency*/ PACKAGE_RESTART_POLICY_REFRESH_EAGER);
        mContext = context;
        mDataRequestRateLimiter =
                new MultiRateLimiter.Builder(context)
                        .addRateLimit(
                                WearableSensingDataRequest.getRateLimit(),
                                WearableSensingDataRequest.getRateLimitWindowSize())
                        .build();
    }

    @Override
@@ -242,7 +254,8 @@ public class WearableSensingManagerService extends
    }

    @NonNull
    private RemoteCallback createDataRequestRemoteCallback(PendingIntent dataRequestPendingIntent) {
    private RemoteCallback createDataRequestRemoteCallback(
            PendingIntent dataRequestPendingIntent, int userId) {
        return new RemoteCallback(
                bundle -> {
                    WearableSensingDataRequest dataRequest =
@@ -261,6 +274,27 @@ public class WearableSensingManagerService extends
                        Slog.e(TAG, "Received data request callback without a status callback.");
                        return;
                    }
                    if (dataRequest.getDataSize()
                            > WearableSensingDataRequest.getMaxRequestSize()) {
                        Slog.w(
                                TAG,
                                TextUtils.formatSimple(
                                        "WearableSensingDataRequest size exceeds the maximum"
                                            + " allowed size of %s bytes. Dropping the request.",
                                        WearableSensingDataRequest.getMaxRequestSize()));
                        WearableSensingManagerPerUserService.notifyStatusCallback(
                                dataRequestStatusCallback,
                                WearableSensingDataRequester.STATUS_TOO_LARGE);
                        return;
                    }
                    if (!mDataRequestRateLimiter.isWithinQuota(
                            userId, RATE_LIMITER_PACKAGE_NAME, RATE_LIMITER_TAG)) {
                        Slog.w(TAG, "Data request exceeded rate limit. Dropping the request.");
                        WearableSensingManagerPerUserService.notifyStatusCallback(
                                dataRequestStatusCallback,
                                WearableSensingDataRequester.STATUS_TOO_FREQUENT);
                        return;
                    }
                    Intent intent = new Intent();
                    intent.putExtra(
                            WearableSensingManager.EXTRA_WEARABLE_SENSING_DATA_REQUEST,
@@ -268,6 +302,8 @@ public class WearableSensingManagerService extends
                    BroadcastOptions options = BroadcastOptions.makeBasic();
                    options.setPendingIntentBackgroundActivityStartMode(
                            ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED);
                    mDataRequestRateLimiter.noteEvent(
                            userId, RATE_LIMITER_PACKAGE_NAME, RATE_LIMITER_TAG);
                    final long previousCallingIdentity = Binder.clearCallingIdentity();
                    try {
                        dataRequestPendingIntent.send(
@@ -395,7 +431,8 @@ public class WearableSensingManagerService extends
                    dataRequestCallback = previousObserverContext.mDataRequestRemoteCallback;
                    dataRequestObserverId = previousObserverContext.mDataRequestObserverId;
                } else {
                    dataRequestCallback = createDataRequestRemoteCallback(dataRequestPendingIntent);
                    dataRequestCallback =
                            createDataRequestRemoteCallback(dataRequestPendingIntent, userId);
                    dataRequestObserverId = mNextDataRequestObserverId.getAndIncrement();
                    mDataRequestObserverContexts.add(
                            new DataRequestObserverContext(