Merge "Fix potential IOOB error in icon reordering" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 0c504b6..3669103 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5448,7 +5448,6 @@
   }
 
   @FlaggedApi("android.security.content_uri_permission_apis") public final class ComponentCaller {
-    ctor public ComponentCaller(@NonNull android.os.IBinder, @Nullable android.os.IBinder);
     method public int checkContentUriPermission(@NonNull android.net.Uri, int);
     method @Nullable public String getPackage();
     method public int getUid();
@@ -7425,6 +7424,7 @@
     method public int onStartCommand(android.content.Intent, int, int);
     method public void onTaskRemoved(android.content.Intent);
     method public void onTimeout(int);
+    method @FlaggedApi("android.app.introduce_new_service_ontimeout_callback") public void onTimeout(int, int);
     method public void onTrimMemory(int);
     method public boolean onUnbind(android.content.Intent);
     method public final void startForeground(int, android.app.Notification);
@@ -10045,11 +10045,22 @@
     method public CharSequence coerceToText(android.content.Context);
     method public String getHtmlText();
     method public android.content.Intent getIntent();
+    method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @Nullable public android.app.PendingIntent getPendingIntent();
     method public CharSequence getText();
     method @Nullable public android.view.textclassifier.TextLinks getTextLinks();
     method public android.net.Uri getUri();
   }
 
+  @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") public static final class ClipData.Item.Builder {
+    ctor public ClipData.Item.Builder();
+    method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item build();
+    method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setHtmlText(@Nullable String);
+    method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setIntent(@Nullable android.content.Intent);
+    method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setPendingIntent(@Nullable android.app.PendingIntent);
+    method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setText(@Nullable CharSequence);
+    method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setUri(@Nullable android.net.Uri);
+  }
+
   public class ClipDescription implements android.os.Parcelable {
     ctor public ClipDescription(CharSequence, String[]);
     ctor public ClipDescription(android.content.ClipDescription);
@@ -11336,6 +11347,7 @@
     field public static final String EXTRA_LOCALE_LIST = "android.intent.extra.LOCALE_LIST";
     field public static final String EXTRA_LOCAL_ONLY = "android.intent.extra.LOCAL_ONLY";
     field public static final String EXTRA_LOCUS_ID = "android.intent.extra.LOCUS_ID";
+    field @FlaggedApi("android.service.chooser.enable_sharesheet_metadata_extra") public static final String EXTRA_METADATA_TEXT = "android.intent.extra.METADATA_TEXT";
     field public static final String EXTRA_MIME_TYPES = "android.intent.extra.MIME_TYPES";
     field public static final String EXTRA_NOT_UNKNOWN_SOURCE = "android.intent.extra.NOT_UNKNOWN_SOURCE";
     field public static final String EXTRA_ORIGINATING_URI = "android.intent.extra.ORIGINATING_URI";
@@ -52896,9 +52908,11 @@
     field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
     field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
     field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
+    field @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") public static final int DRAG_FLAG_GLOBAL_SAME_APPLICATION = 4096; // 0x1000
     field public static final int DRAG_FLAG_GLOBAL_URI_READ = 1; // 0x1
     field public static final int DRAG_FLAG_GLOBAL_URI_WRITE = 2; // 0x2
     field public static final int DRAG_FLAG_OPAQUE = 512; // 0x200
+    field @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") public static final int DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG = 8192; // 0x2000
     field @Deprecated public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0
     field @Deprecated public static final int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000
     field @Deprecated public static final int DRAWING_CACHE_QUALITY_LOW = 524288; // 0x80000
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0f1da41..7936e9f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -282,6 +282,7 @@
     field public static final String RADIO_SCAN_WITHOUT_LOCATION = "android.permission.RADIO_SCAN_WITHOUT_LOCATION";
     field public static final String READ_ACTIVE_EMERGENCY_SESSION = "android.permission.READ_ACTIVE_EMERGENCY_SESSION";
     field public static final String READ_APP_SPECIFIC_LOCALES = "android.permission.READ_APP_SPECIFIC_LOCALES";
+    field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String READ_BLOCKED_NUMBERS = "android.permission.READ_BLOCKED_NUMBERS";
     field public static final String READ_CARRIER_APP_INFO = "android.permission.READ_CARRIER_APP_INFO";
     field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
     field public static final String READ_CLIPBOARD_IN_BACKGROUND = "android.permission.READ_CLIPBOARD_IN_BACKGROUND";
@@ -409,6 +410,7 @@
     field public static final String WIFI_UPDATE_COEX_UNSAFE_CHANNELS = "android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS";
     field public static final String WIFI_UPDATE_USABILITY_STATS_SCORE = "android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE";
     field public static final String WRITE_ALLOWLISTED_DEVICE_CONFIG = "android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG";
+    field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String WRITE_BLOCKED_NUMBERS = "android.permission.WRITE_BLOCKED_NUMBERS";
     field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
     field public static final String WRITE_DREAM_STATE = "android.permission.WRITE_DREAM_STATE";
     field public static final String WRITE_EMBEDDED_SUBSCRIPTIONS = "android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS";
@@ -11457,6 +11459,29 @@
 
 package android.provider {
 
+  public static class BlockedNumberContract.BlockedNumbers {
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static void endBlockSuppression(@NonNull android.content.Context);
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @NonNull @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static android.provider.BlockedNumberContract.BlockedNumbers.BlockSuppressionStatus getBlockSuppressionStatus(@NonNull android.content.Context);
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static boolean getBlockedNumberSetting(@NonNull android.content.Context, @NonNull String);
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static void notifyEmergencyContact(@NonNull android.content.Context);
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static void setBlockedNumberSetting(@NonNull android.content.Context, @NonNull String, boolean);
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static boolean shouldShowEmergencyCallNotification(@NonNull android.content.Context);
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static int shouldSystemBlockNumber(@NonNull android.content.Context, @NonNull String, int, boolean);
+    field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ACTION_BLOCK_SUPPRESSION_STATE_CHANGED = "android.provider.action.BLOCK_SUPPRESSION_STATE_CHANGED";
+    field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_BLOCK_PAYPHONE = "block_payphone_calls_setting";
+    field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_BLOCK_PRIVATE = "block_private_number_calls_setting";
+    field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE = "block_unavailable_calls_setting";
+    field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_BLOCK_UNKNOWN = "block_unknown_calls_setting";
+    field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED = "block_numbers_not_in_contacts_setting";
+    field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION = "show_emergency_call_notification";
+  }
+
+  @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final class BlockedNumberContract.BlockedNumbers.BlockSuppressionStatus {
+    ctor public BlockedNumberContract.BlockedNumbers.BlockSuppressionStatus(boolean, long);
+    method public boolean getIsSuppressed();
+    method public long getUntilTimestampMillis();
+  }
+
   public class CallLog {
     method @RequiresPermission(allOf={android.Manifest.permission.WRITE_CALL_LOG, android.Manifest.permission.INTERACT_ACROSS_USERS}) public static void storeCallComposerPicture(@NonNull android.content.Context, @NonNull java.io.InputStream, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.net.Uri,android.provider.CallLog.CallComposerLoggingException>);
   }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2c00c99..de6a848 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1232,6 +1232,15 @@
         }
 
         @Override
+        public final void scheduleTimeoutServiceForType(IBinder token, int startId, int fgsType) {
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+                Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                        "scheduleTimeoutServiceForType. token=" + token);
+            }
+            sendMessage(H.TIMEOUT_SERVICE_FOR_TYPE, token, startId, fgsType);
+        }
+
+        @Override
         public final void bindApplication(
                 String processName,
                 ApplicationInfo appInfo,
@@ -2288,6 +2297,8 @@
         public static final int INSTRUMENT_WITHOUT_RESTART = 170;
         public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171;
 
+        public static final int TIMEOUT_SERVICE_FOR_TYPE = 172;
+
         String codeToString(int code) {
             if (DEBUG_MESSAGES) {
                 switch (code) {
@@ -2341,6 +2352,7 @@
                     case DUMP_RESOURCES: return "DUMP_RESOURCES";
                     case TIMEOUT_SERVICE: return "TIMEOUT_SERVICE";
                     case PING: return "PING";
+                    case TIMEOUT_SERVICE_FOR_TYPE: return "TIMEOUT_SERVICE_FOR_TYPE";
                 }
             }
             return Integer.toString(code);
@@ -2427,6 +2439,14 @@
                 case PING:
                     ((RemoteCallback) msg.obj).sendResult(null);
                     break;
+                case TIMEOUT_SERVICE_FOR_TYPE:
+                    if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+                        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                                "serviceTimeoutForType: " + msg.obj);
+                    }
+                    handleTimeoutServiceForType((IBinder) msg.obj, msg.arg1, msg.arg2);
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    break;
                 case CONFIGURATION_CHANGED:
                     mConfigurationController.handleConfigurationChanged((Configuration) msg.obj);
                     break;
@@ -5136,6 +5156,26 @@
             Slog.wtf(TAG, "handleTimeoutService: token=" + token + " not found.");
         }
     }
+
+    private void handleTimeoutServiceForType(IBinder token, int startId, int fgsType) {
+        Service s = mServices.get(token);
+        if (s != null) {
+            try {
+                if (localLOGV) Slog.v(TAG, "Timeout service " + s);
+
+                s.callOnTimeLimitExceeded(startId, fgsType);
+            } catch (Exception e) {
+                if (!mInstrumentation.onException(s, e)) {
+                    throw new RuntimeException(
+                            "Unable to call onTimeLimitExceeded on service " + s + ": " + e, e);
+                }
+                Slog.i(TAG, "handleTimeoutServiceForType: exception for " + token, e);
+            }
+        } else {
+            Slog.wtf(TAG, "handleTimeoutServiceForType: token=" + token + " not found.");
+        }
+    }
+
     /**
      * Resume the activity.
      * @param r Target activity record.
diff --git a/core/java/android/app/ComponentCaller.java b/core/java/android/app/ComponentCaller.java
index a440dbc..44e8a0a 100644
--- a/core/java/android/app/ComponentCaller.java
+++ b/core/java/android/app/ComponentCaller.java
@@ -42,6 +42,9 @@
     private final IBinder mActivityToken;
     private final IBinder mCallerToken;
 
+    /**
+     * @hide
+     */
     public ComponentCaller(@NonNull IBinder activityToken, @Nullable IBinder callerToken) {
         mActivityToken = activityToken;
         mCallerToken = callerToken;
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index ceeaf5d..cc0aafd 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -942,6 +942,8 @@
 
     /** Returns if the service is a short-service is still "alive" and past the timeout. */
     boolean shouldServiceTimeOut(in ComponentName className, in IBinder token);
+    /** Returns if the service has a time-limit restricted type and is past the time limit. */
+    boolean hasServiceTimeLimitExceeded(in ComponentName className, in IBinder token);
 
     void registerUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback);
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 59e0e99..a04620c 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -178,5 +178,6 @@
             in TranslationSpec targetSpec, in List<AutofillId> viewIds,
             in UiTranslationSpec uiTranslationSpec);
     void scheduleTimeoutService(IBinder token, int startId);
+    void scheduleTimeoutServiceForType(IBinder token, int startId, int fgsType);
     void schedulePing(in RemoteCallback pong);
 }
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index a155457..d470299 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -20,6 +20,7 @@
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.text.TextUtils.formatSimple;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1161,4 +1162,37 @@
      */
     public void onTimeout(int startId) {
     }
+
+    /** @hide */
+    public final void callOnTimeLimitExceeded(int startId, int fgsType) {
+        // Note, because all the service callbacks (and other similar callbacks, e.g. activity
+        // callbacks) are delivered using the main handler, it's possible the service is already
+        // stopped when before this method is called, so we do a double check here.
+        if (mToken == null) {
+            Log.w(TAG, "Service already destroyed, skipping onTimeLimitExceeded()");
+            return;
+        }
+        try {
+            if (!mActivityManager.hasServiceTimeLimitExceeded(
+                    new ComponentName(this, mClassName), mToken)) {
+                Log.w(TAG, "Service no longer relevant, skipping onTimeLimitExceeded()");
+                return;
+            }
+        } catch (RemoteException ex) {
+        }
+        if (Flags.introduceNewServiceOntimeoutCallback()) {
+            onTimeout(startId, fgsType);
+        }
+    }
+
+    /**
+     * Callback called when a particular foreground service type has timed out.
+     *
+     * @param startId the startId passed to {@link #onStartCommand(Intent, int, int)} when
+     * the service started.
+     * @param fgsType the foreground service type which caused the timeout.
+     */
+    @FlaggedApi(Flags.FLAG_INTRODUCE_NEW_SERVICE_ONTIMEOUT_CALLBACK)
+    public void onTimeout(int startId, int fgsType) {
+    }
 }
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index c0b299b..ff23f09 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -27,3 +27,10 @@
      description: "API to add OnUidImportanceListener with targetted UIDs"
      bug: "286258140"
 }
+
+flag {
+     namespace: "backstage_power"
+     name: "introduce_new_service_ontimeout_callback"
+     description: "Add a new callback in Service to indicate a FGS has reached its timeout."
+     bug: "317799821"
+}
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index 67759f4..eb357fe 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -21,7 +21,13 @@
 import static android.content.ContentResolver.SCHEME_CONTENT;
 import static android.content.ContentResolver.SCHEME_FILE;
 
+import static com.android.window.flags.Flags.FLAG_DELEGATE_UNHANDLED_DRAGS;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.PendingIntent;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo;
 import android.content.res.AssetFileDescriptor;
@@ -207,6 +213,7 @@
         final CharSequence mText;
         final String mHtmlText;
         final Intent mIntent;
+        final PendingIntent mPendingIntent;
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
         Uri mUri;
         private TextLinks mTextLinks;
@@ -214,12 +221,91 @@
         // if the data is obtained from {@link #copyForTransferWithActivityInfo}
         private ActivityInfo mActivityInfo;
 
+        /**
+         * A builder for a ClipData Item.
+         */
+        @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+        @SuppressLint("PackageLayering")
+        public static final class Builder {
+            private CharSequence mText;
+            private String mHtmlText;
+            private Intent mIntent;
+            private PendingIntent mPendingIntent;
+            private Uri mUri;
+
+            /**
+             * Sets the text for the item to be constructed.
+             */
+            @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+            @NonNull
+            public Builder setText(@Nullable CharSequence text) {
+                mText = text;
+                return this;
+            }
+
+            /**
+             * Sets the HTML text for the item to be constructed.
+             */
+            @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+            @NonNull
+            public Builder setHtmlText(@Nullable String htmlText) {
+                mHtmlText = htmlText;
+                return this;
+            }
+
+            /**
+             * Sets the Intent for the item to be constructed.
+             */
+            @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+            @NonNull
+            public Builder setIntent(@Nullable Intent intent) {
+                mIntent = intent;
+                return this;
+            }
+
+            /**
+             * Sets the PendingIntent for the item to be constructed. To prevent receiving apps from
+             * improperly manipulating the intent to launch another activity as this caller, the
+             * provided PendingIntent must be immutable (see {@link PendingIntent#FLAG_IMMUTABLE}).
+             * The system will clean up the PendingIntent when it is no longer used.
+             */
+            @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+            @NonNull
+            public Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
+                if (pendingIntent != null && !pendingIntent.isImmutable()) {
+                    throw new IllegalArgumentException("Expected pending intent to be immutable");
+                }
+                mPendingIntent = pendingIntent;
+                return this;
+            }
+
+            /**
+             * Sets the URI for the item to be constructed.
+             */
+            @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+            @NonNull
+            public Builder setUri(@Nullable Uri uri) {
+                mUri = uri;
+                return this;
+            }
+
+            /**
+             * Constructs a new Item with the properties set on this builder.
+             */
+            @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+            @NonNull
+            public Item build() {
+                return new Item(mText, mHtmlText, mIntent, mPendingIntent, mUri);
+            }
+        }
+
 
         /** @hide */
         public Item(Item other) {
             mText = other.mText;
             mHtmlText = other.mHtmlText;
             mIntent = other.mIntent;
+            mPendingIntent = other.mPendingIntent;
             mUri = other.mUri;
             mActivityInfo = other.mActivityInfo;
             mTextLinks = other.mTextLinks;
@@ -229,10 +315,7 @@
          * Create an Item consisting of a single block of (possibly styled) text.
          */
         public Item(CharSequence text) {
-            mText = text;
-            mHtmlText = null;
-            mIntent = null;
-            mUri = null;
+            this(text, null, null, null, null);
         }
 
         /**
@@ -245,30 +328,21 @@
          * </p>
          */
         public Item(CharSequence text, String htmlText) {
-            mText = text;
-            mHtmlText = htmlText;
-            mIntent = null;
-            mUri = null;
+            this(text, htmlText, null, null, null);
         }
 
         /**
          * Create an Item consisting of an arbitrary Intent.
          */
         public Item(Intent intent) {
-            mText = null;
-            mHtmlText = null;
-            mIntent = intent;
-            mUri = null;
+            this(null, null, intent, null, null);
         }
 
         /**
          * Create an Item consisting of an arbitrary URI.
          */
         public Item(Uri uri) {
-            mText = null;
-            mHtmlText = null;
-            mIntent = null;
-            mUri = uri;
+            this(null, null, null, null, uri);
         }
 
         /**
@@ -276,10 +350,7 @@
          * text, Intent, and/or URI.
          */
         public Item(CharSequence text, Intent intent, Uri uri) {
-            mText = text;
-            mHtmlText = null;
-            mIntent = intent;
-            mUri = uri;
+            this(text, null, intent, null, uri);
         }
 
         /**
@@ -289,6 +360,14 @@
          * will not be done from HTML formatted text into plain text.
          */
         public Item(CharSequence text, String htmlText, Intent intent, Uri uri) {
+            this(text, htmlText, intent, null, uri);
+        }
+
+        /**
+         * Builder ctor.
+         */
+        private Item(CharSequence text, String htmlText, Intent intent, PendingIntent pendingIntent,
+                Uri uri) {
             if (htmlText != null && text == null) {
                 throw new IllegalArgumentException(
                         "Plain text must be supplied if HTML text is supplied");
@@ -296,6 +375,7 @@
             mText = text;
             mHtmlText = htmlText;
             mIntent = intent;
+            mPendingIntent = pendingIntent;
             mUri = uri;
         }
 
@@ -321,6 +401,15 @@
         }
 
         /**
+         * Returns the pending intent in this Item.
+         */
+        @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+        @Nullable
+        public PendingIntent getPendingIntent() {
+            return mPendingIntent;
+        }
+
+        /**
          * Retrieve the raw URI contained in this Item.
          */
         public Uri getUri() {
@@ -777,7 +866,7 @@
             throw new NullPointerException("item is null");
         }
         mIcon = null;
-        mItems = new ArrayList<Item>();
+        mItems = new ArrayList<>();
         mItems.add(item);
         mClipDescription.setIsStyledText(isStyledText());
     }
@@ -794,7 +883,7 @@
             throw new NullPointerException("item is null");
         }
         mIcon = null;
-        mItems = new ArrayList<Item>();
+        mItems = new ArrayList<>();
         mItems.add(item);
         mClipDescription.setIsStyledText(isStyledText());
     }
@@ -826,7 +915,7 @@
     public ClipData(ClipData other) {
         mClipDescription = other.mClipDescription;
         mIcon = other.mIcon;
-        mItems = new ArrayList<Item>(other.mItems);
+        mItems = new ArrayList<>(other.mItems);
     }
 
     /**
@@ -1042,6 +1131,35 @@
     }
 
     /**
+     * Checks if this clip data has a pending intent that is an activity type.
+     * @hide
+     */
+    public boolean hasActivityPendingIntents() {
+        final int size = mItems.size();
+        for (int i = 0; i < size; i++) {
+            final Item item = mItems.get(i);
+            if (item.mPendingIntent != null && item.mPendingIntent.isActivity()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Cleans up all pending intents in the ClipData.
+     * @hide
+     */
+    public void cleanUpPendingIntents() {
+        final int size = mItems.size();
+        for (int i = 0; i < size; i++) {
+            final Item item = mItems.get(i);
+            if (item.mPendingIntent != null) {
+                item.mPendingIntent.cancel();
+            }
+        }
+    }
+
+    /**
      * Prepare this {@link ClipData} to leave an app process.
      *
      * @hide
@@ -1243,6 +1361,7 @@
             TextUtils.writeToParcel(item.mText, dest, flags);
             dest.writeString8(item.mHtmlText);
             dest.writeTypedObject(item.mIntent, flags);
+            dest.writeTypedObject(item.mPendingIntent, flags);
             dest.writeTypedObject(item.mUri, flags);
             dest.writeTypedObject(mParcelItemActivityInfos ? item.mActivityInfo : null, flags);
             dest.writeTypedObject(item.mTextLinks, flags);
@@ -1262,10 +1381,11 @@
             CharSequence text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
             String htmlText = in.readString8();
             Intent intent = in.readTypedObject(Intent.CREATOR);
+            PendingIntent pendingIntent = in.readTypedObject(PendingIntent.CREATOR);
             Uri uri = in.readTypedObject(Uri.CREATOR);
             ActivityInfo info = in.readTypedObject(ActivityInfo.CREATOR);
             TextLinks textLinks = in.readTypedObject(TextLinks.CREATOR);
-            Item item = new Item(text, htmlText, intent, uri);
+            Item item = new Item(text, htmlText, intent, pendingIntent, uri);
             item.setActivityInfo(info);
             item.setTextLinks(textLinks);
             mItems.add(item);
@@ -1273,7 +1393,7 @@
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<ClipData> CREATOR =
-        new Parcelable.Creator<ClipData>() {
+        new Parcelable.Creator<>() {
 
             @Override
             public ClipData createFromParcel(Parcel source) {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 08871d4..333c363 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -19,6 +19,7 @@
 import static android.app.sdksandbox.SdkSandboxManager.ACTION_START_SANDBOXED_ACTIVITY;
 import static android.content.ContentProvider.maybeAddUserId;
 import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
+import static android.service.chooser.Flags.FLAG_ENABLE_SHARESHEET_METADATA_EXTRA;
 
 import android.Manifest;
 import android.accessibilityservice.AccessibilityService;
@@ -6156,6 +6157,17 @@
     public static final String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS";
 
     /**
+     * A CharSequence of additional text describing the content being shared. This text will be
+     * displayed to the user as a part of the sharesheet when included in an
+     * {@link #ACTION_CHOOSER} {@link Intent}.
+     *
+     * <p>e.g. When sharing a photo, metadata could inform the user that location data is included
+     * in the photo they are sharing.</p>
+     */
+    @FlaggedApi(FLAG_ENABLE_SHARESHEET_METADATA_EXTRA)
+    public static final String EXTRA_METADATA_TEXT = "android.intent.extra.METADATA_TEXT";
+
+    /**
      * A {@link IntentSender} to start after instant app installation success.
      * @hide
      */
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 2b378b1..5b0cee7 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -142,6 +142,28 @@
      * the {@link android.R.attr#foregroundServiceType} attribute.
      * Data(photo, file, account) upload/download, backup/restore, import/export, fetch,
      * transfer over network between device and cloud.
+     *
+     * <p>This type has time limit of 6 hours starting from Android version
+     * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}.
+     * A foreground service of this type must be stopped within the timeout by
+     * {@link android.app.Service#stopSelf()},
+     * {@link android.content.Context#stopService(android.content.Intent)} or their overloads).
+     * {@link android.app.Service#stopForeground(int)} will also work, which will demote the
+     * service to a "background" service, which will soon be stopped by the system.
+     *
+     * <p>If the service isn't stopped within the timeout,
+     * {@link android.app.Service#onTimeout(int, int)} will be called.
+     *
+     * <p>Also note, even though
+     * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC} can be used on
+     * Android versions prior to {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, since
+     * {@link android.app.Service#onTimeout(int, int)} did not exist on such versions, it will
+     * never be called.
+     *
+     * Because of this, developers must make sure to stop the foreground service even if
+     * {@link android.app.Service#onTimeout(int, int)} is not called on such versions.
+     *
+     * @see android.app.Service#onTimeout(int, int)
      */
     @RequiresPermission(
             value = Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC,
@@ -483,6 +505,27 @@
      * Constant corresponding to {@code mediaProcessing} in
      * the {@link android.R.attr#foregroundServiceType} attribute.
      * Media processing use cases such as video or photo editing and processing.
+     *
+     * This type has time limit of 6 hours.
+     * A foreground service of this type must be stopped within the timeout by
+     * {@link android.app.Service#stopSelf()},
+     * {@link android.content.Context#stopService(android.content.Intent)} or their overloads).
+     * {@link android.app.Service#stopForeground(int)} will also work, which will demote the
+     * service to a "background" service, which will soon be stopped by the system.
+     *
+     * <p>If the service isn't stopped within the timeout,
+     * {@link android.app.Service#onTimeout(int, int)} will be called.
+     *
+     * <p>Also note, even though
+     * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING} was added in
+     * Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, it can be also used
+     * on prior android versions (just like other new foreground service types can be used).
+     * However, because {@link android.app.Service#onTimeout(int, int)} did not exist on prior
+     * versions, it will never be called on such versions.
+     * Because of this, developers must make sure to stop the foreground service even if
+     * {@link android.app.Service#onTimeout(int, int)} is not called on such versions.
+     *
+     * @see android.app.Service#onTimeout(int, int)
      */
     @RequiresPermission(
             value = Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING
diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java
similarity index 96%
rename from services/core/java/com/android/server/devicestate/DeviceState.java
rename to core/java/android/hardware/devicestate/DeviceState.java
index 2ba59b0..5a34905 100644
--- a/services/core/java/com/android/server/devicestate/DeviceState.java
+++ b/core/java/android/hardware/devicestate/DeviceState.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.devicestate;
+package android.hardware.devicestate;
 
 import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
 import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
@@ -22,7 +22,6 @@
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
-import android.hardware.devicestate.DeviceStateManager;
 
 import com.android.internal.util.Preconditions;
 
@@ -38,9 +37,9 @@
  * state of the system. This is useful for variable-state devices, like foldable or rollable
  * devices, that can be configured by users into differing hardware states, which each may have a
  * different expected use case.
+ * @hide
  *
- * @see DeviceStateProvider
- * @see DeviceStateManagerService
+ * @see DeviceStateManager
  */
 public final class DeviceState {
     /**
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index ce03798..de32423 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1427,8 +1427,8 @@
     public static final String DISALLOW_RECORD_AUDIO = "no_record_audio";
 
     /**
-     * Specifies if a user is not allowed to run in the background and should be stopped during
-     * user switch. The default value is <code>false</code>.
+     * Specifies if a user is not allowed to run in the background and should be stopped and locked
+     * during user switch. The default value is <code>false</code>.
      *
      * <p>This restriction can be set by device owners and profile owners.
      *
diff --git a/core/java/android/provider/BlockedNumberContract.java b/core/java/android/provider/BlockedNumberContract.java
index 5d00b29..4075e90 100644
--- a/core/java/android/provider/BlockedNumberContract.java
+++ b/core/java/android/provider/BlockedNumberContract.java
@@ -15,12 +15,20 @@
  */
 package android.provider;
 
+import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.annotation.WorkerThread;
 import android.content.Context;
 import android.net.Uri;
 import android.os.Bundle;
 import android.telecom.Log;
+import android.telecom.TelecomManager;
+
+import com.android.server.telecom.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -214,6 +222,333 @@
          * <p>TYPE: String</p>
          */
         public static final String COLUMN_E164_NUMBER = "e164_number";
+
+        /**
+         * A protected broadcast intent action for letting components with
+         * {@link android.Manifest.permission#READ_BLOCKED_NUMBERS} know that the block suppression
+         * status as returned by {@link #getBlockSuppressionStatus(Context)} has been updated.
+         * @hide
+         */
+        @SystemApi
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static final String ACTION_BLOCK_SUPPRESSION_STATE_CHANGED =
+                "android.provider.action.BLOCK_SUPPRESSION_STATE_CHANGED";
+
+        /**
+         * Preference key of block numbers not in contacts setting.
+         * @hide
+         */
+        @SystemApi
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static final String ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED =
+                "block_numbers_not_in_contacts_setting";
+
+        /**
+         * Preference key of block private number calls setting.
+         * @hide
+         */
+        @SystemApi
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static final String ENHANCED_SETTING_KEY_BLOCK_PRIVATE =
+                "block_private_number_calls_setting";
+
+        /**
+         * Preference key of block payphone calls setting.
+         * @hide
+         */
+        @SystemApi
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static final String ENHANCED_SETTING_KEY_BLOCK_PAYPHONE =
+                "block_payphone_calls_setting";
+
+        /**
+         * Preference key of block unknown calls setting.
+         * @hide
+         */
+        @SystemApi
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static final String ENHANCED_SETTING_KEY_BLOCK_UNKNOWN =
+                "block_unknown_calls_setting";
+
+        /**
+         * Preference key for whether should show an emergency call notification.
+         * @hide
+         */
+        @SystemApi
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static final String ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION =
+                "show_emergency_call_notification";
+
+        /**
+         * Preference key of block unavailable calls setting.
+         * @hide
+         */
+        @SystemApi
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static final String ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE =
+                "block_unavailable_calls_setting";
+
+        /**
+         * Notifies the provider that emergency services were contacted by the user.
+         * <p> This results in {@link #shouldSystemBlockNumber} returning {@code false} independent
+         * of the contents of the provider for a duration defined by
+         * {@link android.telephony.CarrierConfigManager#KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT}
+         * the provider unless {@link #endBlockSuppression(Context)} is called.
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(allOf = {
+                android.Manifest.permission.READ_BLOCKED_NUMBERS,
+                android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+        })
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static void notifyEmergencyContact(@NonNull Context context) {
+            verifyBlockedNumbersPermission(context);
+            try {
+                Log.i(LOG_TAG, "notifyEmergencyContact; caller=%s", context.getOpPackageName());
+                context.getContentResolver().call(
+                        AUTHORITY_URI, SystemContract.METHOD_NOTIFY_EMERGENCY_CONTACT, null, null);
+            } catch (NullPointerException | IllegalArgumentException ex) {
+                // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
+                // either of these happen.
+                Log.w(null, "notifyEmergencyContact: provider not ready.");
+            }
+        }
+
+        /**
+         * Notifies the provider to disable suppressing blocking. If emergency services were not
+         * contacted recently at all, calling this method is a no-op.
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(allOf = {
+                android.Manifest.permission.READ_BLOCKED_NUMBERS,
+                android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+        })
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static void endBlockSuppression(@NonNull Context context) {
+            verifyBlockedNumbersPermission(context);
+            String caller = context.getOpPackageName();
+            Log.i(LOG_TAG, "endBlockSuppression: caller=%s", caller);
+            context.getContentResolver().call(
+                    AUTHORITY_URI, SystemContract.METHOD_END_BLOCK_SUPPRESSION, null, null);
+        }
+
+        /**
+         * Returns {@code true} if {@code phoneNumber} is blocked taking
+         * {@link #notifyEmergencyContact(Context)} into consideration. If emergency services
+         * have not been contacted recently and enhanced call blocking not been enabled, this
+         * method is equivalent to {@link #isBlocked(Context, String)}.
+         *
+         * @param context the context of the caller.
+         * @param phoneNumber the number to check.
+         * @param numberPresentation the presentation code associated with the call.
+         * @param isNumberInContacts indicates if the provided number exists as a contact.
+         * @return result code indicating if the number should be blocked, and if so why.
+         *         Valid values are: {@link #STATUS_NOT_BLOCKED}, {@link #STATUS_BLOCKED_IN_LIST},
+         *         {@link #STATUS_BLOCKED_NOT_IN_CONTACTS}, {@link #STATUS_BLOCKED_PAYPHONE},
+         *         {@link #STATUS_BLOCKED_RESTRICTED}, {@link #STATUS_BLOCKED_UNKNOWN_NUMBER}.
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(allOf = {
+                android.Manifest.permission.READ_BLOCKED_NUMBERS,
+                android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+        })
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static int shouldSystemBlockNumber(@NonNull Context context,
+                @NonNull String phoneNumber, @TelecomManager.Presentation int numberPresentation,
+                boolean isNumberInContacts) {
+            verifyBlockedNumbersPermission(context);
+            try {
+                String caller = context.getOpPackageName();
+                Bundle extras = new Bundle();
+                extras.putInt(BlockedNumberContract.EXTRA_CALL_PRESENTATION, numberPresentation);
+                extras.putBoolean(BlockedNumberContract.EXTRA_CONTACT_EXIST, isNumberInContacts);
+                final Bundle res = context.getContentResolver().call(AUTHORITY_URI,
+                        SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER, phoneNumber, extras);
+                int blockResult = res != null ? res.getInt(RES_BLOCK_STATUS, STATUS_NOT_BLOCKED) :
+                        BlockedNumberContract.STATUS_NOT_BLOCKED;
+                Log.d(LOG_TAG, "shouldSystemBlockNumber: number=%s, caller=%s, result=%s",
+                        Log.piiHandle(phoneNumber), caller,
+                        SystemContract.blockStatusToString(blockResult));
+                return blockResult;
+            } catch (NullPointerException | IllegalArgumentException ex) {
+                // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
+                // either of these happen.
+                Log.w(null, "shouldSystemBlockNumber: provider not ready.");
+                return BlockedNumberContract.STATUS_NOT_BLOCKED;
+            }
+        }
+
+        /**
+         * Returns the current status of block suppression.
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(allOf = {
+                android.Manifest.permission.READ_BLOCKED_NUMBERS,
+                android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+        })
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static @NonNull BlockSuppressionStatus getBlockSuppressionStatus(
+                @NonNull Context context) {
+            verifyBlockedNumbersPermission(context);
+            final Bundle res = context.getContentResolver().call(
+                    AUTHORITY_URI, SystemContract.METHOD_GET_BLOCK_SUPPRESSION_STATUS, null, null);
+            BlockSuppressionStatus blockSuppressionStatus = new BlockSuppressionStatus(
+                    res.getBoolean(SystemContract.RES_IS_BLOCKING_SUPPRESSED, false),
+                    res.getLong(SystemContract.RES_BLOCKING_SUPPRESSED_UNTIL_TIMESTAMP, 0));
+            Log.d(LOG_TAG, "getBlockSuppressionStatus: caller=%s, status=%s",
+                    context.getOpPackageName(), blockSuppressionStatus);
+            return blockSuppressionStatus;
+        }
+
+        /**
+         * Check whether should show the emergency call notification.
+         *
+         * @param context the context of the caller.
+         * @return {@code true} if should show emergency call notification. {@code false} otherwise.
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(allOf = {
+                android.Manifest.permission.READ_BLOCKED_NUMBERS,
+                android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+        })
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static boolean shouldShowEmergencyCallNotification(@NonNull Context context) {
+            verifyBlockedNumbersPermission(context);
+            try {
+                final Bundle res = context.getContentResolver().call(AUTHORITY_URI,
+                        SystemContract.METHOD_SHOULD_SHOW_EMERGENCY_CALL_NOTIFICATION, null, null);
+                return res != null && res.getBoolean(RES_SHOW_EMERGENCY_CALL_NOTIFICATION, false);
+            } catch (NullPointerException | IllegalArgumentException ex) {
+                // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
+                // either of these happen.
+                Log.w(null, "shouldShowEmergencyCallNotification: provider not ready.");
+                return false;
+            }
+        }
+
+        /**
+         * Check whether the enhanced block setting is enabled.
+         *
+         * @param context the context of the caller.
+         * @param key the key of the setting to check, can be
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED}
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_PRIVATE}
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_PAYPHONE}
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNKNOWN}
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE}
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION}
+         * @return {@code true} if the setting is enabled. {@code false} otherwise.
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(allOf = {
+                android.Manifest.permission.READ_BLOCKED_NUMBERS,
+                android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+        })
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static boolean getBlockedNumberSetting(
+                @NonNull Context context, @NonNull String key) {
+            verifyBlockedNumbersPermission(context);
+            Bundle extras = new Bundle();
+            extras.putString(EXTRA_ENHANCED_SETTING_KEY, key);
+            try {
+                final Bundle res = context.getContentResolver().call(AUTHORITY_URI,
+                        SystemContract.METHOD_GET_ENHANCED_BLOCK_SETTING, null, extras);
+                return res != null && res.getBoolean(RES_ENHANCED_SETTING_IS_ENABLED, false);
+            } catch (NullPointerException | IllegalArgumentException ex) {
+                // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
+                // either of these happen.
+                Log.w(null, "getEnhancedBlockSetting: provider not ready.");
+                return false;
+            }
+        }
+
+        /**
+         * Set the enhanced block setting enabled status.
+         *
+         * @param context the context of the caller.
+         * @param key the key of the setting to set, can be
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED}
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_PRIVATE}
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_PAYPHONE}
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNKNOWN}
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE}
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION}
+         * @param value the enabled statue of the setting to set.
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(allOf = {
+                android.Manifest.permission.READ_BLOCKED_NUMBERS,
+                android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+        })
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static void setBlockedNumberSetting(@NonNull Context context,
+                @NonNull String key, boolean value) {
+            verifyBlockedNumbersPermission(context);
+            Bundle extras = new Bundle();
+            extras.putString(EXTRA_ENHANCED_SETTING_KEY, key);
+            extras.putBoolean(EXTRA_ENHANCED_SETTING_VALUE, value);
+            context.getContentResolver().call(AUTHORITY_URI,
+                    SystemContract.METHOD_SET_ENHANCED_BLOCK_SETTING, null, extras);
+        }
+
+        /**
+         * Represents the current status of
+         * {@link #shouldSystemBlockNumber(Context, String, int, boolean)}. If emergency services
+         * have been contacted recently, {@link #mIsSuppressed} is {@code true}, and blocking
+         * is disabled until the timestamp {@link #mUntilTimestampMillis}.
+         * @hide
+         */
+        @SystemApi
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static final class BlockSuppressionStatus {
+            private boolean mIsSuppressed;
+
+            /**
+             * Timestamp in milliseconds from epoch.
+             */
+            private long mUntilTimestampMillis;
+
+            public BlockSuppressionStatus(boolean isSuppressed, long untilTimestampMillis) {
+                this.mIsSuppressed = isSuppressed;
+                this.mUntilTimestampMillis = untilTimestampMillis;
+            }
+
+            @Override
+            public String toString() {
+                return "[BlockSuppressionStatus; isSuppressed=" + mIsSuppressed + ", until="
+                        + mUntilTimestampMillis + "]";
+            }
+
+            public boolean getIsSuppressed() {
+                return mIsSuppressed;
+            }
+
+            public long getUntilTimestampMillis() {
+                return mUntilTimestampMillis;
+            }
+        }
+
+        /**
+         * Verifies that the caller holds both the
+         * {@link android.Manifest.permission#READ_BLOCKED_NUMBERS} permission and the
+         * {@link android.Manifest.permission#WRITE_BLOCKED_NUMBERS} permission.
+         *
+         * @param context
+         * @throws SecurityException if the caller is missing the necessary permissions
+         */
+        private static void verifyBlockedNumbersPermission(Context context) {
+            context.enforceCallingOrSelfPermission(Manifest.permission.READ_BLOCKED_NUMBERS,
+                    "Caller does not have the android.permission.READ_BLOCKED_NUMBERS permission");
+            context.enforceCallingOrSelfPermission(Manifest.permission.WRITE_BLOCKED_NUMBERS,
+                    "Caller does not have the android.permission.WRITE_BLOCKED_NUMBERS permission");
+        }
     }
 
     /** @hide */
@@ -558,7 +893,7 @@
          *        {@link #ENHANCED_SETTING_KEY_BLOCK_PAYPHONE}
          *        {@link #ENHANCED_SETTING_KEY_BLOCK_UNKNOWN}
          *        {@link #ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE}
-         *        {@link #ENHANCED_SETTING_KEY_EMERGENCY_CALL_NOTIFICATION_SHOWING}
+         *        {@link #ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION}
          * @return {@code true} if the setting is enabled. {@code false} otherwise.
          */
         public static boolean getEnhancedBlockSetting(Context context, String key) {
@@ -586,7 +921,7 @@
          *        {@link #ENHANCED_SETTING_KEY_BLOCK_PAYPHONE}
          *        {@link #ENHANCED_SETTING_KEY_BLOCK_UNKNOWN}
          *        {@link #ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE}
-         *        {@link #ENHANCED_SETTING_KEY_EMERGENCY_CALL_NOTIFICATION_SHOWING}
+         *        {@link #ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION}
          * @param value the enabled statue of the setting to set.
          */
         public static void setEnhancedBlockSetting(Context context, String key, boolean value) {
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 6410609..2028c40 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -711,18 +711,21 @@
     public void draw(Canvas c, Path highlight, Paint highlightpaint,
                      int cursorOffset) {
         if (mDirect != null && highlight == null) {
+            float leftShift = 0;
             if (getUseBoundsForWidth()) {
-                c.save();
                 RectF drawingRect = computeDrawingBoundingBox();
                 if (drawingRect.left < 0) {
-                    c.translate(-drawingRect.left, 0);
+                    leftShift = -drawingRect.left;
+                    c.translate(leftShift, 0);
                 }
             }
 
             c.drawText(mDirect, 0, mBottom - mDesc, mPaint);
 
-            if (getUseBoundsForWidth()) {
-                c.restore();
+            if (leftShift != 0) {
+                // Manually translate back to the original position because of b/324498002, using
+                // save/restore disappears the toggle switch drawables.
+                c.translate(-leftShift, 0);
             }
         } else {
             super.draw(c, highlight, highlightpaint, cursorOffset);
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 8ddb42d..e5d199a 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -464,11 +464,12 @@
             @Nullable Path selectionPath,
             @Nullable Paint selectionPaint,
             int cursorOffsetVertical) {
+        float leftShift = 0;
         if (mUseBoundsForWidth) {
-            canvas.save();
             RectF drawingRect = computeDrawingBoundingBox();
             if (drawingRect.left < 0) {
-                canvas.translate(-drawingRect.left, 0);
+                leftShift = -drawingRect.left;
+                canvas.translate(leftShift, 0);
             }
         }
         final long lineRange = getLineRangeForDraw(canvas);
@@ -479,8 +480,10 @@
         drawWithoutText(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
                 cursorOffsetVertical, firstLine, lastLine);
         drawText(canvas, firstLine, lastLine);
-        if (mUseBoundsForWidth) {
-            canvas.restore();
+        if (leftShift != 0) {
+            // Manually translate back to the original position because of b/324498002, using
+            // save/restore disappears the toggle switch drawables.
+            canvas.translate(-leftShift, 0);
         }
     }
 
diff --git a/core/java/android/tracing/perfetto/TracingContext.java b/core/java/android/tracing/perfetto/TracingContext.java
index 0bce26e..c1a61a7 100644
--- a/core/java/android/tracing/perfetto/TracingContext.java
+++ b/core/java/android/tracing/perfetto/TracingContext.java
@@ -105,6 +105,5 @@
         return res;
     }
 
-    // private static native void nativeFlush(long nativeDataSourcePointer);
     private static native void nativeFlush(TracingContext thiz, long ctxPointer);
 }
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index b5b81d1..29cc859 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -73,6 +73,7 @@
 import android.window.ISurfaceSyncGroupCompletedListener;
 import android.window.ITaskFpsCallback;
 import android.window.ITrustedPresentationListener;
+import android.window.IUnhandledDragListener;
 import android.window.InputTransferToken;
 import android.window.ScreenCapture;
 import android.window.TrustedPresentationThresholds;
@@ -1091,4 +1092,10 @@
 
     @EnforcePermission("DETECT_SCREEN_RECORDING")
     void unregisterScreenRecordingCallback(IScreenRecordingCallback callback);
+
+    /**
+     * Sets the listener to be called back when a cross-window drag and drop operation is unhandled
+     * (ie. not handled by any window which can handle the drag).
+     */
+    void setUnhandledDragListener(IUnhandledDragListener listener);
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c22986b..3dfd68a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -43,6 +43,7 @@
 import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS;
 import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP;
 import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION;
+import static com.android.window.flags.Flags.FLAG_DELEGATE_UNHANDLED_DRAGS;
 
 import static java.lang.Math.max;
 
@@ -68,6 +69,7 @@
 import android.annotation.TestApi;
 import android.annotation.UiContext;
 import android.annotation.UiThread;
+import android.app.PendingIntent;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.AutofillOptions;
 import android.content.ClipData;
@@ -5329,6 +5331,34 @@
     public static final int DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION = 1 << 11;
 
     /**
+     * Flag indicating that a drag can cross window boundaries (within the same application).  When
+     * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)} is called
+     * with this flag set, only visible windows belonging to the same application (ie. share the
+     * same UID) with targetSdkVersion >= {@link android.os.Build.VERSION_CODES#N API 24} will be
+     * able to participate in the drag operation and receive the dragged content.
+     *
+     * If both DRAG_FLAG_GLOBAL_SAME_APPLICATION and DRAG_FLAG_GLOBAL are set, then
+     * DRAG_FLAG_GLOBAL_SAME_APPLICATION takes precedence and the drag will only go to visible
+     * windows from the same application.
+     */
+    @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+    public static final int DRAG_FLAG_GLOBAL_SAME_APPLICATION = 1 << 12;
+
+    /**
+     * Flag indicating that an unhandled drag should be delegated to the system to be started if no
+     * visible window wishes to handle the drop. When using this flag, the caller must provide
+     * ClipData with an Item that contains an immutable PendingIntent to an activity to be launched
+     * (not a broadcast, service, etc).  See
+     * {@link ClipData.Item.Builder#setPendingIntent(PendingIntent)}.
+     *
+     * The system can decide to launch the intent or not based on factors like the current screen
+     * size or windowing mode. If the system does not launch the intent, it will be canceled via the
+     * normal drag and drop flow.
+     */
+    @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+    public static final int DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG = 1 << 13;
+
+    /**
      * Vertical scroll factor cached by {@link #getVerticalScrollFactor}.
      */
     private float mVerticalScrollFactor;
@@ -22269,6 +22299,9 @@
      * Retrieve a unique token identifying the window this view is attached to.
      * @return Return the window's token for use in
      * {@link WindowManager.LayoutParams#token WindowManager.LayoutParams.token}.
+     * This token maybe null if this view is not attached to a window.
+     * @see #isAttachedToWindow() for current window attach state
+     * @see OnAttachStateChangeListener to listen to window attach/detach state changes
      */
     public IBinder getWindowToken() {
         return mAttachInfo != null ? mAttachInfo.mWindowToken : null;
@@ -28496,9 +28529,29 @@
             Log.w(VIEW_LOG_TAG, "startDragAndDrop called with an invalid surface.");
             return false;
         }
+        if ((flags & DRAG_FLAG_GLOBAL) != 0 && ((flags & DRAG_FLAG_GLOBAL_SAME_APPLICATION) != 0)) {
+            Log.w(VIEW_LOG_TAG, "startDragAndDrop called with both DRAG_FLAG_GLOBAL "
+                    + "and DRAG_FLAG_GLOBAL_SAME_APPLICATION, the drag will default to "
+                    + "DRAG_FLAG_GLOBAL_SAME_APPLICATION");
+            flags &= ~DRAG_FLAG_GLOBAL;
+        }
 
         if (data != null) {
-            data.prepareToLeaveProcess((flags & View.DRAG_FLAG_GLOBAL) != 0);
+            if (com.android.window.flags.Flags.delegateUnhandledDrags()) {
+                data.prepareToLeaveProcess(
+                        (flags & (DRAG_FLAG_GLOBAL_SAME_APPLICATION | DRAG_FLAG_GLOBAL)) != 0);
+                if ((flags & DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG) != 0) {
+                    if (!data.hasActivityPendingIntents()) {
+                        // Reset the flag if there is no launchable activity intent
+                        flags &= ~DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG;
+                        Log.w(VIEW_LOG_TAG, "startDragAndDrop called with "
+                                + "DRAG_FLAG_START_INTENT_ON_UNHANDLED_DRAG but the clip data "
+                                + "contains non-activity PendingIntents");
+                    }
+                }
+            } else {
+                data.prepareToLeaveProcess((flags & DRAG_FLAG_GLOBAL) != 0);
+            }
         }
 
         Rect bounds = new Rect();
@@ -28524,6 +28577,7 @@
                 if (token != null) {
                     root.setLocalDragState(myLocalState);
                     mAttachInfo.mDragToken = token;
+                    mAttachInfo.mDragData = data;
                     mAttachInfo.mViewRootImpl.setDragStartedViewForAccessibility(this);
                     setAccessibilityDragStarted(true);
                 }
@@ -28601,8 +28655,12 @@
                 if (mAttachInfo.mDragSurface != null) {
                     mAttachInfo.mDragSurface.release();
                 }
+                if (mAttachInfo.mDragData != null) {
+                    mAttachInfo.mDragData.cleanUpPendingIntents();
+                }
                 mAttachInfo.mDragSurface = surface;
                 mAttachInfo.mDragToken = token;
+                mAttachInfo.mDragData = data;
                 // Cache the local state object for delivery with DragEvents
                 root.setLocalDragState(myLocalState);
                 if (a11yEnabled) {
@@ -31516,11 +31574,15 @@
         IBinder mDragToken;
 
         /**
+         * Used to track the data of the current drag operation for cleanup later.
+         */
+        ClipData mDragData;
+
+        /**
          * The drag shadow surface for the current drag operation.
          */
         public Surface mDragSurface;
 
-
         /**
          * The view that currently has a tooltip displayed.
          */
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 07c9795..28a7334 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -8599,6 +8599,10 @@
                         mAttachInfo.mDragSurface.release();
                         mAttachInfo.mDragSurface = null;
                     }
+                    if (mAttachInfo.mDragData != null) {
+                        mAttachInfo.mDragData.cleanUpPendingIntents();
+                        mAttachInfo.mDragData = null;
+                    }
                 }
             }
         }
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index ae00b70..2fb5213 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -520,11 +520,16 @@
     public void registerTrustedPresentationListener(@NonNull IBinder window,
             @NonNull TrustedPresentationThresholds thresholds, @NonNull Executor executor,
             @NonNull Consumer<Boolean> listener) {
+        Objects.requireNonNull(window, "window must not be null");
+        Objects.requireNonNull(thresholds, "thresholds must not be null");
+        Objects.requireNonNull(executor, "executor must not be null");
+        Objects.requireNonNull(listener, "listener must not be null");
         mGlobal.registerTrustedPresentationListener(window, thresholds, executor, listener);
     }
 
     @Override
     public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
+        Objects.requireNonNull(listener, "listener must not be null");
         mGlobal.unregisterTrustedPresentationListener(listener);
     }
 
diff --git a/core/java/android/window/IUnhandledDragCallback.aidl b/core/java/android/window/IUnhandledDragCallback.aidl
new file mode 100644
index 0000000..7806b1f
--- /dev/null
+++ b/core/java/android/window/IUnhandledDragCallback.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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.window;
+
+import android.view.DragEvent;
+
+/**
+ * A callback for notifying the system when the unhandled drop is complete.
+ * {@hide}
+ */
+oneway interface IUnhandledDragCallback {
+    /**
+     * Called when the IUnhandledDropListener has fully handled the drop, and the drag can be
+     * cleaned up.  If handled is `true`, then cleanup of the drag and drag surface will be
+     * immediate, otherwise, the system will treat the drag as a cancel back to the start of the
+     * drag.
+     */
+    void notifyUnhandledDropComplete(boolean handled);
+}
diff --git a/core/java/android/window/IUnhandledDragListener.aidl b/core/java/android/window/IUnhandledDragListener.aidl
new file mode 100644
index 0000000..52e9895
--- /dev/null
+++ b/core/java/android/window/IUnhandledDragListener.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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.window;
+
+import android.view.DragEvent;
+import android.window.IUnhandledDragCallback;
+
+/**
+ * An interface to a handler for global drags that are not consumed (ie. not handled by any window).
+ * {@hide}
+ */
+oneway interface IUnhandledDragListener {
+    /**
+     * Called when the user finishes the drag gesture but no windows have reported handling the
+     * drop.  The DragEvent is populated with the drag surface for the listener to animate.  The
+     * listener *MUST* call the provided callback exactly once when it has finished handling the
+     * drop.  If the listener calls the callback with `true` then it is responsible for removing
+     * and releasing the drag surface passed through the DragEvent.
+     */
+    void onUnhandledDrop(in DragEvent event, in IUnhandledDragCallback callback);
+}
diff --git a/core/java/com/android/internal/os/TimeoutRecord.java b/core/java/com/android/internal/os/TimeoutRecord.java
index e9a8d4b..1f4abc1 100644
--- a/core/java/com/android/internal/os/TimeoutRecord.java
+++ b/core/java/com/android/internal/os/TimeoutRecord.java
@@ -45,6 +45,7 @@
             TimeoutKind.APP_REGISTERED,
             TimeoutKind.SHORT_FGS_TIMEOUT,
             TimeoutKind.JOB_SERVICE,
+            TimeoutKind.FGS_TIMEOUT,
     })
 
     @Retention(RetentionPolicy.SOURCE)
@@ -59,6 +60,7 @@
         int SHORT_FGS_TIMEOUT = 8;
         int JOB_SERVICE = 9;
         int APP_START = 10;
+        int FGS_TIMEOUT = 11;
     }
 
     /** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */
@@ -186,6 +188,12 @@
         return TimeoutRecord.endingNow(TimeoutKind.SHORT_FGS_TIMEOUT, reason);
     }
 
+    /** Record for a "foreground service" timeout. */
+    @NonNull
+    public static TimeoutRecord forFgsTimeout(String reason) {
+        return TimeoutRecord.endingNow(TimeoutKind.FGS_TIMEOUT, reason);
+    }
+
     /** Record for a job related timeout. */
     @NonNull
     public static TimeoutRecord forJobService(String reason) {
diff --git a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
index f62ff38..e11067d 100644
--- a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
+++ b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
@@ -22,6 +22,7 @@
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__BROADCAST_OF_INTENT;
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__CONTENT_PROVIDER_NOT_RESPONDING;
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__EXECUTING_SERVICE;
+import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__FGS_TIMEOUT;
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT;
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT_NO_FOCUSED_WINDOW;
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__JOB_SERVICE;
@@ -548,6 +549,8 @@
                 return ANRLATENCY_REPORTED__ANR_TYPE__SHORT_FGS_TIMEOUT;
             case TimeoutKind.JOB_SERVICE:
                 return ANRLATENCY_REPORTED__ANR_TYPE__JOB_SERVICE;
+            case TimeoutKind.FGS_TIMEOUT:
+                return ANRLATENCY_REPORTED__ANR_TYPE__FGS_TIMEOUT;
             default:
                 return ANRLATENCY_REPORTED__ANR_TYPE__UNKNOWN_ANR_TYPE;
         }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a425bb0..a2ce212 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3829,6 +3829,7 @@
         @hide This is not a third-party API (intended for OEMs and system apps). -->
     <permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES"
                 android:protectionLevel="signature|installer" />
+    <uses-permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES" />
 
     <!-- @SystemApi @hide Allows an application to set a device owner on retail demo devices.-->
     <permission android:name="android.permission.PROVISION_DEMO_DEVICE"
@@ -7033,12 +7034,16 @@
 
     <!-- Allows the holder to read blocked numbers. See
          {@link android.provider.BlockedNumberContract}.
+         @SystemApi
+         @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")
          @hide -->
     <permission android:name="android.permission.READ_BLOCKED_NUMBERS"
                 android:protectionLevel="signature" />
 
     <!-- Allows the holder to write blocked numbers. See
          {@link android.provider.BlockedNumberContract}.
+         @SystemApi
+         @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")
          @hide -->
     <permission android:name="android.permission.WRITE_BLOCKED_NUMBERS"
                 android:protectionLevel="signature" />
diff --git a/core/tests/devicestatetests/Android.bp b/core/tests/devicestatetests/Android.bp
index 7748de5..60848b3 100644
--- a/core/tests/devicestatetests/Android.bp
+++ b/core/tests/devicestatetests/Android.bp
@@ -29,6 +29,8 @@
         "androidx.test.rules",
         "frameworks-base-testutils",
         "mockito-target-minus-junit4",
+        "platform-test-annotations",
+        "testng",
     ],
     libs: ["android.test.runner"],
     platform_apis: true,
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
similarity index 93%
rename from services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
rename to core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
index d54524e..396d403 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.devicestate;
+package android.hardware.devicestate;
 
 import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
 import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
@@ -25,18 +25,17 @@
 
 import android.platform.test.annotations.Presubmit;
 
-import androidx.test.runner.AndroidJUnit4;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 
 /**
- * Unit tests for {@link DeviceState}.
+ * Unit tests for {@link android.hardware.devicestate.DeviceState}.
  * <p/>
  * Run with <code>atest DeviceStateTest</code>.
  */
 @Presubmit
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnit4.class)
 public final class DeviceStateTest {
     @Test
     public void testConstruct() {
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index d8713f7a..fdb5208 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -575,6 +575,9 @@
         <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
         <!-- Permissions required for CTS test - CtsContactKeysProviderPrivilegedApp -->
         <permission name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"/>
+        <!-- Permission required for CTS test BlockedNumberContractTest -->
+        <permission name="android.permission.WRITE_BLOCKED_NUMBERS" />
+        <permission name="android.permission.READ_BLOCKED_NUMBERS" />
         <!-- Permission required for CTS test - PackageManagerTest -->
         <permission name="android.permission.DOMAIN_VERIFICATION_AGENT"/>
     </privapp-permissions>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index ead5ad2..bd9d89c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -64,6 +64,7 @@
 import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
 import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
 import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.draganddrop.UnhandledDragController;
 import com.android.wm.shell.freeform.FreeformComponents;
 import com.android.wm.shell.freeform.FreeformTaskListener;
 import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
@@ -558,6 +559,14 @@
 
     @WMSingleton
     @Provides
+    static UnhandledDragController provideUnhandledDragController(
+            IWindowManager wmService,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new UnhandledDragController(wmService, mainExecutor);
+    }
+
+    @WMSingleton
+    @Provides
     static DragAndDropController provideDragAndDropController(Context context,
             ShellInit shellInit,
             ShellController shellController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 605600f..ba08b09 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -143,7 +143,7 @@
 
         final SurfaceControl leash = change.getLeash();
         final Rect startBounds = change.getStartAbsBounds();
-        startT.setPosition(leash, startBounds.left, startBounds.right)
+        startT.setPosition(leash, startBounds.left, startBounds.top)
                 .setWindowCrop(leash, startBounds.width(), startBounds.height())
                 .show(leash);
         mDesktopModeWindowDecoration.showResizeVeil(startT, startBounds);
@@ -154,7 +154,7 @@
         SurfaceControl.Transaction t = mTransactionSupplier.get();
         animator.addUpdateListener(animation -> {
             final Rect animationValue = (Rect) animator.getAnimatedValue();
-            t.setPosition(leash, animationValue.left, animationValue.right)
+            t.setPosition(leash, animationValue.left, animationValue.top)
                     .setWindowCrop(leash, animationValue.width(), animationValue.height())
                     .show(leash);
             mDesktopModeWindowDecoration.updateResizeVeil(t, animationValue);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/UnhandledDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/UnhandledDragController.kt
new file mode 100644
index 0000000..ccf48d0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/UnhandledDragController.kt
@@ -0,0 +1,100 @@
+/*
+ * 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 com.android.wm.shell.draganddrop
+
+import android.os.RemoteException
+import android.util.Log
+import android.view.DragEvent
+import android.view.IWindowManager
+import android.window.IUnhandledDragCallback
+import android.window.IUnhandledDragListener
+import androidx.annotation.VisibleForTesting
+import com.android.internal.protolog.common.ProtoLog
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.protolog.ShellProtoLogGroup
+import java.util.function.Consumer
+
+/**
+ * Manages the listener and callbacks for unhandled global drags.
+ */
+class UnhandledDragController(
+    val wmService: IWindowManager,
+    mainExecutor: ShellExecutor
+) {
+    private var callback: UnhandledDragAndDropCallback? = null
+
+    private val unhandledDragListener: IUnhandledDragListener =
+        object : IUnhandledDragListener.Stub() {
+            override fun onUnhandledDrop(event: DragEvent, callback: IUnhandledDragCallback) {
+                mainExecutor.execute() {
+                    this@UnhandledDragController.onUnhandledDrop(event, callback)
+                }
+            }
+        }
+
+    /**
+     * Listener called when an unhandled drag is started.
+     */
+    interface UnhandledDragAndDropCallback {
+        /**
+         * Called when a global drag is unhandled (ie. dropped outside of all visible windows, or
+         * dropped on a window that does not want to handle it).
+         *
+         * The implementer _must_ call onFinishedCallback, and if it consumes the drop, then it is
+         * also responsible for releasing up the drag surface provided via the drag event.
+         */
+        fun onUnhandledDrop(dragEvent: DragEvent, onFinishedCallback: Consumer<Boolean>) {}
+    }
+
+    /**
+     * Sets a listener for callbacks when an unhandled drag happens.
+     */
+    fun setListener(listener: UnhandledDragAndDropCallback?) {
+        val updateWm = (callback == null && listener != null)
+                || (callback != null && listener == null)
+        callback = listener
+        if (updateWm) {
+            try {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+                    "%s unhandled drag listener",
+                    if (callback != null) "Registering" else "Unregistering")
+                wmService.setUnhandledDragListener(
+                    if (callback != null) unhandledDragListener else null)
+            } catch (e: RemoteException) {
+                Log.e(TAG, "Failed to set unhandled drag listener")
+            }
+        }
+    }
+
+    @VisibleForTesting
+    fun onUnhandledDrop(dragEvent: DragEvent, wmCallback: IUnhandledDragCallback) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+            "onUnhandledDrop: %s", dragEvent)
+        if (callback == null) {
+            wmCallback.notifyUnhandledDropComplete(false)
+        }
+
+        callback?.onUnhandledDrop(dragEvent) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+                "Notifying onUnhandledDrop complete: %b", it)
+            wmCallback.notifyUnhandledDropComplete(it)
+        }
+    }
+
+    companion object {
+        private val TAG = UnhandledDragController::class.java.simpleName
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/UnhandledDragControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/UnhandledDragControllerTest.kt
new file mode 100644
index 0000000..522f052
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/UnhandledDragControllerTest.kt
@@ -0,0 +1,115 @@
+/*
+ * 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 com.android.wm.shell.draganddrop
+
+import android.os.RemoteException
+import android.view.DragEvent
+import android.view.DragEvent.ACTION_DROP
+import android.view.IWindowManager
+import android.view.SurfaceControl
+import android.window.IUnhandledDragCallback
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.draganddrop.UnhandledDragController.UnhandledDragAndDropCallback
+import java.util.function.Consumer
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for the unhandled drag controller.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UnhandledDragControllerTest : ShellTestCase() {
+    @Mock
+    private lateinit var mIWindowManager: IWindowManager
+
+    @Mock
+    private lateinit var mMainExecutor: ShellExecutor
+
+    private lateinit var mController: UnhandledDragController
+
+    @Before
+    @Throws(RemoteException::class)
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        mController = UnhandledDragController(mIWindowManager, mMainExecutor)
+    }
+
+    @Test
+    fun setListener_registersUnregistersWithWM() {
+        mController.setListener(object : UnhandledDragAndDropCallback {})
+        mController.setListener(object : UnhandledDragAndDropCallback {})
+        mController.setListener(object : UnhandledDragAndDropCallback {})
+        verify(mIWindowManager, Mockito.times(1))
+                .setUnhandledDragListener(ArgumentMatchers.any())
+
+        reset(mIWindowManager)
+        mController.setListener(null)
+        mController.setListener(null)
+        mController.setListener(null)
+        verify(mIWindowManager, Mockito.times(1))
+                .setUnhandledDragListener(ArgumentMatchers.isNull())
+    }
+
+    @Test
+    fun onUnhandledDrop_noListener_expectNotifyUnhandled() {
+        // Simulate an unhandled drop
+        val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, null, null, null,
+            null, null, false)
+        val wmCallback = mock(IUnhandledDragCallback::class.java)
+        mController.onUnhandledDrop(dropEvent, wmCallback)
+
+        verify(wmCallback).notifyUnhandledDropComplete(ArgumentMatchers.eq(false))
+    }
+
+    @Test
+    fun onUnhandledDrop_withListener_expectNotifyHandled() {
+        val lastDragEvent = arrayOfNulls<DragEvent>(1)
+
+        // Set a listener to listen for unhandled drops
+        mController.setListener(object : UnhandledDragAndDropCallback {
+            override fun onUnhandledDrop(dragEvent: DragEvent,
+                onFinishedCallback: Consumer<Boolean>) {
+                lastDragEvent[0] = dragEvent
+                onFinishedCallback.accept(true)
+                dragEvent.dragSurface.release()
+            }
+        })
+
+        // Simulate an unhandled drop
+        val dragSurface = mock(SurfaceControl::class.java)
+        val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, null, null, null,
+            dragSurface, null, false)
+        val wmCallback = mock(IUnhandledDragCallback::class.java)
+        mController.onUnhandledDrop(dropEvent, wmCallback)
+
+        verify(wmCallback).notifyUnhandledDropComplete(ArgumentMatchers.eq(true))
+        verify(dragSurface).release()
+        assertEquals(lastDragEvent.get(0), dropEvent)
+    }
+}
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index eba26d4..f332f81 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -873,6 +873,9 @@
 
         /**
          * Called when the corresponding TV input selected to a track.
+         *
+         * If the track is deselected and no track is currently selected,
+         * trackId is an empty string.
          */
         public void onTrackSelected(@TvTrackInfo.Type int type, @NonNull String trackId) {
         }
@@ -1845,6 +1848,10 @@
             if (DEBUG) {
                 Log.d(TAG, "notifyTrackSelected (type=" + type + "trackId=" + trackId + ")");
             }
+            // TvInputService accepts a Null String, but onTrackSelected expects NonNull.
+            if (trackId == null) {
+                trackId = "";
+            }
             onTrackSelected(type, trackId);
         }
 
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 926e181..c086baa 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -862,6 +862,8 @@
 
     <!-- Permission required for CTS test - CtsTelephonyProviderTestCases -->
     <uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />
+    <uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS" />
+    <uses-permission android:name="android.permission.READ_BLOCKED_NUMBERS" />
 
     <uses-permission android:name="android.permission.LOG_FOREGROUND_RESOURCE_USE" />
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 9f4a906..f397627 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -444,7 +444,8 @@
                     UserHandle.of(userId))) {
                 boolean hasCACerts = !(conn.getService().getUserCaAliases().getList().isEmpty());
                 idWithCert = new Pair<Integer, Boolean>(userId, hasCACerts);
-            } catch (RemoteException | InterruptedException | AssertionError e) {
+            } catch (RemoteException | InterruptedException | AssertionError
+                     | IllegalStateException e) {
                 Log.i(TAG, "failed to get CA certs", e);
                 idWithCert = new Pair<Integer, Boolean>(userId, null);
             } finally {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index cd45b03..b8f6b3f 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -480,9 +480,14 @@
     /**
      * The available ANR timers.
      */
+    // ActivityManagerConstants.SERVICE_TIMEOUT/ActivityManagerConstants.SERVICE_BACKGROUND_TIMEOUT
     private final ProcessAnrTimer mActiveServiceAnrTimer;
+    // see ServiceRecord$ShortFgsInfo#getAnrTime()
     private final ServiceAnrTimer mShortFGSAnrTimer;
+    // ActivityManagerConstants.DEFAULT_SERVICE_START_FOREGROUND_TIMEOUT_MS
     private final ServiceAnrTimer mServiceFGAnrTimer;
+    // see ServiceRecord#getEarliestStopTypeAndTime()
+    private final ServiceAnrTimer mFGSAnrTimer;
 
     // allowlisted packageName.
     ArraySet<String> mAllowListWhileInUsePermissionInFgs = new ArraySet<>();
@@ -744,10 +749,13 @@
                 "SERVICE_TIMEOUT");
         this.mShortFGSAnrTimer = new ServiceAnrTimer(service,
                 ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG,
-                "FGS_TIMEOUT");
+                "SHORT_FGS_TIMEOUT");
         this.mServiceFGAnrTimer = new ServiceAnrTimer(service,
                 ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG,
                 "SERVICE_FOREGROUND_TIMEOUT");
+        this.mFGSAnrTimer = new ServiceAnrTimer(service,
+                ActivityManagerService.SERVICE_FGS_ANR_TIMEOUT_MSG,
+                "FGS_TIMEOUT");
     }
 
     void systemServicesReady() {
@@ -1570,6 +1578,7 @@
             }
 
             maybeStopShortFgsTimeoutLocked(service);
+            maybeStopFgsTimeoutLocked(service);
 
             final int uid = service.appInfo.uid;
             final String packageName = service.name.getPackageName();
@@ -1758,6 +1767,7 @@
             }
 
             maybeStopShortFgsTimeoutLocked(r);
+            maybeStopFgsTimeoutLocked(r);
 
             final int uid = r.appInfo.uid;
             final String packageName = r.name.getPackageName();
@@ -2243,6 +2253,8 @@
 
                 // Whether to extend the SHORT_SERVICE time out.
                 boolean extendShortServiceTimeout = false;
+                // Whether to extend the timeout for a time-limited FGS type.
+                boolean extendFgsTimeout = false;
 
                 // Whether setFgsRestrictionLocked() is called in here. Only used for logging.
                 boolean fgsRestrictionRecalculated = false;
@@ -2287,6 +2299,19 @@
                     final boolean isOldTypeShortFgsAndTimedOut =
                             r.shouldTriggerShortFgsTimeout(nowUptime);
 
+                    // Calling startForeground on a FGS type which has a time limit will only be
+                    // allowed if the app is in a state where it can normally start another FGS.
+                    // The timeout will behave as follows:
+                    // A) <TIME_LIMITED_TYPE> -> another <TIME_LIMITED_TYPE>
+                    //    - If the start succeeds, the timeout is reset.
+                    // B) <TIME_LIMITED_TYPE> -> non-time-limited type
+                    //    - If the start succeeds, the timeout will stop.
+                    // C) non-time-limited type -> <TIME_LIMITED_TYPE>
+                    //    - If the start succeeds, the timeout will start.
+                    final boolean isOldTypeTimeLimited = r.isFgsTimeLimited();
+                    final boolean isNewTypeTimeLimited =
+                            r.canFgsTypeTimeOut(foregroundServiceType);
+
                     // If true, we skip the BFSL check.
                     boolean bypassBfslCheck = false;
 
@@ -2355,6 +2380,35 @@
                                 // "if (r.mAllowStartForeground == REASON_DENIED...)" block below.
                             }
                         }
+                    } else if (r.isForeground && isOldTypeTimeLimited) {
+
+                        // See if the app could start an FGS or not.
+                        r.clearFgsAllowStart();
+                        setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
+                                r.appInfo.uid, r.intent.getIntent(), r, r.userId,
+                                BackgroundStartPrivileges.NONE, false /* isBindService */);
+                        fgsRestrictionRecalculated = true;
+
+                        final boolean fgsStartAllowed = !isBgFgsRestrictionEnabledForService
+                                                            || r.isFgsAllowedStart();
+
+                        if (fgsStartAllowed) {
+                            if (isNewTypeTimeLimited) {
+                                // Note: in the future, we may want to look into metrics to see if
+                                // apps are constantly switching between a time-limited type and a
+                                // non-time-limited type or constantly calling startForeground()
+                                // opportunistically on the same type to gain runtime and apply the
+                                // stricter timeout. For now, always extend the timeout if the app
+                                // is in a state where it's allowed to start a FGS.
+                                extendFgsTimeout = true;
+                            } else {
+                                // FGS type is changing from a time-restricted type to one without
+                                // a time limit so proceed as normal.
+                                // The timeout will stop later, in maybeUpdateFgsTrackingLocked().
+                            }
+                        } else {
+                            // This case will be handled in the BFSL check below.
+                        }
                     } else if (r.mStartForegroundCount == 0) {
                         /*
                         If the service was started with startService(), not
@@ -2596,6 +2650,7 @@
 
                     maybeUpdateShortFgsTrackingLocked(r,
                             extendShortServiceTimeout);
+                    maybeUpdateFgsTrackingLocked(r, extendFgsTimeout);
                 } else {
                     if (DEBUG_FOREGROUND_SERVICE) {
                         Slog.d(TAG, "Suppressing startForeground() for FAS " + r);
@@ -2631,6 +2686,7 @@
                 }
 
                 maybeStopShortFgsTimeoutLocked(r);
+                maybeStopFgsTimeoutLocked(r);
 
                 // Adjust notification handling before setting isForeground to false, because
                 // that state is relevant to the notification policy side.
@@ -3608,6 +3664,116 @@
         }
     }
 
+    void onFgsTimeout(ServiceRecord sr) {
+        synchronized (mAm) {
+            final long nowUptime = SystemClock.uptimeMillis();
+            final int fgsType = sr.getTimedOutFgsType(nowUptime);
+            if (fgsType == -1) {
+                mFGSAnrTimer.discard(sr);
+                return;
+            }
+            Slog.e(TAG_SERVICE, "FGS (" + ServiceInfo.foregroundServiceTypeToLabel(fgsType)
+                    + ") timed out: " + sr);
+            mFGSAnrTimer.accept(sr);
+            traceInstant("FGS timed out: ", sr);
+
+            logFGSStateChangeLocked(sr,
+                    FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT,
+                    nowUptime > sr.mFgsEnterTime ? (int) (nowUptime - sr.mFgsEnterTime) : 0,
+                    FGS_STOP_REASON_UNKNOWN,
+                    FGS_TYPE_POLICY_CHECK_UNKNOWN,
+                    FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA,
+                    false /* fgsRestrictionRecalculated */
+            );
+            try {
+                sr.app.getThread().scheduleTimeoutServiceForType(sr, sr.getLastStartId(), fgsType);
+            } catch (RemoteException e) {
+                Slog.w(TAG_SERVICE, "Exception from scheduleTimeoutServiceForType: " + e);
+            }
+
+            // ANR the service after giving the service some time to clean up.
+            // ServiceRecord.getEarliestStopTypeAndTime() is an absolute time with a reference that
+            // is not "now". Compute the time from "now" when starting the anr timer.
+            final long anrTime = sr.getEarliestStopTypeAndTime().second
+                    + mAm.mConstants.mFgsAnrExtraWaitDuration - SystemClock.uptimeMillis();
+            mFGSAnrTimer.start(sr, anrTime);
+        }
+    }
+
+    private void maybeUpdateFgsTrackingLocked(ServiceRecord sr, boolean extendTimeout) {
+        if (!sr.isFgsTimeLimited()) {
+            // Reset timers if they exist.
+            sr.setIsFgsTimeLimited(false);
+            mFGSAnrTimer.cancel(sr);
+            mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
+            return;
+        }
+
+        if (extendTimeout || !sr.wasFgsPreviouslyTimeLimited()) {
+            traceInstant("FGS start: ", sr);
+            sr.setIsFgsTimeLimited(true);
+
+            // We'll restart the timeout.
+            mFGSAnrTimer.cancel(sr);
+            mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
+
+            final Message msg = mAm.mHandler.obtainMessage(
+                    ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
+            mAm.mHandler.sendMessageAtTime(msg, sr.getEarliestStopTypeAndTime().second);
+        }
+    }
+
+    private void maybeStopFgsTimeoutLocked(ServiceRecord sr) {
+        sr.setIsFgsTimeLimited(false); // reset fgs boolean holding time-limited type state.
+        if (!sr.isFgsTimeLimited()) {
+            return; // if none of the types are time-limited, return.
+        }
+        Slog.d(TAG_SERVICE, "Stop FGS timeout: " + sr);
+        mFGSAnrTimer.cancel(sr);
+        mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
+    }
+
+    boolean hasServiceTimedOutLocked(ComponentName className, IBinder token) {
+        final int userId = UserHandle.getCallingUserId();
+        final long ident = mAm.mInjector.clearCallingIdentity();
+        try {
+            ServiceRecord sr = findServiceLocked(className, token, userId);
+            if (sr == null) {
+                return false;
+            }
+            final long nowUptime = SystemClock.uptimeMillis();
+            return sr.getTimedOutFgsType(nowUptime) != -1;
+        } finally {
+            mAm.mInjector.restoreCallingIdentity(ident);
+        }
+    }
+
+    void onFgsAnrTimeout(ServiceRecord sr) {
+        final long nowUptime = SystemClock.uptimeMillis();
+        final int fgsType = sr.getTimedOutFgsType(nowUptime);
+        if (fgsType == -1 || !sr.wasFgsPreviouslyTimeLimited()) {
+            return; // no timed out FGS type was found
+        }
+        final String reason = "A foreground service of type "
+                + ServiceInfo.foregroundServiceTypeToLabel(fgsType)
+                + " did not stop within a timeout: " + sr.getComponentName();
+
+        final TimeoutRecord tr = TimeoutRecord.forFgsTimeout(reason);
+
+        tr.mLatencyTracker.waitingOnAMSLockStarted();
+        synchronized (mAm) {
+            tr.mLatencyTracker.waitingOnAMSLockEnded();
+
+            Slog.e(TAG_SERVICE, "FGS ANR'ed: " + sr);
+            traceInstant("FGS ANR: ", sr);
+            mAm.appNotResponding(sr.app, tr);
+
+            // TODO: Can we close the ANR dialog here, if it's still shown? Currently, the ANR
+            // dialog really doesn't remember the "cause" (especially if there have been multiple
+            // ANRs), so it's not doable.
+        }
+    }
+
     private void updateAllowlistManagerLocked(ProcessServiceRecord psr) {
         psr.mAllowlistManager = false;
         for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
@@ -3621,6 +3787,7 @@
 
     private void stopServiceAndUpdateAllowlistManagerLocked(ServiceRecord service) {
         maybeStopShortFgsTimeoutLocked(service);
+        maybeStopFgsTimeoutLocked(service);
         final ProcessServiceRecord psr = service.app.mServices;
         psr.stopService(service);
         psr.updateBoundClientUids();
@@ -5893,6 +6060,7 @@
             Slog.w(TAG_SERVICE, "Short FGS brought down without stopping: " + r);
             maybeStopShortFgsTimeoutLocked(r);
         }
+        maybeStopFgsTimeoutLocked(r);
 
         // Report to all of the connections that the service is no longer
         // available.
@@ -6015,6 +6183,7 @@
         final boolean exitingFg = r.isForeground;
         if (exitingFg) {
             maybeStopShortFgsTimeoutLocked(r);
+            maybeStopFgsTimeoutLocked(r);
             decActiveForegroundAppLocked(smap, r);
             synchronized (mAm.mProcessStats.mLock) {
                 ServiceState stracker = r.getTracker();
@@ -8643,7 +8812,7 @@
             event = EventLogTags.AM_FOREGROUND_SERVICE_DENIED;
         } else if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT) {
             event = EventLogTags.AM_FOREGROUND_SERVICE_TIMED_OUT;
-        }else {
+        } else {
             // Unknown event.
             return;
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 72e62c3..d97731c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -1052,6 +1052,27 @@
     public volatile long mShortFgsProcStateExtraWaitDuration =
             DEFAULT_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION;
 
+    /** Timeout for a mediaProcessing FGS, in milliseconds. */
+    private static final String KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION =
+            "media_processing_fgs_timeout_duration";
+
+    /** @see #KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION */
+    static final long DEFAULT_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION = 6 * 60 * 60_000; // 6 hours
+
+    /** @see #KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION */
+    public volatile long mMediaProcessingFgsTimeoutDuration =
+            DEFAULT_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION;
+
+    /** Timeout for a dataSync FGS, in milliseconds. */
+    private static final String KEY_DATA_SYNC_FGS_TIMEOUT_DURATION =
+            "data_sync_fgs_timeout_duration";
+
+    /** @see #KEY_DATA_SYNC_FGS_TIMEOUT_DURATION */
+    static final long DEFAULT_DATA_SYNC_FGS_TIMEOUT_DURATION = 6 * 60 * 60_000; // 6 hours
+
+    /** @see #KEY_DATA_SYNC_FGS_TIMEOUT_DURATION */
+    public volatile long mDataSyncFgsTimeoutDuration = DEFAULT_DATA_SYNC_FGS_TIMEOUT_DURATION;
+
     /**
      * If enabled, when starting an application, the system will wait for a
      * {@link ActivityManagerService#finishAttachApplication} from the app before scheduling
@@ -1082,6 +1103,20 @@
     public volatile long mShortFgsAnrExtraWaitDuration =
             DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION;
 
+    /**
+     * If a service of a timeout-enforced type doesn't finish within this duration after its
+     * timeout, then we'll declare an ANR.
+     * i.e. if the time limit for a type is 1 hour, and this extra duration is 10 seconds, then
+     * the app will be ANR'ed 1 hour and 10 seconds after it started.
+     */
+    private static final String KEY_FGS_ANR_EXTRA_WAIT_DURATION = "fgs_anr_extra_wait_duration";
+
+    /** @see #KEY_FGS_ANR_EXTRA_WAIT_DURATION */
+    static final long DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION = 10_000;
+
+    /** @see #KEY_FGS_ANR_EXTRA_WAIT_DURATION */
+    public volatile long mFgsAnrExtraWaitDuration = DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION;
+
     /** @see #KEY_USE_TIERED_CACHED_ADJ */
     public boolean USE_TIERED_CACHED_ADJ = DEFAULT_USE_TIERED_CACHED_ADJ;
 
@@ -1264,9 +1299,18 @@
                             case KEY_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION:
                                 updateShortFgsProcStateExtraWaitDuration();
                                 break;
+                            case KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION:
+                                updateMediaProcessingFgsTimeoutDuration();
+                                break;
+                            case KEY_DATA_SYNC_FGS_TIMEOUT_DURATION:
+                                updateDataSyncFgsTimeoutDuration();
+                                break;
                             case KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION:
                                 updateShortFgsAnrExtraWaitDuration();
                                 break;
+                            case KEY_FGS_ANR_EXTRA_WAIT_DURATION:
+                                updateFgsAnrExtraWaitDuration();
+                                break;
                             case KEY_PROACTIVE_KILLS_ENABLED:
                                 updateProactiveKillsEnabled();
                                 break;
@@ -2064,6 +2108,27 @@
                 DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION);
     }
 
+    private void updateMediaProcessingFgsTimeoutDuration() {
+        mMediaProcessingFgsTimeoutDuration = DeviceConfig.getLong(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION,
+                DEFAULT_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION);
+    }
+
+    private void updateDataSyncFgsTimeoutDuration() {
+        mDataSyncFgsTimeoutDuration = DeviceConfig.getLong(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_DATA_SYNC_FGS_TIMEOUT_DURATION,
+                DEFAULT_DATA_SYNC_FGS_TIMEOUT_DURATION);
+    }
+
+    private void updateFgsAnrExtraWaitDuration() {
+        mFgsAnrExtraWaitDuration = DeviceConfig.getLong(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_FGS_ANR_EXTRA_WAIT_DURATION,
+                DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION);
+    }
+
     private void updateEnableWaitForFinishAttachApplication() {
         mEnableWaitForFinishAttachApplication = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -2295,6 +2360,13 @@
         pw.print("  "); pw.print(KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION);
         pw.print("="); pw.println(mShortFgsAnrExtraWaitDuration);
 
+        pw.print("  "); pw.print(KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION);
+        pw.print("="); pw.println(mMediaProcessingFgsTimeoutDuration);
+        pw.print("  "); pw.print(KEY_DATA_SYNC_FGS_TIMEOUT_DURATION);
+        pw.print("="); pw.println(mDataSyncFgsTimeoutDuration);
+        pw.print("  "); pw.print(KEY_FGS_ANR_EXTRA_WAIT_DURATION);
+        pw.print("="); pw.println(mFgsAnrExtraWaitDuration);
+
         pw.print("  "); pw.print(KEY_USE_TIERED_CACHED_ADJ);
         pw.print("="); pw.println(USE_TIERED_CACHED_ADJ);
         pw.print("  "); pw.print(KEY_TIERED_CACHED_ADJ_DECAY_TIME);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2750344..bfdcb95 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1702,6 +1702,8 @@
     static final int REMOVE_UID_FROM_OBSERVER_MSG = 81;
     static final int BIND_APPLICATION_TIMEOUT_SOFT_MSG = 82;
     static final int BIND_APPLICATION_TIMEOUT_HARD_MSG = 83;
+    static final int SERVICE_FGS_TIMEOUT_MSG = 84;
+    static final int SERVICE_FGS_ANR_TIMEOUT_MSG = 85;
 
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
 
@@ -2064,6 +2066,12 @@
                 case BIND_APPLICATION_TIMEOUT_HARD_MSG: {
                     handleBindApplicationTimeoutHard((ProcessRecord) msg.obj);
                 } break;
+                case SERVICE_FGS_TIMEOUT_MSG: {
+                    mServices.onFgsTimeout((ServiceRecord) msg.obj);
+                } break;
+                case SERVICE_FGS_ANR_TIMEOUT_MSG: {
+                    mServices.onFgsAnrTimeout((ServiceRecord) msg.obj);
+                } break;
             }
         }
     }
@@ -13794,6 +13802,13 @@
     }
 
     @Override
+    public boolean hasServiceTimeLimitExceeded(ComponentName className, IBinder token) {
+        synchronized (this) {
+            return mServices.hasServiceTimedOutLocked(className, token);
+        }
+    }
+
+    @Override
     public int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
             boolean requireFull, String name, String callerPackage) {
         return mUserController.handleIncomingUser(callingPid, callingUid, userId, allowAll,
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 2771572..3c8d7fc 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -56,6 +56,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.ArrayMap;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
@@ -236,6 +237,8 @@
     boolean mFgsNotificationShown;
     // Whether FGS package has permissions to show notifications.
     boolean mFgsHasNotificationPermission;
+    // Whether the FGS contains a type that is time limited.
+    private boolean mFgsIsTimeLimited;
 
     // allow the service becomes foreground service? Service started from background may not be
     // allowed to become a foreground service.
@@ -915,6 +918,7 @@
             pw.print(prefix); pw.print("isForeground="); pw.print(isForeground);
             pw.print(" foregroundId="); pw.print(foregroundId);
             pw.printf(" types=%08X", foregroundServiceType);
+            pw.print(" fgsHasTimeLimitedType="); pw.print(mFgsIsTimeLimited);
             pw.print(" foregroundNoti="); pw.println(foregroundNoti);
 
             if (isShortFgs() && mShortFgsInfo != null) {
@@ -1789,6 +1793,83 @@
                 + " " + (mShortFgsInfo == null ? "" : mShortFgsInfo.getDescription());
     }
 
+    /**
+     * @return true if one of the types of this FGS has a time limit.
+     */
+    public boolean isFgsTimeLimited() {
+        return startRequested && isForeground && canFgsTypeTimeOut(foregroundServiceType);
+    }
+
+    /**
+     * Called when a FGS with a time-limited type starts ({@code true}) or stops ({@code false}).
+     */
+    public void setIsFgsTimeLimited(boolean fgsIsTimeLimited) {
+        this.mFgsIsTimeLimited = fgsIsTimeLimited;
+    }
+
+    /**
+     * @return whether {@link #mFgsIsTimeLimited} was previously set or not.
+     */
+    public boolean wasFgsPreviouslyTimeLimited() {
+        return mFgsIsTimeLimited;
+    }
+
+    /**
+     * @return the FGS type if the service has reached its time limit, otherwise -1.
+     */
+    public int getTimedOutFgsType(long nowUptime) {
+        if (!isAppAlive() || !isFgsTimeLimited()) {
+            return -1;
+        }
+
+        final Pair<Integer, Long> fgsTypeAndStopTime = getEarliestStopTypeAndTime();
+        if (fgsTypeAndStopTime.first != -1 && fgsTypeAndStopTime.second <= nowUptime) {
+            return fgsTypeAndStopTime.first;
+        }
+        return -1; // no fgs type exceeded time limit
+    }
+
+    /**
+     * @return a {@code Pair<fgs_type, stop_time>}, representing the earliest time at which the FGS
+     * should be stopped (fgs start time + time limit for most restrictive type)
+     */
+    Pair<Integer, Long> getEarliestStopTypeAndTime() {
+        int fgsType = -1;
+        long timeout = 0;
+        if ((foregroundServiceType & ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING)
+                == ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING) {
+            fgsType = ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING;
+            timeout = ams.mConstants.mMediaProcessingFgsTimeoutDuration;
+        }
+        if ((foregroundServiceType & ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
+                == ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) {
+            // update the timeout and type if this type has a more restrictive time limit
+            if (timeout == 0 || ams.mConstants.mDataSyncFgsTimeoutDuration < timeout) {
+                fgsType = ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC;
+                timeout = ams.mConstants.mDataSyncFgsTimeoutDuration;
+            }
+        }
+        // Add the logic for time limits introduced in the future for other fgs types here.
+        return Pair.create(fgsType, timeout == 0 ? 0 : (mFgsEnterTime + timeout));
+    }
+
+    /**
+     * Check if the given types contain a type which is time restricted.
+     */
+    boolean canFgsTypeTimeOut(int fgsType) {
+        // The below conditionals are not simplified on purpose to help with readability.
+        if ((fgsType & ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING)
+                == ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING) {
+            return true;
+        }
+        if ((fgsType & ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
+                == ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) {
+            return true;
+        }
+        // Additional types which have time limits should be added here in the future.
+        return false;
+    }
+
     private boolean isAppAlive() {
         if (app == null) {
             return false;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 96c6be8..55ac4cf 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1350,41 +1350,57 @@
     }
 
     /**
-     * For mDelayUserDataLocking mode, storage once unlocked is kept unlocked.
-     * Total number of unlocked user storage is limited by mMaxRunningUsers.
-     * If there are more unlocked users, evict and lock the least recently stopped user and
-     * lock that user's data. Regardless of the mode, ephemeral user is always locked
-     * immediately.
+     * Returns which user, if any, should be locked when the given user is stopped.
+     *
+     * For typical (non-mDelayUserDataLocking) devices and users, this will be the provided user.
+     *
+     * However, for some devices or users (based on {@link #canDelayDataLockingForUser(int)}),
+     * storage once unlocked is kept unlocked, even after the user is stopped, so the user to be
+     * locked (if any) may differ.
+     *
+     * For mDelayUserDataLocking devices, the total number of unlocked user storage is limited
+     * (currently by mMaxRunningUsers). If there are more unlocked users, evict and lock the least
+     * recently stopped user and lock that user's data.
+     *
+     * Regardless of the mode, ephemeral user is always locked immediately.
      *
      * @return user id to lock. UserHandler.USER_NULL will be returned if no user should be locked.
      */
     @GuardedBy("mLock")
     private int updateUserToLockLU(@UserIdInt int userId, boolean allowDelayedLocking) {
-        int userIdToLock = userId;
-        // TODO: Decouple the delayed locking flows from mMaxRunningUsers or rename the property to
-        // state maximum running unlocked users specifically
-        if (canDelayDataLockingForUser(userIdToLock) && allowDelayedLocking
-                && !getUserInfo(userId).isEphemeral()
-                && !hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) {
+        if (!canDelayDataLockingForUser(userId)
+                || !allowDelayedLocking
+                || getUserInfo(userId).isEphemeral()
+                || hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) {
+            return userId;
+        }
+
+        // Once we reach here, we are in a delayed locking scenario.
+        // Now, no user will be locked, unless the device's policy dictates we should based on the
+        // maximum of such users allowed for the device.
+        if (mDelayUserDataLocking) {
             // arg should be object, not index
             mLastActiveUsersForDelayedLocking.remove((Integer) userId);
             mLastActiveUsersForDelayedLocking.add(0, userId);
             int totalUnlockedUsers = mStartedUsers.size()
                     + mLastActiveUsersForDelayedLocking.size();
+            // TODO: Decouple the delayed locking flows from mMaxRunningUsers. These users aren't
+            //  running so this calculation shouldn't be based on this parameter. Also note that
+            //  that if these devices ever support background running users (such as profiles), the
+            //  implementation is incorrect since starting such users can cause the max to be
+            //  exceeded.
             if (totalUnlockedUsers > mMaxRunningUsers) { // should lock a user
-                userIdToLock = mLastActiveUsersForDelayedLocking.get(
+                final int userIdToLock = mLastActiveUsersForDelayedLocking.get(
                         mLastActiveUsersForDelayedLocking.size() - 1);
                 mLastActiveUsersForDelayedLocking
                         .remove(mLastActiveUsersForDelayedLocking.size() - 1);
-                Slogf.i(TAG, "finishUserStopped, stopping user:" + userId
-                        + " lock user:" + userIdToLock);
-            } else {
-                Slogf.i(TAG, "finishUserStopped, user:" + userId + ", skip locking");
-                // do not lock
-                userIdToLock = UserHandle.USER_NULL;
+                Slogf.i(TAG, "finishUserStopped: should stop user " + userId
+                        + " but should lock user " + userIdToLock);
+                return userIdToLock;
             }
         }
-        return userIdToLock;
+        Slogf.i(TAG, "finishUserStopped: should stop user " + userId + " but without any locking");
+        return UserHandle.USER_NULL;
     }
 
     /**
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index cb15abc..cd064ae 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -19,11 +19,11 @@
 import static android.Manifest.permission.CONTROL_DEVICE_STATE;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.hardware.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
 import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
 import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
 import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
 
-import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
 import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE;
 import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE;
 import static com.android.server.devicestate.OverrideRequestController.FLAG_POWER_SAVE_ENABLED;
@@ -40,6 +40,7 @@
 import android.app.ActivityManagerInternal;
 import android.app.IProcessObserver;
 import android.content.Context;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateInfo;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.DeviceStateManagerInternal;
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
index 8c6068d..02c9bb3 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.DeviceStateRequest;
 import android.os.Binder;
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index d5945f4..65b393a 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -21,6 +21,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.IntRange;
+import android.hardware.devicestate.DeviceState;
 import android.util.Dumpable;
 
 import java.lang.annotation.Retention;
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java
index 20485c1..d92629f 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequest.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequest.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateRequest;
 import android.os.IBinder;
 
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
index f5f2fa8..6c3fd83d 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateRequest;
 import android.os.IBinder;
 import android.util.Slog;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index b8a63cd..96c0c8a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3560,8 +3560,7 @@
             ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
             mCurStatsToken = null;
 
-            if (!Flags.useHandwritingListenerForTooltype()
-                    && lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
+            if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
                 curMethod.updateEditorToolType(lastClickToolType);
             }
             mVisibilityApplier.performShowIme(windowToken, statsToken,
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index 25a39cc..86d05d9 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -257,7 +257,7 @@
     private boolean matchesActorSignature(@NonNull AndroidPackage targetPackage,
             @NonNull AndroidPackage overlayPackage, int userId) {
         String targetOverlayableName = overlayPackage.getOverlayTargetOverlayableName();
-        if (targetOverlayableName != null) {
+        if (targetOverlayableName != null && !mPackageManager.getNamedActors().isEmpty()) {
             try {
                 OverlayableInfo overlayableInfo = mPackageManager.getOverlayableForTarget(
                         targetPackage.getPackageName(), targetOverlayableName, userId);
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index afcf5a0..76952b3 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -29,6 +29,7 @@
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceState;
 import android.os.Environment;
 import android.os.PowerManager;
 import android.util.ArrayMap;
@@ -40,7 +41,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
-import com.android.server.devicestate.DeviceState;
 import com.android.server.devicestate.DeviceStateProvider;
 import com.android.server.input.InputManagerInternal;
 import com.android.server.policy.devicestate.config.Conditions;
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 6a3cf43..a3e2869 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wm;
 
+import static android.view.View.DRAG_FLAG_GLOBAL;
+import static android.view.View.DRAG_FLAG_GLOBAL_SAME_APPLICATION;
+
 import static com.android.input.flags.Flags.enablePointerChoreographer;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
@@ -30,15 +33,20 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
 import android.util.Slog;
 import android.view.Display;
+import android.view.DragEvent;
 import android.view.IWindow;
 import android.view.InputDevice;
 import android.view.PointerIcon;
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
+import android.window.IUnhandledDragCallback;
+import android.window.IUnhandledDragListener;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wm.WindowManagerInternal.IDragDropCallback;
 
 import java.util.Objects;
@@ -59,6 +67,7 @@
     static final int MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT = 1;
     static final int MSG_ANIMATION_END = 2;
     static final int MSG_REMOVE_DRAG_SURFACE_TIMEOUT = 3;
+    static final int MSG_UNHANDLED_DROP_LISTENER_TIMEOUT = 4;
 
     /**
      * Drag state per operation.
@@ -72,6 +81,21 @@
     private WindowManagerService mService;
     private final Handler mHandler;
 
+    // The unhandled drag listener for handling cross-window drags that end with no target window
+    private IUnhandledDragListener mUnhandledDragListener;
+    private final IBinder.DeathRecipient mUnhandledDragListenerDeathRecipient =
+            new IBinder.DeathRecipient() {
+        @Override
+        public void binderDied() {
+            synchronized (mService.mGlobalLock) {
+                if (hasPendingUnhandledDropCallback()) {
+                    onUnhandledDropCallback(false /* consumedByListeners */);
+                }
+                setUnhandledDragListener(null);
+            }
+        }
+    };
+
     /**
      * Callback which is used to sync drag state with the vendor-specific code.
      */
@@ -83,10 +107,16 @@
         mHandler = new DragHandler(service, looper);
     }
 
+    @VisibleForTesting
+    Handler getHandler() {
+        return mHandler;
+    }
+
     boolean dragDropActiveLocked() {
         return mDragState != null && !mDragState.isClosing();
     }
 
+    @VisibleForTesting
     boolean dragSurfaceRelinquishedToDropTarget() {
         return mDragState != null && mDragState.mRelinquishDragSurfaceToDropTarget;
     }
@@ -96,6 +126,32 @@
         mCallback.set(callback);
     }
 
+    /**
+     * Sets the listener for unhandled cross-window drags.
+     */
+    public void setUnhandledDragListener(IUnhandledDragListener listener) {
+        if (mUnhandledDragListener != null && mUnhandledDragListener.asBinder() != null) {
+            mUnhandledDragListener.asBinder().unlinkToDeath(
+                    mUnhandledDragListenerDeathRecipient, 0);
+        }
+        mUnhandledDragListener = listener;
+        if (listener != null && listener.asBinder() != null) {
+            try {
+                mUnhandledDragListener.asBinder().linkToDeath(
+                        mUnhandledDragListenerDeathRecipient, 0);
+            } catch (RemoteException e) {
+                mUnhandledDragListener = null;
+            }
+        }
+    }
+
+    /**
+     * Returns whether there is an unhandled drag listener set.
+     */
+    boolean hasUnhandledDragListener() {
+        return mUnhandledDragListener != null;
+    }
+
     void sendDragStartedIfNeededLocked(WindowState window) {
         mDragState.sendDragStartedIfNeededLocked(window);
     }
@@ -247,6 +303,10 @@
         }
     }
 
+    /**
+     * This is called from the drop target window that received ACTION_DROP
+     * (see DragState#reportDropWindowLock()).
+     */
     void reportDropResult(IWindow window, boolean consumed) {
         IBinder token = window.asBinder();
         if (DEBUG_DRAG) {
@@ -273,22 +333,89 @@
                 // so be sure to halt the timeout even if the later WindowState
                 // lookup fails.
                 mHandler.removeMessages(MSG_DRAG_END_TIMEOUT, window.asBinder());
+
                 WindowState callingWin = mService.windowForClientLocked(null, window, false);
                 if (callingWin == null) {
                     Slog.w(TAG_WM, "Bad result-reporting window " + window);
                     return;  // !!! TODO: throw here?
                 }
 
-                mDragState.mDragResult = consumed;
-                mDragState.mRelinquishDragSurfaceToDropTarget = consumed
-                        && mDragState.targetInterceptsGlobalDrag(callingWin);
-                mDragState.endDragLocked();
+                // If the drop was not consumed by the target window, then check if it should be
+                // consumed by the system unhandled drag listener
+                if (!consumed && notifyUnhandledDrop(mDragState.mUnhandledDropEvent,
+                        "window-drop")) {
+                    // If the unhandled drag listener is notified, then defer ending the drag until
+                    // the listener calls back
+                    return;
+                }
+
+                final boolean relinquishDragSurfaceToDropTarget =
+                        consumed && mDragState.targetInterceptsGlobalDrag(callingWin);
+                mDragState.endDragLocked(consumed, relinquishDragSurfaceToDropTarget);
             }
         } finally {
             mCallback.get().postReportDropResult();
         }
     }
 
+    /**
+     * Notifies the unhandled drag listener if needed.
+     * @return whether the listener was notified and subsequent drag completion should be deferred
+     *         until the listener calls back
+     */
+    boolean notifyUnhandledDrop(DragEvent dropEvent, String reason) {
+        final boolean isLocalDrag =
+                (mDragState.mFlags & (DRAG_FLAG_GLOBAL_SAME_APPLICATION | DRAG_FLAG_GLOBAL)) == 0;
+        if (!com.android.window.flags.Flags.delegateUnhandledDrags()
+                || mUnhandledDragListener == null
+                || isLocalDrag) {
+            // Skip if the flag is disabled, there is no unhandled-drag listener, or if this is a
+            // purely local drag
+            if (DEBUG_DRAG) Slog.d(TAG_WM, "Skipping unhandled listener "
+                    + "(listener=" + mUnhandledDragListener + ", flags=" + mDragState.mFlags + ")");
+            return false;
+        }
+        if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DROP to unhandled listener (" + reason + ")");
+        try {
+            // Schedule timeout for the unhandled drag listener to call back
+            sendTimeoutMessage(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT, null, DRAG_TIMEOUT_MS);
+            mUnhandledDragListener.onUnhandledDrop(dropEvent, new IUnhandledDragCallback.Stub() {
+                @Override
+                public void notifyUnhandledDropComplete(boolean consumedByListener) {
+                    if (DEBUG_DRAG) Slog.d(TAG_WM, "Unhandled listener finished handling DROP");
+                    synchronized (mService.mGlobalLock) {
+                        onUnhandledDropCallback(consumedByListener);
+                    }
+                }
+            });
+            return true;
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Failed to call unhandled drag listener", e);
+            return false;
+        }
+    }
+
+    /**
+     * Called when the unhandled drag listener has completed handling the drop
+     * (if it was notififed).
+     */
+    @VisibleForTesting
+    void onUnhandledDropCallback(boolean consumedByListener) {
+        mHandler.removeMessages(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT, null);
+        // If handled, then the listeners assume responsibility of cleaning up the drag surface
+        mDragState.mDragResult = consumedByListener;
+        mDragState.mRelinquishDragSurfaceToDropTarget = consumedByListener;
+        mDragState.closeLocked();
+    }
+
+    /**
+     * Returns whether we are currently waiting for the unhandled drag listener to callback after
+     * it was notified of an unhandled drag.
+     */
+    boolean hasPendingUnhandledDropCallback() {
+        return mHandler.hasMessages(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT);
+    }
+
     void cancelDragAndDrop(IBinder dragToken, boolean skipAnimation) {
         if (DEBUG_DRAG) {
             Slog.d(TAG_WM, "cancelDragAndDrop");
@@ -436,8 +563,8 @@
                     synchronized (mService.mGlobalLock) {
                         // !!! TODO: ANR the drag-receiving app
                         if (mDragState != null) {
-                            mDragState.mDragResult = false;
-                            mDragState.endDragLocked();
+                            mDragState.endDragLocked(false /* consumed */,
+                                    false /* relinquishDragSurfaceToDropTarget */);
                         }
                     }
                     break;
@@ -473,6 +600,13 @@
                     }
                     break;
                 }
+
+                case MSG_UNHANDLED_DROP_LISTENER_TIMEOUT: {
+                    synchronized (mService.mGlobalLock) {
+                        onUnhandledDropCallback(false /* consumedByListener */);
+                    }
+                    break;
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index d302f06..76038b9 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -147,6 +147,11 @@
      */
     private boolean mIsClosing;
 
+    // Stores the last drop event which was reported to a valid drop target window, or null
+    // otherwise.  This drop event will contain private info and should only be consumed by the
+    // unhandled drag listener.
+    DragEvent mUnhandledDropEvent;
+
     DragState(WindowManagerService service, DragDropController controller, IBinder token,
             SurfaceControl surface, int flags, IBinder localWin) {
         mService = service;
@@ -287,14 +292,54 @@
         mData = null;
         mThumbOffsetX = mThumbOffsetY = 0;
         mNotifiedWindows = null;
+        if (mUnhandledDropEvent != null) {
+            mUnhandledDropEvent.recycle();
+            mUnhandledDropEvent = null;
+        }
 
         // Notifies the controller that the drag state is closed.
         mDragDropController.onDragStateClosedLocked(this);
     }
 
     /**
+     * Creates the drop event for this drag gesture.  If `touchedWin` is null, then the drop event
+     * will be created for dispatching to the unhandled drag and the drag surface will be provided
+     * as a part of the dispatched event.
+     */
+    private DragEvent createDropEvent(float x, float y, @Nullable WindowState touchedWin,
+            boolean includeDragSurface) {
+        if (touchedWin != null) {
+            final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
+            final DragAndDropPermissionsHandler dragAndDropPermissions;
+            if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0
+                    && mData != null) {
+                dragAndDropPermissions = new DragAndDropPermissionsHandler(mService.mGlobalLock,
+                        mData,
+                        mUid,
+                        touchedWin.getOwningPackage(),
+                        mFlags & DRAG_FLAGS_URI_PERMISSIONS,
+                        mSourceUserId,
+                        targetUserId);
+            } else {
+                dragAndDropPermissions = null;
+            }
+            if (mSourceUserId != targetUserId) {
+                if (mData != null) {
+                    mData.fixUris(mSourceUserId);
+                }
+            }
+            return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mData,
+                    targetInterceptsGlobalDrag(touchedWin), dragAndDropPermissions);
+        } else {
+            return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mData,
+                    includeDragSurface /* includeDragSurface */, null /* dragAndDropPermissions */);
+        }
+    }
+
+    /**
      * Notify the drop target and tells it about the data. If the drop event is not sent to the
-     * target, invokes {@code endDragLocked} immediately.
+     * target, invokes {@code endDragLocked} after the unhandled drag listener gets a chance to
+     * handle the drop.
      */
     boolean reportDropWindowLock(IBinder token, float x, float y) {
         if (mAnimator != null) {
@@ -302,41 +347,27 @@
         }
 
         final WindowState touchedWin = mService.mInputToWindowMap.get(token);
+        final DragEvent unhandledDropEvent = createDropEvent(x, y, null /* touchedWin */,
+                true /* includePrivateInfo */);
         if (!isWindowNotified(touchedWin)) {
-            // "drop" outside a valid window -- no recipient to apply a
-            // timeout to, and we can send the drag-ended message immediately.
-            mDragResult = false;
-            endDragLocked();
+            // Delegate to the unhandled drag listener as a first pass
+            if (mDragDropController.notifyUnhandledDrop(unhandledDropEvent, "unhandled-drop")) {
+                // The unhandled drag listener will call back to notify whether it has consumed
+                // the drag, so return here
+                return true;
+            }
+
+            // "drop" outside a valid window -- no recipient to apply a timeout to, and we can send
+            // the drag-ended message immediately.
+            endDragLocked(false /* consumed */, false /* relinquishDragSurfaceToDropTarget */);
             if (DEBUG_DRAG) Slog.d(TAG_WM, "Drop outside a valid window " + touchedWin);
             return false;
         }
 
         if (DEBUG_DRAG) Slog.d(TAG_WM, "sending DROP to " + touchedWin);
 
-        final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
-
-        final DragAndDropPermissionsHandler dragAndDropPermissions;
-        if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0
-                && mData != null) {
-            dragAndDropPermissions = new DragAndDropPermissionsHandler(mService.mGlobalLock,
-                    mData,
-                    mUid,
-                    touchedWin.getOwningPackage(),
-                    mFlags & DRAG_FLAGS_URI_PERMISSIONS,
-                    mSourceUserId,
-                    targetUserId);
-        } else {
-            dragAndDropPermissions = null;
-        }
-        if (mSourceUserId != targetUserId) {
-            if (mData != null) {
-                mData.fixUris(mSourceUserId);
-            }
-        }
         final IBinder clientToken = touchedWin.mClient.asBinder();
-        final DragEvent event = obtainDragEvent(DragEvent.ACTION_DROP, x, y,
-                mData, targetInterceptsGlobalDrag(touchedWin),
-                dragAndDropPermissions);
+        final DragEvent event = createDropEvent(x, y, touchedWin, false /* includePrivateInfo */);
         try {
             touchedWin.mClient.dispatchDragEvent(event);
 
@@ -345,7 +376,7 @@
                     DragDropController.DRAG_TIMEOUT_MS);
         } catch (RemoteException e) {
             Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin);
-            endDragLocked();
+            endDragLocked(false /* consumed */, false /* relinquishDragSurfaceToDropTarget */);
             return false;
         } finally {
             if (MY_PID != touchedWin.mSession.mPid) {
@@ -353,6 +384,7 @@
             }
         }
         mToken = clientToken;
+        mUnhandledDropEvent = unhandledDropEvent;
         return true;
     }
 
@@ -478,6 +510,9 @@
             boolean containsAppExtras) {
         final boolean interceptsGlobalDrag = targetInterceptsGlobalDrag(newWin);
         if (mDragInProgress && isValidDropTarget(newWin, containsAppExtras, interceptsGlobalDrag)) {
+            if (DEBUG_DRAG) {
+                Slog.d(TAG_WM, "Sending DRAG_STARTED to new window " + newWin);
+            }
             // Only allow the extras to be dispatched to a global-intercepting drag target
             ClipData data = interceptsGlobalDrag ? mData.copyForTransferWithActivityInfo() : null;
             DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED,
@@ -523,14 +558,25 @@
             return false;
         }
         if (!targetWin.isPotentialDragTarget(interceptsGlobalDrag)) {
+            // Window should not be a target
             return false;
         }
-        if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0 || !targetWindowSupportsGlobalDrag(targetWin)) {
+        final boolean isGlobalSameAppDrag = (mFlags & View.DRAG_FLAG_GLOBAL_SAME_APPLICATION) != 0;
+        final boolean isGlobalDrag = (mFlags & View.DRAG_FLAG_GLOBAL) != 0;
+        final boolean isAnyGlobalDrag = isGlobalDrag || isGlobalSameAppDrag;
+        if (!isAnyGlobalDrag || !targetWindowSupportsGlobalDrag(targetWin)) {
             // Drag is limited to the current window.
             if (!isLocalWindow) {
                 return false;
             }
         }
+        if (isGlobalSameAppDrag) {
+            // Drag is limited to app windows from the same uid or windows that can intercept global
+            // drag
+            if (!interceptsGlobalDrag && mUid != targetWin.getUid()) {
+                return false;
+            }
+        }
 
         return interceptsGlobalDrag
                 || mCrossProfileCopyAllowed
@@ -547,7 +593,10 @@
     /**
      * @return whether the given window {@param targetWin} can intercept global drags.
      */
-    public boolean targetInterceptsGlobalDrag(WindowState targetWin) {
+    public boolean targetInterceptsGlobalDrag(@Nullable WindowState targetWin) {
+        if (targetWin == null) {
+            return false;
+        }
         return (targetWin.mAttrs.privateFlags & PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP) != 0;
     }
 
@@ -561,9 +610,6 @@
             if (isWindowNotified(newWin)) {
                 return;
             }
-            if (DEBUG_DRAG) {
-                Slog.d(TAG_WM, "need to send DRAG_STARTED to new window " + newWin);
-            }
             sendDragStartedLocked(newWin, mCurrentX, mCurrentY,
                     containsApplicationExtras(mDataDescription));
         }
@@ -578,7 +624,13 @@
         return false;
     }
 
-    void endDragLocked() {
+    /**
+     * Ends the current drag, animating the drag surface back to the source if the drop was not
+     * consumed by the receiving window.
+     */
+    void endDragLocked(boolean dropConsumed, boolean relinquishDragSurfaceToDropTarget) {
+        mDragResult = dropConsumed;
+        mRelinquishDragSurfaceToDropTarget = relinquishDragSurfaceToDropTarget;
         if (mAnimator != null) {
             return;
         }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4ea76e1..de8d9f9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -310,6 +310,7 @@
 import android.window.ISurfaceSyncGroupCompletedListener;
 import android.window.ITaskFpsCallback;
 import android.window.ITrustedPresentationListener;
+import android.window.IUnhandledDragListener;
 import android.window.InputTransferToken;
 import android.window.ScreenCapture;
 import android.window.SystemPerformanceHinter;
@@ -10026,4 +10027,16 @@
     void onProcessActivityVisibilityChanged(int uid, boolean visible) {
         mScreenRecordingCallbackController.onProcessActivityVisibilityChanged(uid, visible);
     }
+
+    /**
+     * Sets the listener to be called back when a cross-window drag and drop operation is unhandled
+     * (ie. not handled by any window which can handle the drag).
+     */
+    @Override
+    public void setUnhandledDragListener(IUnhandledDragListener listener) throws RemoteException {
+        mAtmService.enforceTaskPermission("setUnhandledDragListener");
+        synchronized (mGlobalLock) {
+            mDragDropController.setUnhandledDragListener(listener);
+        }
+    }
 }
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
index 8b22718..bc264a4 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
@@ -16,11 +16,12 @@
 
 package com.android.server.policy;
 
-import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
-import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
-import static com.android.server.devicestate.DeviceState.FLAG_EMULATED_ONLY;
-import static com.android.server.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE;
-import static com.android.server.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL;
+import static android.hardware.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
+import static android.hardware.devicestate.DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
+import static android.hardware.devicestate.DeviceState.FLAG_EMULATED_ONLY;
+import static android.hardware.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE;
+import static android.hardware.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL;
+
 import static com.android.server.policy.BookStyleStateTransitions.DEFAULT_STATE_TRANSITIONS;
 import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createConfig;
 import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createTentModeClosedState;
@@ -36,7 +37,6 @@
 import com.android.server.devicestate.DeviceStateProvider;
 import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration;
 import com.android.server.policy.feature.flags.FeatureFlags;
-import com.android.server.policy.feature.flags.FeatureFlagsImpl;
 
 import java.io.PrintWriter;
 import java.util.function.Predicate;
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
index 021a667..bf2619b 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
@@ -34,6 +34,7 @@
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.display.DisplayManager;
 import android.os.Handler;
 import android.os.Looper;
@@ -48,7 +49,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
-import com.android.server.devicestate.DeviceState;
 import com.android.server.devicestate.DeviceStateProvider;
 import com.android.server.policy.feature.flags.FeatureFlags;
 import com.android.server.policy.feature.flags.FeatureFlagsImpl;
diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java
index 04cebab..930f4a6 100644
--- a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java
+++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java
@@ -51,6 +51,7 @@
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.display.DisplayManager;
 import android.hardware.input.InputSensorInfo;
 import android.os.Handler;
@@ -58,7 +59,6 @@
 import android.testing.AndroidTestingRunner;
 import android.view.Display;
 
-import com.android.server.devicestate.DeviceState;
 import com.android.server.devicestate.DeviceStateProvider.Listener;
 import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration;
 import com.android.server.policy.feature.flags.FakeFeatureFlagsImpl;
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 26934d8..4307ec5 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -682,19 +682,19 @@
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
 
         setUpAndStartUserInBackground(TEST_USER_ID);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* delayedLocking= */ true,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback= */ null, /* expectLocking= */ true);
 
         setUpAndStartUserInBackground(TEST_USER_ID1);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
 
         setUpAndStartUserInBackground(TEST_USER_ID2);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ false,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ false,
                 /* keyEvictedCallback= */ null, /* expectLocking= */ true);
 
         setUpAndStartUserInBackground(TEST_USER_ID3);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* delayedLocking= */ false,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* allowDelayedLocking= */ false,
                 /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
     }
 
@@ -739,21 +739,21 @@
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
 
-        // delayedLocking set and no KeyEvictedCallback, so it should not lock.
+        // allowDelayedLocking set and no KeyEvictedCallback, so it should not lock.
         setUpAndStartUserInBackground(TEST_USER_ID);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* delayedLocking= */ true,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback= */ null, /* expectLocking= */ false);
 
         setUpAndStartUserInBackground(TEST_USER_ID1);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
 
         setUpAndStartUserInBackground(TEST_USER_ID2);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ false,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ false,
                 /* keyEvictedCallback= */ null, /* expectLocking= */ true);
 
         setUpAndStartUserInBackground(TEST_USER_ID3);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* delayedLocking= */ false,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* allowDelayedLocking= */ false,
                 /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
     }
 
@@ -843,7 +843,7 @@
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
                 android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback */ null, /* expectLocking= */ false);
     }
 
@@ -855,19 +855,20 @@
         mSetFlagsRule.disableFlags(
                 android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback */ null, /* expectLocking= */ true);
 
         mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         mSetFlagsRule.enableFlags(
                 android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
         setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_PRIVATE);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ true,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback */ null, /* expectLocking= */ true);
     }
 
+    /** Delayed-locking users (as opposed to devices) have no limits on how many can be unlocked. */
     @Test
-    public void testStopPrivateProfileWithDelayedLocking_maxRunningUsersBreached()
+    public void testStopPrivateProfileWithDelayedLocking_imperviousToNumberOfRunningUsers()
             throws Exception {
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
                 /* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false);
@@ -875,10 +876,14 @@
                 android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
         setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_MANAGED);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
-                /* keyEvictedCallback */ null, /* expectLocking= */ true);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
+                /* keyEvictedCallback */ null, /* expectLocking= */ false);
     }
 
+    /**
+        * Tests that when a device/user (managed profile) does not permit delayed locking, then
+        * even if allowDelayedLocking is true, the user will still be locked.
+    */
     @Test
     public void testStopManagedProfileWithDelayedLocking() throws Exception {
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
@@ -886,7 +891,7 @@
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
                 android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback */ null, /* expectLocking= */ true);
     }
 
@@ -1087,29 +1092,29 @@
         mUserStates.put(userId, mUserController.getStartedUserState(userId));
     }
 
-    private void assertUserLockedOrUnlockedAfterStopping(int userId, boolean delayedLocking,
+    private void assertUserLockedOrUnlockedAfterStopping(int userId, boolean allowDelayedLocking,
             KeyEvictedCallback keyEvictedCallback, boolean expectLocking) throws Exception {
-        int r = mUserController.stopUser(userId, /* force= */ true, /* delayedLocking= */
-                delayedLocking, null, keyEvictedCallback);
+        int r = mUserController.stopUser(userId, /* force= */ true, /* allowDelayedLocking= */
+                allowDelayedLocking, null, keyEvictedCallback);
         assertThat(r).isEqualTo(ActivityManager.USER_OP_SUCCESS);
-        assertUserLockedOrUnlockedState(userId, delayedLocking, expectLocking);
+        assertUserLockedOrUnlockedState(userId, allowDelayedLocking, expectLocking);
     }
 
     private void assertProfileLockedOrUnlockedAfterStopping(int userId, boolean expectLocking)
             throws Exception {
         boolean profileStopped = mUserController.stopProfile(userId);
         assertThat(profileStopped).isTrue();
-        assertUserLockedOrUnlockedState(userId, /* delayedLocking= */ false, expectLocking);
+        assertUserLockedOrUnlockedState(userId, /* allowDelayedLocking= */ false, expectLocking);
     }
 
-    private void assertUserLockedOrUnlockedState(int userId, boolean delayedLocking,
+    private void assertUserLockedOrUnlockedState(int userId, boolean allowDelayedLocking,
             boolean expectLocking) throws InterruptedException, RemoteException {
         // fake all interim steps
         UserState ussUser = mUserStates.get(userId);
         ussUser.setState(UserState.STATE_SHUTDOWN);
         // Passing delayedLocking invalidates incorrect internal data passing but currently there is
         // no easy way to get that information passed through lambda.
-        mUserController.finishUserStopped(ussUser, delayedLocking);
+        mUserController.finishUserStopped(ussUser, allowDelayedLocking);
         waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
         verify(mInjector.mStorageManagerMock, times(expectLocking ? 1 : 0))
                 .lockCeStorage(userId);
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index fa39364..b705077 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -31,6 +31,7 @@
 import static org.testng.Assert.assertThrows;
 
 import android.annotation.NonNull;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateInfo;
 import android.hardware.devicestate.DeviceStateRequest;
 import android.hardware.devicestate.IDeviceStateManagerCallback;
@@ -72,7 +73,8 @@
             new DeviceState(0, "DEFAULT", 0 /* flags */);
     private static final DeviceState OTHER_DEVICE_STATE =
             new DeviceState(1, "OTHER", 0 /* flags */);
-    private static final DeviceState DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
+    private static final DeviceState
+            DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
             new DeviceState(2, "DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP",
                     DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP /* flags */);
     // A device state that is not reported as being supported for the default test provider.
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
index b3d25f2..cfdb586 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
@@ -25,6 +25,7 @@
 import static junit.framework.Assert.assertNull;
 
 import android.annotation.Nullable;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateRequest;
 import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
@@ -48,8 +49,10 @@
 @RunWith(AndroidJUnit4.class)
 public final class OverrideRequestControllerTest {
 
-    private static final DeviceState TEST_DEVICE_STATE_ZERO = new DeviceState(0, "TEST_STATE", 0);
-    private static final DeviceState TEST_DEVICE_STATE_ONE = new DeviceState(1, "TEST_STATE", 0);
+    private static final DeviceState
+            TEST_DEVICE_STATE_ZERO = new DeviceState(0, "TEST_STATE", 0);
+    private static final DeviceState
+            TEST_DEVICE_STATE_ONE = new DeviceState(1, "TEST_STATE", 0);
 
     private TestStatusChangeListener mStatusListener;
     private OverrideRequestController mController;
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 7e40f96..16909ab 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -40,12 +40,12 @@
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceState;
 import android.os.PowerManager;
 
 import androidx.annotation.NonNull;
 
 import com.android.server.LocalServices;
-import com.android.server.devicestate.DeviceState;
 import com.android.server.devicestate.DeviceStateProvider;
 import com.android.server.input.InputManagerInternal;
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 1fb7cd8..9e2b1ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -32,14 +32,17 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.wm.DragDropController.MSG_UNHANDLED_DROP_LISTENER_TIMEOUT;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.app.PendingIntent;
@@ -49,9 +52,12 @@
 import android.content.pm.ShortcutServiceInternal;
 import android.graphics.PixelFormat;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.Message;
 import android.os.Parcelable;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 import android.view.DragEvent;
@@ -61,6 +67,7 @@
 import android.view.View;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
+import android.window.IUnhandledDragListener;
 
 import androidx.test.filters.SmallTest;
 
@@ -533,14 +540,98 @@
                 });
     }
 
+    @Test
+    public void testUnhandledDragListenerNotCalledForNormalDrags() throws RemoteException {
+        assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
+
+        final IUnhandledDragListener listener = mock(IUnhandledDragListener.class);
+        doReturn(mock(Binder.class)).when(listener).asBinder();
+        mTarget.setUnhandledDragListener(listener);
+        doDragAndDrop(0, ClipData.newPlainText("label", "Test"), 0, 0);
+        verify(listener, times(0)).onUnhandledDrop(any(), any());
+    }
+
+    @Test
+    public void testUnhandledDragListenerReceivesUnhandledDropOverWindow() {
+        assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
+
+        final IUnhandledDragListener listener = mock(IUnhandledDragListener.class);
+        doReturn(mock(Binder.class)).when(listener).asBinder();
+        mTarget.setUnhandledDragListener(listener);
+        final int invalidXY = 100_000;
+        startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
+            // Notify the unhandled drag listener
+            mTarget.reportDropWindow(mWindow.mInputChannelToken, invalidXY, invalidXY);
+            mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+            mTarget.reportDropResult(mWindow.mClient, false);
+            mTarget.onUnhandledDropCallback(true);
+            mToken = null;
+            try {
+                verify(listener, times(1)).onUnhandledDrop(any(), any());
+            } catch (RemoteException e) {
+                fail("Failed to verify unhandled drop: " + e);
+            }
+        });
+    }
+
+    @Test
+    public void testUnhandledDragListenerReceivesUnhandledDropOverNoValidWindow() {
+        assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
+
+        final IUnhandledDragListener listener = mock(IUnhandledDragListener.class);
+        doReturn(mock(Binder.class)).when(listener).asBinder();
+        mTarget.setUnhandledDragListener(listener);
+        final int invalidXY = 100_000;
+        startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
+            // Notify the unhandled drag listener
+            mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
+            mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+            mTarget.onUnhandledDropCallback(true);
+            mToken = null;
+            try {
+                verify(listener, times(1)).onUnhandledDrop(any(), any());
+            } catch (RemoteException e) {
+                fail("Failed to verify unhandled drop: " + e);
+            }
+        });
+    }
+
+    @Test
+    public void testUnhandledDragListenerCallbackTimeout() {
+        assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
+
+        final IUnhandledDragListener listener = mock(IUnhandledDragListener.class);
+        doReturn(mock(Binder.class)).when(listener).asBinder();
+        mTarget.setUnhandledDragListener(listener);
+        final int invalidXY = 100_000;
+        startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
+            // Notify the unhandled drag listener
+            mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
+            mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+
+            // Verify that the unhandled drop listener callback timeout has been scheduled
+            final Handler handler = mTarget.getHandler();
+            assertTrue(handler.hasMessages(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT));
+
+            // Force trigger the timeout and verify that it actually cleans up the drag & timeout
+            handler.handleMessage(Message.obtain(handler, MSG_UNHANDLED_DROP_LISTENER_TIMEOUT));
+            assertFalse(handler.hasMessages(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT));
+            assertFalse(mTarget.dragDropActiveLocked());
+            mToken = null;
+        });
+    }
+
     private void doDragAndDrop(int flags, ClipData data, float dropX, float dropY) {
         startDrag(flags, data, () -> {
             mTarget.reportDropWindow(mWindow.mInputChannelToken, dropX, dropY);
-            mTarget.handleMotionEvent(false, dropX, dropY);
+            mTarget.handleMotionEvent(false /* keepHandling */, dropX, dropY);
             mToken = mWindow.mClient.asBinder();
         });
     }
 
+    /**
+     * Starts a drag with the given parameters, calls Runnable `r` after drag is started.
+     */
     private void startDrag(int flag, ClipData data, Runnable r) {
         final SurfaceSession appSession = new SurfaceSession();
         try {