Merge "Signal dream end to overlay in finish()."
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 2533a0f..d7163d8 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -4805,10 +4805,14 @@
                             }
                             final ArraySet<UserPackage> triggerPackages = new ArraySet<>();
                             final IntArray wakeupUids = new IntArray();
+                            final SparseIntArray countsPerUid = new SparseIntArray();
+                            final SparseIntArray wakeupCountsPerUid = new SparseIntArray();
                             for (int i = 0; i < triggerList.size(); i++) {
                                 final Alarm a = triggerList.get(i);
+                                increment(countsPerUid, a.uid);
                                 if (a.wakeup) {
                                     wakeupUids.add(a.uid);
+                                    increment(wakeupCountsPerUid, a.uid);
                                 }
                                 if (mConstants.USE_TARE_POLICY == EconomyManager.ENABLED_MODE_ON) {
                                     if (!isExemptFromTare(a)) {
@@ -4835,7 +4839,8 @@
                             }
                             rescheduleKernelAlarmsLocked();
                             updateNextAlarmClockLocked();
-                            MetricsHelper.pushAlarmBatchDelivered(triggerList.size(), wakeUps);
+                            logAlarmBatchDelivered(
+                                    triggerList.size(), wakeUps, countsPerUid, wakeupCountsPerUid);
                         }
                     }
 
@@ -4850,6 +4855,32 @@
         }
     }
 
+    private static void increment(SparseIntArray array, int key) {
+        final int index = array.indexOfKey(key);
+        if (index >= 0) {
+            array.setValueAt(index, array.valueAt(index) + 1);
+        } else {
+            array.put(key, 1);
+        }
+    }
+
+    private void logAlarmBatchDelivered(
+            int alarms,
+            int wakeups,
+            SparseIntArray countsPerUid,
+            SparseIntArray wakeupCountsPerUid) {
+        final int[] uids = new int[countsPerUid.size()];
+        final int[] countsArray = new int[countsPerUid.size()];
+        final int[] wakeupCountsArray = new int[countsPerUid.size()];
+        for (int i = 0; i < countsPerUid.size(); i++) {
+            uids[i] = countsPerUid.keyAt(i);
+            countsArray[i] = countsPerUid.valueAt(i);
+            wakeupCountsArray[i] = wakeupCountsPerUid.get(uids[i], 0);
+        }
+        MetricsHelper.pushAlarmBatchDelivered(
+                alarms, wakeups, uids, countsArray, wakeupCountsArray);
+    }
+
     /**
      * Attribute blame for a WakeLock.
      *
@@ -5766,12 +5797,7 @@
     }
 
     private void incrementAlarmCount(int uid) {
-        final int uidIndex = mAlarmsPerUid.indexOfKey(uid);
-        if (uidIndex >= 0) {
-            mAlarmsPerUid.setValueAt(uidIndex, mAlarmsPerUid.valueAt(uidIndex) + 1);
-        } else {
-            mAlarmsPerUid.put(uid, 1);
-        }
+        increment(mAlarmsPerUid, uid);
     }
 
     /**
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java b/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java
index 28acb45..eb1848d 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java
@@ -117,10 +117,14 @@
                 ActivityManager.processStateAmToProto(callerProcState));
     }
 
-    static void pushAlarmBatchDelivered(int numAlarms, int wakeups) {
+    static void pushAlarmBatchDelivered(
+            int numAlarms, int wakeups, int[] uids, int[] alarmsPerUid, int[] wakeupAlarmsPerUid) {
         FrameworkStatsLog.write(
                 FrameworkStatsLog.ALARM_BATCH_DELIVERED,
                 numAlarms,
-                wakeups);
+                wakeups,
+                uids,
+                alarmsPerUid,
+                wakeupAlarmsPerUid);
     }
 }
diff --git a/core/api/current.txt b/core/api/current.txt
index e87487a..2cfc257 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -20341,7 +20341,7 @@
     method @NonNull public android.location.GnssClock getClock();
     method @NonNull public java.util.Collection<android.location.GnssAutomaticGainControl> getGnssAutomaticGainControls();
     method @NonNull public java.util.Collection<android.location.GnssMeasurement> getMeasurements();
-    method public boolean hasFullTracking();
+    method public boolean hasIsFullTracking();
     method public boolean isFullTracking();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssMeasurementsEvent> CREATOR;
@@ -20351,10 +20351,10 @@
     ctor public GnssMeasurementsEvent.Builder();
     ctor public GnssMeasurementsEvent.Builder(@NonNull android.location.GnssMeasurementsEvent);
     method @NonNull public android.location.GnssMeasurementsEvent build();
-    method @NonNull public android.location.GnssMeasurementsEvent.Builder clearFullTracking();
+    method @NonNull public android.location.GnssMeasurementsEvent.Builder clearIsFullTracking();
     method @NonNull public android.location.GnssMeasurementsEvent.Builder setClock(@NonNull android.location.GnssClock);
-    method @NonNull public android.location.GnssMeasurementsEvent.Builder setFullTracking(boolean);
     method @NonNull public android.location.GnssMeasurementsEvent.Builder setGnssAutomaticGainControls(@NonNull java.util.Collection<android.location.GnssAutomaticGainControl>);
+    method @NonNull public android.location.GnssMeasurementsEvent.Builder setIsFullTracking(boolean);
     method @NonNull public android.location.GnssMeasurementsEvent.Builder setMeasurements(@NonNull java.util.Collection<android.location.GnssMeasurement>);
   }
 
@@ -21369,7 +21369,8 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioPlaybackConfiguration> CREATOR;
   }
 
-  public final class AudioPresentation {
+  public final class AudioPresentation implements android.os.Parcelable {
+    method public int describeContents();
     method public java.util.Map<java.util.Locale,java.lang.String> getLabels();
     method public java.util.Locale getLocale();
     method public int getMasteringIndication();
@@ -21378,6 +21379,7 @@
     method public boolean hasAudioDescription();
     method public boolean hasDialogueEnhancement();
     method public boolean hasSpokenSubtitles();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final int CONTENT_COMMENTARY = 5; // 0x5
     field public static final int CONTENT_DIALOG = 4; // 0x4
     field public static final int CONTENT_EMERGENCY = 6; // 0x6
@@ -21387,11 +21389,14 @@
     field public static final int CONTENT_UNKNOWN = -1; // 0xffffffff
     field public static final int CONTENT_VISUALLY_IMPAIRED = 2; // 0x2
     field public static final int CONTENT_VOICEOVER = 7; // 0x7
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioPresentation> CREATOR;
     field public static final int MASTERED_FOR_3D = 3; // 0x3
     field public static final int MASTERED_FOR_HEADPHONE = 4; // 0x4
     field public static final int MASTERED_FOR_STEREO = 1; // 0x1
     field public static final int MASTERED_FOR_SURROUND = 2; // 0x2
     field public static final int MASTERING_NOT_INDICATED = 0; // 0x0
+    field public static final int PRESENTATION_ID_UNKNOWN = -1; // 0xffffffff
+    field public static final int PROGRAM_ID_UNKNOWN = -1; // 0xffffffff
   }
 
   public static final class AudioPresentation.Builder {
@@ -27024,6 +27029,7 @@
     method @Nullable public android.media.tv.TvInputService.RecordingSession onCreateRecordingSession(@NonNull String, @NonNull String);
     method @Nullable public abstract android.media.tv.TvInputService.Session onCreateSession(@NonNull String);
     method @Nullable public android.media.tv.TvInputService.Session onCreateSession(@NonNull String, @NonNull String);
+    method @Nullable public android.media.tv.TvInputService.Session onCreateSession(@NonNull String, @NonNull String, @NonNull android.content.AttributionSource);
     field public static final int PRIORITY_HINT_USE_CASE_TYPE_BACKGROUND = 100; // 0x64
     field public static final int PRIORITY_HINT_USE_CASE_TYPE_LIVE = 400; // 0x190
     field public static final int PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK = 300; // 0x12c
@@ -27062,6 +27068,8 @@
     method public void layoutSurface(int, int, int, int);
     method public void notifyAdResponse(@NonNull android.media.tv.AdResponse);
     method public void notifyAitInfoUpdated(@NonNull android.media.tv.AitInfo);
+    method public void notifyAudioPresentationChanged(@NonNull java.util.List<android.media.AudioPresentation>);
+    method public void notifyAudioPresentationSelected(int, int);
     method public void notifyBroadcastInfoResponse(@NonNull android.media.tv.BroadcastInfoResponse);
     method public void notifyChannelRetuned(android.net.Uri);
     method public void notifyContentAllowed();
@@ -27085,6 +27093,7 @@
     method public void onRemoveBroadcastInfo(int);
     method public void onRequestAd(@NonNull android.media.tv.AdRequest);
     method public void onRequestBroadcastInfo(@NonNull android.media.tv.BroadcastInfoRequest);
+    method public boolean onSelectAudioPresentation(int, int);
     method public boolean onSelectTrack(int, @Nullable String);
     method public abstract void onSetCaptionEnabled(boolean);
     method public void onSetInteractiveAppNotificationEnabled(boolean);
@@ -27214,10 +27223,13 @@
     ctor public TvView(android.content.Context, android.util.AttributeSet);
     ctor public TvView(android.content.Context, android.util.AttributeSet, int);
     method public boolean dispatchUnhandledInputEvent(android.view.InputEvent);
+    method @NonNull public java.util.List<android.media.AudioPresentation> getAudioPresentations();
     method public String getSelectedTrack(int);
     method public java.util.List<android.media.tv.TvTrackInfo> getTracks(int);
     method public boolean onUnhandledInputEvent(android.view.InputEvent);
+    method public void overrideTvAppAttributionSource(@NonNull android.content.AttributionSource);
     method public void reset();
+    method public void selectAudioPresentation(int, int);
     method public void selectTrack(int, String);
     method public void sendAppPrivateCommand(@NonNull String, android.os.Bundle);
     method public void setCallback(@Nullable android.media.tv.TvView.TvInputCallback);
@@ -27250,6 +27262,8 @@
   public abstract static class TvView.TvInputCallback {
     ctor public TvView.TvInputCallback();
     method public void onAitInfoUpdated(@NonNull String, @NonNull android.media.tv.AitInfo);
+    method public void onAudioPresentationSelected(@NonNull String, int, int);
+    method public void onAudioPresentationsChanged(@NonNull String, @NonNull java.util.List<android.media.AudioPresentation>);
     method public void onChannelRetuned(String, android.net.Uri);
     method public void onConnectionFailed(String);
     method public void onContentAllowed(String);
@@ -41625,9 +41639,7 @@
   }
 
   public final class CallEndpointException extends java.lang.RuntimeException implements android.os.Parcelable {
-    ctor public CallEndpointException(@Nullable String);
     ctor public CallEndpointException(@Nullable String, int);
-    ctor public CallEndpointException(@Nullable String, int, @Nullable Throwable);
     method public int describeContents();
     method public int getCode();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 577c8a3..42aa608 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -902,8 +902,8 @@
      * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code s1440p}</td><td id="rb">{@code VIDEO_CALL}</td> <td colspan="3" id="rb"></td> <td>Preview with video call</td> </tr>
      * <tr> <td>{@code YUV / PRIV}</td><td id="rb">{@code s1440p}</td><td id="rb">{@code PREVIEW_VIDEO_STILL}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td colspan="3" id="rb"></td> <td>MultI-purpose stream with JPEG or YUV still capture</td> </tr>
      * <tr> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>{@code JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td colspan="3" id="rb"></td> <td>YUV and JPEG concurrent still image capture (for testing)</td> </tr>
-     * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code VIDEO_RECORD}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>Preview, video record and JPEG or YUV video snapshot</td> </tr>
-     * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>Preview, in-application image processing, and JPEG or YUV still image capture</td> </tr>
+     * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code VIDEO_RECORD}</td> <td>{@code JPEG}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>Preview, video record and JPEG video snapshot</td> </tr>
+     * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>Preview, in-application image processing, and JPEG still image capture</td> </tr>
      * </table><br>
      * </p>
      *
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index 3fc44f8..86c453b 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -1225,14 +1225,6 @@
         new StreamCombinationTemplate(new StreamTemplate [] {
                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
                         STREAM_USE_CASE_PREVIEW),
-                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD,
-                        STREAM_USE_CASE_RECORD),
-                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD,
-                        STREAM_USE_CASE_STILL_CAPTURE)},
-                "Preview, video record and YUV video snapshot"),
-        new StreamCombinationTemplate(new StreamTemplate [] {
-                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
-                        STREAM_USE_CASE_PREVIEW),
                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD,
                         STREAM_USE_CASE_RECORD),
                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.RECORD,
@@ -1241,27 +1233,11 @@
         new StreamCombinationTemplate(new StreamTemplate [] {
                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
                         STREAM_USE_CASE_PREVIEW),
-                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD,
-                        STREAM_USE_CASE_RECORD),
-                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD,
-                        STREAM_USE_CASE_STILL_CAPTURE)},
-                "Preview, in-application video processing and YUV video snapshot"),
-        new StreamCombinationTemplate(new StreamTemplate [] {
-                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
-                        STREAM_USE_CASE_PREVIEW),
                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW,
                         STREAM_USE_CASE_PREVIEW),
                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM,
                         STREAM_USE_CASE_STILL_CAPTURE)},
                 "Preview, in-application image processing, and JPEG still image capture"),
-        new StreamCombinationTemplate(new StreamTemplate [] {
-                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
-                        STREAM_USE_CASE_PREVIEW),
-                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW,
-                        STREAM_USE_CASE_PREVIEW),
-                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM,
-                        STREAM_USE_CASE_STILL_CAPTURE)},
-                "Preview, in-application image processing, and YUV still image capture"),
     };
 
     private static StreamCombinationTemplate sCroppedRawStreamUseCaseCombinations[] = {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7b53843..d68e085 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -25829,10 +25829,6 @@
      * @param selected true if the view must be selected, false otherwise
      */
     public void setSelected(boolean selected) {
-        setSelected(selected, true);
-    }
-
-    void setSelected(boolean selected, boolean sendAccessibilityEvent) {
         //noinspection DoubleNegation
         if (((mPrivateFlags & PFLAG_SELECTED) != 0) != selected) {
             mPrivateFlags = (mPrivateFlags & ~PFLAG_SELECTED) | (selected ? PFLAG_SELECTED : 0);
@@ -25840,13 +25836,11 @@
             invalidate(true);
             refreshDrawableState();
             dispatchSetSelected(selected);
-            if (sendAccessibilityEvent) {
-                if (selected) {
-                    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
-                } else {
-                    notifyViewAccessibilityStateChangedIfNeeded(
-                            AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
-                }
+            if (selected) {
+                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+            } else {
+                notifyViewAccessibilityStateChangedIfNeeded(
+                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
             }
         }
     }
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 9f5015c..0e4ac01 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4631,7 +4631,7 @@
         final View[] children = mChildren;
         final int count = mChildrenCount;
         for (int i = 0; i < count; i++) {
-            children[i].setSelected(selected, false);
+            children[i].setSelected(selected);
         }
     }
 
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index d9cb72a..cee8c1a 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -178,7 +178,14 @@
      * GWP-ASan is activated unconditionally (but still, only a small subset of
      * allocations is protected).
      */
-    public static final int GWP_ASAN_LEVEL_ALWAYS = 1 << 22;
+    public static final int GWP_ASAN_LEVEL_ALWAYS = 2 << 21;
+
+    /**
+     * GWP-ASan's `gwpAsanMode` manifest flag was unspecified. Currently, this
+     * means GWP_ASAN_LEVEL_LOTTERY for system apps, and GWP_ASAN_LEVEL_NONE for
+     * non-system apps.
+     */
+    public static final int GWP_ASAN_LEVEL_DEFAULT = 3 << 21;
 
     /** Enable automatic zero-initialization of native heap memory allocations. */
     public static final int NATIVE_HEAP_ZERO_INIT_ENABLED = 1 << 23;
@@ -1356,15 +1363,13 @@
                     ? GWP_ASAN_LEVEL_ALWAYS
                     : GWP_ASAN_LEVEL_NEVER;
         }
-        // If the app does not specify gwpAsanMode, the default behavior is lottery among the
-        // system apps, and disabled for user apps, unless overwritten by the compat feature.
         if (isCompatChangeEnabled(GWP_ASAN, info, platformCompat, 0)) {
             return GWP_ASAN_LEVEL_ALWAYS;
         }
         if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
             return GWP_ASAN_LEVEL_LOTTERY;
         }
-        return GWP_ASAN_LEVEL_NEVER;
+        return GWP_ASAN_LEVEL_DEFAULT;
     }
 
     private static boolean enableNativeHeapZeroInit(
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index eca85ff..d8dffcd 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -266,7 +266,6 @@
                 "av-types-aidl-cpp",
                 "android.hardware.camera.device@3.2",
                 "libandroid_net",
-                "libandroidicu",
                 "libbattery",
                 "libnetdutils",
                 "libmemtrack",
@@ -287,6 +286,7 @@
                 "libPlatformProperties",
                 "libsensor",
                 "libinput",
+                "libicu",
                 "libcamera_client",
                 "libcamera_metadata",
                 "libprocinfo",
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 664e964..6a92581 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -353,6 +353,7 @@
     GWP_ASAN_LEVEL_NEVER = 0 << 21,
     GWP_ASAN_LEVEL_LOTTERY = 1 << 21,
     GWP_ASAN_LEVEL_ALWAYS = 2 << 21,
+    GWP_ASAN_LEVEL_DEFAULT = 3 << 21,
     NATIVE_HEAP_ZERO_INIT_ENABLED = 1 << 23,
     PROFILEABLE = 1 << 24,
 };
@@ -1932,6 +1933,13 @@
     gwp_asan_options.program_name = nice_name_ptr ?: process_name;
     switch (runtime_flags & RuntimeFlags::GWP_ASAN_LEVEL_MASK) {
         default:
+        case RuntimeFlags::GWP_ASAN_LEVEL_DEFAULT:
+            // TODO(b/247012630): Switch this to Action::TURN_ON_FOR_APP_SAMPLED_NON_CRASHING once
+            // performance and syshealth testing is completed, making the default for non-system
+            // apps that don't specify a `gwpAsanMode` in their manifest to be sampled-recoverable.
+            gwp_asan_options.desire = Action::DONT_TURN_ON_UNLESS_OVERRIDDEN;
+            android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options));
+            break;
         case RuntimeFlags::GWP_ASAN_LEVEL_NEVER:
             gwp_asan_options.desire = Action::DONT_TURN_ON_UNLESS_OVERRIDDEN;
             android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index 94e01e9..5d451a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -16,8 +16,6 @@
 
 package com.android.wm.shell;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -30,27 +28,20 @@
 import android.content.pm.ShortcutInfo;
 import android.graphics.Rect;
 import android.graphics.Region;
-import android.os.Binder;
-import android.util.CloseGuard;
 import android.view.SurfaceControl;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.View;
 import android.view.ViewTreeObserver;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
 
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-import java.io.PrintWriter;
 import java.util.concurrent.Executor;
 
 /**
- * View that can display a task.
+ * A {@link SurfaceView} that can display a task. This is a concrete implementation for
+ * {@link TaskViewBase} which interacts {@link TaskViewTaskController}.
  */
 public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
-        ShellTaskOrganizer.TaskListener, ViewTreeObserver.OnComputeInternalInsetsListener {
-
+        ViewTreeObserver.OnComputeInternalInsetsListener, TaskViewBase {
     /** Callback for listening task state. */
     public interface Listener {
         /**
@@ -75,65 +66,33 @@
         default void onBackPressedOnTaskRoot(int taskId) {}
     }
 
-    private final CloseGuard mGuard = new CloseGuard();
-
-    private final ShellTaskOrganizer mTaskOrganizer;
-    private final Executor mShellExecutor;
-    private final SyncTransactionQueue mSyncQueue;
-    private final TaskViewTransitions mTaskViewTransitions;
-
-    protected ActivityManager.RunningTaskInfo mTaskInfo;
-    private WindowContainerToken mTaskToken;
-    private SurfaceControl mTaskLeash;
-    private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
-    private boolean mSurfaceCreated;
-    private boolean mIsInitialized;
-    private boolean mNotifiedForInitialized;
-    private Listener mListener;
-    private Executor mListenerExecutor;
-    private Region mObscuredTouchRegion;
-
     private final Rect mTmpRect = new Rect();
     private final Rect mTmpRootRect = new Rect();
     private final int[] mTmpLocation = new int[2];
+    private final TaskViewTaskController mTaskViewTaskController;
+    private Region mObscuredTouchRegion;
 
-    public TaskView(Context context, ShellTaskOrganizer organizer,
-            TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) {
+    public TaskView(Context context, TaskViewTaskController taskViewTaskController) {
         super(context, null, 0, 0, true /* disableBackgroundLayer */);
-
-        mTaskOrganizer = organizer;
-        mShellExecutor = organizer.getExecutor();
-        mSyncQueue = syncQueue;
-        mTaskViewTransitions = taskViewTransitions;
-        if (mTaskViewTransitions != null) {
-            mTaskViewTransitions.addTaskView(this);
-        }
+        mTaskViewTaskController = taskViewTaskController;
+        // TODO(b/266736992): Think about a better way to set the TaskViewBase on the
+        //  TaskViewTaskController and vice-versa
+        mTaskViewTaskController.setTaskViewBase(this);
         getHolder().addCallback(this);
-        mGuard.open("release");
     }
 
     /**
-     * @return {@code True} when the TaskView's surface has been created, {@code False} otherwise.
+     * Launch a new activity.
+     *
+     * @param pendingIntent Intent used to launch an activity.
+     * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}
+     * @param options options for the activity.
+     * @param launchBounds the bounds (window size and position) that the activity should be
+     *                     launched in, in pixels and in screen coordinates.
      */
-    public boolean isInitialized() {
-        return mIsInitialized;
-    }
-
-    /** Until all users are converted, we may have mixed-use (eg. Car). */
-    private boolean isUsingShellTransitions() {
-        return mTaskViewTransitions != null && mTaskViewTransitions.isEnabled();
-    }
-
-    /**
-     * Only one listener may be set on the view, throws an exception otherwise.
-     */
-    public void setListener(@NonNull Executor executor, Listener listener) {
-        if (mListener != null) {
-            throw new IllegalStateException(
-                    "Trying to set a listener when one has already been set");
-        }
-        mListener = listener;
-        mListenerExecutor = executor;
+    public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
+            @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
+        mTaskViewTaskController.startActivity(pendingIntent, fillInIntent, options, launchBounds);
     }
 
     /**
@@ -148,61 +107,47 @@
      */
     public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
             @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
-        prepareActivityOptions(options, launchBounds);
-        LauncherApps service = mContext.getSystemService(LauncherApps.class);
-        if (isUsingShellTransitions()) {
-            mShellExecutor.execute(() -> {
-                final WindowContainerTransaction wct = new WindowContainerTransaction();
-                wct.startShortcut(mContext.getPackageName(), shortcut, options.toBundle());
-                mTaskViewTransitions.startTaskView(wct, this);
-            });
-            return;
+        mTaskViewTaskController.startShortcutActivity(shortcut, options, launchBounds);
+    }
+
+    @Override
+    public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+        onLocationChanged();
+        if (taskInfo.taskDescription != null) {
+            setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
         }
-        try {
-            service.startShortcut(shortcut, null /* sourceBounds */, options.toBundle());
-        } catch (Exception e) {
-            throw new RuntimeException(e);
+    }
+
+    @Override
+    public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+        if (taskInfo.taskDescription != null) {
+            setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
         }
     }
 
     /**
-     * Launch a new activity.
-     *
-     * @param pendingIntent Intent used to launch an activity.
-     * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}
-     * @param options options for the activity.
-     * @param launchBounds the bounds (window size and position) that the activity should be
-     *                     launched in, in pixels and in screen coordinates.
+     * @return {@code True} when the TaskView's surface has been created, {@code False} otherwise.
      */
-    public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
-            @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
-        prepareActivityOptions(options, launchBounds);
-        if (isUsingShellTransitions()) {
-            mShellExecutor.execute(() -> {
-                WindowContainerTransaction wct = new WindowContainerTransaction();
-                wct.sendPendingIntent(pendingIntent, fillInIntent, options.toBundle());
-                mTaskViewTransitions.startTaskView(wct, this);
-            });
-            return;
-        }
-        try {
-            pendingIntent.send(mContext, 0 /* code */, fillInIntent,
-                    null /* onFinished */, null /* handler */, null /* requiredPermission */,
-                    options.toBundle());
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
+    public boolean isInitialized() {
+        return mTaskViewTaskController.isInitialized();
     }
 
-    private void prepareActivityOptions(ActivityOptions options, Rect launchBounds) {
-        final Binder launchCookie = new Binder();
-        mShellExecutor.execute(() -> {
-            mTaskOrganizer.setPendingLaunchCookieListener(launchCookie, this);
-        });
-        options.setLaunchBounds(launchBounds);
-        options.setLaunchCookie(launchCookie);
-        options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
-        options.setRemoveWithTaskOrganizer(true);
+    @Override
+    public Rect getCurrentBoundsOnScreen() {
+        getBoundsOnScreen(mTmpRect);
+        return mTmpRect;
+    }
+
+    @Override
+    public void setResizeBgColor(SurfaceControl.Transaction t, int bgColor) {
+        setResizeBackgroundColor(t, bgColor);
+    }
+
+    /**
+     * Only one listener may be set on the view, throws an exception otherwise.
+     */
+    public void setListener(@NonNull Executor executor, TaskView.Listener listener) {
+        mTaskViewTaskController.setListener(executor, listener);
     }
 
     /**
@@ -227,251 +172,38 @@
      * Call when view position or size has changed. Do not call when animating.
      */
     public void onLocationChanged() {
-        if (mTaskToken == null) {
-            return;
-        }
-        // Sync Transactions can't operate simultaneously with shell transition collection.
-        // The transition animation (upon showing) will sync the location itself.
-        if (isUsingShellTransitions() && mTaskViewTransitions.hasPending()) return;
-
-        WindowContainerTransaction wct = new WindowContainerTransaction();
-        updateWindowBounds(wct);
-        mSyncQueue.queue(wct);
-    }
-
-    private void updateWindowBounds(WindowContainerTransaction wct) {
         getBoundsOnScreen(mTmpRect);
-        wct.setBounds(mTaskToken, mTmpRect);
+        mTaskViewTaskController.onLocationChanged(mTmpRect);
     }
 
     /**
      * Release this container if it is initialized.
      */
     public void release() {
-        performRelease();
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            if (mGuard != null) {
-                mGuard.warnIfOpen();
-                performRelease();
-            }
-        } finally {
-            super.finalize();
-        }
-    }
-
-    private void performRelease() {
         getHolder().removeCallback(this);
-        if (mTaskViewTransitions != null) {
-            mTaskViewTransitions.removeTaskView(this);
-        }
-        mShellExecutor.execute(() -> {
-            mTaskOrganizer.removeListener(this);
-            resetTaskInfo();
-        });
-        mGuard.close();
-        mIsInitialized = false;
-        notifyReleased();
-    }
-
-    /** Called when the {@link TaskView} has been released. */
-    protected void notifyReleased() {
-        if (mListener != null && mNotifiedForInitialized) {
-            mListenerExecutor.execute(() -> {
-                mListener.onReleased();
-            });
-            mNotifiedForInitialized = false;
-        }
-    }
-
-    private void resetTaskInfo() {
-        mTaskInfo = null;
-        mTaskToken = null;
-        mTaskLeash = null;
-    }
-
-    private void updateTaskVisibility() {
-        WindowContainerTransaction wct = new WindowContainerTransaction();
-        wct.setHidden(mTaskToken, !mSurfaceCreated /* hidden */);
-        mSyncQueue.queue(wct);
-        if (mListener == null) {
-            return;
-        }
-        int taskId = mTaskInfo.taskId;
-        mSyncQueue.runInSync((t) -> {
-            mListenerExecutor.execute(() -> {
-                mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated);
-            });
-        });
-    }
-
-    @Override
-    public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
-            SurfaceControl leash) {
-        if (isUsingShellTransitions()) {
-            // Everything else handled by enter transition.
-            return;
-        }
-        mTaskInfo = taskInfo;
-        mTaskToken = taskInfo.token;
-        mTaskLeash = leash;
-
-        if (mSurfaceCreated) {
-            // Surface is ready, so just reparent the task to this surface control
-            mTransaction.reparent(mTaskLeash, getSurfaceControl())
-                    .show(mTaskLeash)
-                    .apply();
-        } else {
-            // The surface has already been destroyed before the task has appeared,
-            // so go ahead and hide the task entirely
-            updateTaskVisibility();
-        }
-        mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true);
-        onLocationChanged();
-        if (taskInfo.taskDescription != null) {
-            int backgroundColor = taskInfo.taskDescription.getBackgroundColor();
-            mSyncQueue.runInSync((t) -> {
-                setResizeBackgroundColor(t, backgroundColor);
-            });
-        }
-
-        if (mListener != null) {
-            final int taskId = taskInfo.taskId;
-            final ComponentName baseActivity = taskInfo.baseActivity;
-            mListenerExecutor.execute(() -> {
-                mListener.onTaskCreated(taskId, baseActivity);
-            });
-        }
-    }
-
-    @Override
-    public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
-        // Unlike Appeared, we can't yet guarantee that vanish will happen within a transition that
-        // we know about -- so leave clean-up here even if shell transitions are enabled.
-        if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
-
-        if (mListener != null) {
-            final int taskId = taskInfo.taskId;
-            mListenerExecutor.execute(() -> {
-                mListener.onTaskRemovalStarted(taskId);
-            });
-        }
-        mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
-
-        // Unparent the task when this surface is destroyed
-        mTransaction.reparent(mTaskLeash, null).apply();
-        resetTaskInfo();
-    }
-
-    @Override
-    public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
-        if (taskInfo.taskDescription != null) {
-            setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
-        }
-    }
-
-    @Override
-    public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
-        if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
-        if (mListener != null) {
-            final int taskId = taskInfo.taskId;
-            mListenerExecutor.execute(() -> {
-                mListener.onBackPressedOnTaskRoot(taskId);
-            });
-        }
-    }
-
-    @Override
-    public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
-        b.setParent(findTaskSurface(taskId));
-    }
-
-    @Override
-    public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
-            SurfaceControl.Transaction t) {
-        t.reparent(sc, findTaskSurface(taskId));
-    }
-
-    private SurfaceControl findTaskSurface(int taskId) {
-        if (mTaskInfo == null || mTaskLeash == null || mTaskInfo.taskId != taskId) {
-            throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
-        }
-        return mTaskLeash;
-    }
-
-    @Override
-    public void dump(@androidx.annotation.NonNull PrintWriter pw, String prefix) {
-        final String innerPrefix = prefix + "  ";
-        final String childPrefix = innerPrefix + "  ";
-        pw.println(prefix + this);
+        mTaskViewTaskController.release();
     }
 
     @Override
     public String toString() {
-        return "TaskView" + ":" + (mTaskInfo != null ? mTaskInfo.taskId : "null");
+        return mTaskViewTaskController.toString();
     }
 
     @Override
     public void surfaceCreated(SurfaceHolder holder) {
-        mSurfaceCreated = true;
-        mIsInitialized = true;
-        notifyInitialized();
-        mShellExecutor.execute(() -> {
-            if (mTaskToken == null) {
-                // Nothing to update, task is not yet available
-                return;
-            }
-            if (isUsingShellTransitions()) {
-                mTaskViewTransitions.setTaskViewVisible(this, true /* visible */);
-                return;
-            }
-            // Reparent the task when this surface is created
-            mTransaction.reparent(mTaskLeash, getSurfaceControl())
-                    .show(mTaskLeash)
-                    .apply();
-            updateTaskVisibility();
-        });
-    }
-
-    /** Called when the {@link TaskView} is initialized. */
-    protected void notifyInitialized() {
-        if (mListener != null && !mNotifiedForInitialized) {
-            mNotifiedForInitialized = true;
-            mListenerExecutor.execute(() -> {
-                mListener.onInitialized();
-            });
-        }
+        mTaskViewTaskController.surfaceCreated(getSurfaceControl());
     }
 
     @Override
-    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
-        if (mTaskToken == null) {
-            return;
-        }
-        onLocationChanged();
+    public void surfaceChanged(@androidx.annotation.NonNull SurfaceHolder holder, int format,
+            int width, int height) {
+        getBoundsOnScreen(mTmpRect);
+        mTaskViewTaskController.surfaceChanged(mTmpRect);
     }
 
     @Override
     public void surfaceDestroyed(SurfaceHolder holder) {
-        mSurfaceCreated = false;
-        mShellExecutor.execute(() -> {
-            if (mTaskToken == null) {
-                // Nothing to update, task is not yet available
-                return;
-            }
-
-            if (isUsingShellTransitions()) {
-                mTaskViewTransitions.setTaskViewVisible(this, false /* visible */);
-                return;
-            }
-
-            // Unparent the task when this surface is destroyed
-            mTransaction.reparent(mTaskLeash, null).apply();
-            updateTaskVisibility();
-        });
+        mTaskViewTaskController.surfaceDestroyed();
     }
 
     @Override
@@ -513,89 +245,6 @@
     /** Returns the task info for the task in the TaskView. */
     @Nullable
     public ActivityManager.RunningTaskInfo getTaskInfo() {
-        return mTaskInfo;
-    }
-
-    void prepareHideAnimation(@NonNull SurfaceControl.Transaction finishTransaction) {
-        if (mTaskToken == null) {
-            // Nothing to update, task is not yet available
-            return;
-        }
-
-        finishTransaction.reparent(mTaskLeash, null).apply();
-
-        if (mListener != null) {
-            final int taskId = mTaskInfo.taskId;
-            mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated /* visible */);
-        }
-    }
-
-    /**
-     * Called when the associated Task closes. If the TaskView is just being hidden, prepareHide
-     * is used instead.
-     */
-    void prepareCloseAnimation() {
-        if (mTaskToken != null) {
-            if (mListener != null) {
-                final int taskId = mTaskInfo.taskId;
-                mListenerExecutor.execute(() -> {
-                    mListener.onTaskRemovalStarted(taskId);
-                });
-            }
-            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
-        }
-        resetTaskInfo();
-    }
-
-    void prepareOpenAnimation(final boolean newTask,
-            @NonNull SurfaceControl.Transaction startTransaction,
-            @NonNull SurfaceControl.Transaction finishTransaction,
-            ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
-            WindowContainerTransaction wct) {
-        mTaskInfo = taskInfo;
-        mTaskToken = mTaskInfo.token;
-        mTaskLeash = leash;
-        if (mSurfaceCreated) {
-            // Surface is ready, so just reparent the task to this surface control
-            startTransaction.reparent(mTaskLeash, getSurfaceControl())
-                    .show(mTaskLeash)
-                    .apply();
-            // Also reparent on finishTransaction since the finishTransaction will reparent back
-            // to its "original" parent by default.
-            finishTransaction.reparent(mTaskLeash, getSurfaceControl())
-                    .setPosition(mTaskLeash, 0, 0)
-                    .apply();
-
-            // TODO: determine if this is really necessary or not
-            updateWindowBounds(wct);
-        } else {
-            // The surface has already been destroyed before the task has appeared,
-            // so go ahead and hide the task entirely
-            wct.setHidden(mTaskToken, true /* hidden */);
-            // listener callback is below
-        }
-        if (newTask) {
-            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true /* intercept */);
-        }
-
-        if (mTaskInfo.taskDescription != null) {
-            int backgroundColor = mTaskInfo.taskDescription.getBackgroundColor();
-            setResizeBackgroundColor(startTransaction, backgroundColor);
-        }
-
-        if (mListener != null) {
-            final int taskId = mTaskInfo.taskId;
-            final ComponentName baseActivity = mTaskInfo.baseActivity;
-
-            mListenerExecutor.execute(() -> {
-                if (newTask) {
-                    mListener.onTaskCreated(taskId, baseActivity);
-                }
-                // Even if newTask, send a visibilityChange if the surface was destroyed.
-                if (!newTask || !mSurfaceCreated) {
-                    mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated /* visible */);
-                }
-            });
-        }
+        return mTaskViewTaskController.getTaskInfo();
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewBase.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewBase.java
new file mode 100644
index 0000000..3d0a8fd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewBase.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 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;
+
+import android.app.ActivityManager;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+
+/**
+ * A stub for SurfaceView used by {@link TaskViewTaskController}
+ */
+public interface TaskViewBase {
+    /**
+     * Returns the current bounds on screen for the task view.
+     * @return
+     */
+    // TODO(b/266242294): Remove getBoundsOnScreen() and instead send the bounds from the TaskView
+    //  to TaskViewTaskController.
+    Rect getCurrentBoundsOnScreen();
+
+    /**
+     * This method should set the resize background color on the SurfaceView that is exposed to
+     * clients.
+     * See {@link android.view.SurfaceView#setResizeBackgroundColor(SurfaceControl.Transaction,
+     * int)}
+     */
+    void setResizeBgColor(SurfaceControl.Transaction transaction, int color);
+
+    /**
+     * Called when a task appears on the TaskView. See
+     * {@link TaskViewTaskController#onTaskAppeared(ActivityManager.RunningTaskInfo,
+     * SurfaceControl)} for details.
+     */
+    default void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+    }
+
+    /**
+     * Called when a task is vanished from the TaskView. See
+     * {@link TaskViewTaskController#onTaskVanished(ActivityManager.RunningTaskInfo)} for details.
+     */
+    default void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+    }
+
+    /**
+     * Called when the task in the TaskView is changed. See
+     * {@link TaskViewTaskController#onTaskInfoChanged(ActivityManager.RunningTaskInfo)} for details.
+     */
+    default void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
index 42844b5..735d9bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
@@ -57,7 +57,8 @@
 
     /** Creates an {@link TaskView} */
     public void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate) {
-        TaskView taskView = new TaskView(context, mTaskOrganizer, mTaskViewTransitions, mSyncQueue);
+        TaskView taskView = new TaskView(context, new TaskViewTaskController(context,
+                mTaskOrganizer, mTaskViewTransitions, mSyncQueue));
         executor.execute(() -> {
             onCreate.accept(taskView);
         });
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTaskController.java
new file mode 100644
index 0000000..69ce35f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTaskController.java
@@ -0,0 +1,527 @@
+/*
+ * Copyright (C) 2020 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;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.util.CloseGuard;
+import android.view.SurfaceControl;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import java.io.PrintWriter;
+import java.util.concurrent.Executor;
+
+/**
+ * This class implements the core logic to show a task on the {@link TaskView}. All the {@link
+ * TaskView} to {@link TaskViewTaskController} interactions are done via direct method calls.
+ *
+ * The reverse communication is done via the {@link TaskViewBase} interface.
+ */
+public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
+
+    private final CloseGuard mGuard = new CloseGuard();
+
+    private final ShellTaskOrganizer mTaskOrganizer;
+    private final Executor mShellExecutor;
+    private final SyncTransactionQueue mSyncQueue;
+    private final TaskViewTransitions mTaskViewTransitions;
+    private TaskViewBase mTaskViewBase;
+    private final Context mContext;
+
+    protected ActivityManager.RunningTaskInfo mTaskInfo;
+    private WindowContainerToken mTaskToken;
+    private SurfaceControl mTaskLeash;
+    private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+    private boolean mSurfaceCreated;
+    private SurfaceControl mSurfaceControl;
+    private boolean mIsInitialized;
+    private boolean mNotifiedForInitialized;
+    private TaskView.Listener mListener;
+    private Executor mListenerExecutor;
+
+    public TaskViewTaskController(Context context, ShellTaskOrganizer organizer,
+            TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) {
+        mContext = context;
+        mTaskOrganizer = organizer;
+        mShellExecutor = organizer.getExecutor();
+        mSyncQueue = syncQueue;
+        mTaskViewTransitions = taskViewTransitions;
+        if (mTaskViewTransitions != null) {
+            mTaskViewTransitions.addTaskView(this);
+        }
+        mGuard.open("release");
+    }
+
+    /**
+     * Sets the provided {@link TaskViewBase}, which is used to notify the client part about the
+     * task related changes and getting the current bounds.
+     */
+    public void setTaskViewBase(TaskViewBase taskViewBase) {
+        mTaskViewBase = taskViewBase;
+    }
+
+    /**
+     * @return {@code True} when the TaskView's surface has been created, {@code False} otherwise.
+     */
+    public boolean isInitialized() {
+        return mIsInitialized;
+    }
+
+    /** Until all users are converted, we may have mixed-use (eg. Car). */
+    private boolean isUsingShellTransitions() {
+        return mTaskViewTransitions != null && mTaskViewTransitions.isEnabled();
+    }
+
+    /**
+     * Only one listener may be set on the view, throws an exception otherwise.
+     */
+    void setListener(@NonNull Executor executor, TaskView.Listener listener) {
+        if (mListener != null) {
+            throw new IllegalStateException(
+                    "Trying to set a listener when one has already been set");
+        }
+        mListener = listener;
+        mListenerExecutor = executor;
+    }
+
+    /**
+     * Launch an activity represented by {@link ShortcutInfo}.
+     * <p>The owner of this container must be allowed to access the shortcut information,
+     * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method.
+     *
+     * @param shortcut the shortcut used to launch the activity.
+     * @param options options for the activity.
+     * @param launchBounds the bounds (window size and position) that the activity should be
+     *                     launched in, in pixels and in screen coordinates.
+     */
+    public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
+            @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
+        prepareActivityOptions(options, launchBounds);
+        LauncherApps service = mContext.getSystemService(LauncherApps.class);
+        if (isUsingShellTransitions()) {
+            mShellExecutor.execute(() -> {
+                final WindowContainerTransaction wct = new WindowContainerTransaction();
+                wct.startShortcut(mContext.getPackageName(), shortcut, options.toBundle());
+                mTaskViewTransitions.startTaskView(wct, this);
+            });
+            return;
+        }
+        try {
+            service.startShortcut(shortcut, null /* sourceBounds */, options.toBundle());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Launch a new activity.
+     *
+     * @param pendingIntent Intent used to launch an activity.
+     * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}
+     * @param options options for the activity.
+     * @param launchBounds the bounds (window size and position) that the activity should be
+     *                     launched in, in pixels and in screen coordinates.
+     */
+    public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
+            @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
+        prepareActivityOptions(options, launchBounds);
+        if (isUsingShellTransitions()) {
+            mShellExecutor.execute(() -> {
+                WindowContainerTransaction wct = new WindowContainerTransaction();
+                wct.sendPendingIntent(pendingIntent, fillInIntent, options.toBundle());
+                mTaskViewTransitions.startTaskView(wct, this);
+            });
+            return;
+        }
+        try {
+            pendingIntent.send(mContext, 0 /* code */, fillInIntent,
+                    null /* onFinished */, null /* handler */, null /* requiredPermission */,
+                    options.toBundle());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void prepareActivityOptions(ActivityOptions options, Rect launchBounds) {
+        final Binder launchCookie = new Binder();
+        mShellExecutor.execute(() -> {
+            mTaskOrganizer.setPendingLaunchCookieListener(launchCookie, this);
+        });
+        options.setLaunchBounds(launchBounds);
+        options.setLaunchCookie(launchCookie);
+        options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        options.setRemoveWithTaskOrganizer(true);
+    }
+
+    /**
+     * Call when view position or size has changed. Do not call when animating.
+     */
+    public void onLocationChanged(Rect newBounds) {
+        if (mTaskToken == null) {
+            return;
+        }
+        // Sync Transactions can't operate simultaneously with shell transition collection.
+        // The transition animation (upon showing) will sync the location itself.
+        if (isUsingShellTransitions() && mTaskViewTransitions.hasPending()) return;
+
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        updateWindowBounds(wct);
+        mSyncQueue.queue(wct);
+    }
+
+    private void updateWindowBounds(WindowContainerTransaction wct) {
+        wct.setBounds(mTaskToken, mTaskViewBase.getCurrentBoundsOnScreen());
+    }
+
+    /**
+     * Release this container if it is initialized.
+     */
+    public void release() {
+        performRelease();
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mGuard != null) {
+                mGuard.warnIfOpen();
+                performRelease();
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private void performRelease() {
+        if (mTaskViewTransitions != null) {
+            mTaskViewTransitions.removeTaskView(this);
+        }
+        mShellExecutor.execute(() -> {
+            mTaskOrganizer.removeListener(this);
+            resetTaskInfo();
+        });
+        mGuard.close();
+        mIsInitialized = false;
+        notifyReleased();
+    }
+
+    /** Called when the {@link TaskViewTaskController} has been released. */
+    protected void notifyReleased() {
+        if (mListener != null && mNotifiedForInitialized) {
+            mListenerExecutor.execute(() -> {
+                mListener.onReleased();
+            });
+            mNotifiedForInitialized = false;
+        }
+    }
+
+    private void resetTaskInfo() {
+        mTaskInfo = null;
+        mTaskToken = null;
+        mTaskLeash = null;
+    }
+
+    private void updateTaskVisibility() {
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.setHidden(mTaskToken, !mSurfaceCreated /* hidden */);
+        mSyncQueue.queue(wct);
+        if (mListener == null) {
+            return;
+        }
+        int taskId = mTaskInfo.taskId;
+        mSyncQueue.runInSync((t) -> {
+            mListenerExecutor.execute(() -> {
+                mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated);
+            });
+        });
+    }
+
+    @Override
+    public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
+            SurfaceControl leash) {
+        if (isUsingShellTransitions()) {
+            // Everything else handled by enter transition.
+            return;
+        }
+        mTaskInfo = taskInfo;
+        mTaskToken = taskInfo.token;
+        mTaskLeash = leash;
+
+        if (mSurfaceCreated) {
+            // Surface is ready, so just reparent the task to this surface control
+            mTransaction.reparent(mTaskLeash, mSurfaceControl)
+                    .show(mTaskLeash)
+                    .apply();
+        } else {
+            // The surface has already been destroyed before the task has appeared,
+            // so go ahead and hide the task entirely
+            updateTaskVisibility();
+        }
+        mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true);
+        mSyncQueue.runInSync((t) -> {
+            mTaskViewBase.onTaskAppeared(taskInfo, leash);
+        });
+
+        if (mListener != null) {
+            final int taskId = taskInfo.taskId;
+            final ComponentName baseActivity = taskInfo.baseActivity;
+            mListenerExecutor.execute(() -> {
+                mListener.onTaskCreated(taskId, baseActivity);
+            });
+        }
+    }
+
+    @Override
+    public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+        // Unlike Appeared, we can't yet guarantee that vanish will happen within a transition that
+        // we know about -- so leave clean-up here even if shell transitions are enabled.
+        if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
+
+        if (mListener != null) {
+            final int taskId = taskInfo.taskId;
+            mListenerExecutor.execute(() -> {
+                mListener.onTaskRemovalStarted(taskId);
+            });
+        }
+        mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
+
+        // Unparent the task when this surface is destroyed
+        mTransaction.reparent(mTaskLeash, null).apply();
+        resetTaskInfo();
+        mTaskViewBase.onTaskVanished(taskInfo);
+    }
+
+    @Override
+    public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+        mTaskViewBase.onTaskInfoChanged(taskInfo);
+    }
+
+    @Override
+    public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
+        if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
+        if (mListener != null) {
+            final int taskId = taskInfo.taskId;
+            mListenerExecutor.execute(() -> {
+                mListener.onBackPressedOnTaskRoot(taskId);
+            });
+        }
+    }
+
+    @Override
+    public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+        b.setParent(findTaskSurface(taskId));
+    }
+
+    @Override
+    public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
+            SurfaceControl.Transaction t) {
+        t.reparent(sc, findTaskSurface(taskId));
+    }
+
+    private SurfaceControl findTaskSurface(int taskId) {
+        if (mTaskInfo == null || mTaskLeash == null || mTaskInfo.taskId != taskId) {
+            throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+        }
+        return mTaskLeash;
+    }
+
+    @Override
+    public void dump(@androidx.annotation.NonNull PrintWriter pw, String prefix) {
+        final String innerPrefix = prefix + "  ";
+        final String childPrefix = innerPrefix + "  ";
+        pw.println(prefix + this);
+    }
+
+    @Override
+    public String toString() {
+        return "TaskViewTaskController" + ":" + (mTaskInfo != null ? mTaskInfo.taskId : "null");
+    }
+
+    /**
+     * Should be called when the client surface is created.
+     *
+     * @param surfaceControl the {@link SurfaceControl} for the underlying surface.
+     */
+    public void surfaceCreated(SurfaceControl surfaceControl) {
+        mSurfaceCreated = true;
+        mIsInitialized = true;
+        mSurfaceControl = surfaceControl;
+        notifyInitialized();
+        mShellExecutor.execute(() -> {
+            if (mTaskToken == null) {
+                // Nothing to update, task is not yet available
+                return;
+            }
+            if (isUsingShellTransitions()) {
+                mTaskViewTransitions.setTaskViewVisible(this, true /* visible */);
+                return;
+            }
+            // Reparent the task when this surface is created
+            mTransaction.reparent(mTaskLeash, mSurfaceControl)
+                    .show(mTaskLeash)
+                    .apply();
+            updateTaskVisibility();
+        });
+    }
+
+    /**
+     * Should be called when the client surface is changed.
+     *
+     * @param boundsOnScreen the on screen bounds of the surface view.
+     */
+    public void surfaceChanged(Rect boundsOnScreen) {
+        if (mTaskToken == null) {
+            return;
+        }
+        onLocationChanged(boundsOnScreen);
+    }
+
+    /** Should be called when the client surface is destroyed. */
+    public void surfaceDestroyed() {
+        mSurfaceCreated = false;
+        mSurfaceControl = null;
+        mShellExecutor.execute(() -> {
+            if (mTaskToken == null) {
+                // Nothing to update, task is not yet available
+                return;
+            }
+
+            if (isUsingShellTransitions()) {
+                mTaskViewTransitions.setTaskViewVisible(this, false /* visible */);
+                return;
+            }
+
+            // Unparent the task when this surface is destroyed
+            mTransaction.reparent(mTaskLeash, null).apply();
+            updateTaskVisibility();
+        });
+    }
+
+    /** Called when the {@link TaskViewTaskController} is initialized. */
+    protected void notifyInitialized() {
+        if (mListener != null && !mNotifiedForInitialized) {
+            mNotifiedForInitialized = true;
+            mListenerExecutor.execute(() -> {
+                mListener.onInitialized();
+            });
+        }
+    }
+
+    /** Returns the task info for the task in the TaskView. */
+    @Nullable
+    public ActivityManager.RunningTaskInfo getTaskInfo() {
+        return mTaskInfo;
+    }
+
+    void prepareHideAnimation(@NonNull SurfaceControl.Transaction finishTransaction) {
+        if (mTaskToken == null) {
+            // Nothing to update, task is not yet available
+            return;
+        }
+
+        finishTransaction.reparent(mTaskLeash, null).apply();
+
+        if (mListener != null) {
+            final int taskId = mTaskInfo.taskId;
+            mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated /* visible */);
+        }
+    }
+
+    /**
+     * Called when the associated Task closes. If the TaskView is just being hidden, prepareHide
+     * is used instead.
+     */
+    void prepareCloseAnimation() {
+        if (mTaskToken != null) {
+            if (mListener != null) {
+                final int taskId = mTaskInfo.taskId;
+                mListenerExecutor.execute(() -> {
+                    mListener.onTaskRemovalStarted(taskId);
+                });
+            }
+            mTaskViewBase.onTaskVanished(mTaskInfo);
+            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
+        }
+        resetTaskInfo();
+    }
+
+    void prepareOpenAnimation(final boolean newTask,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
+            WindowContainerTransaction wct) {
+        mTaskInfo = taskInfo;
+        mTaskToken = mTaskInfo.token;
+        mTaskLeash = leash;
+        if (mSurfaceCreated) {
+            // Surface is ready, so just reparent the task to this surface control
+            startTransaction.reparent(mTaskLeash, mSurfaceControl)
+                    .show(mTaskLeash)
+                    .apply();
+            // Also reparent on finishTransaction since the finishTransaction will reparent back
+            // to its "original" parent by default.
+            finishTransaction.reparent(mTaskLeash, mSurfaceControl)
+                    .setPosition(mTaskLeash, 0, 0)
+                    .apply();
+
+            updateWindowBounds(wct);
+        } else {
+            // The surface has already been destroyed before the task has appeared,
+            // so go ahead and hide the task entirely
+            wct.setHidden(mTaskToken, true /* hidden */);
+            // listener callback is below
+        }
+        if (newTask) {
+            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true /* intercept */);
+        }
+
+        if (mTaskInfo.taskDescription != null) {
+            int backgroundColor = mTaskInfo.taskDescription.getBackgroundColor();
+            mTaskViewBase.setResizeBgColor(startTransaction, backgroundColor);
+        }
+
+        if (mListener != null) {
+            final int taskId = mTaskInfo.taskId;
+            final ComponentName baseActivity = mTaskInfo.baseActivity;
+
+            mListenerExecutor.execute(() -> {
+                if (newTask) {
+                    mListener.onTaskCreated(taskId, baseActivity);
+                }
+                // Even if newTask, send a visibilityChange if the surface was destroyed.
+                if (!newTask || !mSurfaceCreated) {
+                    mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated /* visible */);
+                }
+            });
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
index 07d5012..aab257e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
@@ -41,7 +41,7 @@
 public class TaskViewTransitions implements Transitions.TransitionHandler {
     private static final String TAG = "TaskViewTransitions";
 
-    private final ArrayList<TaskView> mTaskViews = new ArrayList<>();
+    private final ArrayList<TaskViewTaskController> mTaskViews = new ArrayList<>();
     private final ArrayList<PendingTransition> mPending = new ArrayList<>();
     private final Transitions mTransitions;
     private final boolean[] mRegistered = new boolean[]{ false };
@@ -54,11 +54,12 @@
     private static class PendingTransition {
         final @WindowManager.TransitionType int mType;
         final WindowContainerTransaction mWct;
-        final @NonNull TaskView mTaskView;
+        final @NonNull TaskViewTaskController mTaskView;
         IBinder mClaimed;
 
         PendingTransition(@WindowManager.TransitionType int type,
-                @Nullable WindowContainerTransaction wct, @NonNull TaskView taskView) {
+                @Nullable WindowContainerTransaction wct,
+                @NonNull TaskViewTaskController taskView) {
             mType = type;
             mWct = wct;
             mTaskView = taskView;
@@ -72,7 +73,7 @@
         // TODO(210041388): register here once we have an explicit ordering mechanism.
     }
 
-    void addTaskView(TaskView tv) {
+    void addTaskView(TaskViewTaskController tv) {
         synchronized (mRegistered) {
             if (!mRegistered[0]) {
                 mRegistered[0] = true;
@@ -82,7 +83,7 @@
         mTaskViews.add(tv);
     }
 
-    void removeTaskView(TaskView tv) {
+    void removeTaskView(TaskViewTaskController tv) {
         mTaskViews.remove(tv);
         // Note: Don't unregister handler since this is a singleton with lifetime bound to Shell
     }
@@ -101,7 +102,8 @@
      *               if there is a match earlier. The idea behind this is to check the state of
      *               the taskviews "as if all transitions already happened".
      */
-    private PendingTransition findPending(TaskView taskView, boolean closing, boolean latest) {
+    private PendingTransition findPending(TaskViewTaskController taskView, boolean closing,
+            boolean latest) {
         for (int i = mPending.size() - 1; i >= 0; --i) {
             if (mPending.get(i).mTaskView != taskView) continue;
             if (Transitions.isClosingType(mPending.get(i).mType) == closing) {
@@ -134,7 +136,7 @@
         if (triggerTask == null) {
             return null;
         }
-        final TaskView taskView = findTaskView(triggerTask);
+        final TaskViewTaskController taskView = findTaskView(triggerTask);
         if (taskView == null) return null;
         // Opening types should all be initiated by shell
         if (!Transitions.isClosingType(request.getType())) return null;
@@ -150,7 +152,7 @@
         return new WindowContainerTransaction();
     }
 
-    private TaskView findTaskView(ActivityManager.RunningTaskInfo taskInfo) {
+    private TaskViewTaskController findTaskView(ActivityManager.RunningTaskInfo taskInfo) {
         for (int i = 0; i < mTaskViews.size(); ++i) {
             if (mTaskViews.get(i).getTaskInfo() == null) continue;
             if (taskInfo.token.equals(mTaskViews.get(i).getTaskInfo().token)) {
@@ -160,12 +162,12 @@
         return null;
     }
 
-    void startTaskView(WindowContainerTransaction wct, TaskView taskView) {
+    void startTaskView(WindowContainerTransaction wct, TaskViewTaskController taskView) {
         mPending.add(new PendingTransition(TRANSIT_OPEN, wct, taskView));
         startNextTransition();
     }
 
-    void setTaskViewVisible(TaskView taskView, boolean visible) {
+    void setTaskViewVisible(TaskViewTaskController taskView, boolean visible) {
         PendingTransition pending = findPending(taskView, !visible, true /* latest */);
         if (pending != null) {
             // Already opening or creating a task, so no need to do anything here.
@@ -203,7 +205,7 @@
         PendingTransition pending = findPending(transition);
         if (pending == null) return false;
         mPending.remove(pending);
-        TaskView taskView = pending.mTaskView;
+        TaskViewTaskController taskView = pending.mTaskView;
         final ArrayList<TransitionInfo.Change> tasks = new ArrayList<>();
         for (int i = 0; i < info.getChanges().size(); ++i) {
             final TransitionInfo.Change chg = info.getChanges().get(i);
@@ -219,7 +221,7 @@
             TransitionInfo.Change chg = tasks.get(i);
             if (Transitions.isClosingType(chg.getMode())) {
                 final boolean isHide = chg.getMode() == TRANSIT_TO_BACK;
-                TaskView tv = findTaskView(chg.getTaskInfo());
+                TaskViewTaskController tv = findTaskView(chg.getTaskInfo());
                 if (tv == null) {
                     throw new IllegalStateException("TaskView transition is closing a "
                             + "non-taskview task ");
@@ -232,7 +234,7 @@
             } else if (Transitions.isOpeningType(chg.getMode())) {
                 final boolean taskIsNew = chg.getMode() == TRANSIT_OPEN;
                 if (wct == null) wct = new WindowContainerTransaction();
-                TaskView tv = taskView;
+                TaskViewTaskController tv = taskView;
                 if (!taskIsNew) {
                     tv = findTaskView(chg.getTaskInfo());
                     if (tv == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index e85b3c7..1feff18 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -67,6 +67,7 @@
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.wm.shell.R;
 import com.android.wm.shell.TaskView;
+import com.android.wm.shell.TaskViewTaskController;
 import com.android.wm.shell.common.AlphaOptimizedButton;
 import com.android.wm.shell.common.TriangleShape;
 
@@ -140,6 +141,7 @@
 
     private AlphaOptimizedButton mManageButton;
     private TaskView mTaskView;
+    private TaskViewTaskController mTaskViewTaskController;
     private BubbleOverflowContainerView mOverflowView;
 
     private int mTaskId = INVALID_TASK_ID;
@@ -409,8 +411,10 @@
             bringChildToFront(mOverflowView);
             mManageButton.setVisibility(GONE);
         } else {
-            mTaskView = new TaskView(mContext, mController.getTaskOrganizer(),
+            mTaskViewTaskController = new TaskViewTaskController(mContext,
+                    mController.getTaskOrganizer(),
                     mController.getTaskViewTransitions(), mController.getSyncTransactionQueue());
+            mTaskView = new TaskView(mContext, mTaskViewTaskController);
             mTaskView.setListener(mController.getMainExecutor(), mTaskViewListener);
             mExpandedViewContainer.addView(mTaskView);
             bringChildToFront(mTaskView);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index f77ac81..476a7ec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -174,7 +174,7 @@
         final CaptionTouchEventListener touchEventListener =
                 new CaptionTouchEventListener(taskInfo, taskPositioner);
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
-        windowDecoration.setDragResizeCallback(taskPositioner);
+        windowDecoration.setDragPositioningCallback(taskPositioner);
         windowDecoration.setDragDetector(touchEventListener.mDragDetector);
         windowDecoration.relayout(taskInfo, startT, finishT);
         setupCaptionColor(taskInfo, windowDecoration);
@@ -185,17 +185,17 @@
 
         private final int mTaskId;
         private final WindowContainerToken mTaskToken;
-        private final DragResizeCallback mDragResizeCallback;
+        private final DragPositioningCallback mDragPositioningCallback;
         private final DragDetector mDragDetector;
 
         private int mDragPointerId = -1;
 
         private CaptionTouchEventListener(
                 RunningTaskInfo taskInfo,
-                DragResizeCallback dragResizeCallback) {
+                DragPositioningCallback dragPositioningCallback) {
             mTaskId = taskInfo.taskId;
             mTaskToken = taskInfo.token;
-            mDragResizeCallback = dragResizeCallback;
+            mDragPositioningCallback = dragPositioningCallback;
             mDragDetector = new DragDetector(this);
         }
 
@@ -247,20 +247,20 @@
             switch (e.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN: {
                     mDragPointerId = e.getPointerId(0);
-                    mDragResizeCallback.onDragResizeStart(
+                    mDragPositioningCallback.onDragPositioningStart(
                             0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
                     break;
                 }
                 case MotionEvent.ACTION_MOVE: {
                     int dragPointerIdx = e.findPointerIndex(mDragPointerId);
-                    mDragResizeCallback.onDragResizeMove(
+                    mDragPositioningCallback.onDragPositioningMove(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     break;
                 }
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL: {
                     int dragPointerIdx = e.findPointerIndex(mDragPointerId);
-                    mDragResizeCallback.onDragResizeEnd(
+                    mDragPositioningCallback.onDragPositioningEnd(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     break;
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index f94fbfc..060dc4e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -47,7 +47,7 @@
 
     private View.OnClickListener mOnCaptionButtonClickListener;
     private View.OnTouchListener mOnCaptionTouchListener;
-    private DragResizeCallback mDragResizeCallback;
+    private DragPositioningCallback mDragPositioningCallback;
     private DragResizeInputListener mDragResizeListener;
     private DragDetector mDragDetector;
 
@@ -78,8 +78,8 @@
         mOnCaptionTouchListener = onCaptionTouchListener;
     }
 
-    void setDragResizeCallback(DragResizeCallback dragResizeCallback) {
-        mDragResizeCallback = dragResizeCallback;
+    void setDragPositioningCallback(DragPositioningCallback dragPositioningCallback) {
+        mDragPositioningCallback = dragPositioningCallback;
     }
 
     void setDragDetector(DragDetector dragDetector) {
@@ -151,7 +151,7 @@
                     mChoreographer,
                     mDisplay.getDisplayId(),
                     mDecorationContainerSurface,
-                    mDragResizeCallback);
+                    mDragPositioningCallback);
         }
 
         final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index c0c0ab9..606cf28 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -209,17 +209,17 @@
 
         private final int mTaskId;
         private final WindowContainerToken mTaskToken;
-        private final DragResizeCallback mDragResizeCallback;
+        private final DragPositioningCallback mDragPositioningCallback;
         private final DragDetector mDragDetector;
 
         private int mDragPointerId = -1;
 
         private DesktopModeTouchEventListener(
                 RunningTaskInfo taskInfo,
-                DragResizeCallback dragResizeCallback) {
+                DragPositioningCallback dragPositioningCallback) {
             mTaskId = taskInfo.taskId;
             mTaskToken = taskInfo.token;
-            mDragResizeCallback = dragResizeCallback;
+            mDragPositioningCallback = dragPositioningCallback;
             mDragDetector = new DragDetector(this);
         }
 
@@ -283,13 +283,13 @@
             switch (e.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN: {
                     mDragPointerId = e.getPointerId(0);
-                    mDragResizeCallback.onDragResizeStart(
+                    mDragPositioningCallback.onDragPositioningStart(
                             0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
                     break;
                 }
                 case MotionEvent.ACTION_MOVE: {
                     final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
-                    mDragResizeCallback.onDragResizeMove(
+                    mDragPositioningCallback.onDragPositioningMove(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     break;
                 }
@@ -298,7 +298,7 @@
                     final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
                     final int statusBarHeight = mDisplayController
                             .getDisplayLayout(taskInfo.displayId).stableInsets().top;
-                    mDragResizeCallback.onDragResizeEnd(
+                    mDragPositioningCallback.onDragPositioningEnd(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     if (e.getRawY(dragPointerIdx) <= statusBarHeight) {
                         if (DesktopModeStatus.isProto2Enabled()) {
@@ -557,7 +557,7 @@
         final DesktopModeTouchEventListener touchEventListener =
                 new DesktopModeTouchEventListener(taskInfo, taskPositioner);
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
-        windowDecoration.setDragResizeCallback(taskPositioner);
+        windowDecoration.setDragPositioningCallback(taskPositioner);
         windowDecoration.setDragDetector(touchEventListener.mDragDetector);
         windowDecoration.relayout(taskInfo, startT, finishT);
         incrementEventReceiverTasks(taskInfo.displayId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 31b56d3..744c18f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -54,7 +54,7 @@
 
     private View.OnClickListener mOnCaptionButtonClickListener;
     private View.OnTouchListener mOnCaptionTouchListener;
-    private DragResizeCallback mDragResizeCallback;
+    private DragPositioningCallback mDragPositioningCallback;
     private DragResizeInputListener mDragResizeListener;
     private DragDetector mDragDetector;
 
@@ -90,8 +90,8 @@
         mOnCaptionTouchListener = onCaptionTouchListener;
     }
 
-    void setDragResizeCallback(DragResizeCallback dragResizeCallback) {
-        mDragResizeCallback = dragResizeCallback;
+    void setDragPositioningCallback(DragPositioningCallback dragPositioningCallback) {
+        mDragPositioningCallback = dragPositioningCallback;
     }
 
     void setDragDetector(DragDetector dragDetector) {
@@ -179,7 +179,7 @@
                     mChoreographer,
                     mDisplay.getDisplayId(),
                     mDecorationContainerSurface,
-                    mDragResizeCallback);
+                    mDragPositioningCallback);
         }
 
         final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
similarity index 76%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
index ee160a1..0191c60 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
@@ -19,28 +19,28 @@
 /**
  * Callback called when receiving drag-resize or drag-move related input events.
  */
-public interface DragResizeCallback {
+public interface DragPositioningCallback {
     /**
-     * Called when a drag resize starts.
+     * Called when a drag-resize or drag-move starts.
      *
      * @param ctrlType {@link TaskPositioner.CtrlType} indicating the direction of resizing, use
      *                 {@code 0} to indicate it's a move
-     * @param x x coordinate in window decoration coordinate system where the drag resize starts
-     * @param y y coordinate in window decoration coordinate system where the drag resize starts
+     * @param x x coordinate in window decoration coordinate system where the drag starts
+     * @param y y coordinate in window decoration coordinate system where the drag starts
      */
-    void onDragResizeStart(@TaskPositioner.CtrlType int ctrlType, float x, float y);
+    void onDragPositioningStart(@TaskPositioner.CtrlType int ctrlType, float x, float y);
 
     /**
-     * Called when the pointer moves during a drag resize.
+     * Called when the pointer moves during a drag-resize or drag-move.
      * @param x x coordinate in window decoration coordinate system of the new pointer location
      * @param y y coordinate in window decoration coordinate system of the new pointer location
      */
-    void onDragResizeMove(float x, float y);
+    void onDragPositioningMove(float x, float y);
 
     /**
-     * Called when a drag resize stops.
+     * Called when a drag-resize or drag-move stops.
      * @param x x coordinate in window decoration coordinate system where the drag resize stops
      * @param y y coordinate in window decoration coordinate system where the drag resize stops
      */
-    void onDragResizeEnd(float x, float y);
+    void onDragPositioningEnd(float x, float y);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index ea417b1..3d1edc9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -62,7 +62,7 @@
     private final SurfaceControl mDecorationSurface;
     private final InputChannel mInputChannel;
     private final TaskResizeInputEventReceiver mInputEventReceiver;
-    private final com.android.wm.shell.windowdecor.DragResizeCallback mCallback;
+    private final DragPositioningCallback mCallback;
 
     private int mWidth;
     private int mHeight;
@@ -83,7 +83,7 @@
             Choreographer choreographer,
             int displayId,
             SurfaceControl decorationSurface,
-            DragResizeCallback callback) {
+            DragPositioningCallback callback) {
         mInputManager = context.getSystemService(InputManager.class);
         mHandler = handler;
         mChoreographer = choreographer;
@@ -294,7 +294,7 @@
                         float rawX = e.getRawX(0);
                         float rawY = e.getRawY(0);
                         int ctrlType = calculateCtrlType(isTouch, x, y);
-                        mCallback.onDragResizeStart(ctrlType, rawX, rawY);
+                        mCallback.onDragPositioningStart(ctrlType, rawX, rawY);
                         result = true;
                     }
                     break;
@@ -306,7 +306,7 @@
                     int dragPointerIndex = e.findPointerIndex(mDragPointerId);
                     float rawX = e.getRawX(dragPointerIndex);
                     float rawY = e.getRawY(dragPointerIndex);
-                    mCallback.onDragResizeMove(rawX, rawY);
+                    mCallback.onDragPositioningMove(rawX, rawY);
                     result = true;
                     break;
                 }
@@ -314,7 +314,7 @@
                 case MotionEvent.ACTION_CANCEL: {
                     if (mShouldHandleEvents) {
                         int dragPointerIndex = e.findPointerIndex(mDragPointerId);
-                        mCallback.onDragResizeEnd(
+                        mCallback.onDragPositioningEnd(
                                 e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
                     }
                     mShouldHandleEvents = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
index 8cd2a59..d3f9227 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -23,7 +23,7 @@
 
 import com.android.wm.shell.ShellTaskOrganizer;
 
-class TaskPositioner implements DragResizeCallback {
+class TaskPositioner implements DragPositioningCallback {
 
     @IntDef({CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM})
     @interface CtrlType {}
@@ -38,8 +38,8 @@
     private final WindowDecoration mWindowDecoration;
 
     private final Rect mTaskBoundsAtDragStart = new Rect();
-    private final PointF mResizeStartPoint = new PointF();
-    private final Rect mResizeTaskBounds = new Rect();
+    private final PointF mRepositionStartPoint = new PointF();
+    private final Rect mRepositionTaskBounds = new Rect();
     private boolean mHasMoved = false;
 
     private int mCtrlType;
@@ -57,7 +57,7 @@
     }
 
     @Override
-    public void onDragResizeStart(int ctrlType, float x, float y) {
+    public void onDragPositioningStart(int ctrlType, float x, float y) {
         mHasMoved = false;
 
         mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId);
@@ -65,11 +65,11 @@
 
         mTaskBoundsAtDragStart.set(
                 mWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
-        mResizeStartPoint.set(x, y);
+        mRepositionStartPoint.set(x, y);
     }
 
     @Override
-    public void onDragResizeMove(float x, float y) {
+    public void onDragPositioningMove(float x, float y) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (changeBounds(wct, x, y)) {
             // The task is being resized, send the |dragResizing| hint to core with the first
@@ -84,7 +84,7 @@
     }
 
     @Override
-    public void onDragResizeEnd(float x, float y) {
+    public void onDragPositioningEnd(float x, float y) {
         // |mHasMoved| being false means there is no real change to the task bounds in WM core, so
         // we don't need a WCT to finish it.
         if (mHasMoved) {
@@ -96,42 +96,44 @@
 
         mCtrlType = CTRL_TYPE_UNDEFINED;
         mTaskBoundsAtDragStart.setEmpty();
-        mResizeStartPoint.set(0, 0);
+        mRepositionStartPoint.set(0, 0);
         mHasMoved = false;
     }
 
     private boolean changeBounds(WindowContainerTransaction wct, float x, float y) {
-        // |mResizeTaskBounds| is the bounds last reported if |mHasMoved| is true. If it's not true,
-        // we can compare it against |mTaskBoundsAtDragStart|.
-        final int oldLeft = mHasMoved ? mResizeTaskBounds.left : mTaskBoundsAtDragStart.left;
-        final int oldTop = mHasMoved ? mResizeTaskBounds.top : mTaskBoundsAtDragStart.top;
-        final int oldRight = mHasMoved ? mResizeTaskBounds.right : mTaskBoundsAtDragStart.right;
-        final int oldBottom = mHasMoved ? mResizeTaskBounds.bottom : mTaskBoundsAtDragStart.bottom;
+        // |mRepositionTaskBounds| is the bounds last reported if |mHasMoved| is true. If it's not
+        // true, we can compare it against |mTaskBoundsAtDragStart|.
+        final int oldLeft = mHasMoved ? mRepositionTaskBounds.left : mTaskBoundsAtDragStart.left;
+        final int oldTop = mHasMoved ? mRepositionTaskBounds.top : mTaskBoundsAtDragStart.top;
+        final int oldRight = mHasMoved ? mRepositionTaskBounds.right : mTaskBoundsAtDragStart.right;
+        final int oldBottom =
+                mHasMoved ? mRepositionTaskBounds.bottom : mTaskBoundsAtDragStart.bottom;
 
-        final float deltaX = x - mResizeStartPoint.x;
-        final float deltaY = y - mResizeStartPoint.y;
-        mResizeTaskBounds.set(mTaskBoundsAtDragStart);
+        final float deltaX = x - mRepositionStartPoint.x;
+        final float deltaY = y - mRepositionStartPoint.y;
+        mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
         if ((mCtrlType & CTRL_TYPE_LEFT) != 0) {
-            mResizeTaskBounds.left += deltaX;
+            mRepositionTaskBounds.left += deltaX;
         }
         if ((mCtrlType & CTRL_TYPE_RIGHT) != 0) {
-            mResizeTaskBounds.right += deltaX;
+            mRepositionTaskBounds.right += deltaX;
         }
         if ((mCtrlType & CTRL_TYPE_TOP) != 0) {
-            mResizeTaskBounds.top += deltaY;
+            mRepositionTaskBounds.top += deltaY;
         }
         if ((mCtrlType & CTRL_TYPE_BOTTOM) != 0) {
-            mResizeTaskBounds.bottom += deltaY;
+            mRepositionTaskBounds.bottom += deltaY;
         }
         if (mCtrlType == CTRL_TYPE_UNDEFINED) {
-            mResizeTaskBounds.offset((int) deltaX, (int) deltaY);
+            mRepositionTaskBounds.offset((int) deltaX, (int) deltaY);
         }
 
-        if (oldLeft == mResizeTaskBounds.left && oldTop == mResizeTaskBounds.top
-                && oldRight == mResizeTaskBounds.right && oldBottom == mResizeTaskBounds.bottom) {
+        if (oldLeft == mRepositionTaskBounds.left && oldTop == mRepositionTaskBounds.top
+                && oldRight == mRepositionTaskBounds.right
+                && oldBottom == mRepositionTaskBounds.bottom) {
             return false;
         }
-        wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds);
+        wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
         return true;
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
index d5bb901..62bfd17 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
@@ -92,6 +92,7 @@
     Context mContext;
     TaskView mTaskView;
     TaskViewTransitions mTaskViewTransitions;
+    TaskViewTaskController mTaskViewTaskController;
 
     @Before
     public void setUp() {
@@ -125,7 +126,9 @@
             doReturn(true).when(mTransitions).isRegistered();
         }
         mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions));
-        mTaskView = new TaskView(mContext, mOrganizer, mTaskViewTransitions, mSyncQueue);
+        mTaskViewTaskController = new TaskViewTaskController(mContext, mOrganizer,
+                mTaskViewTransitions, mSyncQueue);
+        mTaskView = new TaskView(mContext, mTaskViewTaskController);
         mTaskView.setListener(mExecutor, mViewListener);
     }
 
@@ -138,7 +141,8 @@
 
     @Test
     public void testSetPendingListener_throwsException() {
-        TaskView taskView = new TaskView(mContext, mOrganizer, mTaskViewTransitions, mSyncQueue);
+        TaskView taskView = new TaskView(mContext,
+                new TaskViewTaskController(mContext, mOrganizer, mTaskViewTransitions, mSyncQueue));
         taskView.setListener(mExecutor, mViewListener);
         try {
             taskView.setListener(mExecutor, mViewListener);
@@ -152,16 +156,17 @@
     @Test
     public void testStartActivity() {
         ActivityOptions options = ActivityOptions.makeBasic();
-        mTaskView.startActivity(mock(PendingIntent.class), null, options, new Rect(0, 0, 100, 100));
+        mTaskView.startActivity(mock(PendingIntent.class), null, options,
+                new Rect(0, 0, 100, 100));
 
-        verify(mOrganizer).setPendingLaunchCookieListener(any(), eq(mTaskView));
+        verify(mOrganizer).setPendingLaunchCookieListener(any(), eq(mTaskViewTaskController));
         assertThat(options.getLaunchWindowingMode()).isEqualTo(WINDOWING_MODE_MULTI_WINDOW);
     }
 
     @Test
     public void testOnTaskAppeared_noSurface_legacyTransitions() {
         assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
-        mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+        mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
 
         verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
         verify(mViewListener, never()).onInitialized();
@@ -173,7 +178,7 @@
     public void testOnTaskAppeared_withSurface_legacyTransitions() {
         assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
-        mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+        mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
 
         verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
         assertThat(mTaskView.isInitialized()).isTrue();
@@ -194,7 +199,7 @@
     @Test
     public void testSurfaceCreated_withTask_legacyTransitions() {
         assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
-        mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+        mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
 
         verify(mViewListener).onInitialized();
@@ -216,7 +221,7 @@
     public void testSurfaceDestroyed_withTask_legacyTransitions() {
         assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
         SurfaceHolder sh = mock(SurfaceHolder.class);
-        mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+        mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
         mTaskView.surfaceCreated(sh);
         reset(mViewListener);
         mTaskView.surfaceDestroyed(sh);
@@ -227,11 +232,11 @@
     @Test
     public void testOnReleased_legacyTransitions() {
         assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
-        mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+        mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
         mTaskView.release();
 
-        verify(mOrganizer).removeListener(eq(mTaskView));
+        verify(mOrganizer).removeListener(eq(mTaskViewTaskController));
         verify(mViewListener).onReleased();
         assertThat(mTaskView.isInitialized()).isFalse();
     }
@@ -239,9 +244,9 @@
     @Test
     public void testOnTaskVanished_legacyTransitions() {
         assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
-        mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+        mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
-        mTaskView.onTaskVanished(mTaskInfo);
+        mTaskViewTaskController.onTaskVanished(mTaskInfo);
 
         verify(mViewListener).onTaskRemovalStarted(eq(mTaskInfo.taskId));
     }
@@ -249,8 +254,8 @@
     @Test
     public void testOnBackPressedOnTaskRoot_legacyTransitions() {
         assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
-        mTaskView.onTaskAppeared(mTaskInfo, mLeash);
-        mTaskView.onBackPressedOnTaskRoot(mTaskInfo);
+        mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
+        mTaskViewTaskController.onBackPressedOnTaskRoot(mTaskInfo);
 
         verify(mViewListener).onBackPressedOnTaskRoot(eq(mTaskInfo.taskId));
     }
@@ -258,17 +263,17 @@
     @Test
     public void testSetOnBackPressedOnTaskRoot_legacyTransitions() {
         assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
-        mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+        mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
         verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true));
     }
 
     @Test
     public void testUnsetOnBackPressedOnTaskRoot_legacyTransitions() {
         assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
-        mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+        mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
         verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true));
 
-        mTaskView.onTaskVanished(mTaskInfo);
+        mTaskViewTaskController.onTaskVanished(mTaskInfo);
         verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(false));
     }
 
@@ -276,8 +281,9 @@
     public void testOnNewTask_noSurface() {
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
-                new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+                new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+                mLeash, wct);
 
         verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
         verify(mViewListener, never()).onInitialized();
@@ -303,8 +309,9 @@
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
-                new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+                new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+                mLeash, wct);
 
         verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
         verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
@@ -314,15 +321,17 @@
     public void testSurfaceCreated_withTask() {
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
-                new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+                new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+                mLeash, wct);
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
 
         verify(mViewListener).onInitialized();
-        verify(mTaskViewTransitions).setTaskViewVisible(eq(mTaskView), eq(true));
+        verify(mTaskViewTransitions).setTaskViewVisible(eq(mTaskViewTaskController), eq(true));
 
-        mTaskView.prepareOpenAnimation(false /* newTask */, new SurfaceControl.Transaction(),
-                new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+        mTaskViewTaskController.prepareOpenAnimation(false /* newTask */,
+                new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+                mLeash, wct);
 
         verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(true));
     }
@@ -342,15 +351,16 @@
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
         SurfaceHolder sh = mock(SurfaceHolder.class);
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
-                new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+                new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+                mLeash, wct);
         mTaskView.surfaceCreated(sh);
         reset(mViewListener);
         mTaskView.surfaceDestroyed(sh);
 
-        verify(mTaskViewTransitions).setTaskViewVisible(eq(mTaskView), eq(false));
+        verify(mTaskViewTransitions).setTaskViewVisible(eq(mTaskViewTaskController), eq(false));
 
-        mTaskView.prepareHideAnimation(new SurfaceControl.Transaction());
+        mTaskViewTaskController.prepareHideAnimation(new SurfaceControl.Transaction());
 
         verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(false));
     }
@@ -359,25 +369,27 @@
     public void testOnReleased() {
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
-                new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+                new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+                mLeash, wct);
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
         mTaskView.release();
 
-        verify(mOrganizer).removeListener(eq(mTaskView));
+        verify(mOrganizer).removeListener(eq(mTaskViewTaskController));
         verify(mViewListener).onReleased();
         assertThat(mTaskView.isInitialized()).isFalse();
-        verify(mTaskViewTransitions).removeTaskView(eq(mTaskView));
+        verify(mTaskViewTransitions).removeTaskView(eq(mTaskViewTaskController));
     }
 
     @Test
     public void testOnTaskVanished() {
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
-                new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+                new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+                mLeash, wct);
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
-        mTaskView.prepareCloseAnimation();
+        mTaskViewTaskController.prepareCloseAnimation();
 
         verify(mViewListener).onTaskRemovalStarted(eq(mTaskInfo.taskId));
     }
@@ -386,9 +398,10 @@
     public void testOnBackPressedOnTaskRoot() {
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
-                new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
-        mTaskView.onBackPressedOnTaskRoot(mTaskInfo);
+        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+                new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+                mLeash, wct);
+        mTaskViewTaskController.onBackPressedOnTaskRoot(mTaskInfo);
 
         verify(mViewListener).onBackPressedOnTaskRoot(eq(mTaskInfo.taskId));
     }
@@ -397,8 +410,9 @@
     public void testSetOnBackPressedOnTaskRoot() {
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
-                new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+                new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+                mLeash, wct);
         verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true));
     }
 
@@ -406,11 +420,12 @@
     public void testUnsetOnBackPressedOnTaskRoot() {
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
-                new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+                new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+                mLeash, wct);
         verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true));
 
-        mTaskView.prepareCloseAnimation();
+        mTaskViewTaskController.prepareCloseAnimation();
         verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(false));
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
index 804c416..f185a8a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
@@ -66,13 +66,13 @@
 
     @Test
     fun testDragResize_notMove_skipsTransactionOnEnd() {
-        taskPositioner.onDragResizeStart(
+        taskPositioner.onDragPositioningStart(
                 CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
                 STARTING_BOUNDS.left.toFloat(),
                 STARTING_BOUNDS.top.toFloat()
         )
 
-        taskPositioner.onDragResizeEnd(
+        taskPositioner.onDragPositioningEnd(
                 STARTING_BOUNDS.left.toFloat() + 10,
                 STARTING_BOUNDS.top.toFloat() + 10
         )
@@ -87,18 +87,18 @@
 
     @Test
     fun testDragResize_noEffectiveMove_skipsTransactionOnMoveAndEnd() {
-        taskPositioner.onDragResizeStart(
+        taskPositioner.onDragPositioningStart(
                 CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
                 STARTING_BOUNDS.left.toFloat(),
                 STARTING_BOUNDS.top.toFloat()
         )
 
-        taskPositioner.onDragResizeMove(
+        taskPositioner.onDragPositioningMove(
                 STARTING_BOUNDS.left.toFloat(),
                 STARTING_BOUNDS.top.toFloat()
         )
 
-        taskPositioner.onDragResizeEnd(
+        taskPositioner.onDragPositioningEnd(
                 STARTING_BOUNDS.left.toFloat() + 10,
                 STARTING_BOUNDS.top.toFloat() + 10
         )
@@ -113,13 +113,13 @@
 
     @Test
     fun testDragResize_hasEffectiveMove_issuesTransactionOnMoveAndEnd() {
-        taskPositioner.onDragResizeStart(
+        taskPositioner.onDragPositioningStart(
                 CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
                 STARTING_BOUNDS.left.toFloat(),
                 STARTING_BOUNDS.top.toFloat()
         )
 
-        taskPositioner.onDragResizeMove(
+        taskPositioner.onDragPositioningMove(
                 STARTING_BOUNDS.left.toFloat() + 10,
                 STARTING_BOUNDS.top.toFloat()
         )
@@ -133,7 +133,7 @@
             }
         })
 
-        taskPositioner.onDragResizeEnd(
+        taskPositioner.onDragPositioningEnd(
                 STARTING_BOUNDS.left.toFloat() + 10,
                 STARTING_BOUNDS.top.toFloat() + 10
         )
@@ -150,7 +150,7 @@
 
     @Test
     fun testDragResize_move_skipsDragResizingFlag() {
-        taskPositioner.onDragResizeStart(
+        taskPositioner.onDragPositioningStart(
                 CTRL_TYPE_UNDEFINED, // Move
                 STARTING_BOUNDS.left.toFloat(),
                 STARTING_BOUNDS.top.toFloat()
@@ -159,12 +159,12 @@
         // Move the task 10px to the right.
         val newX = STARTING_BOUNDS.left.toFloat() + 10
         val newY = STARTING_BOUNDS.top.toFloat()
-        taskPositioner.onDragResizeMove(
+        taskPositioner.onDragPositioningMove(
                 newX,
                 newY
         )
 
-        taskPositioner.onDragResizeEnd(newX, newY)
+        taskPositioner.onDragPositioningEnd(newX, newY)
 
         verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
             return@argThat wct.changes.any { (token, change) ->
@@ -177,7 +177,7 @@
 
     @Test
     fun testDragResize_resize_setsDragResizingFlag() {
-        taskPositioner.onDragResizeStart(
+        taskPositioner.onDragPositioningStart(
                 CTRL_TYPE_RIGHT, // Resize right
                 STARTING_BOUNDS.left.toFloat(),
                 STARTING_BOUNDS.top.toFloat()
@@ -186,12 +186,12 @@
         // Resize the task by 10px to the right.
         val newX = STARTING_BOUNDS.right.toFloat() + 10
         val newY = STARTING_BOUNDS.top.toFloat()
-        taskPositioner.onDragResizeMove(
+        taskPositioner.onDragPositioningMove(
                 newX,
                 newY
         )
 
-        taskPositioner.onDragResizeEnd(newX, newY)
+        taskPositioner.onDragPositioningEnd(newX, newY)
 
         verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
             return@argThat wct.changes.any { (token, change) ->
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index b563627..13357fa 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -37,7 +37,6 @@
 #include "SkShader.h"
 #include "SkBlendMode.h"
 #include "unicode/uloc.h"
-#include "unicode/ushape.h"
 #include "utils/Blur.h"
 
 #include <hwui/BlurDrawLooper.h>
diff --git a/location/java/android/location/GnssMeasurementsEvent.java b/location/java/android/location/GnssMeasurementsEvent.java
index d4b861f..4fc2ee8 100644
--- a/location/java/android/location/GnssMeasurementsEvent.java
+++ b/location/java/android/location/GnssMeasurementsEvent.java
@@ -43,7 +43,7 @@
     private final List<GnssAutomaticGainControl> mGnssAgcs;
     private final boolean mIsFullTracking;
 
-    private static final int HAS_FULL_TRACKING = 1;
+    private static final int HAS_IS_FULL_TRACKING = 1;
 
     /**
      * Used for receiving GNSS satellite measurements from the GNSS engine.
@@ -174,7 +174,7 @@
      * False indicates that the GNSS chipset may optimize power via duty cycling, constellations and
      * frequency limits, etc.
      *
-     * <p>The value is only available if {@link #hasFullTracking()} is {@code true}.
+     * <p>The value is only available if {@link #hasIsFullTracking()} is {@code true}.
      */
     public boolean isFullTracking() {
         return mIsFullTracking;
@@ -183,8 +183,8 @@
     /**
      * Return {@code true} if {@link #isFullTracking()} is available, {@code false} otherwise.
      */
-    public boolean hasFullTracking() {
-        return (mFlag & HAS_FULL_TRACKING) == HAS_FULL_TRACKING;
+    public boolean hasIsFullTracking() {
+        return (mFlag & HAS_IS_FULL_TRACKING) == HAS_IS_FULL_TRACKING;
     }
 
     public static final @android.annotation.NonNull Creator<GnssMeasurementsEvent> CREATOR =
@@ -227,7 +227,7 @@
         builder.append(mClock);
         builder.append(' ').append(mMeasurements.toString());
         builder.append(' ').append(mGnssAgcs.toString());
-        if (hasFullTracking()) {
+        if (hasIsFullTracking()) {
             builder.append(" isFullTracking=").append(mIsFullTracking);
         }
         builder.append("]");
@@ -334,8 +334,8 @@
          * and frequency limits, etc.
          */
         @NonNull
-        public Builder setFullTracking(boolean isFullTracking) {
-            mFlag |= HAS_FULL_TRACKING;
+        public Builder setIsFullTracking(boolean isFullTracking) {
+            mFlag |= HAS_IS_FULL_TRACKING;
             mIsFullTracking = isFullTracking;
             return this;
         }
@@ -344,8 +344,8 @@
          * Clears the full tracking mode indicator.
          */
         @NonNull
-        public Builder clearFullTracking() {
-            mFlag &= ~HAS_FULL_TRACKING;
+        public Builder clearIsFullTracking() {
+            mFlag &= ~HAS_IS_FULL_TRACKING;
             return this;
         }
 
diff --git a/media/java/android/media/AudioPresentation.aidl b/media/java/android/media/AudioPresentation.aidl
new file mode 100644
index 0000000..3fb5a5b
--- /dev/null
+++ b/media/java/android/media/AudioPresentation.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 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.media;
+
+parcelable AudioPresentation;
diff --git a/media/java/android/media/AudioPresentation.java b/media/java/android/media/AudioPresentation.java
index 05f3c5a..a413545 100644
--- a/media/java/android/media/AudioPresentation.java
+++ b/media/java/android/media/AudioPresentation.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -19,6 +19,9 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.icu.util.ULocale;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -49,7 +52,7 @@
  * Applications that parse media streams and extract presentation information on their own
  * can create instances of AudioPresentation by using {@link AudioPresentation.Builder} class.
  */
-public final class AudioPresentation {
+public final class AudioPresentation implements Parcelable {
     private final int mPresentationId;
     private final int mProgramId;
     private final ULocale mLanguage;
@@ -126,7 +129,7 @@
     private final boolean mAudioDescriptionAvailable;
     private final boolean mSpokenSubtitlesAvailable;
     private final boolean mDialogueEnhancementAvailable;
-    private final Map<ULocale, CharSequence> mLabels;
+    private final HashMap<ULocale, String> mLabels;
 
     /**
      * No preferred reproduction channel layout.
@@ -160,9 +163,14 @@
     public static final int MASTERED_FOR_HEADPHONE          = 4;
 
     /**
-     * This ID is reserved. No items can be explicitly assigned this ID.
+     * Unknown audio presentation ID, this indicates audio presentation ID is not selected.
      */
-    private static final int UNKNOWN_ID = -1;
+    public static final int PRESENTATION_ID_UNKNOWN = -1;
+
+    /**
+     * Unknown audio program ID, this indicates audio program ID is not selected.
+     */
+    public static final int PROGRAM_ID_UNKNOWN = -1;
 
     /**
      * This allows an application developer to construct an AudioPresentation object with all the
@@ -191,7 +199,7 @@
                              boolean audioDescriptionAvailable,
                              boolean spokenSubtitlesAvailable,
                              boolean dialogueEnhancementAvailable,
-                             @NonNull Map<ULocale, CharSequence> labels) {
+                             @NonNull Map<ULocale, String> labels) {
         mPresentationId = presentationId;
         mProgramId = programId;
         mLanguage = language;
@@ -199,7 +207,18 @@
         mAudioDescriptionAvailable = audioDescriptionAvailable;
         mSpokenSubtitlesAvailable = spokenSubtitlesAvailable;
         mDialogueEnhancementAvailable = dialogueEnhancementAvailable;
-        mLabels = new HashMap<ULocale, CharSequence>(labels);
+        mLabels = new HashMap<ULocale, String>(labels);
+    }
+
+    private AudioPresentation(@NonNull Parcel in) {
+        mPresentationId = in.readInt();
+        mProgramId = in.readInt();
+        mLanguage = in.readSerializable(ULocale.class.getClassLoader(), ULocale.class);
+        mMasteringIndication = in.readInt();
+        mAudioDescriptionAvailable = in.readBoolean();
+        mSpokenSubtitlesAvailable = in.readBoolean();
+        mDialogueEnhancementAvailable = in.readBoolean();
+        mLabels = in.readSerializable(HashMap.class.getClassLoader(), HashMap.class);
     }
 
     /**
@@ -225,13 +244,13 @@
      */
     public Map<Locale, String> getLabels() {
         Map<Locale, String> localeLabels = new HashMap<Locale, String>(mLabels.size());
-        for (Map.Entry<ULocale, CharSequence> entry : mLabels.entrySet()) {
-            localeLabels.put(entry.getKey().toLocale(), entry.getValue().toString());
+        for (Map.Entry<ULocale, String> entry : mLabels.entrySet()) {
+            localeLabels.put(entry.getKey().toLocale(), entry.getValue());
         }
         return localeLabels;
     }
 
-    private Map<ULocale, CharSequence> getULabels() {
+    private Map<ULocale, String> getULabels() {
         return mLabels;
     }
 
@@ -335,13 +354,13 @@
      */
     public static final class Builder {
         private final int mPresentationId;
-        private int mProgramId = UNKNOWN_ID;
+        private int mProgramId = PROGRAM_ID_UNKNOWN;
         private ULocale mLanguage = new ULocale("");
         private int mMasteringIndication = MASTERING_NOT_INDICATED;
         private boolean mAudioDescriptionAvailable = false;
         private boolean mSpokenSubtitlesAvailable = false;
         private boolean mDialogueEnhancementAvailable = false;
-        private Map<ULocale, CharSequence> mLabels = new HashMap<ULocale, CharSequence>();
+        private HashMap<ULocale, String> mLabels = new HashMap<ULocale, String>();
 
         /**
          * Create a {@link Builder}. Any field that should be included in the
@@ -402,7 +421,10 @@
          * @param labels Text label indexed by its locale corresponding to the language code.
          */
         public @NonNull Builder setLabels(@NonNull Map<ULocale, CharSequence> labels) {
-            mLabels = new HashMap<ULocale, CharSequence>(labels);
+            mLabels.clear();
+            for (Map.Entry<ULocale, CharSequence> entry : labels.entrySet()) {
+                mLabels.put(entry.getKey(), entry.getValue().toString());
+            }
             return this;
         }
 
@@ -448,4 +470,35 @@
                                            mDialogueEnhancementAvailable, mLabels);
         }
     }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(getPresentationId());
+        dest.writeInt(getProgramId());
+        dest.writeSerializable(getULocale());
+        dest.writeInt(getMasteringIndication());
+        dest.writeBoolean(hasAudioDescription());
+        dest.writeBoolean(hasSpokenSubtitles());
+        dest.writeBoolean(hasDialogueEnhancement());
+        dest.writeSerializable(mLabels);
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<AudioPresentation> CREATOR =
+            new Parcelable.Creator<AudioPresentation>() {
+            @Override
+            public AudioPresentation createFromParcel(@NonNull Parcel in) {
+                return new AudioPresentation(in);
+            }
+
+            @Override
+            public AudioPresentation[] newArray(int size) {
+                return new AudioPresentation[size];
+            }
+    };
 }
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
index d55d287..c4f60c3 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -17,6 +17,7 @@
 package android.media.tv;
 
 import android.content.ComponentName;
+import android.media.AudioPresentation;
 import android.media.tv.AdBuffer;
 import android.media.tv.AdResponse;
 import android.media.tv.AitInfo;
@@ -37,6 +38,8 @@
     void onSessionReleased(int seq);
     void onSessionEvent(in String name, in Bundle args, int seq);
     void onChannelRetuned(in Uri channelUri, int seq);
+    void onAudioPresentationsChanged(in List<AudioPresentation> AudioPresentations, int seq);
+    void onAudioPresentationSelected(int presentationId, int programId, int seq);
     void onTracksChanged(in List<TvTrackInfo> tracks, int seq);
     void onTrackSelected(int type, in String trackId, int seq);
     void onVideoAvailable(int seq);
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index f7c1e3c..746cfa2 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -16,9 +16,11 @@
 
 package android.media.tv;
 
+import android.content.AttributionSource;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.graphics.Rect;
+import android.media.AudioPresentation;
 import android.media.PlaybackParams;
 import android.media.tv.AdBuffer;
 import android.media.tv.AdRequest;
@@ -63,7 +65,7 @@
     void addBlockedRating(in String rating, int userId);
     void removeBlockedRating(in String rating, int userId);
 
-    void createSession(in ITvInputClient client, in String inputId, boolean isRecordingSession,
+    void createSession(in ITvInputClient client, in String inputId, in AttributionSource tvAppAttributionSource, boolean isRecordingSession,
             int seq, int userId);
     void releaseSession(in IBinder sessionToken, int userId);
     int getClientPid(in String sessionId);
@@ -77,6 +79,8 @@
     void tune(in IBinder sessionToken, in Uri channelUri, in Bundle params, int userId);
     void setCaptionEnabled(in IBinder sessionToken, boolean enabled, int userId);
     void selectTrack(in IBinder sessionToken, int type, in String trackId, int userId);
+    void selectAudioPresentation(in IBinder sessionToken, int presentationId, int programId,
+            int userId);
 
     void setInteractiveAppNotificationEnabled(in IBinder sessionToken, boolean enabled, int userId);
 
diff --git a/media/java/android/media/tv/ITvInputService.aidl b/media/java/android/media/tv/ITvInputService.aidl
index 64a23a2..be73c0c 100755
--- a/media/java/android/media/tv/ITvInputService.aidl
+++ b/media/java/android/media/tv/ITvInputService.aidl
@@ -16,6 +16,7 @@
 
 package android.media.tv;
 
+import android.content.AttributionSource;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.media.tv.ITvInputServiceCallback;
 import android.media.tv.ITvInputSessionCallback;
@@ -30,7 +31,7 @@
     oneway void registerCallback(in ITvInputServiceCallback callback);
     oneway void unregisterCallback(in ITvInputServiceCallback callback);
     oneway void createSession(in InputChannel channel, in ITvInputSessionCallback callback,
-            in String inputId, in String sessionId);
+            in String inputId, in String sessionId, in AttributionSource tvAppAttributionSource);
     oneway void createRecordingSession(in ITvInputSessionCallback callback, in String inputId,
             in String sessionId);
     List<String> getAvailableExtensionInterfaceNames();
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index 326b98d..e6c09a9 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -17,6 +17,7 @@
 package android.media.tv;
 
 import android.graphics.Rect;
+import android.media.AudioPresentation;
 import android.media.PlaybackParams;
 import android.media.tv.AdBuffer;
 import android.media.tv.AdRequest;
@@ -41,6 +42,7 @@
     void setVolume(float volume);
     void tune(in Uri channelUri, in Bundle params);
     void setCaptionEnabled(boolean enabled);
+    void selectAudioPresentation(int presentationId, int programId);
     void selectTrack(int type, in String trackId);
 
     void setInteractiveAppNotificationEnabled(boolean enable);
diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl
index 8216622..0111f09 100644
--- a/media/java/android/media/tv/ITvInputSessionCallback.aidl
+++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl
@@ -16,6 +16,7 @@
 
 package android.media.tv;
 
+import android.media.AudioPresentation;
 import android.media.tv.AdBuffer;
 import android.media.tv.AdResponse;
 import android.media.tv.AitInfo;
@@ -34,6 +35,8 @@
     void onSessionCreated(ITvInputSession session, in IBinder hardwareSessionToken);
     void onSessionEvent(in String name, in Bundle args);
     void onChannelRetuned(in Uri channelUri);
+    void onAudioPresentationsChanged(in List<AudioPresentation> tvAudioPresentations);
+    void onAudioPresentationSelected(int presentationId, int programId);
     void onTracksChanged(in List<TvTrackInfo> tracks);
     void onTrackSelected(int type, in String trackId);
     void onVideoAvailable();
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 634f102..847762f 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -30,7 +30,6 @@
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
 import android.view.Surface;
-
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
 
@@ -75,6 +74,7 @@
     private static final int DO_SET_IAPP_NOTIFICATION_ENABLED = 26;
     private static final int DO_REQUEST_AD = 27;
     private static final int DO_NOTIFY_AD_BUFFER = 28;
+    private static final int DO_SELECT_AUDIO_PRESENTATION = 29;
 
     private final boolean mIsRecordingSession;
     private final HandlerCaller mCaller;
@@ -240,6 +240,12 @@
                 mTvInputRecordingSessionImpl.resumeRecording((Bundle) msg.obj);
                 break;
             }
+            case DO_SELECT_AUDIO_PRESENTATION: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                mTvInputSessionImpl.selectAudioPresentation(args.argi1, args.argi2);
+                args.recycle();
+                break;
+            }
             case DO_REQUEST_BROADCAST_INFO: {
                 mTvInputSessionImpl.requestBroadcastInfo((BroadcastInfoRequest) msg.obj);
                 break;
@@ -275,8 +281,8 @@
                         + "Consider handling the tune request in a separate thread.");
             }
             if (durationMs > EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS) {
-                throw new RuntimeException("Too much time to handle a request. (type=" + msg.what +
-                        ", " + durationMs + "ms > " + EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS + "ms).");
+                throw new RuntimeException("Too much time to handle a request. (type=" + msg.what
+                    + ", " + durationMs + "ms > " + EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS + "ms).");
             }
         }
     }
@@ -323,6 +329,12 @@
     }
 
     @Override
+    public void selectAudioPresentation(int presentationId, int programId) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageII(DO_SELECT_AUDIO_PRESENTATION,
+                                                             presentationId, programId));
+    }
+
+    @Override
     public void selectTrack(int type, String trackId) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_SELECT_TRACK, type, trackId));
     }
@@ -436,7 +448,7 @@
     }
 
     private final class TvInputEventReceiver extends InputEventReceiver {
-        public TvInputEventReceiver(InputChannel inputChannel, Looper looper) {
+        TvInputEventReceiver(InputChannel inputChannel, Looper looper) {
             super(inputChannel, looper);
         }
 
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index e8127df..a4e2212 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -26,11 +26,13 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.content.AttributionSource;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Rect;
 import android.media.AudioDeviceInfo;
 import android.media.AudioFormat.Encoding;
+import android.media.AudioPresentation;
 import android.media.PlaybackParams;
 import android.media.tv.interactive.TvInteractiveAppManager;
 import android.net.Uri;
@@ -54,9 +56,7 @@
 import android.view.KeyEvent;
 import android.view.Surface;
 import android.view.View;
-
 import com.android.internal.util.Preconditions;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -550,6 +550,27 @@
         }
 
         /**
+         * This is called when the audio presentation information of the session has been changed.
+         *
+         * @param session A {@link TvInputManager.Session} associated with this callback.
+         * @param audioPresentations An updated list of selectable audio presentations.
+         */
+        public void onAudioPresentationsChanged(Session session,
+                List<AudioPresentation> audioPresentations) {
+        }
+
+        /**
+         * This is called when an audio presentation is selected.
+         *
+         * @param session A {@link TvInputManager.Session} associated with this callback.
+         * @param presentationId The ID of the selected audio presentation.
+         * @param programId The ID of the program providing the selected audio presentation.
+         */
+        public void onAudioPresentationSelected(Session session, int presentationId,
+                int programId) {
+        }
+
+        /**
          * This is called when the track information of the session has been changed.
          *
          * @param session A {@link TvInputManager.Session} associated with this callback.
@@ -777,6 +798,25 @@
             });
         }
 
+        void postAudioPresentationsChanged(final List<AudioPresentation> audioPresentations) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onAudioPresentationsChanged(mSession, audioPresentations);
+                }
+            });
+        }
+
+        void postAudioPresentationSelected(final int presentationId, final int programId) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onAudioPresentationSelected(mSession, presentationId,
+                            programId);
+                }
+            });
+        }
+
         void postTracksChanged(final List<TvTrackInfo> tracks) {
             mHandler.post(new Runnable() {
                 @Override
@@ -1234,6 +1274,36 @@
                     record.postChannelRetuned(channelUri);
                 }
             }
+            @Override
+            public void onAudioPresentationsChanged(List<AudioPresentation> audioPresentations,
+                    int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    if (record.mSession.updateAudioPresentations(audioPresentations)) {
+                        record.postAudioPresentationsChanged(audioPresentations);
+                    }
+                }
+            }
+
+            @Override
+            public void onAudioPresentationSelected(int presentationId, int programId, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    if (record.mSession.updateAudioPresentationSelection(presentationId,
+                            programId)) {
+                        record.postAudioPresentationSelected(presentationId, programId);
+                    }
+                }
+            }
+
 
             @Override
             public void onTracksChanged(List<TvTrackInfo> tracks, int seq) {
@@ -1910,13 +1980,15 @@
      * of the given TV input.
      *
      * @param inputId The ID of the TV input.
+     * @param tvAppAttributionSource The Attribution Source of the TV App.
      * @param callback A callback used to receive the created session.
      * @param handler A {@link Handler} that the session creation will be delivered to.
      * @hide
      */
-    public void createSession(@NonNull String inputId, @NonNull final SessionCallback callback,
-            @NonNull Handler handler) {
-        createSessionInternal(inputId, false, callback, handler);
+    public void createSession(@NonNull String inputId,
+            @NonNull AttributionSource tvAppAttributionSource,
+            @NonNull final SessionCallback callback, @NonNull Handler handler) {
+        createSessionInternal(inputId, tvAppAttributionSource, false, callback, handler);
     }
 
     /**
@@ -1941,7 +2013,7 @@
      * @param useCase the use case type of the client.
      *        {@see TvInputService#PriorityHintUseCaseType}.
      * @param sessionId the unique id of the session owned by the client.
-     *        {@see TvInputService#onCreateSession(String, String)}.
+     *        {@see TvInputService#onCreateSession(String, String, AttributionSource)}.
      *
      * @return the use case priority value for the given use case type and the client's foreground
      *         or background status.
@@ -1992,11 +2064,11 @@
      */
     public void createRecordingSession(@NonNull String inputId,
             @NonNull final SessionCallback callback, @NonNull Handler handler) {
-        createSessionInternal(inputId, true, callback, handler);
+        createSessionInternal(inputId, null, true, callback, handler);
     }
 
-    private void createSessionInternal(String inputId, boolean isRecordingSession,
-            SessionCallback callback, Handler handler) {
+    private void createSessionInternal(String inputId, AttributionSource tvAppAttributionSource,
+            boolean isRecordingSession, SessionCallback callback, Handler handler) {
         Preconditions.checkNotNull(inputId);
         Preconditions.checkNotNull(callback);
         Preconditions.checkNotNull(handler);
@@ -2005,7 +2077,8 @@
             int seq = mNextSeq++;
             mSessionCallbackRecordMap.put(seq, record);
             try {
-                mService.createSession(mClient, inputId, isRecordingSession, seq, mUserId);
+                mService.createSession(
+                        mClient, inputId, tvAppAttributionSource, isRecordingSession, seq, mUserId);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -2176,8 +2249,8 @@
      * @param deviceId The device ID to acquire Hardware for.
      * @param info The TV input which will use the acquired Hardware.
      * @param tvInputSessionId a String returned to TIS when the session was created.
-     *        {@see TvInputService#onCreateSession(String, String)}. If null, the client will be
-     *        treated as a background app.
+     *        {@see TvInputService#onCreateSession(String, String, AttributionSource)}. If null, the
+     *        client will be treated as a background app.
      * @param priorityHint The use case of the client. {@see TvInputService#PriorityHintUseCaseType}
      * @param executor the executor on which the listener would be invoked.
      * @param callback A callback to receive updates on Hardware.
@@ -2399,12 +2472,18 @@
 
         private final Object mMetadataLock = new Object();
         // @GuardedBy("mMetadataLock")
+        private final List<AudioPresentation> mAudioPresentations = new ArrayList<>();
+        // @GuardedBy("mMetadataLock")
         private final List<TvTrackInfo> mAudioTracks = new ArrayList<>();
         // @GuardedBy("mMetadataLock")
         private final List<TvTrackInfo> mVideoTracks = new ArrayList<>();
         // @GuardedBy("mMetadataLock")
         private final List<TvTrackInfo> mSubtitleTracks = new ArrayList<>();
         // @GuardedBy("mMetadataLock")
+        private int mSelectedAudioProgramId = AudioPresentation.PROGRAM_ID_UNKNOWN;
+        // @GuardedBy("mMetadataLock")
+        private int mSelectedAudioPresentationId = AudioPresentation.PRESENTATION_ID_UNKNOWN;
+        // @GuardedBy("mMetadataLock")
         private String mSelectedAudioTrackId;
         // @GuardedBy("mMetadataLock")
         private String mSelectedVideoTrackId;
@@ -2552,9 +2631,12 @@
                 return;
             }
             synchronized (mMetadataLock) {
+                mAudioPresentations.clear();
                 mAudioTracks.clear();
                 mVideoTracks.clear();
                 mSubtitleTracks.clear();
+                mSelectedAudioProgramId = AudioPresentation.PROGRAM_ID_UNKNOWN;
+                mSelectedAudioPresentationId = AudioPresentation.PRESENTATION_ID_UNKNOWN;
                 mSelectedAudioTrackId = null;
                 mSelectedVideoTrackId = null;
                 mSelectedSubtitleTrackId = null;
@@ -2586,6 +2668,119 @@
         }
 
         /**
+         * Selects an audio presentation
+         *
+         * @param presentationId The ID of the audio presentation to select.
+         * @param programId The ID of the program offering the selected audio presentation.
+         * @see #getAudioPresentations
+         */
+        public void selectAudioPresentation(int presentationId, int programId) {
+            synchronized (mMetadataLock) {
+                if (presentationId != AudioPresentation.PRESENTATION_ID_UNKNOWN
+                        && !containsAudioPresentation(mAudioPresentations, presentationId)) {
+                    Log.w(TAG, "Invalid audio presentation id: " + presentationId);
+                    return;
+                }
+            }
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.selectAudioPresentation(mToken, presentationId, programId, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        private boolean containsAudioPresentation(List<AudioPresentation> audioPresentations,
+                    int presentationId) {
+            synchronized (mMetadataLock) {
+                for (AudioPresentation audioPresentation : audioPresentations) {
+                    if (audioPresentation.getPresentationId() == presentationId) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+        }
+
+        /**
+         * Returns a list of audio presentations.
+         *
+         * @return the list of audio presentations.
+         * Returns empty AudioPresentation list if no presentations are available.
+         */
+        public List<AudioPresentation> getAudioPresentations() {
+            synchronized (mMetadataLock) {
+                if (mAudioPresentations == null) {
+                    return new ArrayList<AudioPresentation>();
+                }
+                return new ArrayList<AudioPresentation>(mAudioPresentations);
+            }
+        }
+
+        /**
+         * Returns the program ID of the selected audio presentation.
+         *
+         * @return The ID of the program providing the selected audio presentation.
+         * Returns {@value AudioPresentation.PROGRAM_ID_UNKNOWN} if no audio presentation has
+         * been selected from a program.
+         * @see #selectAudioPresentation
+         */
+        public int getSelectedProgramId() {
+            synchronized (mMetadataLock) {
+                return mSelectedAudioProgramId;
+            }
+        }
+
+        /**
+         * Returns the presentation ID of the selected audio presentation.
+         *
+         * @return The ID of the selected audio presentation.
+         * Returns {@value AudioPresentation.PRESENTATION_ID_UNKNOWN} if no audio presentation
+         * has been selected.
+         * @see #selectAudioPresentation
+         */
+        public int getSelectedAudioPresentationId() {
+            synchronized (mMetadataLock) {
+                return mSelectedAudioPresentationId;
+            }
+        }
+
+        /**
+         * Responds to onAudioPresentationsChanged() and updates the internal audio presentation
+         * information.
+         * @return true if there is an update.
+         */
+        boolean updateAudioPresentations(List<AudioPresentation> audioPresentations) {
+            synchronized (mMetadataLock) {
+                mAudioPresentations.clear();
+                for (AudioPresentation presentation : audioPresentations) {
+                    mAudioPresentations.add(presentation);
+                }
+                return !mAudioPresentations.isEmpty();
+            }
+        }
+
+        /**
+         * Responds to onAudioPresentationSelected() and updates the internal audio presentation
+         * selection information.
+         * @return true if there is an update.
+         */
+        boolean updateAudioPresentationSelection(int presentationId, int programId) {
+            synchronized (mMetadataLock) {
+                if ((programId != mSelectedAudioProgramId)
+                        || (presentationId != mSelectedAudioPresentationId)) {
+                    mSelectedAudioPresentationId = presentationId;
+                    mSelectedAudioProgramId = programId;
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /**
          * Selects a track.
          *
          * @param type The type of the track to select. The type can be
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
old mode 100755
new mode 100644
index 15f511b..b769757
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -26,11 +26,13 @@
 import android.app.ActivityManager;
 import android.app.Service;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.AttributionSource;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.AudioPresentation;
 import android.media.PlaybackParams;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -57,10 +59,8 @@
 import android.view.WindowManager;
 import android.view.accessibility.CaptioningManager;
 import android.widget.FrameLayout;
-
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.Preconditions;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -171,7 +171,7 @@
 
             @Override
             public void createSession(InputChannel channel, ITvInputSessionCallback cb,
-                    String inputId, String sessionId) {
+                    String inputId, String sessionId, AttributionSource tvAppAttributionSource) {
                 if (channel == null) {
                     Log.w(TAG, "Creating session without input channel");
                 }
@@ -183,6 +183,7 @@
                 args.arg2 = cb;
                 args.arg3 = inputId;
                 args.arg4 = sessionId;
+                args.arg5 = tvAppAttributionSource;
                 mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION,
                         args).sendToTarget();
             }
@@ -370,6 +371,24 @@
     }
 
     /**
+     * Returns a concrete implementation of {@link Session}.
+     *
+     * <p>For any apps that needs sessionId to request tuner resources from TunerResourceManager and
+     * needs to specify custom AttributionSource to AudioTrack, it needs to override this method to
+     * get the sessionId and AttrubutionSource passed. When no overriding, this method calls {@link
+     * #onCreateSession(String, String)} defaultly.
+     *
+     * @param inputId The ID of the TV input associated with the session.
+     * @param sessionId the unique sessionId created by TIF when session is created.
+     * @param tvAppAttributionSource The Attribution Source of the TV App.
+     */
+    @Nullable
+    public Session onCreateSession(@NonNull String inputId, @NonNull String sessionId,
+            @NonNull AttributionSource tvAppAttributionSource) {
+        return onCreateSession(inputId, sessionId);
+    }
+
+    /**
      * Returns a concrete implementation of {@link RecordingSession}.
      *
      * <p>For any apps that needs sessionId to request tuner resources from TunerResourceManager,
@@ -739,6 +758,74 @@
         }
 
         /**
+         * Sends an updated list of all audio presentations available from a Next Generation Audio
+         * service. This is used by the framework to maintain the audio presentation information for
+         * a given track of {@link TvTrackInfo#TYPE_AUDIO}, which in turn is used by
+         * {@link TvView#getAudioPresentations} for the application to retrieve metadata for the
+         * current audio track. The TV input service must call this method as soon as the audio
+         * track presentation information becomes available or is updated. Note that in a case
+         * where a part of the information for the current track is updated, it is not necessary
+         * to create a new {@link TvTrackInfo} object with a different track ID.
+         *
+         * @param audioPresentations A list of audio presentation information pertaining to the
+         * selected track.
+         */
+        public void notifyAudioPresentationChanged(@NonNull final List<AudioPresentation>
+                audioPresentations) {
+            final List<AudioPresentation> ap = new ArrayList<>(audioPresentations);
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) {
+                            Log.d(TAG, "notifyAudioPresentationsChanged");
+                        }
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onAudioPresentationsChanged(ap);
+                        }
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "error in notifyAudioPresentationsChanged", e);
+                    }
+                }
+            });
+        }
+
+        /**
+         * Sends the presentation and program IDs of the selected audio presentation. This is used
+         * to inform the application that a specific audio presentation is selected. The TV input
+         * service must call this method as soon as an audio presentation is selected either by
+         * default or in response to a call to {@link #onSelectTrack}. The selected audio
+         * presentation ID for a currently selected audio track is maintained in the framework until
+         * the next call to this method even after the entire audio presentation list for the track
+         * is updated (but is reset when the session is tuned to a new channel), so care must be
+         * taken not to result in an obsolete track audio presentation ID.
+         *
+         * @param presentationId The ID of the selected audio presentation for the current track.
+         * @param programId The ID of the program providing the selected audio presentation.
+         * @see #onSelectAudioPresentation
+         */
+        public void notifyAudioPresentationSelected(final int presentationId, final int programId) {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) {
+                            Log.d(TAG, "notifyAudioPresentationSelected");
+                        }
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onAudioPresentationSelected(presentationId, programId);
+                        }
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "error in notifyAudioPresentationSelected", e);
+                    }
+                }
+            });
+        }
+
+
+        /**
          * Informs the application that the user is allowed to watch the current program content.
          *
          * <p>Each TV input service is required to query the system whether the user is allowed to
@@ -1128,7 +1215,7 @@
         public abstract void onSetStreamVolume(@FloatRange(from = 0.0, to = 1.0) float volume);
 
         /**
-         * called when broadcast info is requested.
+         * Called when broadcast info is requested.
          *
          * @param request broadcast info request
          */
@@ -1136,7 +1223,7 @@
         }
 
         /**
-         * called when broadcast info is removed.
+         * Called when broadcast info is removed.
          */
         public void onRemoveBroadcastInfo(int requestId) {
         }
@@ -1246,6 +1333,23 @@
         }
 
         /**
+         * Selects an audio presentation.
+         *
+         * <p>On successfully selecting the audio presentation,
+         * {@link #notifyAudioPresentationSelected} is invoked to provide updated information about
+         * the selected audio presentation to applications.
+         *
+         * @param presentationId The ID of the audio presentation to select.
+         * @param programId The ID of the program providing the selected audio presentation.
+         * @return {@code true} if the audio presentation selection was successful,
+         *         {@code false} otherwise.
+         * @see #notifyAudioPresentationSelected
+         */
+        public boolean onSelectAudioPresentation(int presentationId, int programId) {
+            return false;
+        }
+
+        /**
          * Processes a private command sent from the application to the TV input. This can be used
          * to provide domain-specific features that are only known between certain TV inputs and
          * their clients.
@@ -1579,6 +1683,13 @@
         }
 
         /**
+         * Calls {@link #onSelectAudioPresentation}.
+         */
+        void selectAudioPresentation(int presentationId, int programId) {
+            onSelectAudioPresentation(presentationId, programId);
+        }
+
+        /**
          * Calls {@link #onSelectTrack}.
          */
         void selectTrack(int type, String trackId) {
@@ -2497,8 +2608,10 @@
                     ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
                     String inputId = (String) args.arg3;
                     String sessionId = (String) args.arg4;
+                    AttributionSource tvAppAttributionSource = (AttributionSource) args.arg5;
                     args.recycle();
-                    Session sessionImpl = onCreateSession(inputId, sessionId);
+                    Session sessionImpl =
+                            onCreateSession(inputId, sessionId, tvAppAttributionSource);
                     if (sessionImpl == null) {
                         try {
                             // Failed to create a session.
@@ -2534,7 +2647,7 @@
                         proxySession.mServiceHandler = mServiceHandler;
                         TvInputManager manager = (TvInputManager) getSystemService(
                                 Context.TV_INPUT_SERVICE);
-                        manager.createSession(hardwareInputId,
+                        manager.createSession(hardwareInputId, tvAppAttributionSource,
                                 proxySession.mHardwareSessionCallback, mServiceHandler);
                     } else {
                         SomeArgs someArgs = SomeArgs.obtain();
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 2fdbc3b..c7a63ac 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.content.AttributionSource;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
@@ -30,6 +31,7 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
+import android.media.AudioPresentation;
 import android.media.PlaybackParams;
 import android.media.tv.TvInputManager.Session;
 import android.media.tv.TvInputManager.Session.FinishedInputEventCallback;
@@ -51,9 +53,9 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewRootImpl;
-
 import java.lang.ref.WeakReference;
 import java.util.ArrayDeque;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Queue;
 
@@ -111,6 +113,7 @@
     private int mSurfaceViewTop;
     private int mSurfaceViewBottom;
     private TimeShiftPositionCallback mTimeShiftPositionCallback;
+    private AttributionSource mTvAppAttributionSource;
 
     private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
         @Override
@@ -185,6 +188,7 @@
         mDefStyleAttr = defStyleAttr;
         resetSurfaceView();
         mTvInputManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE);
+        mTvAppAttributionSource = getContext().getAttributionSource();
     }
 
     /**
@@ -304,6 +308,22 @@
     }
 
     /**
+     * Override default attribution source of TV App.
+     *
+     * <p>An attribution source of TV App is used to attribute work to TV Input Service.
+     * The default attribution source is created by {@link Context#getAttributionSource()}.
+     * Call this method before calling {@link #tune(String, Uri, Bundle)} or {@link
+     * #timeShiftPlay(String, Uri)} to override the default attribution source.
+     *
+     * @param tvAppAttributionSource The attribution source of the TV App.
+     */
+    public void overrideTvAppAttributionSource(@NonNull AttributionSource tvAppAttributionSource) {
+        if (tvAppAttributionSource != null) {
+            mTvAppAttributionSource = tvAppAttributionSource;
+        }
+    }
+
+    /**
      * Tunes to a given channel.
      *
      * @param inputId The ID of the TV input for the given channel.
@@ -355,7 +375,8 @@
             // is obsolete and should ignore it.
             mSessionCallback = new MySessionCallback(inputId, channelUri, params);
             if (mTvInputManager != null) {
-                mTvInputManager.createSession(inputId, mSessionCallback, mHandler);
+                mTvInputManager.createSession(
+                        inputId, mTvAppAttributionSource, mSessionCallback, mHandler);
             }
         }
     }
@@ -434,6 +455,35 @@
     }
 
     /**
+     * Selects an audio presentation.
+     *
+     * @param presentationId The ID of the audio presentation to select.
+     * @param programId The ID of the program providing the selected audio presentation.
+     * @see #getAudioPresentations
+     */
+    public void selectAudioPresentation(int presentationId, int programId) {
+        if (mSession != null) {
+            mSession.selectAudioPresentation(presentationId, programId);
+        }
+    }
+
+    /**
+     * Returns the list of audio presentations from the selected track of type
+     * {@link TvTrackInfo#TYPE_AUDIO}.
+     *
+     * @return the list of audio presentations from the selected audio track, or an empty list if no
+     * audio presentations are available.
+     * @see #selectAudioPresentation
+     */
+    @NonNull
+    public List<AudioPresentation> getAudioPresentations() {
+        if (mSession == null) {
+            return new ArrayList<AudioPresentation>();
+        }
+        return mSession.getAudioPresentations();
+    }
+
+    /**
      * Selects a track.
      *
      * @param type The type of the track to select. The type can be {@link TvTrackInfo#TYPE_AUDIO},
@@ -526,7 +576,8 @@
             resetInternal();
             mSessionCallback = new MySessionCallback(inputId, recordedProgramUri);
             if (mTvInputManager != null) {
-                mTvInputManager.createSession(inputId, mSessionCallback, mHandler);
+                mTvInputManager.createSession(
+                        inputId, mTvAppAttributionSource, mSessionCallback, mHandler);
             }
         }
     }
@@ -977,6 +1028,27 @@
         }
 
         /**
+         * This is called when the audio presentation information has been changed.
+         *
+         * @param inputId The ID of the TV input bound to this view.
+         * @param audioPresentations A list of updated audio presentation information.
+         */
+        public void onAudioPresentationsChanged(@NonNull String inputId,
+                @NonNull List<AudioPresentation> audioPresentations) {
+        }
+
+        /**
+         * This is called when audio presentation selection has changed.
+         *
+         * @param inputId The ID of the TV input bound to this view.
+         * @param presentationId The ID of the audio presentation selected.
+         * @param programId The ID of the program providing the selected audio presentation.
+         */
+        public void onAudioPresentationSelected(@NonNull String inputId, int presentationId,
+                int programId) {
+        }
+
+        /**
          * This is called when the track information has been changed.
          *
          * @param inputId The ID of the TV input bound to this view.
@@ -1242,6 +1314,37 @@
         }
 
         @Override
+        public void onAudioPresentationsChanged(Session session,
+                List<AudioPresentation> audioPresentations) {
+            if (DEBUG) {
+                Log.d(TAG, "onAudioPresentationsChanged(" + audioPresentations + ")");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onAudioPresentationsChanged - session not created");
+                return;
+            }
+            if (mCallback != null) {
+                mCallback.onAudioPresentationsChanged(mInputId, audioPresentations);
+            }
+        }
+
+        @Override
+        public void onAudioPresentationSelected(Session session, int presentationId,
+                int programId) {
+            if (DEBUG) {
+                Log.d(TAG, "onAudioPresentationSelected(presentationId=" + presentationId
+                            + ", programId=" + programId + ")");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onAudioPresentationSelected - session not created");
+                return;
+            }
+            if (mCallback != null) {
+                mCallback.onAudioPresentationSelected(mInputId, presentationId, programId);
+            }
+        }
+
+        @Override
         public void onTracksChanged(Session session, List<TvTrackInfo> tracks) {
             if (DEBUG) {
                 Log.d(TAG, "onTracksChanged(" + tracks + ")");
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index ac920d2..7d08b81 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -1297,6 +1297,13 @@
                         return RESULT_UNAVAILABLE;
                     }
                 }
+                if (mFrontendType == FrontendSettings.TYPE_IPTV) {
+                    if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+                            TunerVersionChecker.TUNER_VERSION_3_0,
+                            "Tuner with IPTV Frontend")) {
+                        return RESULT_UNAVAILABLE;
+                    }
+                }
                 if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND,
                           mFrontendLock)) {
                     mScanCallback = scanCallback;
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 35ee3ee9..625e842 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -132,6 +132,11 @@
 #include <aidl/android/hardware/tv/tuner/FrontendIsdbtPartialReceptionFlag.h>
 #include <aidl/android/hardware/tv/tuner/FrontendIsdbtSettings.h>
 #include <aidl/android/hardware/tv/tuner/FrontendIsdbtTimeInterleaveMode.h>
+#include <aidl/android/hardware/tv/tuner/FrontendIptvSettings.h>
+#include <aidl/android/hardware/tv/tuner/FrontendIptvSettingsFec.h>
+#include <aidl/android/hardware/tv/tuner/FrontendIptvSettingsProtocol.h>
+#include <aidl/android/hardware/tv/tuner/FrontendIptvSettingsIgmp.h>
+#include <aidl/android/hardware/tv/tuner/FrontendIptvSettingsFecType.h>
 #include <aidl/android/hardware/tv/tuner/FrontendModulation.h>
 #include <aidl/android/hardware/tv/tuner/FrontendModulationStatus.h>
 #include <aidl/android/hardware/tv/tuner/FrontendRollOff.h>
@@ -283,6 +288,11 @@
 using ::aidl::android::hardware::tv::tuner::FrontendIsdbtPartialReceptionFlag;
 using ::aidl::android::hardware::tv::tuner::FrontendIsdbtSettings;
 using ::aidl::android::hardware::tv::tuner::FrontendIsdbtTimeInterleaveMode;
+using ::aidl::android::hardware::tv::tuner::FrontendIptvSettings;
+using ::aidl::android::hardware::tv::tuner::FrontendIptvSettingsFec;
+using ::aidl::android::hardware::tv::tuner::FrontendIptvSettingsProtocol;
+using ::aidl::android::hardware::tv::tuner::FrontendIptvSettingsIgmp;
+using ::aidl::android::hardware::tv::tuner::FrontendIptvSettingsFecType;
 using ::aidl::android::hardware::tv::tuner::FrontendModulation;
 using ::aidl::android::hardware::tv::tuner::FrontendModulationStatus;
 using ::aidl::android::hardware::tv::tuner::FrontendRollOff;
@@ -3430,6 +3440,106 @@
     return frontendSettings;
 }
 
+static DemuxIpAddress getDemuxIpAddress(JNIEnv *env, const jobject& config, const char* className) {
+    jclass clazz = env->FindClass(className);
+
+    jbyteArray jsrcIpAddress = static_cast<jbyteArray>(
+            env->GetObjectField(config, env->GetFieldID(clazz, "mSrcIpAddress", "[B")));
+    jsize srcSize = env->GetArrayLength(jsrcIpAddress);
+    jbyteArray jdstIpAddress = static_cast<jbyteArray>(
+            env->GetObjectField(config, env->GetFieldID(clazz, "mDstIpAddress", "[B")));
+    jsize dstSize = env->GetArrayLength(jdstIpAddress);
+
+    DemuxIpAddress res;
+
+    if (srcSize != dstSize) {
+        // should never happen. Validated on Java size.
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+            "IP address lengths don't match. srcLength=%d, dstLength=%d", srcSize, dstSize);
+        return res;
+    }
+
+    if (srcSize == IP_V4_LENGTH) {
+        vector<uint8_t> srcAddr;
+        vector<uint8_t> dstAddr;
+        srcAddr.resize(IP_V4_LENGTH);
+        dstAddr.resize(IP_V4_LENGTH);
+        env->GetByteArrayRegion(jsrcIpAddress, 0, srcSize, reinterpret_cast<jbyte *>(&srcAddr[0]));
+        env->GetByteArrayRegion(jdstIpAddress, 0, dstSize, reinterpret_cast<jbyte *>(&dstAddr[0]));
+        res.srcIpAddress.set<DemuxIpAddressIpAddress::Tag::v4>(srcAddr);
+        res.dstIpAddress.set<DemuxIpAddressIpAddress::Tag::v4>(dstAddr);
+    } else if (srcSize == IP_V6_LENGTH) {
+        vector<uint8_t> srcAddr;
+        vector<uint8_t> dstAddr;
+        srcAddr.resize(IP_V6_LENGTH);
+        dstAddr.resize(IP_V6_LENGTH);
+        env->GetByteArrayRegion(jsrcIpAddress, 0, srcSize, reinterpret_cast<jbyte *>(&srcAddr[0]));
+        env->GetByteArrayRegion(jdstIpAddress, 0, dstSize, reinterpret_cast<jbyte *>(&dstAddr[0]));
+        res.srcIpAddress.set<DemuxIpAddressIpAddress::Tag::v6>(srcAddr);
+        res.dstIpAddress.set<DemuxIpAddressIpAddress::Tag::v6>(dstAddr);
+    } else {
+        // should never happen. Validated on Java size.
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+            "Invalid IP address length %d", srcSize);
+        return res;
+    }
+
+    res.srcPort = env->GetIntField(config, env->GetFieldID(clazz, "mSrcPort", "I"));
+    res.dstPort = env->GetIntField(config, env->GetFieldID(clazz, "mDstPort", "I"));
+
+    return res;
+}
+
+static FrontendIptvSettingsFec getIptvFrontendSettingsFec(JNIEnv *env, const jobject &settings) {
+    jclass clazz = env->FindClass("android/media/tv/tuner/frontend/IptvFrontendSettings");
+    jobject fec = env->GetObjectField(settings, env->GetFieldID(clazz, "mFec",
+            "[Landroid/media/tv/tuner/frontend/IptvFrontendSettingsFec;"));
+    jclass fecClazz = env->FindClass("android/media/tv/tuner/frontend/IptvFrontendSettingsFec");
+    FrontendIptvSettingsFecType fecType =
+            static_cast<FrontendIptvSettingsFecType>(
+                    env->GetIntField(fec, env->GetFieldID(fecClazz, "mFec", "I")));
+    int32_t fecColNum = env->GetIntField(fec, env->GetFieldID(fecClazz, "mFecColNum", "I"));
+    int32_t fecRowNum = env->GetIntField(fec, env->GetFieldID(fecClazz, "mFecRowNum", "I"));
+
+    FrontendIptvSettingsFec frontendIptvSettingsFec {
+        .type = fecType,
+        .fecColNum = fecColNum,
+        .fecRowNum = fecRowNum,
+    };
+
+    return frontendIptvSettingsFec;
+}
+
+static FrontendSettings getIptvFrontendSettings(JNIEnv *env, const jobject &settings) {
+    FrontendSettings frontendSettings;
+    const char *clazzName = "android/media/tv/tuner/frontend/IptvFrontendSettings";
+    jclass clazz = env->FindClass(clazzName);
+    FrontendIptvSettingsProtocol protocol =
+            static_cast<FrontendIptvSettingsProtocol>(
+                    env->GetIntField(settings, env->GetFieldID(clazz, "mProtocol", "I")));
+    FrontendIptvSettingsIgmp igmp =
+            static_cast<FrontendIptvSettingsIgmp>(
+                    env->GetIntField(settings, env->GetFieldID(clazz, "mIgmp", "I")));
+    FrontendIptvSettingsFec fec = getIptvFrontendSettingsFec(env, settings);
+    int64_t bitrate = env->GetIntField(settings, env->GetFieldID(clazz, "mBitrate", "J"));
+    jstring contentUrlJString = (jstring) env->GetObjectField(settings, env->GetFieldID(
+                                clazz, "mContentUrl",
+                                "[Landroid/media/tv/tuner/frontend/IptvFrontendSettings;"));
+    const char *contentUrl = env->GetStringUTFChars(contentUrlJString, 0);
+    DemuxIpAddress ipAddr = getDemuxIpAddress(env, settings, clazzName);
+
+    FrontendIptvSettings frontendIptvSettings{
+            .protocol = protocol,
+            .fec = fec,
+            .igmp = igmp,
+            .bitrate = bitrate,
+            .ipAddr = ipAddr,
+            .contentUrl = contentUrl,
+    };
+    frontendSettings.set<FrontendSettings::Tag::iptv>(frontendIptvSettings);
+    return frontendSettings;
+}
+
 static FrontendSettings getFrontendSettings(JNIEnv *env, int type, jobject settings) {
     ALOGV("getFrontendSettings %d", type);
     FrontendType feType = static_cast<FrontendType>(type);
@@ -3454,6 +3564,8 @@
             return getIsdbtFrontendSettings(env, settings);
         case FrontendType::DTMB:
             return getDtmbFrontendSettings(env, settings);
+        case FrontendType::IPTV:
+            return getIptvFrontendSettings(env, settings);
         default:
             // should never happen because a type is associated with a subclass of
             // FrontendSettings and not set by users
@@ -3943,56 +4055,6 @@
     return filterDownloadSettings;
 }
 
-static DemuxIpAddress getDemuxIpAddress(JNIEnv *env, const jobject& config) {
-    jclass clazz = env->FindClass("android/media/tv/tuner/filter/IpFilterConfiguration");
-
-    jbyteArray jsrcIpAddress = static_cast<jbyteArray>(
-            env->GetObjectField(config, env->GetFieldID(clazz, "mSrcIpAddress", "[B")));
-    jsize srcSize = env->GetArrayLength(jsrcIpAddress);
-    jbyteArray jdstIpAddress = static_cast<jbyteArray>(
-            env->GetObjectField(config, env->GetFieldID(clazz, "mDstIpAddress", "[B")));
-    jsize dstSize = env->GetArrayLength(jdstIpAddress);
-
-    DemuxIpAddress res;
-
-    if (srcSize != dstSize) {
-        // should never happen. Validated on Java size.
-        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
-            "IP address lengths don't match. srcLength=%d, dstLength=%d", srcSize, dstSize);
-        return res;
-    }
-
-    if (srcSize == IP_V4_LENGTH) {
-        vector<uint8_t> srcAddr;
-        vector<uint8_t> dstAddr;
-        srcAddr.resize(IP_V4_LENGTH);
-        dstAddr.resize(IP_V4_LENGTH);
-        env->GetByteArrayRegion(jsrcIpAddress, 0, srcSize, reinterpret_cast<jbyte *>(&srcAddr[0]));
-        env->GetByteArrayRegion(jdstIpAddress, 0, dstSize, reinterpret_cast<jbyte *>(&dstAddr[0]));
-        res.srcIpAddress.set<DemuxIpAddressIpAddress::Tag::v4>(srcAddr);
-        res.dstIpAddress.set<DemuxIpAddressIpAddress::Tag::v4>(dstAddr);
-    } else if (srcSize == IP_V6_LENGTH) {
-        vector<uint8_t> srcAddr;
-        vector<uint8_t> dstAddr;
-        srcAddr.resize(IP_V6_LENGTH);
-        dstAddr.resize(IP_V6_LENGTH);
-        env->GetByteArrayRegion(jsrcIpAddress, 0, srcSize, reinterpret_cast<jbyte *>(&srcAddr[0]));
-        env->GetByteArrayRegion(jdstIpAddress, 0, dstSize, reinterpret_cast<jbyte *>(&dstAddr[0]));
-        res.srcIpAddress.set<DemuxIpAddressIpAddress::Tag::v6>(srcAddr);
-        res.dstIpAddress.set<DemuxIpAddressIpAddress::Tag::v6>(dstAddr);
-    } else {
-        // should never happen. Validated on Java size.
-        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
-            "Invalid IP address length %d", srcSize);
-        return res;
-    }
-
-    res.srcPort = env->GetIntField(config, env->GetFieldID(clazz, "mSrcPort", "I"));
-    res.dstPort = env->GetIntField(config, env->GetFieldID(clazz, "mDstPort", "I"));
-
-    return res;
-}
-
 static DemuxFilterSettings getFilterConfiguration(
         JNIEnv *env, int type, int subtype, jobject filterConfigObj) {
     DemuxFilterSettings filterSettings;
@@ -4088,7 +4150,8 @@
             break;
         }
         case DemuxFilterMainType::IP: {
-            DemuxIpAddress ipAddr = getDemuxIpAddress(env, filterConfigObj);
+            DemuxIpAddress ipAddr = getDemuxIpAddress(env, filterConfigObj,
+                                    "android/media/tv/tuner/filter/IpFilterConfiguration");
             DemuxIpFilterSettings ipFilterSettings {
                 .ipAddr = ipAddr,
             };
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
index aad708a..0538e7d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
@@ -42,6 +42,7 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.View;
 import android.view.accessibility.AccessibilityManager;
 
 import androidx.annotation.NonNull;
@@ -66,6 +67,9 @@
     private static final float DEFAULT_MENU_POSITION_X_PERCENT = 1.0f;
 
     @FloatRange(from = 0.0, to = 1.0)
+    private static final float DEFAULT_MENU_POSITION_X_PERCENT_RTL = 0.0f;
+
+    @FloatRange(from = 0.0, to = 1.0)
     private static final float DEFAULT_MENU_POSITION_Y_PERCENT = 0.77f;
     private static final boolean DEFAULT_MOVE_TO_TUCKED_VALUE = false;
     private static final boolean DEFAULT_HAS_SEEN_DOCK_TOOLTIP_VALUE = false;
@@ -226,8 +230,12 @@
         final String absolutePositionString = Prefs.getString(mContext,
                 Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null);
 
+        final float defaultPositionXPercent =
+                mConfiguration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+                        ? DEFAULT_MENU_POSITION_X_PERCENT_RTL
+                        : DEFAULT_MENU_POSITION_X_PERCENT;
         return TextUtils.isEmpty(absolutePositionString)
-                ? new Position(DEFAULT_MENU_POSITION_X_PERCENT, DEFAULT_MENU_POSITION_Y_PERCENT)
+                ? new Position(defaultPositionXPercent, DEFAULT_MENU_POSITION_Y_PERCENT)
                 : Position.fromString(absolutePositionString);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index e42f051..3e5d16a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -878,7 +878,7 @@
         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT,
                 ViewGroup.LayoutParams.MATCH_PARENT,
-                WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
+                WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
                 windowFlags,
                 PixelFormat.TRANSLUCENT);
         lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index e9d7a5b..3319f9d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -49,6 +49,7 @@
 import kotlin.math.sqrt
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flatMapLatest
@@ -163,12 +164,26 @@
 
                         ambientIndicationArea?.alpha = alpha
                         indicationArea.alpha = alpha
-                        startButton.alpha = alpha
-                        endButton.alpha = alpha
                     }
                 }
 
                 launch {
+                    updateButtonAlpha(
+                        view = startButton,
+                        viewModel = viewModel.startButton,
+                        alphaFlow = viewModel.alpha,
+                    )
+                }
+
+                launch {
+                    updateButtonAlpha(
+                        view = endButton,
+                        viewModel = viewModel.endButton,
+                        alphaFlow = viewModel.alpha,
+                    )
+                }
+
+                launch {
                     viewModel.indicationAreaTranslationX.collect { translationX ->
                         indicationArea.translationX = translationX
                         ambientIndicationArea?.translationX = translationX
@@ -321,7 +336,6 @@
             .animate()
             .scaleX(if (viewModel.isSelected) SCALE_SELECTED_BUTTON else 1f)
             .scaleY(if (viewModel.isSelected) SCALE_SELECTED_BUTTON else 1f)
-            .alpha(if (viewModel.isDimmed) DIM_ALPHA else 1f)
             .start()
 
         view.isClickable = viewModel.isClickable
@@ -341,6 +355,17 @@
         view.isSelected = viewModel.isSelected
     }
 
+    private suspend fun updateButtonAlpha(
+        view: View,
+        viewModel: Flow<KeyguardQuickAffordanceViewModel>,
+        alphaFlow: Flow<Float>,
+    ) {
+        combine(viewModel.map { it.isDimmed }, alphaFlow) { isDimmed, alpha ->
+                if (isDimmed) DIM_ALPHA else alpha
+            }
+            .collect { view.alpha = it }
+    }
+
     private class OnTouchListener(
         private val view: View,
         private val viewModel: KeyguardQuickAffordanceViewModel,
diff --git a/rs/jni/Android.bp b/rs/jni/Android.bp
index 9a6fa8e..8a6897c 100644
--- a/rs/jni/Android.bp
+++ b/rs/jni/Android.bp
@@ -51,4 +51,10 @@
         "-Wunreachable-code",
         "-Wno-deprecated-declarations",
     ],
+
+    target: {
+        android_riscv64: {
+            enabled: false,
+        },
+    },
 }
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index e06ce2c9..4ad43fa 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1831,7 +1831,7 @@
             final BatteryStatsImpl bstats = mService.mBatteryStatsService.getActiveStatistics();
             synchronized (bstats) {
                 if (haveNewCpuStats) {
-                    if (bstats.startAddingCpuLocked()) {
+                    if (bstats.startAddingCpuStatsLocked()) {
                         int totalUTime = 0;
                         int totalSTime = 0;
                         final int statsCount = mProcessCpuTracker.countStats();
@@ -1877,9 +1877,10 @@
                         final int irqTime = mProcessCpuTracker.getLastIrqTime();
                         final int softIrqTime = mProcessCpuTracker.getLastSoftIrqTime();
                         final int idleTime = mProcessCpuTracker.getLastIdleTime();
-                        bstats.finishAddingCpuLocked(totalUTime, totalSTime, userTime,
+                        bstats.addCpuStatsLocked(totalUTime, totalSTime, userTime,
                                 systemTime, iowaitTime, irqTime, softIrqTime, idleTime);
                     }
+                    bstats.finishAddingCpuStatsLocked();
                 }
 
                 if (mLastWriteTime < (now - BATTERY_STATS_TIME)) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 42e0978..3759a8b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2396,7 +2396,9 @@
                 }
                 // fallthrough
             default:
-                Slog.w(TAG, "Display " + info + " does not support input device matching.");
+                if (DEBUG) {
+                    Slog.w(TAG, "Display " + info + " does not support input device matching.");
+                }
         }
         return Optional.empty();
     }
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index f8d6c5f..52e20d6 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -635,7 +635,9 @@
             //     which is within the render rate range
             //   - 90hz is not in range as none of the even divisors (i.e. 90, 45, 30)
             //     fall within the acceptable render range.
-            final int divisor = (int) Math.ceil(physicalRefreshRate / summary.maxRenderFrameRate);
+            final int divisor =
+                    (int) Math.ceil((physicalRefreshRate / summary.maxRenderFrameRate)
+                            - FLOAT_TOLERANCE);
             float adjustedPhysicalRefreshRate = physicalRefreshRate / divisor;
             if (adjustedPhysicalRefreshRate < (summary.minRenderFrameRate - FLOAT_TOLERANCE)) {
                 if (mLoggingEnabled) {
@@ -3081,10 +3083,10 @@
 
         @Override
         public boolean supportsFrameRateOverride() {
-            return SurfaceFlingerProperties.enable_frame_rate_override().orElse(false)
+            return SurfaceFlingerProperties.enable_frame_rate_override().orElse(true)
                             && !SurfaceFlingerProperties.frame_rate_override_for_native_rates()
-                                    .orElse(true)
-                            && SurfaceFlingerProperties.frame_rate_override_global().orElse(false);
+                                    .orElse(false)
+                            && SurfaceFlingerProperties.frame_rate_override_global().orElse(true);
         }
 
         private DisplayManager getDisplayManager() {
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index f870c0a..d7306b7 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -119,6 +119,8 @@
         //   are yet to be delivered.
         options.setDeliveryGroupMatchingKey(
                 DREAMING_DELIVERY_GROUP_NAMESPACE, DREAMING_DELIVERY_GROUP_KEY);
+        // This allows the broadcast delivery to be delayed to apps in the Cached state.
+        options.setDeferUntilActive(true);
         return options.toBundle();
     }
 
diff --git a/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java b/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java
index 6e86123..5bdcbb9 100644
--- a/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java
+++ b/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java
@@ -36,6 +36,7 @@
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
+import com.android.internal.os.RoSystemProperties;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
 import com.android.server.UiThread;
@@ -100,6 +101,11 @@
         pw.println("          --reboot (which does a full reboot) or");
         pw.println("          --no-restart (which requires a manual restart)");
         pw.println();
+        pw.println("  is-headless-system-user-mode [-v | --verbose]");
+        pw.println("    Checks whether the device uses headless system user mode.");
+        pw.println("    It returns the effective mode, even when using emulation");
+        pw.println("    (to get the real mode as well, use -v or --verbose)");
+        pw.println();
         pw.println("  is-user-visible [--display DISPLAY_ID] <USER_ID>");
         pw.println("    Checks if the given user is visible in the given display.");
         pw.println("    If the display option is not set, it uses the user's context to check");
@@ -121,6 +127,8 @@
                     return runReportPackageAllowlistProblems();
                 case "set-system-user-mode-emulation":
                     return runSetSystemUserModeEmulation();
+                case "is-headless-system-user-mode":
+                    return runIsHeadlessSystemUserMode();
                 case "is-user-visible":
                     return runIsUserVisible();
                 default:
@@ -407,6 +415,35 @@
         return 0;
     }
 
+    private int runIsHeadlessSystemUserMode() {
+        PrintWriter pw = getOutPrintWriter();
+
+        boolean verbose = false;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-v":
+                case "--verbose":
+                    verbose = true;
+                    break;
+                default:
+                    pw.println("Invalid option: " + opt);
+                    return -1;
+            }
+        }
+
+        boolean isHsum = mService.isHeadlessSystemUserMode();
+        if (!verbose) {
+            // NOTE: do not change output below, as it's used by ITestDevice
+            // (it's ok to change the verbose option though)
+            pw.println(isHsum);
+        } else {
+            pw.printf("effective=%b real=%b\n", isHsum,
+                    RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER);
+        }
+        return 0;
+    }
+
     /**
      * Gets the {@link UserManager} associated with the context of the given user.
      */
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 89c5c9e..bc90c89 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -3925,6 +3925,7 @@
         private final HistoryStepDetails mDetails = new HistoryStepDetails();
 
         private boolean mHasHistoryStepDetails;
+        private boolean mUpdateRequested;
 
         /**
          * Total time (in milliseconds) spent executing in user code.
@@ -3954,15 +3955,20 @@
 
         @Override
         public HistoryStepDetails getHistoryStepDetails() {
-            // Perform a CPU update right after we do this collection, so we have started
-            // collecting good data for the next step.
-            requestImmediateCpuUpdate();
+            if (!mUpdateRequested) {
+                mUpdateRequested = true;
+                // Perform a CPU update right after we do this collection, so we have started
+                // collecting good data for the next step.
+                requestImmediateCpuUpdate();
 
-            if (mPlatformIdleStateCallback != null) {
-                mDetails.statSubsystemPowerState =
-                        mPlatformIdleStateCallback.getSubsystemLowPowerStats();
-                if (DEBUG) Slog.i(TAG, "WRITE SubsystemPowerState:" +
-                        mDetails.statSubsystemPowerState);
+                if (mPlatformIdleStateCallback != null) {
+                    mDetails.statSubsystemPowerState =
+                            mPlatformIdleStateCallback.getSubsystemLowPowerStats();
+                    if (DEBUG) {
+                        Slog.i(TAG,
+                                "WRITE SubsystemPowerState:" + mDetails.statSubsystemPowerState);
+                    }
+                }
             }
 
             if (!mHasHistoryStepDetails) {
@@ -4072,7 +4078,11 @@
             mCurStepStatIrqTimeMs += statIrqTimeMs;
             mCurStepStatSoftIrqTimeMs += statSoftIrqTimeMs;
             mCurStepStatIdleTimeMs += statIdleTimeMs;
+        }
+
+        public void finishAddingCpuLocked() {
             mHasHistoryStepDetails = true;
+            mUpdateRequested = false;
         }
 
         @Override
@@ -4953,13 +4963,13 @@
     }
 
     @GuardedBy("this")
-    public boolean startAddingCpuLocked() {
+    public boolean startAddingCpuStatsLocked() {
         mExternalSync.cancelCpuSyncDueToWakelockChange();
         return mOnBatteryInternal;
     }
 
     @GuardedBy("this")
-    public void finishAddingCpuLocked(int totalUTimeMs, int totalSTimeMs, int statUserTimeMs,
+    public void addCpuStatsLocked(int totalUTimeMs, int totalSTimeMs, int statUserTimeMs,
             int statSystemTimeMs, int statIOWaitTimeMs, int statIrqTimeMs,
             int statSoftIrqTimeMs, int statIdleTimeMs) {
         mStepDetailsCalculator.addCpuStats(totalUTimeMs, totalSTimeMs, statUserTimeMs,
@@ -4967,6 +4977,14 @@
                 statSoftIrqTimeMs, statIdleTimeMs);
     }
 
+    /**
+     * Called after {@link #addCpuStatsLocked} has been invoked for all active apps.
+     */
+    @GuardedBy("this")
+    public void finishAddingCpuStatsLocked() {
+        mStepDetailsCalculator.finishAddingCpuLocked();
+    }
+
     public void noteProcessDiedLocked(int uid, int pid) {
         uid = mapUid(uid);
         Uid u = mUidStats.get(uid);
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
old mode 100755
new mode 100644
index 29b37ce..0928bef
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -24,6 +24,7 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -43,6 +44,7 @@
 import android.graphics.Rect;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.AudioPresentation;
 import android.media.PlaybackParams;
 import android.media.tv.AdBuffer;
 import android.media.tv.AdRequest;
@@ -91,7 +93,6 @@
 import android.util.SparseArray;
 import android.view.InputChannel;
 import android.view.Surface;
-
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
@@ -102,9 +103,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.IoThread;
 import com.android.server.SystemService;
-
 import dalvik.annotation.optimization.NeverCompile;
-
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
@@ -814,7 +813,7 @@
 
     @GuardedBy("mLock")
     private boolean createSessionInternalLocked(ITvInputService service, IBinder sessionToken,
-            int userId) {
+            int userId, AttributionSource tvAppAttributionSource) {
         UserState userState = getOrCreateUserStateLocked(userId);
         SessionState sessionState = userState.sessionStateMap.get(sessionToken);
         if (DEBUG) {
@@ -833,8 +832,8 @@
                 service.createRecordingSession(
                         callback, sessionState.inputId, sessionState.sessionId);
             } else {
-                service.createSession(
-                        channels[1], callback, sessionState.inputId, sessionState.sessionId);
+                service.createSession(channels[1], callback, sessionState.inputId,
+                        sessionState.sessionId, tvAppAttributionSource);
             }
         } catch (RemoteException e) {
             Slog.e(TAG, "error in createSession", e);
@@ -1486,7 +1485,8 @@
 
         @Override
         public void createSession(final ITvInputClient client, final String inputId,
-                boolean isRecordingSession, int seq, int userId) {
+                AttributionSource tvAppAttributionSource, boolean isRecordingSession, int seq,
+                int userId) {
             final int callingUid = Binder.getCallingUid();
             final int callingPid = Binder.getCallingPid();
             final int resolvedUserId = resolveCallingUserId(callingPid, callingUid,
@@ -1557,7 +1557,7 @@
 
                     if (serviceState.service != null) {
                         if (!createSessionInternalLocked(serviceState.service, sessionToken,
-                                resolvedUserId)) {
+                                    resolvedUserId, tvAppAttributionSource)) {
                             removeSessionStateLocked(sessionToken, resolvedUserId);
                         }
                     } else {
@@ -1836,6 +1836,28 @@
         }
 
         @Override
+        public void selectAudioPresentation(IBinder sessionToken, int presentationId,
+                                            int programId, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "selectAudioPresentation");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        getSessionLocked(sessionToken, callingUid,
+                                        resolvedUserId).selectAudioPresentation(
+                                        presentationId, programId);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slog.e(TAG, "error in selectAudioPresentation", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+       @Override
         public void selectTrack(IBinder sessionToken, int type, String trackId, int userId) {
             final int callingUid = Binder.getCallingUid();
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
@@ -3137,7 +3159,8 @@
 
                 // And create sessions, if any.
                 for (IBinder sessionToken : serviceState.sessionTokens) {
-                    if (!createSessionInternalLocked(serviceState.service, sessionToken, mUserId)) {
+                    if (!createSessionInternalLocked(
+                                serviceState.service, sessionToken, mUserId, null)) {
                         tokensToBeRemoved.add(sessionToken);
                     }
                 }
@@ -3360,6 +3383,43 @@
         }
 
         @Override
+        public void onAudioPresentationsChanged(List<AudioPresentation> audioPresentations) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onAudioPresentationChanged(" + audioPresentations + ")");
+                }
+                if (mSessionState.session == null || mSessionState.client == null) {
+                    return;
+                }
+                try {
+                    mSessionState.client.onAudioPresentationsChanged(audioPresentations,
+                                                                     mSessionState.seq);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in onAudioPresentationsChanged", e);
+                }
+            }
+        }
+
+        @Override
+        public void onAudioPresentationSelected(int presentationId, int programId) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onAudioPresentationSelected(presentationId=" + presentationId
+                                                            + ", programId=" + programId + ")");
+                }
+                if (mSessionState.session == null || mSessionState.client == null) {
+                    return;
+                }
+                try {
+                    mSessionState.client.onAudioPresentationSelected(presentationId, programId,
+                                                                     mSessionState.seq);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in onAudioPresentationSelected", e);
+                }
+            }
+        }
+
+        @Override
         public void onTracksChanged(List<TvTrackInfo> tracks) {
             synchronized (mLock) {
                 if (DEBUG) {
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.cpp b/services/core/jni/gnss/GnssMeasurementCallback.cpp
index a1c5708..2982546 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.cpp
+++ b/services/core/jni/gnss/GnssMeasurementCallback.cpp
@@ -58,7 +58,7 @@
 jmethodID method_gnssAgcBuilderBuild;
 jmethodID method_gnssMeasurementsEventBuilderCtor;
 jmethodID method_gnssMeasurementsEventBuilderSetClock;
-jmethodID method_gnssMeasurementsEventBuilderSetFullTracking;
+jmethodID method_gnssMeasurementsEventBuilderSetIsFullTracking;
 jmethodID method_gnssMeasurementsEventBuilderSetMeasurements;
 jmethodID method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls;
 jmethodID method_gnssMeasurementsEventBuilderBuild;
@@ -110,8 +110,8 @@
             env->GetMethodID(class_gnssMeasurementsEventBuilder, "setGnssAutomaticGainControls",
                              "([Landroid/location/GnssAutomaticGainControl;)"
                              "Landroid/location/GnssMeasurementsEvent$Builder;");
-    method_gnssMeasurementsEventBuilderSetFullTracking =
-            env->GetMethodID(class_gnssMeasurementsEventBuilder, "setFullTracking",
+    method_gnssMeasurementsEventBuilderSetIsFullTracking =
+            env->GetMethodID(class_gnssMeasurementsEventBuilder, "setIsFullTracking",
                              "(Z)"
                              "Landroid/location/GnssMeasurementsEvent$Builder;");
     method_gnssMeasurementsEventBuilderBuild =
@@ -234,7 +234,7 @@
 
 void setMeasurementData(JNIEnv* env, jobject& callbacksObj, jobject clock,
                         jobjectArray measurementArray, jobjectArray gnssAgcArray,
-                        bool hasFullTracking, jboolean isFullTracking) {
+                        bool hasIsFullTracking, jboolean isFullTracking) {
     jobject gnssMeasurementsEventBuilderObject =
             env->NewObject(class_gnssMeasurementsEventBuilder,
                            method_gnssMeasurementsEventBuilderCtor);
@@ -246,9 +246,9 @@
     callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject,
                                    method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls,
                                    gnssAgcArray);
-    if (hasFullTracking) {
+    if (hasIsFullTracking) {
         callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject,
-                                       method_gnssMeasurementsEventBuilderSetFullTracking,
+                                       method_gnssMeasurementsEventBuilderSetIsFullTracking,
                                        isFullTracking);
     }
     jobject gnssMeasurementsEventObject =
@@ -394,10 +394,10 @@
     gnssAgcArray = translateAllGnssAgcs(env, data.gnssAgcs);
     if (this->getInterfaceVersion() >= 3) {
         setMeasurementData(env, mCallbacksObj, clock, measurementArray, gnssAgcArray,
-                           /*hasFullTracking=*/true, data.isFullTracking);
+                           /*hasIsFullTracking=*/true, data.isFullTracking);
     } else {
         setMeasurementData(env, mCallbacksObj, clock, measurementArray, gnssAgcArray,
-                           /*hasFullTracking=*/false,
+                           /*hasIsFullTracking=*/false,
                            /*isFullTracking=*/JNI_FALSE);
     }
 
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.h b/services/core/jni/gnss/GnssMeasurementCallback.h
index fde56881..b3de486 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.h
+++ b/services/core/jni/gnss/GnssMeasurementCallback.h
@@ -49,7 +49,7 @@
 
 void setMeasurementData(JNIEnv* env, jobject& callbacksObj, jobject clock,
                         jobjectArray measurementArray, jobjectArray gnssAgcArray,
-                        bool hasFullTracking, jboolean isFullTracking);
+                        bool hasIsFullTracking, jboolean isFullTracking);
 
 class GnssMeasurementCallbackAidl : public hardware::gnss::BnGnssMeasurementCallback {
 public:
@@ -142,7 +142,7 @@
     jobjectArray measurementArray =
             translateAllGnssMeasurements(env, data.measurements.data(), count);
     setMeasurementData(env, mCallbacksObj, clock, measurementArray, /*gnssAgcArray=*/nullptr,
-                       /*hasFullTracking=*/false,
+                       /*hasIsFullTracking=*/false,
                        /*isFullTracking=*/JNI_FALSE);
 
     env->DeleteLocalRef(clock);
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index f915298..101498a 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -92,7 +92,10 @@
 
     certificate: "platform",
     platform_apis: true,
-    test_suites: ["device-tests"],
+    test_suites: [
+        "device-tests",
+        "automotive-tests",
+    ],
 
     optimize: {
         enabled: false,
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 57c5a6e..ef470fe 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -208,6 +208,7 @@
     private static final String TAG = AlarmManagerServiceTest.class.getSimpleName();
     private static final int SYSTEM_UI_UID = 12345;
     private static final int TEST_CALLING_USER = UserHandle.getUserId(TEST_CALLING_UID);
+    private static final int TEST_CALLING_UID_2 = TEST_CALLING_UID + 1;
 
     private long mAppStandbyWindow;
     private long mAllowWhileIdleWindow;
@@ -3412,10 +3413,40 @@
             final int type = ((i & 1) == 0) ? ELAPSED_REALTIME : ELAPSED_REALTIME_WAKEUP;
             setTestAlarm(type, mNowElapsedTest + i, getNewMockPendingIntent());
         }
+        for (int i = 0; i < 4; i++) {
+            final int type = ((i & 1) == 0) ? ELAPSED_REALTIME : ELAPSED_REALTIME_WAKEUP;
+            setTestAlarm(
+                    type,
+                    mNowElapsedTest + i,
+                    getNewMockPendingIntent(),
+                    0,
+                    FLAG_STANDALONE,
+                    TEST_CALLING_UID_2);
+        }
         mNowElapsedTest += 100;
         mTestTimer.expire();
 
-        verify(() -> MetricsHelper.pushAlarmBatchDelivered(10, 5));
+        final ArgumentCaptor<int[]> uidsCaptor = ArgumentCaptor.forClass(int[].class);
+        final ArgumentCaptor<int[]> alarmsPerUidCaptor = ArgumentCaptor.forClass(int[].class);
+        final ArgumentCaptor<int[]> wakeupAlarmsPerUidCaptor = ArgumentCaptor.forClass(int[].class);
+
+        verify(() -> MetricsHelper.pushAlarmBatchDelivered(
+                eq(14),
+                eq(7),
+                uidsCaptor.capture(),
+                alarmsPerUidCaptor.capture(),
+                wakeupAlarmsPerUidCaptor.capture()));
+        assertEquals(2, uidsCaptor.getValue().length);
+        assertEquals(2, alarmsPerUidCaptor.getValue().length);
+        assertEquals(2, wakeupAlarmsPerUidCaptor.getValue().length);
+        final int uid1Idx = uidsCaptor.getValue()[0] == TEST_CALLING_UID ? 0 : 1;
+        final int uid2Idx = 1 - uid1Idx;
+        assertEquals(TEST_CALLING_UID, uidsCaptor.getValue()[uid1Idx]);
+        assertEquals(TEST_CALLING_UID_2, uidsCaptor.getValue()[uid2Idx]);
+        assertEquals(10, alarmsPerUidCaptor.getValue()[uid1Idx]);
+        assertEquals(5, wakeupAlarmsPerUidCaptor.getValue()[uid1Idx]);
+        assertEquals(4, alarmsPerUidCaptor.getValue()[uid2Idx]);
+        assertEquals(2, wakeupAlarmsPerUidCaptor.getValue()[uid2Idx]);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 69a0b87..3a3a507 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -298,6 +298,18 @@
         assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
         assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
         assertThat(desiredSpecs.baseModeId).isEqualTo(60);
+
+        votes.clear();
+        votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(0, 60 - error));
+        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(60 + error, Float.POSITIVE_INFINITY));
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(60);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
index 74189fd..4fde73b 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
@@ -41,10 +41,10 @@
 public class BatteryStatsHistoryIteratorTest {
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
 
-    private MockClock mMockClock = new MockClock();
+    private final MockClock mMockClock = new MockClock();
     private MockBatteryStatsImpl mBatteryStats;
-    private Random mRandom = new Random();
-    private MockExternalStatsSync mExternalStatsSync = new MockExternalStatsSync();
+    private final Random mRandom = new Random();
+    private final MockExternalStatsSync mExternalStatsSync = new MockExternalStatsSync();
 
     @Before
     public void setup() {
@@ -184,29 +184,27 @@
                 100, /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0,
                 1_000_000, 1_000_000, 1_000_000);
 
-        assertThat(mExternalStatsSync.mSyncScheduled).isTrue();
-        mBatteryStats.finishAddingCpuLocked(100, 0, 0, 0, 0, 0, 0, 0);
-        mExternalStatsSync.mSyncScheduled = false;
+        mExternalStatsSync.updateCpuStats(100, 1_100_000, 1_100_000);
 
         // Device was suspended for 3_000 seconds, note the difference in elapsed time and uptime
         mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
                 100, /* plugType */ 0, 80, 72, 3700, 2_400_000, 4_000_000, 0,
                 5_000_000, 2_000_000, 5_000_000);
 
-        assertThat(mExternalStatsSync.mSyncScheduled).isTrue();
-        mBatteryStats.finishAddingCpuLocked(200, 0, 0, 0, 0, 0, 0, 0);
-        mExternalStatsSync.mSyncScheduled = false;
+        mExternalStatsSync.updateCpuStats(200, 5_100_000, 2_100_000);
 
         // Battery level is unchanged, so we don't write battery level details in history
         mBatteryStats.noteAlarmStartLocked("wakeup", null, APP_UID, 6_000_000, 3_000_000);
 
-        assertThat(mExternalStatsSync.mSyncScheduled).isFalse();
+        assertThat(mExternalStatsSync.isSyncScheduled()).isFalse();
 
         // Battery level drops, so we write the accumulated battery level details
         mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
                 100, /* plugType */ 0, 79, 72, 3700, 2_000_000, 4_000_000, 0,
                 7_000_000, 4_000_000, 6_000_000);
 
+        mExternalStatsSync.updateCpuStats(300, 7_100_000, 4_100_000);
+
         final BatteryStatsHistoryIterator iterator = mBatteryStats.iterateBatteryStatsHistory();
 
         BatteryStats.HistoryItem item;
@@ -220,6 +218,10 @@
 
         assertThat(item = iterator.next()).isNotNull();
         assertThat(item.batteryLevel).isEqualTo(90);
+        assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS);
+
+        assertThat(item = iterator.next()).isNotNull();
+        assertThat(item.batteryLevel).isEqualTo(90);
         assertThat(item.states & BatteryStats.HistoryItem.STATE_CPU_RUNNING_FLAG).isEqualTo(0);
         assertThat(item.stepDetails.userTime).isEqualTo(100);
 
@@ -230,6 +232,10 @@
 
         assertThat(item = iterator.next()).isNotNull();
         assertThat(item.batteryLevel).isEqualTo(80);
+        assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS);
+
+        assertThat(item = iterator.next()).isNotNull();
+        assertThat(item.batteryLevel).isEqualTo(80);
         assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_ALARM_START);
         assertThat(item.stepDetails).isNull();
 
@@ -238,6 +244,10 @@
         assertThat(item.states & BatteryStats.HistoryItem.STATE_CPU_RUNNING_FLAG).isNotEqualTo(0);
         assertThat(item.stepDetails.userTime).isEqualTo(200);
 
+        assertThat(item = iterator.next()).isNotNull();
+        assertThat(item.batteryLevel).isEqualTo(79);
+        assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS);
+
         assertThat(item = iterator.next()).isNull();
     }
 
@@ -258,7 +268,7 @@
         assertThat(item.time).isEqualTo(elapsedTimeMs);
     }
 
-    private static class MockExternalStatsSync extends MockBatteryStatsImpl.DummyExternalStatsSync {
+    private class MockExternalStatsSync extends MockBatteryStatsImpl.DummyExternalStatsSync {
         private boolean mSyncScheduled;
 
         @Override
@@ -266,5 +276,18 @@
             mSyncScheduled = true;
             return null;
         }
+
+        public boolean isSyncScheduled() {
+            return mSyncScheduled;
+        }
+
+        public void updateCpuStats(int totalUTimeMs, long elapsedRealtime, long uptime) {
+            assertThat(mExternalStatsSync.mSyncScheduled).isTrue();
+            mBatteryStats.recordHistoryEventLocked(elapsedRealtime, uptime,
+                    BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS, "wakelock-update", 0);
+            mBatteryStats.addCpuStatsLocked(totalUTimeMs, 0, 0, 0, 0, 0, 0, 0);
+            mBatteryStats.finishAddingCpuStatsLocked();
+            mExternalStatsSync.mSyncScheduled = false;
+        }
     }
 }
diff --git a/telecomm/java/android/telecom/CallEndpointException.java b/telecomm/java/android/telecom/CallEndpointException.java
index e2238928..994e1c9 100644
--- a/telecomm/java/android/telecom/CallEndpointException.java
+++ b/telecomm/java/android/telecom/CallEndpointException.java
@@ -92,25 +92,12 @@
     public @interface CallEndpointErrorCode {
     }
 
-    public CallEndpointException(@Nullable String message) {
-        super(getMessage(message, ERROR_UNSPECIFIED));
-        mMessage = message;
-    }
-
     public CallEndpointException(@Nullable String message, @CallEndpointErrorCode int code) {
         super(getMessage(message, code));
         mCode = code;
         mMessage = message;
     }
 
-    public CallEndpointException(@Nullable String message, @CallEndpointErrorCode int code,
-            @Nullable Throwable cause) {
-        super(getMessage(message, code), cause);
-        mCode = code;
-        mMessage = message;
-    }
-
-
     public @CallEndpointErrorCode int getCode() {
         return mCode;
     }