Merge "Reload StackStateAnimator resources on configuration changes" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index ddc7e02..bfa486b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1375,6 +1375,7 @@
     field public static final int reqTouchScreen = 16843303; // 0x1010227
     field public static final int requestLegacyExternalStorage = 16844291; // 0x1010603
     field public static final int requestRawExternalStorageAccess = 16844357; // 0x1010645
+    field @FlaggedApi("android.security.content_uri_permission_apis") public static final int requireContentUriPermissionFromCaller;
     field public static final int requireDeviceScreenOn = 16844317; // 0x101061d
     field public static final int requireDeviceUnlock = 16843756; // 0x10103ec
     field public static final int required = 16843406; // 0x101028e
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 03b8141..a942e0d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3151,16 +3151,38 @@
 
 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.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>);
diff --git a/core/java/android/app/wearable/IWearableSensingManager.aidl b/core/java/android/app/wearable/IWearableSensingManager.aidl
index 9d55ce28..3cbc8a2 100644
--- a/core/java/android/app/wearable/IWearableSensingManager.aidl
+++ b/core/java/android/app/wearable/IWearableSensingManager.aidl
@@ -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 @@
      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
diff --git a/core/java/android/app/wearable/WearableSensingDataRequest.java b/core/java/android/app/wearable/WearableSensingDataRequest.java
new file mode 100644
index 0000000..9329b37
--- /dev/null
+++ b/core/java/android/app/wearable/WearableSensingDataRequest.java
@@ -0,0 +1,190 @@
+/*
+ * 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);
+        }
+    }
+}
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index 401d0b7..077f7b5 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -25,9 +25,11 @@
 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 @@
  *
  * @hide
  */
-
 @SystemApi
 @SystemService(Context.WEARABLE_SENSING_SERVICE)
 public class WearableSensingManager {
@@ -71,6 +72,14 @@
     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 @@
      * 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,20 +128,42 @@
     @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 = {
-            STATUS_UNKNOWN,
-            STATUS_SUCCESS,
-            STATUS_UNSUPPORTED,
-            STATUS_SERVICE_UNAVAILABLE,
-            STATUS_WEARABLE_UNAVAILABLE,
-            STATUS_ACCESS_DENIED,
-            STATUS_UNSUPPORTED_OPERATION,
-            STATUS_CHANNEL_ERROR
-    })
+    @IntDef(
+            prefix = {"STATUS_"},
+            value = {
+                STATUS_UNKNOWN,
+                STATUS_SUCCESS,
+                STATUS_UNSUPPORTED,
+                STATUS_SERVICE_UNAVAILABLE,
+                STATUS_WEARABLE_UNAVAILABLE,
+                STATUS_ACCESS_DENIED,
+                STATUS_UNSUPPORTED_OPERATION,
+                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 @@
         }
     }
 
+    /**
+     * 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(
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 9fe8af5..a64ee5b 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -239,6 +239,110 @@
     public String requiredDisplayCategory;
 
     /**
+     * Constant corresponding to {@code none} in the
+     * {@link android.R.attr#requireContentUriPermissionFromCaller} attribute.
+     * @hide
+     */
+    public static final int CONTENT_URI_PERMISSION_NONE = 0;
+
+    /**
+     * Constant corresponding to {@code read} in the
+     * {@link android.R.attr#requireContentUriPermissionFromCaller} attribute.
+     * @hide
+     */
+    public static final int CONTENT_URI_PERMISSION_READ = 1;
+
+    /**
+     * Constant corresponding to {@code write} in the
+     * {@link android.R.attr#requireContentUriPermissionFromCaller} attribute.
+     * @hide
+     */
+    public static final int CONTENT_URI_PERMISSION_WRITE = 2;
+
+    /**
+     * Constant corresponding to {@code readOrWrite} in the
+     * {@link android.R.attr#requireContentUriPermissionFromCaller} attribute.
+     * @hide
+     */
+    public static final int CONTENT_URI_PERMISSION_READ_OR_WRITE = 3;
+
+    /**
+     * Constant corresponding to {@code readAndWrite} in the
+     * {@link android.R.attr#requireContentUriPermissionFromCaller} attribute.
+     * @hide
+     */
+    public static final int CONTENT_URI_PERMISSION_READ_AND_WRITE = 4;
+
+    /** @hide */
+    @SuppressWarnings("SwitchIntDef")
+    public static boolean isRequiredContentUriPermissionRead(
+            @RequiredContentUriPermission int permission) {
+        return switch (permission) {
+            case CONTENT_URI_PERMISSION_READ_AND_WRITE, CONTENT_URI_PERMISSION_READ_OR_WRITE,
+                    CONTENT_URI_PERMISSION_READ -> true;
+            default -> false;
+        };
+    }
+
+    /** @hide */
+    @SuppressWarnings("SwitchIntDef")
+    public static boolean isRequiredContentUriPermissionWrite(
+            @RequiredContentUriPermission int permission) {
+        return switch (permission) {
+            case CONTENT_URI_PERMISSION_READ_AND_WRITE, CONTENT_URI_PERMISSION_READ_OR_WRITE,
+                    CONTENT_URI_PERMISSION_WRITE -> true;
+            default -> false;
+        };
+    }
+
+    /** @hide */
+    @IntDef(prefix = "CONTENT_URI_PERMISSION_", value = {
+            CONTENT_URI_PERMISSION_NONE,
+            CONTENT_URI_PERMISSION_READ,
+            CONTENT_URI_PERMISSION_WRITE,
+            CONTENT_URI_PERMISSION_READ_OR_WRITE,
+            CONTENT_URI_PERMISSION_READ_AND_WRITE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RequiredContentUriPermission {
+    }
+
+    private String requiredContentUriPermissionToFullString(
+            @RequiredContentUriPermission int permission) {
+        return switch (permission) {
+            case CONTENT_URI_PERMISSION_NONE -> "CONTENT_URI_PERMISSION_NONE";
+            case CONTENT_URI_PERMISSION_READ -> "CONTENT_URI_PERMISSION_READ";
+            case CONTENT_URI_PERMISSION_WRITE -> "CONTENT_URI_PERMISSION_WRITE";
+            case CONTENT_URI_PERMISSION_READ_OR_WRITE -> "CONTENT_URI_PERMISSION_READ_OR_WRITE";
+            case CONTENT_URI_PERMISSION_READ_AND_WRITE -> "CONTENT_URI_PERMISSION_READ_AND_WRITE";
+            default -> "unknown=" + permission;
+        };
+    }
+
+    /** @hide */
+    public static String requiredContentUriPermissionToShortString(
+            @RequiredContentUriPermission int permission) {
+        return switch (permission) {
+            case CONTENT_URI_PERMISSION_NONE -> "none";
+            case CONTENT_URI_PERMISSION_READ -> "read";
+            case CONTENT_URI_PERMISSION_WRITE -> "write";
+            case CONTENT_URI_PERMISSION_READ_OR_WRITE -> "read or write";
+            case CONTENT_URI_PERMISSION_READ_AND_WRITE -> "read and write";
+            default -> "unknown=" + permission;
+        };
+    }
+
+    /**
+     * Specifies permissions necessary to launch this activity via
+     * {@link android.content.Context#startActivity} when passing content URIs. The default value is
+     * {@code none}, meaning no specific permissions are required. Setting this attribute restricts
+     * activity invocation based on the invoker's permissions.
+     * @hide
+     */
+    @RequiredContentUriPermission
+    public int requireContentUriPermissionFromCaller;
+
+    /**
      * Activity can not be resized and always occupies the fullscreen area with all windows fully
      * visible.
      * @hide
@@ -1590,6 +1694,7 @@
         mMinAspectRatio = orig.mMinAspectRatio;
         supportsSizeChanges = orig.supportsSizeChanges;
         requiredDisplayCategory = orig.requiredDisplayCategory;
+        requireContentUriPermissionFromCaller = orig.requireContentUriPermissionFromCaller;
     }
 
     /**
@@ -1946,6 +2051,11 @@
         if (requiredDisplayCategory != null) {
             pw.println(prefix + "requiredDisplayCategory=" + requiredDisplayCategory);
         }
+        if ((dumpFlags & DUMP_FLAG_DETAILS) != 0) {
+            pw.println(prefix + "requireContentUriPermissionFromCaller="
+                    + requiredContentUriPermissionToFullString(
+                            requireContentUriPermissionFromCaller));
+        }
         super.dumpBack(pw, prefix, dumpFlags);
     }
 
@@ -1993,6 +2103,7 @@
         dest.writeBoolean(supportsSizeChanges);
         sForStringSet.parcel(mKnownActivityEmbeddingCerts, dest, flags);
         dest.writeString8(requiredDisplayCategory);
+        dest.writeInt(requireContentUriPermissionFromCaller);
     }
 
     /**
@@ -2119,6 +2230,7 @@
             mKnownActivityEmbeddingCerts = null;
         }
         requiredDisplayCategory = source.readString8();
+        requireContentUriPermissionFromCaller = source.readInt();
     }
 
     /**
diff --git a/core/java/android/service/wearable/IWearableSensingService.aidl b/core/java/android/service/wearable/IWearableSensingService.aidl
index 8fdb2c2..0556188 100644
--- a/core/java/android/service/wearable/IWearableSensingService.aidl
+++ b/core/java/android/service/wearable/IWearableSensingService.aidl
@@ -31,6 +31,8 @@
     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);
diff --git a/core/java/android/service/wearable/WearableSensingDataRequester.java b/core/java/android/service/wearable/WearableSensingDataRequester.java
new file mode 100644
index 0000000..5a8104f
--- /dev/null
+++ b/core/java/android/service/wearable/WearableSensingDataRequester.java
@@ -0,0 +1,87 @@
+/*
+ * 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.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;
+import java.lang.annotation.RetentionPolicy;
+import java.util.function.Consumer;
+
+/**
+ * An interface to request wearable sensing data.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
+@SystemApi
+public interface WearableSensingDataRequester {
+
+    /** An unknown status. */
+    int STATUS_UNKNOWN = 0;
+
+    /** The value of the status code that indicates success. */
+    int STATUS_SUCCESS = 1;
+
+    /**
+     * The value of the status code that indicates the request is rejected because the data request
+     * observer PendingIntent has been cancelled.
+     */
+    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,
+                STATUS_TOO_LARGE,
+                STATUS_TOO_FREQUENT
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface StatusCode {}
+
+    /**
+     * 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.
+     */
+    void requestData(
+            @NonNull WearableSensingDataRequest dataRequest,
+            @NonNull @StatusCode Consumer<Integer> statusConsumer);
+}
diff --git a/core/java/android/service/wearable/WearableSensingService.java b/core/java/android/service/wearable/WearableSensingService.java
index d21115b..d25cff7 100644
--- a/core/java/android/service/wearable/WearableSensingService.java
+++ b/core/java/android/service/wearable/WearableSensingService.java
@@ -25,6 +25,7 @@
 import android.app.ambientcontext.AmbientContextEvent;
 import android.app.ambientcontext.AmbientContextEventRequest;
 import android.app.wearable.Flags;
+import android.app.wearable.WearableSensingDataRequest;
 import android.app.wearable.WearableSensingManager;
 import android.content.Intent;
 import android.os.Bundle;
@@ -36,6 +37,7 @@
 import android.service.ambientcontext.AmbientContextDetectionResult;
 import android.service.ambientcontext.AmbientContextDetectionServiceStatus;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import java.util.Arrays;
 import java.util.HashSet;
@@ -90,6 +92,9 @@
     public static final String SERVICE_INTERFACE =
             "android.service.wearable.WearableSensingService";
 
+    private final SparseArray<WearableSensingDataRequester> mDataRequestObserverIdToRequesterMap =
+            new SparseArray<>();
+
     @Nullable
     @Override
     public final IBinder onBind(@NonNull Intent intent) {
@@ -128,6 +133,55 @@
 
                 /** {@inheritDoc} */
                 @Override
+                public void registerDataRequestObserver(
+                        int dataType,
+                        RemoteCallback dataRequestCallback,
+                        int dataRequestObserverId,
+                        String packageName,
+                        RemoteCallback statusCallback) {
+                    Objects.requireNonNull(dataRequestCallback);
+                    Objects.requireNonNull(statusCallback);
+                    WearableSensingDataRequester dataRequester;
+                    synchronized (mDataRequestObserverIdToRequesterMap) {
+                        dataRequester =
+                                mDataRequestObserverIdToRequesterMap.get(dataRequestObserverId);
+                        if (dataRequester == null) {
+                            dataRequester = createDataRequester(dataRequestCallback);
+                            mDataRequestObserverIdToRequesterMap.put(
+                                    dataRequestObserverId, dataRequester);
+                        }
+                    }
+                    Consumer<Integer> statusConsumer = createWearableStatusConsumer(statusCallback);
+                    WearableSensingService.this.onDataRequestObserverRegistered(
+                            dataType, packageName, dataRequester, statusConsumer);
+                }
+
+                @Override
+                public void unregisterDataRequestObserver(
+                        int dataType,
+                        int dataRequestObserverId,
+                        String packageName,
+                        RemoteCallback statusCallback) {
+                    WearableSensingDataRequester dataRequester;
+                    synchronized (mDataRequestObserverIdToRequesterMap) {
+                        dataRequester =
+                                mDataRequestObserverIdToRequesterMap.get(dataRequestObserverId);
+                        if (dataRequester == null) {
+                            Slog.w(
+                                    TAG,
+                                    "dataRequestObserverId not found, cannot unregister data"
+                                            + " request observer.");
+                            return;
+                        }
+                        mDataRequestObserverIdToRequesterMap.remove(dataRequestObserverId);
+                    }
+                    Consumer<Integer> statusConsumer = createWearableStatusConsumer(statusCallback);
+                    WearableSensingService.this.onDataRequestObserverUnregistered(
+                            dataType, packageName, dataRequester, statusConsumer);
+                }
+
+                /** {@inheritDoc} */
+                @Override
                 public void startDetection(
                         @NonNull AmbientContextEventRequest request,
                         String packageName,
@@ -231,19 +285,19 @@
             @NonNull Consumer<Integer> statusConsumer);
 
     /**
-     * Called when configurations and read-only data in a {@link PersistableBundle}
-     * can 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 provided.
+     * Called when configurations and read-only data in a {@link PersistableBundle} can 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
+     * provided.
      *
      * @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.
+     *     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.
      * @param statusConsumer the consumer for the service status.
      */
     @BinderThread
@@ -253,6 +307,68 @@
             @NonNull Consumer<Integer> statusConsumer);
 
     /**
+     * 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
+     * statusConsumer} unless it encounters an error condition described by a status code listed in
+     * {@link WearableSensingManager}, such as {@link
+     * WearableSensingManager#STATUS_WEARABLE_UNAVAILABLE}, in which case it should return the
+     * corresponding status code.
+     *
+     * @param dataType The data type the observer is registered for. Values are defined by the
+     *     application that implements this class.
+     * @param packageName The package name of the app that will receive the requests.
+     * @param dataRequester A handle to the observer registered. It can be used to request data of
+     *     the specified data type.
+     * @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,
+            @NonNull String packageName,
+            @NonNull WearableSensingDataRequester dataRequester,
+            @NonNull Consumer<Integer> statusConsumer) {
+        statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION);
+    }
+
+    /**
+     * Called when a data request observer is unregistered.
+     *
+     * <p>The implementing class should override this method. It should send a {@link
+     * WearableSensingManager#STATUS_SUCCESS} status code to the {@code statusConsumer} unless it
+     * encounters an error condition described by a status code listed in {@link
+     * WearableSensingManager}, such as {@link WearableSensingManager#STATUS_WEARABLE_UNAVAILABLE},
+     * in which case it should return the corresponding status code.
+     *
+     * @param dataType The data type the observer is for.
+     * @param packageName The package name of the app that will receive the requests sent to the
+     *     dataRequester.
+     * @param dataRequester A handle to the observer to be unregistered. It is the exact same
+     *     instance provided in a previous {@link #onDataRequestConsumerRegistered(int, String,
+     *     WearableSensingDataRequester, Consumer)} invocation.
+     * @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,
+            @NonNull String packageName,
+            @NonNull WearableSensingDataRequester dataRequester,
+            @NonNull Consumer<Integer> statusConsumer) {
+        statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION);
+    }
+
+    /**
      * 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
@@ -309,6 +425,25 @@
         return intArray;
     }
 
+    private static WearableSensingDataRequester createDataRequester(
+            RemoteCallback dataRequestCallback) {
+        return (request, requestStatusConsumer) -> {
+            Bundle bundle = new Bundle();
+            bundle.putParcelable(WearableSensingDataRequest.REQUEST_BUNDLE_KEY, request);
+            RemoteCallback requestStatusCallback =
+                    new RemoteCallback(
+                            requestStatusBundle -> {
+                                requestStatusConsumer.accept(
+                                        requestStatusBundle.getInt(
+                                                WearableSensingManager.STATUS_RESPONSE_BUNDLE_KEY));
+                            });
+            bundle.putParcelable(
+                    WearableSensingDataRequest.REQUEST_STATUS_CALLBACK_BUNDLE_KEY,
+                    requestStatusCallback);
+            dataRequestCallback.sendResult(bundle);
+        };
+    }
+
     @NonNull
     private static Consumer<Integer> createWearableStatusConsumer(RemoteCallback statusCallback) {
         return response -> {
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java
index b0f3578..a051c1b 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java
@@ -89,4 +89,7 @@
      */
     @Nullable
     String getRequiredDisplayCategory();
+
+    /** Gets the permissions necessary for launching the activity when using content URIs. */
+    int getRequireContentUriPermissionFromCaller();
 }
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java
index 2f977ee..1218793 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java
@@ -36,9 +36,9 @@
 import android.text.TextUtils;
 import android.util.ArraySet;
 
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
-import com.android.internal.pm.pkg.parsing.ParsingUtils;
 
 import java.util.Collections;
 import java.util.Locale;
@@ -97,6 +97,8 @@
     @Nullable
     private String mRequiredDisplayCategory;
 
+    private int mRequireContentUriPermissionFromCaller;
+
     public ParsedActivityImpl(ParsedActivityImpl other) {
         super(other);
         this.theme = other.theme;
@@ -124,6 +126,7 @@
         this.windowLayout = other.windowLayout;
         this.mKnownActivityEmbeddingCerts = other.mKnownActivityEmbeddingCerts;
         this.mRequiredDisplayCategory = other.mRequiredDisplayCategory;
+        this.mRequireContentUriPermissionFromCaller = other.mRequireContentUriPermissionFromCaller;
     }
 
     /**
@@ -192,6 +195,8 @@
         alias.setDirectBootAware(target.isDirectBootAware());
         alias.setProcessName(target.getProcessName());
         alias.setRequiredDisplayCategory(target.getRequiredDisplayCategory());
+        alias.setRequireContentUriPermissionFromCaller(
+                target.getRequireContentUriPermissionFromCaller());
         return alias;
 
         // Not all attributes from the target ParsedActivity are copied to the alias.
@@ -320,6 +325,7 @@
         }
         sForStringSet.parcel(this.mKnownActivityEmbeddingCerts, dest, flags);
         dest.writeString8(this.mRequiredDisplayCategory);
+        dest.writeInt(this.mRequireContentUriPermissionFromCaller);
     }
 
     public ParsedActivityImpl() {
@@ -355,6 +361,7 @@
         }
         this.mKnownActivityEmbeddingCerts = sForStringSet.unparcel(in);
         this.mRequiredDisplayCategory = in.readString8();
+        this.mRequireContentUriPermissionFromCaller = in.readInt();
     }
 
     @NonNull
@@ -412,7 +419,8 @@
             int rotationAnimation,
             int colorMode,
             @Nullable ActivityInfo.WindowLayout windowLayout,
-            @Nullable String requiredDisplayCategory) {
+            @Nullable String requiredDisplayCategory,
+            int requireContentUriPermissionFromCaller) {
         this.theme = theme;
         this.uiOptions = uiOptions;
         this.targetActivity = targetActivity;
@@ -438,6 +446,7 @@
         this.colorMode = colorMode;
         this.windowLayout = windowLayout;
         this.mRequiredDisplayCategory = requiredDisplayCategory;
+        this.mRequireContentUriPermissionFromCaller = requireContentUriPermissionFromCaller;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -563,6 +572,11 @@
     }
 
     @DataClass.Generated.Member
+    public int getRequireContentUriPermissionFromCaller() {
+        return mRequireContentUriPermissionFromCaller;
+    }
+
+    @DataClass.Generated.Member
     public @NonNull ParsedActivityImpl setTheme( int value) {
         theme = value;
         return this;
@@ -694,11 +708,17 @@
         return this;
     }
 
+    @DataClass.Generated.Member
+    public @NonNull ParsedActivityImpl setRequireContentUriPermissionFromCaller( int value) {
+        mRequireContentUriPermissionFromCaller = value;
+        return this;
+    }
+
     @DataClass.Generated(
-            time = 1701338377709L,
+            time = 1706180262165L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java",
-            inputSignatures = "private  int theme\nprivate  int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate  int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate  int launchMode\nprivate  int documentLaunchMode\nprivate  int maxRecents\nprivate  int configChanges\nprivate  int softInputMode\nprivate  int persistableMode\nprivate  int lockTaskLaunchMode\nprivate  int screenOrientation\nprivate  int resizeMode\nprivate  float maxAspectRatio\nprivate  float minAspectRatio\nprivate  boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate  int rotationAnimation\nprivate  int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mRequiredDisplayCategory\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedActivityImpl> CREATOR\npublic static @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.internal.pm.pkg.component.ParsedActivity)\npublic  com.android.internal.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic  com.android.internal.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic  com.android.internal.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic  com.android.internal.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic  void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.internal.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+            inputSignatures = "private  int theme\nprivate  int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate  int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate  int launchMode\nprivate  int documentLaunchMode\nprivate  int maxRecents\nprivate  int configChanges\nprivate  int softInputMode\nprivate  int persistableMode\nprivate  int lockTaskLaunchMode\nprivate  int screenOrientation\nprivate  int resizeMode\nprivate  float maxAspectRatio\nprivate  float minAspectRatio\nprivate  boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate  int rotationAnimation\nprivate  int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mRequiredDisplayCategory\nprivate  int mRequireContentUriPermissionFromCaller\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedActivityImpl> CREATOR\npublic static @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.internal.pm.pkg.component.ParsedActivity)\npublic  com.android.internal.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic  com.android.internal.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic  com.android.internal.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic  com.android.internal.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic  void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.internal.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
index c3f7dab..9f71d88 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
@@ -241,6 +241,10 @@
 
             activity.setRequiredDisplayCategory(requiredDisplayCategory);
 
+            activity.setRequireContentUriPermissionFromCaller(sa.getInt(
+                    R.styleable.AndroidManifestActivity_requireContentUriPermissionFromCaller,
+                    ActivityInfo.CONTENT_URI_PERMISSION_NONE));
+
             return parseActivityOrAlias(activity, pkg, tag, parser, res, sa, receiver,
                     false /*isAlias*/, visibleToEphemeral, input,
                     R.styleable.AndroidManifestActivity_parentActivityName,
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index d910940..4741012 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -3278,6 +3278,31 @@
              <p> By default, the behavior is configured by the same attribute in application.
         -->
         <attr name="enableOnBackInvokedCallback" format="boolean"/>
+
+        <!-- Specifies permissions necessary to launch this activity via
+             {@link android.content.Context#startActivity} when passing content URIs. The default
+             value is {@code none}, meaning no specific permissions are required. Setting this
+             attribute restricts activity invocation based on the invoker's permissions. If the
+             invoker doesn't have the required permissions, the activity start will be denied via a
+             {@link java.lang.SecurityException}.
+
+             <p> Note that the enforcement works for content URIs inside
+             {@link android.content.Intent#getData} and {@link android.content.Intent#getClipData}.
+             @FlaggedApi("android.security.content_uri_permission_apis") -->
+        <attr name="requireContentUriPermissionFromCaller" format="string">
+            <!-- Default, no specific permissions are required. -->
+            <enum name="none" value="0" />
+            <!-- Enforces the invoker to have read access to the passed content URIs. -->
+            <enum name="read" value="1" />
+            <!-- Enforces the invoker to have write access to the passed content URIs. -->
+            <enum name="write" value="2" />
+            <!-- Enforces the invoker to have either read or write access to the passed content
+                 URIs. -->
+            <enum name="readOrWrite" value="3" />
+            <!-- Enforces the invoker to have both read and write access to the passed content
+                 URIs. -->
+            <enum name="readAndWrite" value="4" />
+        </attr>
     </declare-styleable>
 
     <!-- The <code>activity-alias</code> tag declares a new
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 4799c37..f9cf28c 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -149,6 +149,8 @@
     <public name="autoTransact"/>
     <!-- @FlaggedApi("com.android.window.flags.enforce_edge_to_edge") -->
     <public name="windowOptOutEdgeToEdgeEnforcement"/>
+    <!-- @FlaggedApi("android.security.content_uri_permission_apis") -->
+    <public name="requireContentUriPermissionFromCaller" />
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01bc0000">
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index b3d2bc9..c8fbad4 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -46,7 +46,7 @@
     val draggable: DraggableHandler = SceneDraggableHandler(this)
 
     private var _swipeTransition: SwipeTransition? = null
-    internal var swipeTransition: SwipeTransition
+    private var swipeTransition: SwipeTransition
         get() = _swipeTransition ?: error("SwipeTransition needs to be initialized")
         set(value) {
             _swipeTransition = value
@@ -92,10 +92,6 @@
     /** The [Swipes] associated to the current gesture. */
     private var swipes: Swipes? = null
 
-    /** The [UserActionResult] associated to up and down swipes. */
-    private var upOrLeftResult: UserActionResult? = null
-    private var downOrRightResult: UserActionResult? = null
-
     /**
      * Whether we should immediately intercept a gesture.
      *
@@ -128,7 +124,7 @@
             // This [transition] was already driving the animation: simply take over it.
             // Stop animating and start from where the current offset.
             swipeTransition.cancelOffsetAnimation()
-            updateSwipesResults(swipeTransition._fromScene)
+            swipes!!.updateSwipesResults(swipeTransition._fromScene)
             return
         }
 
@@ -144,16 +140,24 @@
         }
 
         val fromScene = layoutImpl.scene(transitionState.currentScene)
-        updateSwipes(fromScene, startedPosition, pointersDown)
+        val newSwipes = computeSwipes(fromScene, startedPosition, pointersDown)
+        swipes = newSwipes
+        val result = newSwipes.findUserActionResult(fromScene, overSlop, true)
 
-        val result =
-            findUserActionResult(fromScene, directionOffset = overSlop, updateSwipesResults = true)
-                ?: return
-        updateTransition(SwipeTransition(fromScene, result), force = true)
-    }
+        // As we were unable to locate a valid target scene, the initial SwipeTransition cannot be
+        // defined.
+        if (result == null) return
 
-    private fun updateSwipes(fromScene: Scene, startedPosition: Offset?, pointersDown: Int) {
-        this.swipes = computeSwipes(fromScene, startedPosition, pointersDown)
+        val newSwipeTransition =
+            SwipeTransition(
+                fromScene = fromScene,
+                result = result,
+                swipes = newSwipes,
+                layoutImpl = layoutImpl,
+                orientation = orientation
+            )
+
+        updateTransition(newSwipeTransition, force = true)
     }
 
     private fun computeSwipes(
@@ -210,13 +214,6 @@
         }
     }
 
-    private fun Scene.getAbsoluteDistance(distance: UserActionDistance?): Float {
-        val targetSize = this.targetSize
-        return with(distance ?: DefaultSwipeDistance) {
-            layoutImpl.density.absoluteDistance(targetSize, orientation)
-        }
-    }
-
     internal fun onDrag(delta: Float) {
         if (delta == 0f || !isDrivingTransition) return
         swipeTransition.dragOffset += delta
@@ -226,15 +223,17 @@
 
         val isNewFromScene = fromScene.key != swipeTransition.fromScene
         val result =
-            findUserActionResult(
-                fromScene,
-                swipeTransition.dragOffset,
-                updateSwipesResults = isNewFromScene,
+            swipes!!.findUserActionResult(
+                fromScene = fromScene,
+                directionOffset = swipeTransition.dragOffset,
+                updateSwipesResults = isNewFromScene
             )
-                ?: run {
-                    onDragStopped(delta, true)
-                    return
-                }
+
+        if (result == null) {
+            onDragStopped(velocity = delta, canChangeScene = true)
+            return
+        }
+
         swipeTransition.dragOffset += acceleratedOffset
 
         if (
@@ -242,25 +241,20 @@
                 result.toScene != swipeTransition.toScene ||
                 result.transitionKey != swipeTransition.key
         ) {
-            updateTransition(
-                SwipeTransition(fromScene, result).apply {
-                    this.dragOffset = swipeTransition.dragOffset
-                }
-            )
+            val newSwipeTransition =
+                SwipeTransition(
+                        fromScene = fromScene,
+                        result = result,
+                        swipes = swipes!!,
+                        layoutImpl = layoutImpl,
+                        orientation = orientation
+                    )
+                    .apply { dragOffset = swipeTransition.dragOffset }
+
+            updateTransition(newSwipeTransition)
         }
     }
 
-    private fun updateSwipesResults(fromScene: Scene) {
-        val (upOrLeftResult, downOrRightResult) =
-            computeSwipesResults(
-                fromScene,
-                this.swipes ?: error("updateSwipes() should be called before updateSwipesResults()")
-            )
-
-        this.upOrLeftResult = upOrLeftResult
-        this.downOrRightResult = downOrRightResult
-    }
-
     private fun computeSwipesResults(
         fromScene: Scene,
         swipes: Swipes
@@ -295,74 +289,20 @@
 
         // If the swipe was not committed, don't do anything.
         if (swipeTransition._currentScene != toScene) {
-            return Pair(fromScene, 0f)
+            return fromScene to 0f
         }
 
         // If the offset is past the distance then let's change fromScene so that the user can swipe
         // to the next screen or go back to the previous one.
         val offset = swipeTransition.dragOffset
-        return if (offset <= -absoluteDistance && upOrLeftResult?.toScene == toScene.key) {
-            Pair(toScene, absoluteDistance)
-        } else if (offset >= absoluteDistance && downOrRightResult?.toScene == toScene.key) {
-            Pair(toScene, -absoluteDistance)
+        return if (offset <= -absoluteDistance && swipes!!.upOrLeftResult?.toScene == toScene.key) {
+            toScene to absoluteDistance
+        } else if (
+            offset >= absoluteDistance && swipes!!.downOrRightResult?.toScene == toScene.key
+        ) {
+            toScene to -absoluteDistance
         } else {
-            Pair(fromScene, 0f)
-        }
-    }
-
-    /**
-     * Returns the [UserActionResult] from [fromScene] in the direction of [directionOffset].
-     *
-     * @param fromScene the scene from which we look for the target
-     * @param directionOffset signed float that indicates the direction. Positive is down or right
-     *   negative is up or left.
-     * @param updateSwipesResults whether the target scenes should be updated to the current values
-     *   held in the Scenes map. Usually we don't want to update them while doing a drag, because
-     *   this could change the target scene (jump cutting) to a different scene, when some system
-     *   state changed the targets the background. However, an update is needed any time we
-     *   calculate the targets for a new fromScene.
-     * @return null when there are no targets in either direction. If one direction is null and you
-     *   drag into the null direction this function will return the opposite direction, assuming
-     *   that the users intention is to start the drag into the other direction eventually. If
-     *   [directionOffset] is 0f and both direction are available, it will default to
-     *   [upOrLeftResult].
-     */
-    private fun findUserActionResult(
-        fromScene: Scene,
-        directionOffset: Float,
-        updateSwipesResults: Boolean,
-    ): UserActionResult? {
-        if (updateSwipesResults) updateSwipesResults(fromScene)
-
-        return when {
-            upOrLeftResult == null && downOrRightResult == null -> null
-            (directionOffset < 0f && upOrLeftResult != null) || downOrRightResult == null ->
-                upOrLeftResult
-            else -> downOrRightResult
-        }
-    }
-
-    /**
-     * A strict version of [findUserActionResult] that will return null when there is no Scene in
-     * [directionOffset] direction
-     */
-    private fun findUserActionResultStrict(directionOffset: Float): UserActionResult? {
-        return when {
-            directionOffset > 0f -> upOrLeftResult
-            directionOffset < 0f -> downOrRightResult
-            else -> null
-        }
-    }
-
-    private fun computeAbsoluteDistance(
-        fromScene: Scene,
-        result: UserActionResult,
-    ): Float {
-        return if (result == upOrLeftResult) {
-            -fromScene.getAbsoluteDistance(result.distance)
-        } else {
-            check(result == downOrRightResult)
-            fromScene.getAbsoluteDistance(result.distance)
+            fromScene to 0f
         }
     }
 
@@ -430,19 +370,24 @@
 
             if (startFromIdlePosition) {
                 // If there is a target scene, we start the overscroll animation.
-                val result =
-                    findUserActionResultStrict(velocity)
-                        ?: run {
-                            // We will not animate
-                            layoutState.finishTransition(swipeTransition, idleScene = fromScene.key)
-                            return
-                        }
+                val result = swipes!!.findUserActionResultStrict(velocity)
+                if (result == null) {
+                    // We will not animate
+                    layoutState.finishTransition(swipeTransition, idleScene = fromScene.key)
+                    return
+                }
 
-                updateTransition(
-                    SwipeTransition(fromScene, result).apply {
-                        _currentScene = swipeTransition._currentScene
-                    }
-                )
+                val newSwipeTransition =
+                    SwipeTransition(
+                            fromScene = fromScene,
+                            result = result,
+                            swipes = swipes!!,
+                            layoutImpl = layoutImpl,
+                            orientation = orientation
+                        )
+                        .apply { _currentScene = swipeTransition._currentScene }
+
+                updateTransition(newSwipeTransition)
                 animateTo(targetScene = fromScene, targetOffset = 0f)
             } else {
                 // We were between two scenes: animate to the initial scene.
@@ -486,134 +431,220 @@
         }
     }
 
-    private fun SwipeTransition(fromScene: Scene, result: UserActionResult): SwipeTransition {
-        return SwipeTransition(
-            result.transitionKey,
-            fromScene,
-            layoutImpl.scene(result.toScene),
-            computeAbsoluteDistance(fromScene, result),
-        )
-    }
-
-    internal class SwipeTransition(
-        val key: TransitionKey?,
-        val _fromScene: Scene,
-        val _toScene: Scene,
-        /**
-         * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is
-         * above or to the left of [toScene].
-         */
-        val distance: Float,
-    ) : TransitionState.Transition(_fromScene.key, _toScene.key) {
-        var _currentScene by mutableStateOf(_fromScene)
-        override val currentScene: SceneKey
-            get() = _currentScene.key
-
-        override val progress: Float
-            get() {
-                val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
-                return offset / distance
-            }
-
-        override val isInitiatedByUserInput = true
-
-        /** The current offset caused by the drag gesture. */
-        var dragOffset by mutableFloatStateOf(0f)
-
-        /**
-         * Whether the offset is animated (the user lifted their finger) or if it is driven by
-         * gesture.
-         */
-        var isAnimatingOffset by mutableStateOf(false)
-
-        // If we are not animating offset, it means the offset is being driven by the user's finger.
-        override val isUserInputOngoing: Boolean
-            get() = !isAnimatingOffset
-
-        /** The animatable used to animate the offset once the user lifted its finger. */
-        val offsetAnimatable = Animatable(0f, OffsetVisibilityThreshold)
-
-        /** Job to check that there is at most one offset animation in progress. */
-        private var offsetAnimationJob: Job? = null
-
-        /** The spec to use when animating this transition to either [fromScene] or [toScene]. */
-        lateinit var swipeSpec: SpringSpec<Float>
-
-        /** Ends any previous [offsetAnimationJob] and runs the new [job]. */
-        private fun startOffsetAnimation(job: () -> Job) {
-            cancelOffsetAnimation()
-            offsetAnimationJob = job()
-        }
-
-        /** Cancel any ongoing offset animation. */
-        // TODO(b/317063114) This should be a suspended function to avoid multiple jobs running at
-        // the same time.
-        fun cancelOffsetAnimation() {
-            offsetAnimationJob?.cancel()
-            finishOffsetAnimation()
-        }
-
-        fun finishOffsetAnimation() {
-            if (isAnimatingOffset) {
-                isAnimatingOffset = false
-                dragOffset = offsetAnimatable.value
-            }
-        }
-
-        fun animateOffset(
-            // TODO(b/317063114) The CoroutineScope should be removed.
-            coroutineScope: CoroutineScope,
-            initialVelocity: Float,
-            targetOffset: Float,
-            onAnimationCompleted: () -> Unit,
-        ) {
-            startOffsetAnimation {
-                coroutineScope.launch {
-                    animateOffset(targetOffset, initialVelocity)
-                    onAnimationCompleted()
-                }
-            }
-        }
-
-        private suspend fun animateOffset(targetOffset: Float, initialVelocity: Float) {
-            if (!isAnimatingOffset) {
-                offsetAnimatable.snapTo(dragOffset)
-            }
-            isAnimatingOffset = true
-
-            offsetAnimatable.animateTo(
-                targetValue = targetOffset,
-                animationSpec = swipeSpec,
-                initialVelocity = initialVelocity,
-            )
-
-            finishOffsetAnimation()
-        }
-    }
-
     companion object {
         private const val TAG = "SceneGestureHandler"
     }
+}
 
-    private object DefaultSwipeDistance : UserActionDistance {
-        override fun Density.absoluteDistance(
-            fromSceneSize: IntSize,
-            orientation: Orientation,
-        ): Float {
-            return when (orientation) {
-                Orientation.Horizontal -> fromSceneSize.width
-                Orientation.Vertical -> fromSceneSize.height
-            }.toFloat()
+private fun SwipeTransition(
+    fromScene: Scene,
+    result: UserActionResult,
+    swipes: Swipes,
+    layoutImpl: SceneTransitionLayoutImpl,
+    orientation: Orientation,
+): SwipeTransition {
+    val upOrLeftResult = swipes.upOrLeftResult
+    val downOrRightResult = swipes.downOrRightResult
+    val userActionDistance = result.distance ?: DefaultSwipeDistance
+    val absoluteDistance =
+        with(userActionDistance) {
+            layoutImpl.density.absoluteDistance(fromScene.targetSize, orientation)
+        }
+
+    return SwipeTransition(
+        key = result.transitionKey,
+        _fromScene = fromScene,
+        _toScene = layoutImpl.scene(result.toScene),
+        distance =
+            when (result) {
+                upOrLeftResult -> -absoluteDistance
+                downOrRightResult -> absoluteDistance
+                else -> error("Unknown result $result ($upOrLeftResult $downOrRightResult)")
+            },
+    )
+}
+
+private class SwipeTransition(
+    val key: TransitionKey?,
+    val _fromScene: Scene,
+    val _toScene: Scene,
+    /**
+     * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is above
+     * or to the left of [toScene]
+     */
+    val distance: Float,
+) : TransitionState.Transition(_fromScene.key, _toScene.key) {
+    var _currentScene by mutableStateOf(_fromScene)
+    override val currentScene: SceneKey
+        get() = _currentScene.key
+
+    override val progress: Float
+        get() {
+            val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
+            return offset / distance
+        }
+
+    override val isInitiatedByUserInput = true
+
+    /** The current offset caused by the drag gesture. */
+    var dragOffset by mutableFloatStateOf(0f)
+
+    /**
+     * Whether the offset is animated (the user lifted their finger) or if it is driven by gesture.
+     */
+    var isAnimatingOffset by mutableStateOf(false)
+
+    // If we are not animating offset, it means the offset is being driven by the user's finger.
+    override val isUserInputOngoing: Boolean
+        get() = !isAnimatingOffset
+
+    /** The animatable used to animate the offset once the user lifted its finger. */
+    val offsetAnimatable = Animatable(0f, OffsetVisibilityThreshold)
+
+    /** Job to check that there is at most one offset animation in progress. */
+    private var offsetAnimationJob: Job? = null
+
+    /** The spec to use when animating this transition to either [fromScene] or [toScene]. */
+    lateinit var swipeSpec: SpringSpec<Float>
+
+    /** Ends any previous [offsetAnimationJob] and runs the new [job]. */
+    private fun startOffsetAnimation(job: () -> Job) {
+        cancelOffsetAnimation()
+        offsetAnimationJob = job()
+    }
+
+    /** Cancel any ongoing offset animation. */
+    // TODO(b/317063114) This should be a suspended function to avoid multiple jobs running at
+    // the same time.
+    fun cancelOffsetAnimation() {
+        offsetAnimationJob?.cancel()
+        finishOffsetAnimation()
+    }
+
+    fun finishOffsetAnimation() {
+        if (isAnimatingOffset) {
+            isAnimatingOffset = false
+            dragOffset = offsetAnimatable.value
         }
     }
 
-    /** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */
-    private class Swipes(
-        val upOrLeft: Swipe?,
-        val downOrRight: Swipe?,
-        val upOrLeftNoSource: Swipe?,
-        val downOrRightNoSource: Swipe?,
-    )
+    fun animateOffset(
+        // TODO(b/317063114) The CoroutineScope should be removed.
+        coroutineScope: CoroutineScope,
+        initialVelocity: Float,
+        targetOffset: Float,
+        onAnimationCompleted: () -> Unit,
+    ) {
+        startOffsetAnimation {
+            coroutineScope.launch {
+                animateOffset(targetOffset, initialVelocity)
+                onAnimationCompleted()
+            }
+        }
+    }
+
+    private suspend fun animateOffset(targetOffset: Float, initialVelocity: Float) {
+        if (!isAnimatingOffset) {
+            offsetAnimatable.snapTo(dragOffset)
+        }
+        isAnimatingOffset = true
+
+        offsetAnimatable.animateTo(
+            targetValue = targetOffset,
+            animationSpec = swipeSpec,
+            initialVelocity = initialVelocity,
+        )
+
+        finishOffsetAnimation()
+    }
+}
+
+private object DefaultSwipeDistance : UserActionDistance {
+    override fun Density.absoluteDistance(
+        fromSceneSize: IntSize,
+        orientation: Orientation,
+    ): Float {
+        return when (orientation) {
+            Orientation.Horizontal -> fromSceneSize.width
+            Orientation.Vertical -> fromSceneSize.height
+        }.toFloat()
+    }
+}
+
+/** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */
+private class Swipes(
+    val upOrLeft: Swipe?,
+    val downOrRight: Swipe?,
+    val upOrLeftNoSource: Swipe?,
+    val downOrRightNoSource: Swipe?,
+) {
+    /** The [UserActionResult] associated to up and down swipes. */
+    var upOrLeftResult: UserActionResult? = null
+    var downOrRightResult: UserActionResult? = null
+
+    fun computeSwipesResults(fromScene: Scene): Pair<UserActionResult?, UserActionResult?> {
+        val userActions = fromScene.userActions
+        fun result(swipe: Swipe?): UserActionResult? {
+            return userActions[swipe ?: return null]
+        }
+
+        val upOrLeftResult = result(upOrLeft) ?: result(upOrLeftNoSource)
+        val downOrRightResult = result(downOrRight) ?: result(downOrRightNoSource)
+        return upOrLeftResult to downOrRightResult
+    }
+
+    fun updateSwipesResults(fromScene: Scene) {
+        val (upOrLeftResult, downOrRightResult) = computeSwipesResults(fromScene)
+
+        this.upOrLeftResult = upOrLeftResult
+        this.downOrRightResult = downOrRightResult
+    }
+
+    /**
+     * Returns the [UserActionResult] from [fromScene] in the direction of [directionOffset].
+     *
+     * @param fromScene the scene from which we look for the target
+     * @param directionOffset signed float that indicates the direction. Positive is down or right
+     *   negative is up or left.
+     * @param updateSwipesResults whether the target scenes should be updated to the current values
+     *   held in the Scenes map. Usually we don't want to update them while doing a drag, because
+     *   this could change the target scene (jump cutting) to a different scene, when some system
+     *   state changed the targets the background. However, an update is needed any time we
+     *   calculate the targets for a new fromScene.
+     * @return null when there are no targets in either direction. If one direction is null and you
+     *   drag into the null direction this function will return the opposite direction, assuming
+     *   that the users intention is to start the drag into the other direction eventually. If
+     *   [directionOffset] is 0f and both direction are available, it will default to
+     *   [upOrLeftResult].
+     */
+    fun findUserActionResult(
+        fromScene: Scene,
+        directionOffset: Float,
+        updateSwipesResults: Boolean,
+    ): UserActionResult? {
+        if (updateSwipesResults) {
+            updateSwipesResults(fromScene)
+        }
+
+        return when {
+            upOrLeftResult == null && downOrRightResult == null -> null
+            (directionOffset < 0f && upOrLeftResult != null) || downOrRightResult == null ->
+                upOrLeftResult
+            else -> downOrRightResult
+        }
+    }
+
+    /**
+     * A strict version of [findUserActionResult] that will return null when there is no Scene in
+     * [directionOffset] direction
+     */
+    fun findUserActionResultStrict(directionOffset: Float): UserActionResult? {
+        return when {
+            directionOffset > 0f -> upOrLeftResult
+            directionOffset < 0f -> downOrRightResult
+            else -> null
+        }
+    }
 }
 
 private class SceneDraggableHandler(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index dacbdb4..c91d298 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -127,6 +127,9 @@
         val progress: Float
             get() = (transitionState as Transition).progress
 
+        val isUserInputOngoing: Boolean
+            get() = (transitionState as Transition).isUserInputOngoing
+
         fun advanceUntilIdle() {
             testScope.testScheduler.advanceUntilIdle()
         }
@@ -538,12 +541,11 @@
         onDragStopped(velocity = velocityThreshold)
 
         assertTransition(currentScene = SceneC)
-        assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
-        assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue()
+        assertThat(isUserInputOngoing).isFalse()
 
         // Start a new gesture while the offset is animating
         onDragStartedImmediately()
-        assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
+        assertThat(isUserInputOngoing).isTrue()
     }
 
     @Test
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
index aab0b1e..e88aaf01 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
@@ -22,6 +22,7 @@
 import android.content.ContentProvider
 import android.content.Context
 import android.content.Intent
+import android.util.Log
 import androidx.core.app.AppComponentFactory
 import com.android.systemui.dagger.ContextComponentHelper
 import com.android.systemui.dagger.SysUIComponent
@@ -90,8 +91,7 @@
         return app
     }
 
-    @UsesReflection(
-            KeepTarget(instanceOfClassConstant = SysUIComponent::class, methodName = "inject"))
+    @UsesReflection(KeepTarget(instanceOfClassConstant = SysUIComponent::class, methodName = "inject"))
     override fun instantiateProviderCompat(cl: ClassLoader, className: String): ContentProvider {
         val contentProvider = super.instantiateProviderCompat(cl, className)
         if (contentProvider is ContextInitializer) {
@@ -103,12 +103,11 @@
                         .getMethod("inject", contentProvider.javaClass)
                     injectMethod.invoke(rootComponent, contentProvider)
                 } catch (e: NoSuchMethodException) {
-                    throw RuntimeException("No injector for class: " + contentProvider.javaClass, e)
+                    Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e)
                 } catch (e: IllegalAccessException) {
-                    throw RuntimeException("Injector inaccessible for class: " +
-                            contentProvider.javaClass, e)
+                    Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e)
                 } catch (e: InvocationTargetException) {
-                    throw RuntimeException("Error while injecting: " + contentProvider.javaClass, e)
+                    Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e)
                 }
                 initializer
             }
diff --git a/packages/SystemUI/src/com/android/systemui/media/OWNERS b/packages/SystemUI/src/com/android/systemui/media/OWNERS
index b2d00df..69ea57b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/media/OWNERS
@@ -1,5 +1 @@
 per-file MediaProjectionPermissionActivity.java = michaelwr@google.com
-
-# Haptics team also works on Ringtone
-per-file NotificationPlayer.java = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file RingtonePlayer.java = file:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/OWNERS b/packages/SystemUI/tests/src/com/android/systemui/media/OWNERS
deleted file mode 100644
index 142862d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/media/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Haptics team also works on Ringtones (RingtonePlayer/NotificationPlayer)
-file:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index a9e5a54..1c70af0 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -594,6 +594,7 @@
         }
         ai.applicationInfo = applicationInfo;
         ai.requiredDisplayCategory = a.getRequiredDisplayCategory();
+        ai.requireContentUriPermissionFromCaller = a.getRequireContentUriPermissionFromCaller();
         ai.setKnownActivityEmbeddingCerts(a.getKnownActivityEmbeddingCerts());
         assignFieldsComponentInfoParsedMainComponent(ai, a, pkgSetting, userId);
         return ai;
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
index 03c75e0..195e91c 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Intent;
+import android.content.pm.ActivityInfo.RequiredContentUriPermission;
 import android.content.pm.ProviderInfo;
 import android.net.Uri;
 import android.os.IBinder;
@@ -63,6 +64,15 @@
             String targetPkg, int targetUserId);
 
     /**
+     * Same as {@link #checkGrantUriPermissionFromIntent(Intent, int, String, int)}, but with an
+     * extra parameter {@code requireContentUriPermissionFromCaller}, which is the value from {@link
+     * android.R.attr#requireContentUriPermissionFromCaller} attribute.
+     */
+    NeededUriGrants checkGrantUriPermissionFromIntent(Intent intent, int callingUid,
+            String targetPkg, int targetUserId,
+            @RequiredContentUriPermission int requireContentUriPermissionFromCaller);
+
+    /**
      * Extend a previously calculated set of permissions grants to the given
      * owner. All security checks will have already been performed as part of
      * calculating {@link NeededUriGrants}.
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index ce2cbed..d2f6701 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -23,6 +23,10 @@
 import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
 import static android.content.Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
 import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+import static android.content.pm.ActivityInfo.CONTENT_URI_PERMISSION_NONE;
+import static android.content.pm.ActivityInfo.CONTENT_URI_PERMISSION_READ_OR_WRITE;
+import static android.content.pm.ActivityInfo.isRequiredContentUriPermissionRead;
+import static android.content.pm.ActivityInfo.isRequiredContentUriPermissionWrite;
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
 import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
@@ -53,6 +57,8 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ActivityInfo.RequiredContentUriPermission;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
@@ -609,7 +615,8 @@
 
     /** Like checkGrantUriPermission, but takes an Intent. */
     private NeededUriGrants checkGrantUriPermissionFromIntentUnlocked(int callingUid,
-            String targetPkg, Intent intent, int mode, NeededUriGrants needed, int targetUserId) {
+            String targetPkg, Intent intent, int mode, NeededUriGrants needed, int targetUserId,
+            @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller) {
         if (DEBUG) Slog.v(TAG,
                 "Checking URI perm to data=" + (intent != null ? intent.getData() : null)
                         + " clip=" + (intent != null ? intent.getClipData() : null)
@@ -647,6 +654,10 @@
         }
         if (data != null) {
             GrantUri grantUri = GrantUri.resolve(contentUserHint, data, mode);
+            if (android.security.Flags.contentUriPermissionApis()) {
+                enforceRequireContentUriPermissionFromCaller(requireContentUriPermissionFromCaller,
+                        grantUri, callingUid);
+            }
             targetUid = checkGrantUriPermissionUnlocked(callingUid, targetPkg, grantUri, mode,
                     targetUid);
             if (targetUid > 0) {
@@ -661,6 +672,10 @@
                 Uri uri = clip.getItemAt(i).getUri();
                 if (uri != null) {
                     GrantUri grantUri = GrantUri.resolve(contentUserHint, uri, mode);
+                    if (android.security.Flags.contentUriPermissionApis()) {
+                        enforceRequireContentUriPermissionFromCaller(
+                                requireContentUriPermissionFromCaller, grantUri, callingUid);
+                    }
                     targetUid = checkGrantUriPermissionUnlocked(callingUid, targetPkg,
                             grantUri, mode, targetUid);
                     if (targetUid > 0) {
@@ -673,7 +688,8 @@
                     Intent clipIntent = clip.getItemAt(i).getIntent();
                     if (clipIntent != null) {
                         NeededUriGrants newNeeded = checkGrantUriPermissionFromIntentUnlocked(
-                                callingUid, targetPkg, clipIntent, mode, needed, targetUserId);
+                                callingUid, targetPkg, clipIntent, mode, needed, targetUserId,
+                                requireContentUriPermissionFromCaller);
                         if (newNeeded != null) {
                             needed = newNeeded;
                         }
@@ -685,6 +701,38 @@
         return needed;
     }
 
+    private void enforceRequireContentUriPermissionFromCaller(
+            @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller,
+            GrantUri grantUri, int uid) {
+        // Ignore if requireContentUriPermissionFromCaller hasn't been set or the URI is a
+        // non-content URI.
+        if (requireContentUriPermissionFromCaller == null
+                || requireContentUriPermissionFromCaller == CONTENT_URI_PERMISSION_NONE
+                || !ContentResolver.SCHEME_CONTENT.equals(grantUri.uri.getScheme())) {
+            return;
+        }
+
+        final boolean readMet = !isRequiredContentUriPermissionRead(
+                requireContentUriPermissionFromCaller)
+                || checkContentUriPermissionFullUnlocked(grantUri, uid,
+                Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+        final boolean writeMet = !isRequiredContentUriPermissionWrite(
+                requireContentUriPermissionFromCaller)
+                || checkContentUriPermissionFullUnlocked(grantUri, uid,
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+        boolean hasPermission =
+                requireContentUriPermissionFromCaller == CONTENT_URI_PERMISSION_READ_OR_WRITE
+                        ? (readMet || writeMet) : (readMet && writeMet);
+
+        if (!hasPermission) {
+            throw new SecurityException("You can't launch this activity because you don't have the"
+                    + " required " + ActivityInfo.requiredContentUriPermissionToShortString(
+                            requireContentUriPermissionFromCaller) + " access to " + grantUri.uri);
+        }
+    }
+
     @GuardedBy("mLock")
     private void readGrantedUriPermissionsLocked() {
         if (DEBUG) Slog.v(TAG, "readGrantedUriPermissions()");
@@ -1560,9 +1608,24 @@
         @Override
         public NeededUriGrants checkGrantUriPermissionFromIntent(Intent intent, int callingUid,
                 String targetPkg, int targetUserId) {
+            return internalCheckGrantUriPermissionFromIntent(intent, callingUid, targetPkg,
+                    targetUserId, /* requireContentUriPermissionFromCaller */ null);
+        }
+
+        @Override
+        public NeededUriGrants checkGrantUriPermissionFromIntent(Intent intent, int callingUid,
+                String targetPkg, int targetUserId, int requireContentUriPermissionFromCaller) {
+            return internalCheckGrantUriPermissionFromIntent(intent, callingUid, targetPkg,
+                    targetUserId, requireContentUriPermissionFromCaller);
+        }
+
+        private NeededUriGrants internalCheckGrantUriPermissionFromIntent(Intent intent,
+                int callingUid, String targetPkg, int targetUserId,
+                @Nullable Integer requireContentUriPermissionFromCaller) {
             final int mode = (intent != null) ? intent.getFlags() : 0;
             return UriGrantsManagerService.this.checkGrantUriPermissionFromIntentUnlocked(
-                    callingUid, targetPkg, intent, mode, null, targetUserId);
+                    callingUid, targetPkg, intent, mode, null, targetUserId,
+                    requireContentUriPermissionFromCaller);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
index e1abae8..88d3daf 100644
--- a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
+++ b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
@@ -118,4 +118,62 @@
         }
         post(service -> service.provideData(data, sharedMemory, callback));
     }
+
+    /**
+     * Registers a data request observer with WearableSensingService.
+     *
+     * @param dataType The data type to listen to. Values are defined by the application that
+     *     implements WearableSensingService.
+     * @param dataRequestCallback The observer to send data requests to.
+     * @param dataRequestObserverId The unique ID for the data request observer. It will be used for
+     *     unregistering the observer.
+     * @param packageName The package name of the app that will receive the data requests.
+     * @param statusCallback The callback for status of the method call.
+     */
+    public void registerDataRequestObserver(
+            int dataType,
+            RemoteCallback dataRequestCallback,
+            int dataRequestObserverId,
+            String packageName,
+            RemoteCallback statusCallback) {
+        if (DEBUG) {
+            Slog.i(TAG, "Registering data request observer.");
+        }
+        var unused =
+                post(
+                        service ->
+                                service.registerDataRequestObserver(
+                                        dataType,
+                                        dataRequestCallback,
+                                        dataRequestObserverId,
+                                        packageName,
+                                        statusCallback));
+    }
+
+    /**
+     * Unregisters a previously registered data request observer.
+     *
+     * @param dataType The data type the observer was registered against.
+     * @param dataRequestObserverId The unique ID of the observer to unregister.
+     * @param packageName The package name of the app that will receive requests sent to the
+     *     observer.
+     * @param statusCallback The callback for status of the method call.
+     */
+    public void unregisterDataRequestObserver(
+            int dataType,
+            int dataRequestObserverId,
+            String packageName,
+            RemoteCallback statusCallback) {
+        if (DEBUG) {
+            Slog.i(TAG, "Unregistering data request observer.");
+        }
+        var unused =
+                post(
+                        service ->
+                                service.unregisterDataRequestObserver(
+                                        dataType,
+                                        dataRequestObserverId,
+                                        packageName,
+                                        statusCallback));
+    }
 }
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
index a8d6322..0e8b82f 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
@@ -262,4 +262,65 @@
             mRemoteService.provideData(data, sharedMemory, callback);
         }
     }
+
+    /**
+     * Handles registering a data request observer.
+     *
+     * @param dataType The data type to listen to. Values are defined by the application that
+     *     implements WearableSensingService.
+     * @param dataRequestObserver The observer to register.
+     * @param dataRequestObserverId The unique ID for the data request observer. It will be used for
+     *     unregistering the observer.
+     * @param packageName The package name of the app that will receive the data requests.
+     * @param statusCallback The callback for status of the method call.
+     */
+    public void onRegisterDataRequestObserver(
+            int dataType,
+            RemoteCallback dataRequestObserver,
+            int dataRequestObserverId,
+            String packageName,
+            RemoteCallback statusCallback) {
+        synchronized (mLock) {
+            if (!setUpServiceIfNeeded()) {
+                Slog.w(TAG, "Detection service is not available at this moment.");
+                notifyStatusCallback(
+                        statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+            ensureRemoteServiceInitiated();
+            mRemoteService.registerDataRequestObserver(
+                    dataType,
+                    dataRequestObserver,
+                    dataRequestObserverId,
+                    packageName,
+                    statusCallback);
+        }
+    }
+
+    /**
+     * Handles unregistering a previously registered data request observer.
+     *
+     * @param dataType The data type the observer was registered against.
+     * @param dataRequestObserverId The unique ID of the observer to unregister.
+     * @param packageName The package name of the app that will receive requests sent to the
+     *     observer.
+     * @param statusCallback The callback for status of the method call.
+     */
+    public void onUnregisterDataRequestObserver(
+            int dataType,
+            int dataRequestObserverId,
+            String packageName,
+            RemoteCallback statusCallback) {
+        synchronized (mLock) {
+            if (!setUpServiceIfNeeded()) {
+                Slog.w(TAG, "Detection service is not available at this moment.");
+                notifyStatusCallback(
+                        statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+            ensureRemoteServiceInitiated();
+            mRemoteService.unregisterDataRequestObserver(
+                    dataType, dataRequestObserverId, packageName, statusCallback);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index 28c8f87..78952fa 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -21,12 +21,18 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.BroadcastOptions;
+import android.app.ComponentOptions;
+import android.app.PendingIntent;
 import android.app.ambientcontext.AmbientContextEvent;
 import android.app.wearable.IWearableSensingManager;
+import android.app.wearable.WearableSensingDataRequest;
 import android.app.wearable.WearableSensingManager;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.RemoteCallback;
@@ -35,6 +41,8 @@
 import android.os.ShellCallback;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
+import android.service.wearable.WearableSensingDataRequester;
+import android.text.TextUtils;
 import android.util.Slog;
 
 import com.android.internal.R;
@@ -44,10 +52,13 @@
 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;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 
 /**
@@ -64,9 +75,38 @@
 
     /** Default value in absence of {@link DeviceConfig} override. */
     private static final boolean DEFAULT_SERVICE_ENABLED = true;
+
     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;
+        final int mDataRequestObserverId;
+        @NonNull final PendingIntent mDataRequestPendingIntent;
+        @NonNull final RemoteCallback mDataRequestRemoteCallback;
+
+        DataRequestObserverContext(
+                int dataType,
+                int userId,
+                int dataRequestObserverId,
+                PendingIntent dataRequestPendingIntent,
+                RemoteCallback dataRequestRemoteCallback) {
+            mDataType = dataType;
+            mUserId = userId;
+            mDataRequestObserverId = dataRequestObserverId;
+            mDataRequestPendingIntent = dataRequestPendingIntent;
+            mDataRequestRemoteCallback = dataRequestRemoteCallback;
+        }
+    }
+
     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) {
@@ -78,6 +118,12 @@
                 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
@@ -192,6 +238,96 @@
         }
     }
 
+    private DataRequestObserverContext getDataRequestObserverContext(
+            int dataType, int userId, PendingIntent dataRequestPendingIntent) {
+        synchronized (mDataRequestObserverContexts) {
+            for (DataRequestObserverContext observerContext : mDataRequestObserverContexts) {
+                if (observerContext.mDataType == dataType
+                        && observerContext.mUserId == userId
+                        && observerContext.mDataRequestPendingIntent.equals(
+                                dataRequestPendingIntent)) {
+                    return observerContext;
+                }
+            }
+        }
+        return null;
+    }
+
+    @NonNull
+    private RemoteCallback createDataRequestRemoteCallback(
+            PendingIntent dataRequestPendingIntent, int userId) {
+        return new RemoteCallback(
+                bundle -> {
+                    WearableSensingDataRequest dataRequest =
+                            bundle.getParcelable(
+                                    WearableSensingDataRequest.REQUEST_BUNDLE_KEY,
+                                    WearableSensingDataRequest.class);
+                    if (dataRequest == null) {
+                        Slog.e(TAG, "Received data request callback without a request.");
+                        return;
+                    }
+                    RemoteCallback dataRequestStatusCallback =
+                            bundle.getParcelable(
+                                    WearableSensingDataRequest.REQUEST_STATUS_CALLBACK_BUNDLE_KEY,
+                                    RemoteCallback.class);
+                    if (dataRequestStatusCallback == null) {
+                        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,
+                            dataRequest);
+                    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(
+                                getContext(), 0, intent, null, null, null, options.toBundle());
+                        WearableSensingManagerPerUserService.notifyStatusCallback(
+                                dataRequestStatusCallback,
+                                WearableSensingDataRequester.STATUS_SUCCESS);
+                        Slog.i(
+                                TAG,
+                                TextUtils.formatSimple(
+                                        "Sending data request to %s: %s",
+                                        dataRequestPendingIntent.getCreatorPackage(),
+                                        dataRequest.toExpandedString()));
+                    } catch (PendingIntent.CanceledException e) {
+                        Slog.w(TAG, "Could not deliver pendingIntent: " + dataRequestPendingIntent);
+                        WearableSensingManagerPerUserService.notifyStatusCallback(
+                                dataRequestStatusCallback,
+                                WearableSensingDataRequester.STATUS_OBSERVER_CANCELLED);
+                    } finally {
+                        Binder.restoreCallingIdentity(previousCallingIdentity);
+                    }
+                });
+    }
+
     private void callPerUserServiceIfExist(
             Consumer<WearableSensingManagerPerUserService> serviceConsumer,
             RemoteCallback statusCallback) {
@@ -260,8 +396,8 @@
                     Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG);
             if (!mIsServiceEnabled) {
                 Slog.w(TAG, "Service not available.");
-                WearableSensingManagerPerUserService.notifyStatusCallback(callback,
-                        WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+                WearableSensingManagerPerUserService.notifyStatusCallback(
+                        callback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
                 return;
             }
             callPerUserServiceIfExist(
@@ -270,6 +406,96 @@
         }
 
         @Override
+        public void registerDataRequestObserver(
+                int dataType,
+                PendingIntent dataRequestPendingIntent,
+                RemoteCallback statusCallback) {
+            Slog.i(TAG, "WearableSensingManagerInternal registerDataRequestObserver.");
+            Objects.requireNonNull(dataRequestPendingIntent);
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG);
+            if (!mIsServiceEnabled) {
+                Slog.w(TAG, "Service not available.");
+                WearableSensingManagerPerUserService.notifyStatusCallback(
+                        statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+            int userId = UserHandle.getCallingUserId();
+            RemoteCallback dataRequestCallback;
+            int dataRequestObserverId;
+            synchronized (mDataRequestObserverContexts) {
+                DataRequestObserverContext previousObserverContext =
+                        getDataRequestObserverContext(dataType, userId, dataRequestPendingIntent);
+                if (previousObserverContext != null) {
+                    Slog.i(TAG, "Received duplicate data request observer.");
+                    dataRequestCallback = previousObserverContext.mDataRequestRemoteCallback;
+                    dataRequestObserverId = previousObserverContext.mDataRequestObserverId;
+                } else {
+                    dataRequestCallback =
+                            createDataRequestRemoteCallback(dataRequestPendingIntent, userId);
+                    dataRequestObserverId = mNextDataRequestObserverId.getAndIncrement();
+                    mDataRequestObserverContexts.add(
+                            new DataRequestObserverContext(
+                                    dataType,
+                                    userId,
+                                    dataRequestObserverId,
+                                    dataRequestPendingIntent,
+                                    dataRequestCallback));
+                }
+            }
+            callPerUserServiceIfExist(
+                    service ->
+                            service.onRegisterDataRequestObserver(
+                                    dataType,
+                                    dataRequestCallback,
+                                    dataRequestObserverId,
+                                    dataRequestPendingIntent.getCreatorPackage(),
+                                    statusCallback),
+                    statusCallback);
+        }
+
+        @Override
+        public void unregisterDataRequestObserver(
+                int dataType,
+                PendingIntent dataRequestPendingIntent,
+                RemoteCallback statusCallback) {
+            Slog.i(TAG, "WearableSensingManagerInternal unregisterDataRequestObserver.");
+            Objects.requireNonNull(dataRequestPendingIntent);
+            Objects.requireNonNull(statusCallback);
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG);
+            if (!mIsServiceEnabled) {
+                Slog.w(TAG, "Service not available.");
+                WearableSensingManagerPerUserService.notifyStatusCallback(
+                        statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+            int userId = UserHandle.getCallingUserId();
+            int previousDataRequestObserverId;
+            String pendingIntentCreatorPackage;
+            synchronized (mDataRequestObserverContexts) {
+                DataRequestObserverContext previousObserverContext =
+                        getDataRequestObserverContext(dataType, userId, dataRequestPendingIntent);
+                if (previousObserverContext == null) {
+                    Slog.w(TAG, "Previous observer not found, cannot unregister.");
+                    return;
+                }
+                mDataRequestObserverContexts.remove(previousObserverContext);
+                previousDataRequestObserverId = previousObserverContext.mDataRequestObserverId;
+                pendingIntentCreatorPackage =
+                        previousObserverContext.mDataRequestPendingIntent.getCreatorPackage();
+            }
+            callPerUserServiceIfExist(
+                    service ->
+                            service.onUnregisterDataRequestObserver(
+                                    dataType,
+                                    previousDataRequestObserverId,
+                                    pendingIntentCreatorPackage,
+                                    statusCallback),
+                    statusCallback);
+        }
+
+        @Override
         public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
                 String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
             new WearableSensingShellCommand(WearableSensingManagerService.this).exec(
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 85580ac..d99000e 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -591,9 +591,18 @@
 
             // Carefully collect grants without holding lock
             if (activityInfo != null) {
-                intentGrants = supervisor.mService.mUgmInternal.checkGrantUriPermissionFromIntent(
-                        intent, resolvedCallingUid, activityInfo.applicationInfo.packageName,
-                        UserHandle.getUserId(activityInfo.applicationInfo.uid));
+                if (android.security.Flags.contentUriPermissionApis()) {
+                    intentGrants = supervisor.mService.mUgmInternal
+                            .checkGrantUriPermissionFromIntent(intent, resolvedCallingUid,
+                                    activityInfo.applicationInfo.packageName,
+                                    UserHandle.getUserId(activityInfo.applicationInfo.uid),
+                                    activityInfo.requireContentUriPermissionFromCaller);
+                } else {
+                    intentGrants = supervisor.mService.mUgmInternal
+                            .checkGrantUriPermissionFromIntent(intent, resolvedCallingUid,
+                                    activityInfo.applicationInfo.packageName,
+                                    UserHandle.getUserId(activityInfo.applicationInfo.uid));
+                }
             }
         }
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
index 2c8b1cd..349b831 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
@@ -54,7 +54,8 @@
         ParsedActivity::getTheme,
         ParsedActivity::getUiOptions,
         ParsedActivity::isSupportsSizeChanges,
-        ParsedActivity::getRequiredDisplayCategory
+        ParsedActivity::getRequiredDisplayCategory,
+        ParsedActivity::getRequireContentUriPermissionFromCaller
     )
 
     override fun mainComponentSubclassExtraParams() = listOf(
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index ad6e2c6..3743483 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -154,6 +154,7 @@
         "androidx.annotation_annotation",
         "androidx.test.rules",
         "services.core",
+        "flag-junit",
     ],
     srcs: [
         "src/com/android/server/uri/**/*.java",
diff --git a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
index 3218586..24abc18 100644
--- a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.uri;
 
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.NULL_DEFAULT;
+
 import static com.android.server.uri.UriGrantsMockContext.FLAG_PERSISTABLE;
 import static com.android.server.uri.UriGrantsMockContext.FLAG_PREFIX;
 import static com.android.server.uri.UriGrantsMockContext.FLAG_READ;
@@ -57,22 +59,49 @@
 import android.net.Uri;
 import android.os.Process;
 import android.os.UserHandle;
+import android.platform.test.flag.junit.FlagsParameterization;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.ArraySet;
 
-import androidx.test.InstrumentationRegistry;
-
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 import java.util.Arrays;
+import java.util.List;
 import java.util.Set;
 
+@RunWith(Parameterized.class)
 public class UriGrantsManagerServiceTest {
     @Rule
     public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
+    /**
+     * Why this class needs to test all combinations of
+     * {@link android.security.Flags#FLAG_CONTENT_URI_PERMISSION_APIS}:
+     *
+     * <p>Although tests in this class don't directly query the flag, its value
+     * is needed for {@link UriGrantsManagerInternal#checkGrantUriPermissionFromIntent}. This is
+     * particularly important for host side tests (Ravenwood), which cannot read flag values from
+     * the device and must have them set explicitly.
+     */
+    @Parameters(name = "{0}")
+    public static List<FlagsParameterization> getFlags() {
+        return FlagsParameterization.allCombinationsOf(
+                android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS);
+    }
+
+    public UriGrantsManagerServiceTest(FlagsParameterization flags) {
+        mSetFlagsRule = new SetFlagsRule(NULL_DEFAULT, flags);
+    }
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule;
+
     private UriGrantsMockContext mContext;
     private UriGrantsManagerInternal mService;