Merge "Fix PendingIntent hijacking for adb notifications." into rvc-dev
diff --git a/StubLibraries.bp b/StubLibraries.bp
index f06f279..270c160 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -76,9 +76,14 @@
     name: "metalava-non-updatable-api-stubs-default",
     defaults: ["metalava-base-api-stubs-default"],
     sdk_version: "core_platform",
-    libs: ["framework-all"],
+    // There are a few classes from modules used as type arguments that
+    // need to be resolved by metalava. For now, we can use a previously
+    // finalized stub library to resolve them. If a new class gets added,
+    // this may be need to be revisited to use a manually maintained stub
+    // library with empty classes in order to resolve those references.
+    libs: ["sdk_system_29_android"],
     aidl: {
-        local_include_dirs: ["apex/media/framework/java"],
+        local_include_dirs: ["apex/media/aidl/stable"],
     },
 }
 
@@ -293,7 +298,7 @@
     name: "android_module_lib_stubs_current",
     srcs: [ ":module-lib-api-stubs-docs" ],
     defaults: ["android_defaults_stubs_current"],
-    libs: ["android_system_stubs_current"],
+    libs: ["sdk_system_29_android"],
 }
 
 /////////////////////////////////////////////////////////////////////
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index 073fddf..202decc 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -21,7 +21,6 @@
 import android.annotation.Nullable;
 import android.annotation.StringDef;
 import android.media.MediaCodec.CryptoInfo;
-import android.net.Uri;
 import android.text.TextUtils;
 import android.util.Pair;
 import android.util.SparseArray;
@@ -52,9 +51,6 @@
 import com.google.android.exoplayer2.extractor.ts.TsExtractor;
 import com.google.android.exoplayer2.extractor.wav.WavExtractor;
 import com.google.android.exoplayer2.upstream.DataReader;
-import com.google.android.exoplayer2.upstream.DataSource;
-import com.google.android.exoplayer2.upstream.DataSpec;
-import com.google.android.exoplayer2.upstream.TransferListener;
 import com.google.android.exoplayer2.util.ParsableByteArray;
 import com.google.android.exoplayer2.util.Util;
 import com.google.android.exoplayer2.video.ColorInfo;
@@ -847,7 +843,7 @@
     private final OutputConsumer mOutputConsumer;
     private final String[] mParserNamesPool;
     private final PositionHolder mPositionHolder;
-    private final InputReadingDataSource mDataSource;
+    private final InputReadingDataReader mExoDataReader;
     private final DataReaderAdapter mScratchDataReaderAdapter;
     private final ParsableByteArrayAdapter mScratchParsableByteArrayAdapter;
     private String mParserName;
@@ -950,11 +946,11 @@
             // clearBuffers() method, or similar.
             mExtractorInput =
                     new DefaultExtractorInput(
-                            mDataSource,
+                            mExoDataReader,
                             seekableInputReader.getPosition(),
                             seekableInputReader.getLength());
         }
-        mDataSource.mInputReader = seekableInputReader;
+        mExoDataReader.mInputReader = seekableInputReader;
 
         // TODO: Apply parameters when creating extractor instances.
         if (mExtractor == null) {
@@ -1046,7 +1042,7 @@
         mParserNamesPool = parserNamesPool;
         mParserName = sniff ? PARSER_NAME_UNKNOWN : parserNamesPool[0];
         mPositionHolder = new PositionHolder();
-        mDataSource = new InputReadingDataSource();
+        mExoDataReader = new InputReadingDataReader();
         removePendingSeek();
         mScratchDataReaderAdapter = new DataReaderAdapter();
         mScratchParsableByteArrayAdapter = new ParsableByteArrayAdapter();
@@ -1181,39 +1177,14 @@
 
     // Private classes.
 
-    private static final class InputReadingDataSource implements DataSource {
+    private static final class InputReadingDataReader implements DataReader {
 
         public InputReader mInputReader;
 
         @Override
-        public void addTransferListener(TransferListener transferListener) {
-            // Do nothing.
-        }
-
-        @Override
-        public long open(DataSpec dataSpec) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
         public int read(byte[] buffer, int offset, int readLength) throws IOException {
             return mInputReader.read(buffer, offset, readLength);
         }
-
-        @Override
-        public Uri getUri() {
-            return null;
-        }
-
-        @Override
-        public Map<String, List<String>> getResponseHeaders() {
-            return null;
-        }
-
-        @Override
-        public void close() {
-            throw new UnsupportedOperationException();
-        }
     }
 
     private final class ExtractorOutputAdapter implements ExtractorOutput {
diff --git a/api/test-current.txt b/api/test-current.txt
index 0dff41b..8e8c8c4 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -107,6 +107,7 @@
   }
 
   public class ActivityOptions {
+    method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
     method public static void setExitTransitionTimeout(long);
     method public void setLaunchActivityType(int);
     method public void setLaunchTaskId(int);
@@ -115,6 +116,14 @@
     method public void setTaskOverlay(boolean, boolean);
   }
 
+  public static interface ActivityOptions.OnAnimationFinishedListener {
+    method public void onAnimationFinished();
+  }
+
+  public static interface ActivityOptions.OnAnimationStartedListener {
+    method public void onAnimationStarted();
+  }
+
   public class ActivityTaskManager {
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void clearLaunchParamsForPackages(java.util.List<java.lang.String>);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public String listAllStacks();
@@ -2263,7 +2272,11 @@
 
   public class Environment {
     method public static java.io.File buildPath(java.io.File, java.lang.String...);
+    method @NonNull public static java.io.File getOdmDirectory();
+    method @NonNull public static java.io.File getOemDirectory();
     method @NonNull public static java.io.File getProductDirectory();
+    method @NonNull public static java.io.File getSystemExtDirectory();
+    method @NonNull public static java.io.File getVendorDirectory();
   }
 
   public final class FileUtils {
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 7fd02112..0129aab 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -22,6 +22,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.INVALID_DISPLAY;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.TestApi;
@@ -51,6 +52,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
+import android.window.WindowContainerToken;
 
 import java.util.ArrayList;
 
@@ -184,6 +186,14 @@
     private static final String KEY_CALLER_DISPLAY_ID = "android.activity.callerDisplayId";
 
     /**
+     * The task display area token the activity should be launched into.
+     * @see #setLaunchTaskDisplayArea(WindowContainerToken)
+     * @hide
+     */
+    private static final String KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN =
+            "android.activity.launchTaskDisplayAreaToken";
+
+    /**
      * The windowing mode the activity should be launched into.
      * @hide
      */
@@ -334,6 +344,7 @@
     private PendingIntent mUsageTimeReport;
     private int mLaunchDisplayId = INVALID_DISPLAY;
     private int mCallerDisplayId = INVALID_DISPLAY;
+    private WindowContainerToken mLaunchTaskDisplayArea;
     @WindowConfiguration.WindowingMode
     private int mLaunchWindowingMode = WINDOWING_MODE_UNDEFINED;
     @WindowConfiguration.ActivityType
@@ -369,7 +380,7 @@
      */
     public static ActivityOptions makeCustomAnimation(Context context,
             int enterResId, int exitResId) {
-        return makeCustomAnimation(context, enterResId, exitResId, null, null);
+        return makeCustomAnimation(context, enterResId, exitResId, null, null, null);
     }
 
     /**
@@ -404,6 +415,38 @@
     }
 
     /**
+     * Create an ActivityOptions specifying a custom animation to run when
+     * the activity is displayed.
+     *
+     * @param context Who is defining this.  This is the application that the
+     * animation resources will be loaded from.
+     * @param enterResId A resource ID of the animation resource to use for
+     * the incoming activity.  Use 0 for no animation.
+     * @param exitResId A resource ID of the animation resource to use for
+     * the outgoing activity.  Use 0 for no animation.
+     * @param handler If <var>listener</var> is non-null this must be a valid
+     * Handler on which to dispatch the callback; otherwise it should be null.
+     * @param startedListener Optional OnAnimationStartedListener to find out when the
+     * requested animation has started running.  If for some reason the animation
+     * is not executed, the callback will happen immediately.
+     * @param finishedListener Optional OnAnimationFinishedListener when the animation
+     * has finished running.
+     * @return Returns a new ActivityOptions object that you can use to
+     * supply these options as the options Bundle when starting an activity.
+     * @hide
+     */
+    @TestApi
+    public static @NonNull ActivityOptions makeCustomAnimation(@NonNull Context context,
+            int enterResId, int exitResId, @Nullable Handler handler,
+            @Nullable OnAnimationStartedListener startedListener,
+            @Nullable OnAnimationFinishedListener finishedListener) {
+        ActivityOptions opts = makeCustomAnimation(context, enterResId, exitResId, handler,
+                startedListener);
+        opts.setOnAnimationFinishedListener(handler, finishedListener);
+        return opts;
+    }
+
+    /**
      * Creates an ActivityOptions specifying a custom animation to run in place on an existing
      * activity.
      *
@@ -448,6 +491,7 @@
      * to find out when the given animation has started running.
      * @hide
      */
+    @TestApi
     public interface OnAnimationStartedListener {
         void onAnimationStarted();
     }
@@ -474,6 +518,7 @@
      * to find out when the given animation has drawn its last frame.
      * @hide
      */
+    @TestApi
     public interface OnAnimationFinishedListener {
         void onAnimationFinished();
     }
@@ -974,6 +1019,7 @@
         mLockTaskMode = opts.getBoolean(KEY_LOCK_TASK_MODE, false);
         mLaunchDisplayId = opts.getInt(KEY_LAUNCH_DISPLAY_ID, INVALID_DISPLAY);
         mCallerDisplayId = opts.getInt(KEY_CALLER_DISPLAY_ID, INVALID_DISPLAY);
+        mLaunchTaskDisplayArea = opts.getParcelable(KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN);
         mLaunchWindowingMode = opts.getInt(KEY_LAUNCH_WINDOWING_MODE, WINDOWING_MODE_UNDEFINED);
         mLaunchActivityType = opts.getInt(KEY_LAUNCH_ACTIVITY_TYPE, ACTIVITY_TYPE_UNDEFINED);
         mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1);
@@ -1089,7 +1135,7 @@
     }
 
     /** @hide */
-    public IRemoteCallback getOnAnimationStartListener() {
+    public IRemoteCallback getAnimationStartedListener() {
         return mAnimationStartedListener;
     }
 
@@ -1245,6 +1291,18 @@
     }
 
     /** @hide */
+    public WindowContainerToken getLaunchTaskDisplayArea() {
+        return mLaunchTaskDisplayArea;
+    }
+
+    /** @hide */
+    public ActivityOptions setLaunchTaskDisplayArea(
+            WindowContainerToken windowContainerToken) {
+        mLaunchTaskDisplayArea = windowContainerToken;
+        return this;
+    }
+
+    /** @hide */
     public int getLaunchWindowingMode() {
         return mLaunchWindowingMode;
     }
@@ -1568,6 +1626,9 @@
         if (mCallerDisplayId != INVALID_DISPLAY) {
             b.putInt(KEY_CALLER_DISPLAY_ID, mCallerDisplayId);
         }
+        if (mLaunchTaskDisplayArea != null) {
+            b.putParcelable(KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN, mLaunchTaskDisplayArea);
+        }
         if (mLaunchWindowingMode != WINDOWING_MODE_UNDEFINED) {
             b.putInt(KEY_LAUNCH_WINDOWING_MODE, mLaunchWindowingMode);
         }
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index ce0d04b..a24a5b78 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -22,6 +22,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Objects;
@@ -168,6 +169,17 @@
     private static final boolean ENABLE = true;
     private static final boolean VERIFY = false;
 
+    private static final Object sCorkLock = new Object();
+
+    /**
+     * A map of cache keys that we've "corked". (The values are counts.)  When a cache key is
+     * corked, we skip the cache invalidate when the cache key is in the unset state --- that
+     * is, when a cache key is corked, an invalidation does not enable the cache if somebody
+     * else hasn't disabled it.
+     */
+    @GuardedBy("sCorkLock")
+    private static final HashMap<String, Integer> sCorks = new HashMap<>();
+
     private final Object mLock = new Object();
 
     /**
@@ -421,6 +433,25 @@
      * @param name Name of the cache-key property to invalidate
      */
     public static void invalidateCache(@NonNull String name) {
+        // Take the cork lock so invalidateCache() racing against corkInvalidations() doesn't
+        // clobber a cork-written NONCE_UNSET with a cache key we compute before the cork.
+        // The property service is single-threaded anyway, so we don't lose any concurrency by
+        // taking the cork lock around cache invalidations.  If we see contention on this lock,
+        // we're invalidating too often.
+        synchronized (sCorkLock) {
+            Integer numberCorks = sCorks.get(name);
+            if (numberCorks != null && numberCorks > 0) {
+                if (DEBUG) {
+                    Log.d(TAG, "ignoring invalidation due to cork: " + name);
+                }
+                return;
+            }
+            invalidateCacheLocked(name);
+        }
+    }
+
+    @GuardedBy("sCorkLock")
+    private static void invalidateCacheLocked(@NonNull String name) {
         // There's no race here: we don't require that values strictly increase, but instead
         // only that each is unique in a single runtime-restart session.
         final long nonce = SystemProperties.getLong(name, NONCE_UNSET);
@@ -430,6 +461,7 @@
             }
             return;
         }
+
         long newValue;
         do {
             newValue = NoPreloadHolder.next();
@@ -445,6 +477,67 @@
         SystemProperties.set(name, newValueString);
     }
 
+    /**
+     * Temporarily put the cache in the uninitialized state and prevent invalidations from
+     * moving it out of that state: useful in cases where we want to avoid the overhead of a
+     * large number of cache invalidations in a short time.  While the cache is corked, clients
+     * bypass the cache and talk to backing services directly.  This property makes corking
+     * correctness-preserving even if corked outside the lock that controls access to the
+     * cache's backing service.
+     *
+     * corkInvalidations() and uncorkInvalidations() must be called in pairs.
+     *
+     * @param name Name of the cache-key property to cork
+     */
+    public static void corkInvalidations(@NonNull String name) {
+        synchronized (sCorkLock) {
+            int numberCorks = sCorks.getOrDefault(name, 0);
+            // If we're the first ones to cork this cache, set the cache to the unset state so
+            // existing caches talk directly to their services while we've corked updates.
+            // Make sure we don't clobber a disabled cache value.
+
+            // TODO(dancol): we can skip this property write and leave the cache enabled if the
+            // caller promises not to make observable changes to the cache backing state before
+            // uncorking the cache, e.g., by holding a read lock across the cork-uncork pair.
+            // Implement this more dangerous mode of operation if necessary.
+            if (numberCorks == 0) {
+                final long nonce = SystemProperties.getLong(name, NONCE_UNSET);
+                if (nonce != NONCE_UNSET && nonce != NONCE_DISABLED) {
+                    SystemProperties.set(name, Long.toString(NONCE_UNSET));
+                }
+            }
+            sCorks.put(name, numberCorks + 1);
+            if (DEBUG) {
+                Log.d(TAG, "corked: " + name);
+            }
+        }
+    }
+
+    /**
+     * Undo the effect of a cork, allowing cache invalidations to proceed normally.
+     * Removing the last cork on a cache name invalidates the cache by side effect,
+     * transitioning it to normal operation (unless explicitly disabled system-wide).
+     *
+     * @param name Name of the cache-key property to uncork
+     */
+    public static void uncorkInvalidations(@NonNull String name) {
+        synchronized (sCorkLock) {
+            int numberCorks = sCorks.getOrDefault(name, 0);
+            if (numberCorks < 1) {
+                throw new AssertionError("cork underflow: " + name);
+            }
+            if (numberCorks == 1) {
+                sCorks.remove(name);
+                invalidateCacheLocked(name);
+                if (DEBUG) {
+                    Log.d(TAG, "uncorked: " + name);
+                }
+            } else {
+                sCorks.put(name, numberCorks - 1);
+            }
+        }
+    }
+
     protected Result maybeCheckConsistency(Query query, Result proposedResult) {
         if (VERIFY) {
             Result resultToCompare = recompute(query);
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 5f8c4f5..c7355dd 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -235,6 +235,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public static @NonNull File getOemDirectory() {
         return DIR_OEM_ROOT;
     }
@@ -246,6 +247,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public static @NonNull File getOdmDirectory() {
         return DIR_ODM_ROOT;
     }
@@ -256,6 +258,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public static @NonNull File getVendorDirectory() {
         return DIR_VENDOR_ROOT;
     }
@@ -294,6 +297,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public static @NonNull File getSystemExtDirectory() {
         return DIR_SYSTEM_EXT_ROOT;
     }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 4be9e1a..e933f18a 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3285,7 +3285,7 @@
 
     /**
      * Applies a tint to the compound drawables. Does not modify the
-     * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+     * current tint mode, which is {@link BlendMode#SRC_IN} by default.
      * <p>
      * Subsequent calls to
      * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
diff --git a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
index b814e3f..7c9c51c 100644
--- a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
+++ b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
@@ -79,21 +79,6 @@
     }
 
     /**
-     * Annotation for different shortcut target.
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({
-            TargetType.ACCESSIBILITY_SERVICE,
-            TargetType.ACCESSIBILITY_ACTIVITY,
-            TargetType.WHITE_LISTING,
-    })
-    public @interface TargetType {
-        int ACCESSIBILITY_SERVICE = 0;
-        int ACCESSIBILITY_ACTIVITY = 1;
-        int WHITE_LISTING = 2;
-    }
-
-    /**
      * Annotation for different shortcut menu mode.
      *
      * {@code LAUNCH} for clicking list item to trigger the service callback.
@@ -108,30 +93,4 @@
         int LAUNCH = 0;
         int EDIT = 1;
     }
-
-    /**
-     * Annotation for align the element index of white listing feature
-     * {@code WHITE_LISTING_FEATURES}.
-     *
-     * {@code COMPONENT_ID} is to get the service component name.
-     * {@code LABEL_ID} is to get the service label text.
-     * {@code ICON_ID} is to get the service icon.
-     * {@code FRAGMENT_TYPE} is to get the service fragment type.
-     * {@code SETTINGS_KEY} is to get the service settings key.
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({
-            WhiteListingFeatureElementIndex.COMPONENT_ID,
-            WhiteListingFeatureElementIndex.LABEL_ID,
-            WhiteListingFeatureElementIndex.ICON_ID,
-            WhiteListingFeatureElementIndex.FRAGMENT_TYPE,
-            WhiteListingFeatureElementIndex.SETTINGS_KEY,
-    })
-    public @interface WhiteListingFeatureElementIndex {
-        int COMPONENT_ID = 0;
-        int LABEL_ID = 1;
-        int ICON_ID = 2;
-        int FRAGMENT_TYPE = 3;
-        int SETTINGS_KEY = 4;
-    }
 }
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java
new file mode 100644
index 0000000..4c7d93b
--- /dev/null
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java
@@ -0,0 +1,47 @@
+/*
+ * 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.internal.accessibility.dialog;
+
+import static com.android.internal.accessibility.util.ShortcutUtils.convertToKey;
+import static com.android.internal.accessibility.util.ShortcutUtils.convertToUserType;
+import static com.android.internal.accessibility.util.ShortcutUtils.isShortcutContained;
+
+import android.accessibilityservice.AccessibilityShortcutInfo;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
+
+import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
+
+/**
+ * Base class for creating accessibility activity target.
+ */
+class AccessibilityActivityTarget extends AccessibilityTarget {
+
+    AccessibilityActivityTarget(Context context, @ShortcutType int shortcutType,
+            @NonNull AccessibilityShortcutInfo shortcutInfo) {
+        super(context,
+                shortcutType,
+                AccessibilityFragmentType.LAUNCH_ACTIVITY,
+                isShortcutContained(context, shortcutType,
+                        shortcutInfo.getComponentName().flattenToString()),
+                shortcutInfo.getComponentName().flattenToString(),
+                shortcutInfo.getActivityInfo().loadLabel(context.getPackageManager()),
+                shortcutInfo.getActivityInfo().loadIcon(context.getPackageManager()),
+                convertToKey(convertToUserType(shortcutType)));
+    }
+}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
new file mode 100644
index 0000000..e64f78a
--- /dev/null
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
@@ -0,0 +1,49 @@
+/*
+ * 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.internal.accessibility.dialog;
+
+import static com.android.internal.accessibility.util.ShortcutUtils.convertToKey;
+import static com.android.internal.accessibility.util.ShortcutUtils.convertToUserType;
+import static com.android.internal.accessibility.util.ShortcutUtils.isShortcutContained;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
+
+import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
+
+/**
+ * Base class for creating accessibility service target with various fragment types related to
+ * legacy type, invisible type and intuitive type.
+ */
+class AccessibilityServiceTarget extends AccessibilityTarget {
+
+    AccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
+            @AccessibilityFragmentType int fragmentType,
+            @NonNull AccessibilityServiceInfo serviceInfo) {
+        super(context,
+                shortcutType,
+                fragmentType,
+                isShortcutContained(context, shortcutType,
+                        serviceInfo.getComponentName().flattenToString()),
+                serviceInfo.getComponentName().flattenToString(),
+                serviceInfo.getResolveInfo().loadLabel(context.getPackageManager()),
+                serviceInfo.getResolveInfo().loadIcon(context.getPackageManager()),
+                convertToKey(convertToUserType(shortcutType)));
+    }
+}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
index 9338c3c..e8d2813 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
@@ -19,64 +19,27 @@
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 import static android.view.accessibility.AccessibilityManager.ShortcutType;
 
-import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
-import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
-import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
-import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
 import static com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
-import static com.android.internal.accessibility.common.ShortcutConstants.TargetType;
-import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
-import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
-import static com.android.internal.accessibility.common.ShortcutConstants.WhiteListingFeatureElementIndex.COMPONENT_ID;
-import static com.android.internal.accessibility.common.ShortcutConstants.WhiteListingFeatureElementIndex.FRAGMENT_TYPE;
-import static com.android.internal.accessibility.common.ShortcutConstants.WhiteListingFeatureElementIndex.ICON_ID;
-import static com.android.internal.accessibility.common.ShortcutConstants.WhiteListingFeatureElementIndex.LABEL_ID;
-import static com.android.internal.accessibility.common.ShortcutConstants.WhiteListingFeatureElementIndex.SETTINGS_KEY;
-import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
-import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
-import static com.android.internal.accessibility.util.ShortcutUtils.convertToUserType;
-import static com.android.internal.accessibility.util.ShortcutUtils.hasValuesInSettings;
-import static com.android.internal.accessibility.util.ShortcutUtils.optInValueToSettings;
-import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings;
+import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.createEnableDialogContentView;
+import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getInstalledTargets;
+import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
 import static com.android.internal.util.Preconditions.checkArgument;
 
-import android.accessibilityservice.AccessibilityServiceInfo;
-import android.accessibilityservice.AccessibilityShortcutInfo;
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
-import android.app.ActivityManager;
 import android.app.AlertDialog;
-import android.content.ComponentName;
-import android.content.Context;
 import android.content.DialogInterface;
 import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.os.Bundle;
-import android.os.storage.StorageManager;
-import android.provider.Settings;
-import android.text.BidiFormatter;
-import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.Window;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.ImageView;
-import android.widget.Switch;
-import android.widget.TextView;
-import android.widget.Toast;
 
 import com.android.internal.R;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
-import java.util.Locale;
 
 /**
  * Activity used to display various targets related to accessibility service, accessibility
@@ -84,38 +47,11 @@
  */
 public class AccessibilityShortcutChooserActivity extends Activity {
     @ShortcutType
-    private static int sShortcutType;
-    @UserShortcutType
-    private int mShortcutUserType;
-    private final List<AccessibilityButtonTarget> mTargets = new ArrayList<>();
-    private AlertDialog mAlertDialog;
-    private AlertDialog mEnableDialog;
-    private TargetAdapter mTargetAdapter;
-    private AccessibilityButtonTarget mCurrentCheckedTarget;
-
-    private static final String[][] WHITE_LISTING_FEATURES = {
-            {
-                    COLOR_INVERSION_COMPONENT_NAME.flattenToString(),
-                    String.valueOf(R.string.color_inversion_feature_name),
-                    String.valueOf(R.drawable.ic_accessibility_color_inversion),
-                    String.valueOf(AccessibilityFragmentType.TOGGLE),
-                    Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
-            },
-            {
-                    DALTONIZER_COMPONENT_NAME.flattenToString(),
-                    String.valueOf(R.string.color_correction_feature_name),
-                    String.valueOf(R.drawable.ic_accessibility_color_correction),
-                    String.valueOf(AccessibilityFragmentType.TOGGLE),
-                    Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
-            },
-            {
-                    MAGNIFICATION_CONTROLLER_NAME,
-                    String.valueOf(R.string.accessibility_magnification_chooser_text),
-                    String.valueOf(R.drawable.ic_accessibility_magnification),
-                    String.valueOf(AccessibilityFragmentType.INVISIBLE_TOGGLE),
-                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
-            },
-    };
+    private int mShortcutType;
+    private final List<AccessibilityTarget> mTargets = new ArrayList<>();
+    private AlertDialog mMenuDialog;
+    private AlertDialog mPermissionDialog;
+    private ShortcutTargetAdapter mTargetAdapter;
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -126,20 +62,18 @@
             requestWindowFeature(Window.FEATURE_NO_TITLE);
         }
 
-        sShortcutType = getIntent().getIntExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE,
+        mShortcutType = getIntent().getIntExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE,
                 /* unexpectedShortcutType */ -1);
-        final boolean existInShortcutType = (sShortcutType == ACCESSIBILITY_BUTTON)
-                || (sShortcutType == ACCESSIBILITY_SHORTCUT_KEY);
-        checkArgument(existInShortcutType, "Unexpected shortcut type: " + sShortcutType);
+        final boolean existInShortcutType = (mShortcutType == ACCESSIBILITY_BUTTON)
+                || (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY);
+        checkArgument(existInShortcutType, "Unexpected shortcut type: " + mShortcutType);
 
-        mShortcutUserType = convertToUserType(sShortcutType);
-
-        mTargets.addAll(getServiceTargets(this, sShortcutType));
+        mTargets.addAll(getTargets(this, mShortcutType));
 
         final String selectDialogTitle =
                 getString(R.string.accessibility_select_shortcut_menu_title);
-        mTargetAdapter = new TargetAdapter(mTargets);
-        mAlertDialog = new AlertDialog.Builder(this)
+        mTargetAdapter = new ShortcutTargetAdapter(mTargets);
+        mMenuDialog = new AlertDialog.Builder(this)
                 .setTitle(selectDialogTitle)
                 .setAdapter(mTargetAdapter, /* listener= */ null)
                 .setPositiveButton(
@@ -147,561 +81,55 @@
                         /* listener= */ null)
                 .setOnDismissListener(dialog -> finish())
                 .create();
-        mAlertDialog.setOnShowListener(dialog -> updateDialogListeners());
-        mAlertDialog.show();
+        mMenuDialog.setOnShowListener(dialog -> updateDialogListeners());
+        mMenuDialog.show();
     }
 
     @Override
     protected void onDestroy() {
-        mAlertDialog.dismiss();
+        mMenuDialog.dismiss();
         super.onDestroy();
     }
 
-    private static List<AccessibilityButtonTarget> getServiceTargets(@NonNull Context context,
-            @ShortcutType int shortcutType) {
-        final List<AccessibilityButtonTarget> targets = getInstalledServiceTargets(context);
-        final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
-        final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType);
-        targets.removeIf(target -> !requiredTargets.contains(target.getId()));
-
-        return targets;
-    }
-
-    private static List<AccessibilityButtonTarget> getInstalledServiceTargets(
-            @NonNull Context context) {
-        final List<AccessibilityButtonTarget> targets = new ArrayList<>();
-        targets.addAll(getAccessibilityFilteredTargets(context));
-        targets.addAll(getWhiteListingServiceTargets(context));
-
-        return targets;
-    }
-
-    private static List<AccessibilityButtonTarget> getAccessibilityFilteredTargets(
-            @NonNull Context context) {
-        final List<AccessibilityButtonTarget> serviceTargets =
-                getAccessibilityServiceTargets(context);
-        final List<AccessibilityButtonTarget> activityTargets =
-                getAccessibilityActivityTargets(context);
-
-        for (AccessibilityButtonTarget activityTarget : activityTargets) {
-            serviceTargets.removeIf(serviceTarget -> {
-                final ComponentName serviceComponentName =
-                        ComponentName.unflattenFromString(serviceTarget.getId());
-                final ComponentName activityComponentName =
-                        ComponentName.unflattenFromString(activityTarget.getId());
-                final boolean isSamePackageName = activityComponentName.getPackageName().equals(
-                        serviceComponentName.getPackageName());
-                final boolean isSameLabel = activityTarget.getLabel().equals(
-                        serviceTarget.getLabel());
-
-                return isSamePackageName && isSameLabel;
-            });
-        }
-
-        final List<AccessibilityButtonTarget> targets = new ArrayList<>();
-        targets.addAll(serviceTargets);
-        targets.addAll(activityTargets);
-
-        return targets;
-    }
-
-    private static List<AccessibilityButtonTarget> getAccessibilityServiceTargets(
-            @NonNull Context context) {
-        final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
-        final List<AccessibilityServiceInfo> installedServices =
-                ams.getInstalledAccessibilityServiceList();
-        if (installedServices == null) {
-            return Collections.emptyList();
-        }
-
-        final List<AccessibilityButtonTarget> targets = new ArrayList<>(installedServices.size());
-        for (AccessibilityServiceInfo info : installedServices) {
-            final int targetSdk =
-                    info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion;
-            final boolean hasRequestAccessibilityButtonFlag =
-                    (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
-            if ((targetSdk < Build.VERSION_CODES.R) && !hasRequestAccessibilityButtonFlag
-                    && (sShortcutType == ACCESSIBILITY_BUTTON)) {
-                continue;
-            }
-            targets.add(new AccessibilityButtonTarget(context, info));
-        }
-
-        return targets;
-    }
-
-    private static List<AccessibilityButtonTarget> getAccessibilityActivityTargets(
-            @NonNull Context context) {
-        final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
-        final List<AccessibilityShortcutInfo> installedServices =
-                ams.getInstalledAccessibilityShortcutListAsUser(context,
-                        ActivityManager.getCurrentUser());
-        if (installedServices == null) {
-            return Collections.emptyList();
-        }
-
-        final List<AccessibilityButtonTarget> targets = new ArrayList<>(installedServices.size());
-        for (AccessibilityShortcutInfo info : installedServices) {
-            targets.add(new AccessibilityButtonTarget(context, info));
-        }
-
-        return targets;
-    }
-
-    private static List<AccessibilityButtonTarget> getWhiteListingServiceTargets(
-            @NonNull Context context) {
-        final List<AccessibilityButtonTarget> targets = new ArrayList<>();
-
-        for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
-            final AccessibilityButtonTarget target = new AccessibilityButtonTarget(
-                    context,
-                    WHITE_LISTING_FEATURES[i][COMPONENT_ID],
-                    Integer.parseInt(WHITE_LISTING_FEATURES[i][LABEL_ID]),
-                    Integer.parseInt(WHITE_LISTING_FEATURES[i][ICON_ID]),
-                    Integer.parseInt(WHITE_LISTING_FEATURES[i][FRAGMENT_TYPE]));
-            targets.add(target);
-        }
-
-        return targets;
-    }
-
-    private static boolean isWhiteListingServiceEnabled(@NonNull Context context,
-            AccessibilityButtonTarget target) {
-
-        for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
-            if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(target.getId())) {
-                return Settings.Secure.getInt(context.getContentResolver(),
-                        WHITE_LISTING_FEATURES[i][SETTINGS_KEY],
-                        /* settingsValueOff */ 0) == /* settingsValueOn */ 1;
-            }
-        }
-
-        return false;
-    }
-
-    private static boolean isWhiteListingService(String componentId) {
-        for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
-            if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(componentId)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    private void setWhiteListingServiceEnabled(String componentId, int settingsValue) {
-        for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
-            if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(componentId)) {
-                Settings.Secure.putInt(getContentResolver(),
-                        WHITE_LISTING_FEATURES[i][SETTINGS_KEY], settingsValue);
-                return;
-            }
-        }
-    }
-
-    private void setServiceEnabled(String componentId, boolean enabled) {
-        if (isWhiteListingService(componentId)) {
-            setWhiteListingServiceEnabled(componentId,
-                    enabled ? /* settingsValueOn */ 1 : /* settingsValueOff */ 0);
-        } else {
-            final ComponentName componentName = ComponentName.unflattenFromString(componentId);
-            setAccessibilityServiceState(this, componentName, enabled);
-        }
-    }
-
-    private static class ViewHolder {
-        View mItemView;
-        CheckBox mCheckBox;
-        ImageView mIconView;
-        TextView mLabelView;
-        Switch mSwitchItem;
-    }
-
-    private static class TargetAdapter extends BaseAdapter {
-        @ShortcutMenuMode
-        private int mShortcutMenuMode = ShortcutMenuMode.LAUNCH;
-        private List<AccessibilityButtonTarget> mButtonTargets;
-
-        TargetAdapter(List<AccessibilityButtonTarget> targets) {
-            this.mButtonTargets = targets;
-        }
-
-        void setShortcutMenuMode(@ShortcutMenuMode int shortcutMenuMode) {
-            mShortcutMenuMode = shortcutMenuMode;
-        }
-
-        @ShortcutMenuMode
-        int getShortcutMenuMode() {
-            return mShortcutMenuMode;
-        }
-
-        @Override
-        public int getCount() {
-            return mButtonTargets.size();
-        }
-
-        @Override
-        public Object getItem(int position) {
-            return mButtonTargets.get(position);
-        }
-
-        @Override
-        public long getItemId(int position) {
-            return position;
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            final Context context = parent.getContext();
-            ViewHolder holder;
-            if (convertView == null) {
-                convertView = LayoutInflater.from(context).inflate(
-                        R.layout.accessibility_shortcut_chooser_item, parent, /* attachToRoot= */
-                        false);
-                holder = new ViewHolder();
-                holder.mItemView = convertView;
-                holder.mCheckBox = convertView.findViewById(
-                        R.id.accessibility_shortcut_target_checkbox);
-                holder.mIconView = convertView.findViewById(
-                        R.id.accessibility_shortcut_target_icon);
-                holder.mLabelView = convertView.findViewById(
-                        R.id.accessibility_shortcut_target_label);
-                holder.mSwitchItem = convertView.findViewById(
-                        R.id.accessibility_shortcut_target_switch_item);
-                convertView.setTag(holder);
-            } else {
-                holder = (ViewHolder) convertView.getTag();
-            }
-
-            final AccessibilityButtonTarget target = mButtonTargets.get(position);
-            updateActionItem(context, holder, target);
-
-            return convertView;
-        }
-
-        private void updateActionItem(@NonNull Context context,
-                @NonNull ViewHolder holder, AccessibilityButtonTarget target) {
-
-            switch (target.getFragmentType()) {
-                case AccessibilityFragmentType.VOLUME_SHORTCUT_TOGGLE:
-                    updateVolumeShortcutToggleTargetActionItemVisibility(holder, target);
-                    break;
-                case AccessibilityFragmentType.INVISIBLE_TOGGLE:
-                    updateInvisibleToggleTargetActionItemVisibility(holder, target);
-                    break;
-                case AccessibilityFragmentType.TOGGLE:
-                    updateToggleTargetActionItemVisibility(context, holder, target);
-                    break;
-                case AccessibilityFragmentType.LAUNCH_ACTIVITY:
-                    updateLaunchActivityTargetActionItemVisibility(holder, target);
-                    break;
-                default:
-                    throw new IllegalStateException("Unexpected fragment type");
-            }
-        }
-
-        private void updateVolumeShortcutToggleTargetActionItemVisibility(
-                @NonNull ViewHolder holder, AccessibilityButtonTarget target) {
-            final boolean isLaunchMenuMode = (mShortcutMenuMode == ShortcutMenuMode.LAUNCH);
-
-            holder.mCheckBox.setChecked(!isLaunchMenuMode && target.isChecked());
-            holder.mCheckBox.setVisibility(isLaunchMenuMode ? View.GONE : View.VISIBLE);
-            holder.mIconView.setImageDrawable(target.getDrawable());
-            holder.mLabelView.setText(target.getLabel());
-            holder.mSwitchItem.setVisibility(View.GONE);
-        }
-
-        private void updateInvisibleToggleTargetActionItemVisibility(@NonNull ViewHolder holder,
-                AccessibilityButtonTarget target) {
-            final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT);
-
-            holder.mCheckBox.setChecked(isEditMenuMode && target.isChecked());
-            holder.mCheckBox.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
-            holder.mIconView.setImageDrawable(target.getDrawable());
-            holder.mLabelView.setText(target.getLabel());
-            holder.mSwitchItem.setVisibility(View.GONE);
-        }
-
-        private void updateToggleTargetActionItemVisibility(@NonNull Context context,
-                @NonNull ViewHolder holder, AccessibilityButtonTarget target) {
-            final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT);
-            final boolean isServiceEnabled = isWhiteListingService(target.getId())
-                    ? isWhiteListingServiceEnabled(context, target)
-                    : isAccessibilityServiceEnabled(context, target);
-
-            holder.mCheckBox.setChecked(isEditMenuMode && target.isChecked());
-            holder.mCheckBox.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
-            holder.mIconView.setImageDrawable(target.getDrawable());
-            holder.mLabelView.setText(target.getLabel());
-            holder.mSwitchItem.setVisibility(isEditMenuMode ? View.GONE : View.VISIBLE);
-            holder.mSwitchItem.setChecked(!isEditMenuMode && isServiceEnabled);
-        }
-
-        private void updateLaunchActivityTargetActionItemVisibility(@NonNull ViewHolder holder,
-                AccessibilityButtonTarget target) {
-            final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT);
-
-            holder.mCheckBox.setChecked(isEditMenuMode && target.isChecked());
-            holder.mCheckBox.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
-            holder.mIconView.setImageDrawable(target.getDrawable());
-            holder.mLabelView.setText(target.getLabel());
-            holder.mSwitchItem.setVisibility(View.GONE);
-        }
-    }
-
-    private static class AccessibilityButtonTarget {
-        private String mId;
-        @TargetType
-        private int mType;
-        private boolean mChecked;
-        private CharSequence mLabel;
-        private Drawable mDrawable;
-        @AccessibilityFragmentType
-        private int mFragmentType;
-
-        AccessibilityButtonTarget(@NonNull Context context,
-                @NonNull AccessibilityServiceInfo serviceInfo) {
-            this.mId = serviceInfo.getComponentName().flattenToString();
-            this.mType = TargetType.ACCESSIBILITY_SERVICE;
-            this.mChecked = isTargetShortcutUsed(context, mId);
-            this.mLabel = serviceInfo.getResolveInfo().loadLabel(context.getPackageManager());
-            this.mDrawable = serviceInfo.getResolveInfo().loadIcon(context.getPackageManager());
-            this.mFragmentType = getAccessibilityServiceFragmentType(serviceInfo);
-        }
-
-        AccessibilityButtonTarget(@NonNull Context context,
-                @NonNull AccessibilityShortcutInfo shortcutInfo) {
-            this.mId = shortcutInfo.getComponentName().flattenToString();
-            this.mType = TargetType.ACCESSIBILITY_ACTIVITY;
-            this.mChecked = isTargetShortcutUsed(context, mId);
-            this.mLabel = shortcutInfo.getActivityInfo().loadLabel(context.getPackageManager());
-            this.mDrawable = shortcutInfo.getActivityInfo().loadIcon(context.getPackageManager());
-            this.mFragmentType = AccessibilityFragmentType.LAUNCH_ACTIVITY;
-        }
-
-        AccessibilityButtonTarget(Context context, @NonNull String id, int labelResId,
-                int iconRes, @AccessibilityFragmentType int fragmentType) {
-            this.mId = id;
-            this.mType = TargetType.WHITE_LISTING;
-            this.mChecked = isTargetShortcutUsed(context, mId);
-            this.mLabel = context.getText(labelResId);
-            this.mDrawable = context.getDrawable(iconRes);
-            this.mFragmentType = fragmentType;
-        }
-
-        public void setChecked(boolean checked) {
-            mChecked = checked;
-        }
-
-        public String getId() {
-            return mId;
-        }
-
-        public int getType() {
-            return mType;
-        }
-
-        public boolean isChecked() {
-            return mChecked;
-        }
-
-        public CharSequence getLabel() {
-            return mLabel;
-        }
-
-        public Drawable getDrawable() {
-            return mDrawable;
-        }
-
-        public int getFragmentType() {
-            return mFragmentType;
-        }
-    }
-
-    private static boolean isAccessibilityServiceEnabled(@NonNull Context context,
-            AccessibilityButtonTarget target) {
-        final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
-        final List<AccessibilityServiceInfo> enabledServices =
-                ams.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
-
-        for (AccessibilityServiceInfo info : enabledServices) {
-            final String id = info.getComponentName().flattenToString();
-            if (id.equals(target.getId())) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
     private void onTargetSelected(AdapterView<?> parent, View view, int position, long id) {
-        final AccessibilityButtonTarget target = mTargets.get(position);
-        switch (target.getFragmentType()) {
-            case AccessibilityFragmentType.VOLUME_SHORTCUT_TOGGLE:
-                onVolumeShortcutToggleTargetSelected(target);
-                break;
-            case AccessibilityFragmentType.INVISIBLE_TOGGLE:
-                onInvisibleToggleTargetSelected(target);
-                break;
-            case AccessibilityFragmentType.TOGGLE:
-                onToggleTargetSelected(target);
-                break;
-            case AccessibilityFragmentType.LAUNCH_ACTIVITY:
-                onLaunchActivityTargetSelected(target);
-                break;
-            default:
-                throw new IllegalStateException("Unexpected fragment type");
-        }
-
-        mAlertDialog.dismiss();
-    }
-
-    private void onVolumeShortcutToggleTargetSelected(AccessibilityButtonTarget target) {
-        if (sShortcutType == ACCESSIBILITY_BUTTON) {
-            final AccessibilityManager ams = getSystemService(AccessibilityManager.class);
-            ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId());
-        } else if (sShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
-            switchServiceState(target);
-        }
-    }
-
-    private void onInvisibleToggleTargetSelected(AccessibilityButtonTarget target) {
-        final AccessibilityManager ams = getSystemService(AccessibilityManager.class);
-        if (sShortcutType == ACCESSIBILITY_BUTTON) {
-            ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId());
-        } else if (sShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
-            ams.performAccessibilityShortcut(target.getId());
-        }
-    }
-
-    private void onToggleTargetSelected(AccessibilityButtonTarget target) {
-        switchServiceState(target);
-    }
-
-    private void onLaunchActivityTargetSelected(AccessibilityButtonTarget target) {
-        final AccessibilityManager ams = getSystemService(AccessibilityManager.class);
-        if (sShortcutType == ACCESSIBILITY_BUTTON) {
-            ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId());
-        } else if (sShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
-            ams.performAccessibilityShortcut(target.getId());
-        }
-    }
-
-    private void switchServiceState(AccessibilityButtonTarget target) {
-        final ComponentName componentName =
-                ComponentName.unflattenFromString(target.getId());
-        final String componentId = componentName.flattenToString();
-
-        if (isWhiteListingService(componentId)) {
-            setWhiteListingServiceEnabled(componentId,
-                    isWhiteListingServiceEnabled(this, target)
-                            ? /* settingsValueOff */ 0
-                            : /* settingsValueOn */ 1);
-        } else {
-            setAccessibilityServiceState(this, componentName,
-                    /* enabled= */!isAccessibilityServiceEnabled(this, target));
-        }
+        final AccessibilityTarget target = mTargets.get(position);
+        target.onSelected();
+        mMenuDialog.dismiss();
     }
 
     private void onTargetChecked(AdapterView<?> parent, View view, int position, long id) {
-        mCurrentCheckedTarget = mTargets.get(position);
+        final AccessibilityTarget target = mTargets.get(position);
 
-        if ((mCurrentCheckedTarget.getType() == TargetType.ACCESSIBILITY_SERVICE)
-                && !mCurrentCheckedTarget.isChecked()) {
-            mEnableDialog = new AlertDialog.Builder(this)
-                    .setView(createEnableDialogContentView(this, mCurrentCheckedTarget,
-                            this::onPermissionAllowButtonClicked,
-                            this::onPermissionDenyButtonClicked))
+        if ((target instanceof AccessibilityServiceTarget) && !target.isShortcutEnabled()) {
+            mPermissionDialog = new AlertDialog.Builder(this)
+                    .setView(createEnableDialogContentView(this,
+                            (AccessibilityServiceTarget) target,
+                            v -> {
+                                mPermissionDialog.dismiss();
+                                mTargetAdapter.notifyDataSetChanged();
+                            },
+                            v -> mPermissionDialog.dismiss()))
                     .create();
-            mEnableDialog.show();
+            mPermissionDialog.show();
             return;
         }
 
-        onTargetChecked(mCurrentCheckedTarget, !mCurrentCheckedTarget.isChecked());
-    }
-
-    private void onTargetChecked(AccessibilityButtonTarget target, boolean checked) {
-        switch (target.getFragmentType()) {
-            case AccessibilityFragmentType.VOLUME_SHORTCUT_TOGGLE:
-                onVolumeShortcutToggleTargetChecked(checked);
-                break;
-            case AccessibilityFragmentType.INVISIBLE_TOGGLE:
-                onInvisibleToggleTargetChecked(checked);
-                break;
-            case AccessibilityFragmentType.TOGGLE:
-                onToggleTargetChecked(checked);
-                break;
-            case AccessibilityFragmentType.LAUNCH_ACTIVITY:
-                onLaunchActivityTargetChecked(checked);
-                break;
-            default:
-                throw new IllegalStateException("Unexpected fragment type");
-        }
-    }
-
-    private void onVolumeShortcutToggleTargetChecked(boolean checked) {
-        if (sShortcutType == ACCESSIBILITY_BUTTON) {
-            setServiceEnabled(mCurrentCheckedTarget.getId(), checked);
-            if (!checked) {
-                optOutValueFromSettings(this, HARDWARE, mCurrentCheckedTarget.getId());
-                final String warningText =
-                        getString(R.string.accessibility_uncheck_legacy_item_warning,
-                                mCurrentCheckedTarget.getLabel());
-                Toast.makeText(this, warningText, Toast.LENGTH_SHORT).show();
-            }
-        } else if (sShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
-            updateValueToSettings(mCurrentCheckedTarget.getId(), checked);
-        } else {
-            throw new IllegalStateException("Unexpected shortcut type");
-        }
-
-        mCurrentCheckedTarget.setChecked(checked);
+        target.onCheckedChanged(!target.isShortcutEnabled());
         mTargetAdapter.notifyDataSetChanged();
     }
 
-    private void onInvisibleToggleTargetChecked(boolean checked) {
-        final int shortcutTypes = UserShortcutType.SOFTWARE | HARDWARE;
-        if (!hasValuesInSettings(this, shortcutTypes, mCurrentCheckedTarget.getId())) {
-            setServiceEnabled(mCurrentCheckedTarget.getId(), checked);
-        }
-
-        updateValueToSettings(mCurrentCheckedTarget.getId(), checked);
-        mCurrentCheckedTarget.setChecked(checked);
-        mTargetAdapter.notifyDataSetChanged();
-    }
-
-    private void onToggleTargetChecked(boolean checked) {
-        updateValueToSettings(mCurrentCheckedTarget.getId(), checked);
-        mCurrentCheckedTarget.setChecked(checked);
-        mTargetAdapter.notifyDataSetChanged();
-    }
-
-    private void onLaunchActivityTargetChecked(boolean checked) {
-        updateValueToSettings(mCurrentCheckedTarget.getId(), checked);
-        mCurrentCheckedTarget.setChecked(checked);
-        mTargetAdapter.notifyDataSetChanged();
-    }
-
-    private void updateValueToSettings(String componentId, boolean checked) {
-        if (checked) {
-            optInValueToSettings(this, mShortcutUserType, componentId);
-        } else  {
-            optOutValueFromSettings(this, mShortcutUserType, componentId);
-        }
-    }
-
     private void onDoneButtonClicked() {
         mTargets.clear();
-        mTargets.addAll(getServiceTargets(this, sShortcutType));
+        mTargets.addAll(getTargets(this, mShortcutType));
         if (mTargets.isEmpty()) {
-            mAlertDialog.dismiss();
+            mMenuDialog.dismiss();
             return;
         }
 
         mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH);
         mTargetAdapter.notifyDataSetChanged();
 
-        mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
+        mMenuDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
                 getString(R.string.edit_accessibility_shortcut_menu_button));
 
         updateDialogListeners();
@@ -709,11 +137,11 @@
 
     private void onEditButtonClicked() {
         mTargets.clear();
-        mTargets.addAll(getInstalledServiceTargets(this));
+        mTargets.addAll(getInstalledTargets(this, mShortcutType));
         mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.EDIT);
         mTargetAdapter.notifyDataSetChanged();
 
-        mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
+        mMenuDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
                 getString(R.string.done_accessibility_shortcut_menu_button));
 
         updateDialogListeners();
@@ -724,79 +152,14 @@
                 (mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT);
         final int selectDialogTitleId = R.string.accessibility_select_shortcut_menu_title;
         final int editDialogTitleId =
-                (sShortcutType == ACCESSIBILITY_BUTTON)
+                (mShortcutType == ACCESSIBILITY_BUTTON)
                         ? R.string.accessibility_edit_shortcut_menu_button_title
                         : R.string.accessibility_edit_shortcut_menu_volume_title;
 
-        mAlertDialog.setTitle(getString(isEditMenuMode ? editDialogTitleId : selectDialogTitleId));
-        mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(
+        mMenuDialog.setTitle(getString(isEditMenuMode ? editDialogTitleId : selectDialogTitleId));
+        mMenuDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(
                 isEditMenuMode ? view -> onDoneButtonClicked() : view -> onEditButtonClicked());
-        mAlertDialog.getListView().setOnItemClickListener(
+        mMenuDialog.getListView().setOnItemClickListener(
                 isEditMenuMode ? this::onTargetChecked : this::onTargetSelected);
     }
-
-    private static boolean isTargetShortcutUsed(@NonNull Context context, String id) {
-        final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
-        final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(sShortcutType);
-        return requiredTargets.contains(id);
-    }
-
-    private void onPermissionAllowButtonClicked(View view) {
-        if (mCurrentCheckedTarget.getFragmentType()
-                != AccessibilityFragmentType.VOLUME_SHORTCUT_TOGGLE) {
-            updateValueToSettings(mCurrentCheckedTarget.getId(), /* checked= */ true);
-        }
-        onTargetChecked(mCurrentCheckedTarget, /* checked= */ true);
-        mEnableDialog.dismiss();
-    }
-
-    private void onPermissionDenyButtonClicked(View view) {
-        mEnableDialog.dismiss();
-    }
-
-    private static View createEnableDialogContentView(Context context,
-            AccessibilityButtonTarget target, View.OnClickListener allowListener,
-            View.OnClickListener denyListener) {
-        final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
-                Context.LAYOUT_INFLATER_SERVICE);
-
-        final View content = inflater.inflate(
-                R.layout.accessibility_enable_service_encryption_warning, /* root= */ null);
-
-        final TextView encryptionWarningView = (TextView) content.findViewById(
-                R.id.accessibility_encryption_warning);
-        if (StorageManager.isNonDefaultBlockEncrypted()) {
-            final String text = context.getString(
-                    R.string.accessibility_enable_service_encryption_warning,
-                    getServiceName(context, target.getLabel()));
-            encryptionWarningView.setText(text);
-            encryptionWarningView.setVisibility(View.VISIBLE);
-        } else {
-            encryptionWarningView.setVisibility(View.GONE);
-        }
-
-        final ImageView permissionDialogIcon = content.findViewById(
-                R.id.accessibility_permissionDialog_icon);
-        permissionDialogIcon.setImageDrawable(target.getDrawable());
-
-        final TextView permissionDialogTitle = content.findViewById(
-                R.id.accessibility_permissionDialog_title);
-        permissionDialogTitle.setText(context.getString(R.string.accessibility_enable_service_title,
-                getServiceName(context, target.getLabel())));
-
-        final Button permissionAllowButton = content.findViewById(
-                R.id.accessibility_permission_enable_allow_button);
-        final Button permissionDenyButton = content.findViewById(
-                R.id.accessibility_permission_enable_deny_button);
-        permissionAllowButton.setOnClickListener(allowListener);
-        permissionDenyButton.setOnClickListener(denyListener);
-
-        return content;
-    }
-
-    // Gets the service name and bidi wrap it to protect from bidi side effects.
-    private static CharSequence getServiceName(Context context, CharSequence label) {
-        final Locale locale = context.getResources().getConfiguration().getLocales().get(0);
-        return BidiFormatter.getInstance(locale).unicodeWrap(label);
-    }
 }
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
new file mode 100644
index 0000000..72ebc58
--- /dev/null
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
@@ -0,0 +1,141 @@
+/*
+ * 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.internal.accessibility.dialog;
+
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+
+import static com.android.internal.accessibility.util.ShortcutUtils.convertToUserType;
+import static com.android.internal.accessibility.util.ShortcutUtils.optInValueToSettings;
+import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
+
+import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
+import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
+
+/**
+ * Abstract base class for creating various target related to accessibility service,
+ * accessibility activity, and white listing feature.
+ */
+abstract class AccessibilityTarget implements TargetOperations, OnTargetSelectedListener,
+        OnTargetCheckedChangeListener {
+    private Context mContext;
+    @ShortcutType
+    private int mShortcutType;
+    @AccessibilityFragmentType
+    private int mFragmentType;
+    private boolean mShortcutEnabled;
+    private String mId;
+    private CharSequence mLabel;
+    private Drawable mIcon;
+    private String mKey;
+
+    AccessibilityTarget(Context context, @ShortcutType int shortcutType,
+            @AccessibilityFragmentType int fragmentType, boolean isShortcutSwitched, String id,
+            CharSequence label, Drawable icon, String key) {
+        mContext = context;
+        mShortcutType = shortcutType;
+        mFragmentType = fragmentType;
+        mShortcutEnabled = isShortcutSwitched;
+        mId = id;
+        mLabel = label;
+        mIcon = icon;
+        mKey = key;
+    }
+
+    @Override
+    public void updateActionItem(@NonNull ViewHolder holder,
+            @ShortcutConstants.ShortcutMenuMode int shortcutMenuMode) {
+        final boolean isEditMenuMode =
+                shortcutMenuMode == ShortcutConstants.ShortcutMenuMode.EDIT;
+
+        holder.mCheckBoxView.setChecked(isEditMenuMode && isShortcutEnabled());
+        holder.mCheckBoxView.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
+        holder.mIconView.setImageDrawable(getIcon());
+        holder.mLabelView.setText(getLabel());
+        holder.mSwitchItem.setVisibility(View.GONE);
+    }
+
+    @Override
+    public void onSelected() {
+        final AccessibilityManager am =
+                getContext().getSystemService(AccessibilityManager.class);
+        switch (getShortcutType()) {
+            case ACCESSIBILITY_BUTTON:
+                am.notifyAccessibilityButtonClicked(getContext().getDisplayId(), getId());
+                return;
+            case ACCESSIBILITY_SHORTCUT_KEY:
+                am.performAccessibilityShortcut(getId());
+                return;
+            default:
+                throw new IllegalStateException("Unexpected shortcut type");
+        }
+    }
+
+    @Override
+    public void onCheckedChanged(boolean isChecked) {
+        setShortcutEnabled(isChecked);
+        if (isChecked) {
+            optInValueToSettings(getContext(), convertToUserType(getShortcutType()), getId());
+        } else {
+            optOutValueFromSettings(getContext(), convertToUserType(getShortcutType()), getId());
+        }
+    }
+
+    public void setShortcutEnabled(boolean enabled) {
+        mShortcutEnabled = enabled;
+    }
+
+    public Context getContext() {
+        return mContext;
+    }
+
+    public @ShortcutType int getShortcutType() {
+        return mShortcutType;
+    }
+
+    public @AccessibilityFragmentType int getFragmentType() {
+        return mFragmentType;
+    }
+
+    public boolean isShortcutEnabled() {
+        return mShortcutEnabled;
+    }
+
+    public String getId() {
+        return mId;
+    }
+
+    public CharSequence getLabel() {
+        return mLabel;
+    }
+
+    public Drawable getIcon() {
+        return mIcon;
+    }
+
+    public String getKey() {
+        return mKey;
+    }
+}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
new file mode 100644
index 0000000..f63cbe0
--- /dev/null
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -0,0 +1,261 @@
+/*
+ * 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.internal.accessibility.dialog;
+
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+
+import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
+import static com.android.internal.accessibility.util.ShortcutUtils.isShortcutContained;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityShortcutInfo;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Build;
+import android.os.storage.StorageManager;
+import android.provider.Settings;
+import android.text.BidiFormatter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Collection of utilities for accessibility target.
+ */
+final class AccessibilityTargetHelper {
+    private AccessibilityTargetHelper() {}
+
+    static List<AccessibilityTarget> getTargets(Context context,
+            @ShortcutType int shortcutType) {
+        final List<AccessibilityTarget> targets = getInstalledTargets(context, shortcutType);
+        final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
+        final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType);
+        targets.removeIf(target -> !requiredTargets.contains(target.getId()));
+
+        return targets;
+    }
+
+    static List<AccessibilityTarget> getInstalledTargets(Context context,
+            @ShortcutType int shortcutType) {
+        final List<AccessibilityTarget> targets = new ArrayList<>();
+        targets.addAll(getAccessibilityFilteredTargets(context, shortcutType));
+        targets.addAll(getWhiteListingFeatureTargets(context, shortcutType));
+
+        return targets;
+    }
+
+    private static List<AccessibilityTarget> getAccessibilityFilteredTargets(Context context,
+            @ShortcutType int shortcutType) {
+        final List<AccessibilityTarget> serviceTargets =
+                getAccessibilityServiceTargets(context, shortcutType);
+        final List<AccessibilityTarget> activityTargets =
+                getAccessibilityActivityTargets(context, shortcutType);
+
+        for (AccessibilityTarget activityTarget : activityTargets) {
+            serviceTargets.removeIf(
+                    serviceTarget -> arePackageNameAndLabelTheSame(serviceTarget, activityTarget));
+        }
+
+        final List<AccessibilityTarget> targets = new ArrayList<>();
+        targets.addAll(serviceTargets);
+        targets.addAll(activityTargets);
+
+        return targets;
+    }
+
+    private static boolean arePackageNameAndLabelTheSame(@NonNull AccessibilityTarget serviceTarget,
+            @NonNull AccessibilityTarget activityTarget) {
+        final ComponentName serviceComponentName =
+                ComponentName.unflattenFromString(serviceTarget.getId());
+        final ComponentName activityComponentName =
+                ComponentName.unflattenFromString(activityTarget.getId());
+        final boolean isSamePackageName = activityComponentName.getPackageName().equals(
+                serviceComponentName.getPackageName());
+        final boolean isSameLabel = activityTarget.getLabel().equals(
+                serviceTarget.getLabel());
+
+        return isSamePackageName && isSameLabel;
+    }
+
+    private static List<AccessibilityTarget> getAccessibilityServiceTargets(Context context,
+            @ShortcutType int shortcutType) {
+        final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
+        final List<AccessibilityServiceInfo> installedServices =
+                ams.getInstalledAccessibilityServiceList();
+        if (installedServices == null) {
+            return Collections.emptyList();
+        }
+
+        final List<AccessibilityTarget> targets = new ArrayList<>(installedServices.size());
+        for (AccessibilityServiceInfo info : installedServices) {
+            final int targetSdk =
+                    info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion;
+            final boolean hasRequestAccessibilityButtonFlag =
+                    (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+            if ((targetSdk <= Build.VERSION_CODES.Q) && !hasRequestAccessibilityButtonFlag
+                    && (shortcutType == ACCESSIBILITY_BUTTON)) {
+                continue;
+            }
+
+            targets.add(createAccessibilityServiceTarget(context, shortcutType, info));
+        }
+
+        return targets;
+    }
+
+    private static List<AccessibilityTarget> getAccessibilityActivityTargets(Context context,
+            @ShortcutType int shortcutType) {
+        final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
+        final List<AccessibilityShortcutInfo> installedServices =
+                ams.getInstalledAccessibilityShortcutListAsUser(context,
+                        ActivityManager.getCurrentUser());
+        if (installedServices == null) {
+            return Collections.emptyList();
+        }
+
+        final List<AccessibilityTarget> targets = new ArrayList<>(installedServices.size());
+        for (AccessibilityShortcutInfo info : installedServices) {
+            targets.add(new AccessibilityActivityTarget(context, shortcutType, info));
+        }
+
+        return targets;
+    }
+
+    private static List<AccessibilityTarget> getWhiteListingFeatureTargets(Context context,
+            @ShortcutType int shortcutType) {
+        final List<AccessibilityTarget> targets = new ArrayList<>();
+
+        final InvisibleToggleWhiteListingFeatureTarget magnification =
+                new InvisibleToggleWhiteListingFeatureTarget(context,
+                shortcutType,
+                isShortcutContained(context, shortcutType, MAGNIFICATION_CONTROLLER_NAME),
+                MAGNIFICATION_CONTROLLER_NAME,
+                context.getString(R.string.accessibility_magnification_chooser_text),
+                context.getDrawable(R.drawable.ic_accessibility_magnification),
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
+
+        final ToggleWhiteListingFeatureTarget daltonizer =
+                new ToggleWhiteListingFeatureTarget(context,
+                shortcutType,
+                isShortcutContained(context, shortcutType,
+                        DALTONIZER_COMPONENT_NAME.flattenToString()),
+                DALTONIZER_COMPONENT_NAME.flattenToString(),
+                context.getString(R.string.color_correction_feature_name),
+                context.getDrawable(R.drawable.ic_accessibility_color_correction),
+                Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED);
+
+        final ToggleWhiteListingFeatureTarget colorInversion =
+                new ToggleWhiteListingFeatureTarget(context,
+                shortcutType,
+                isShortcutContained(context, shortcutType,
+                        COLOR_INVERSION_COMPONENT_NAME.flattenToString()),
+                COLOR_INVERSION_COMPONENT_NAME.flattenToString(),
+                context.getString(R.string.color_inversion_feature_name),
+                context.getDrawable(R.drawable.ic_accessibility_color_inversion),
+                Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
+
+        targets.add(magnification);
+        targets.add(daltonizer);
+        targets.add(colorInversion);
+
+        return targets;
+    }
+
+    private static AccessibilityTarget createAccessibilityServiceTarget(Context context,
+            @ShortcutType int shortcutType, @NonNull AccessibilityServiceInfo info) {
+        switch (getAccessibilityServiceFragmentType(info)) {
+            case AccessibilityFragmentType.VOLUME_SHORTCUT_TOGGLE:
+                return new VolumeShortcutToggleAccessibilityServiceTarget(context, shortcutType,
+                        info);
+            case AccessibilityFragmentType.INVISIBLE_TOGGLE:
+                return new InvisibleToggleAccessibilityServiceTarget(context, shortcutType, info);
+            case AccessibilityFragmentType.TOGGLE:
+                return new ToggleAccessibilityServiceTarget(context, shortcutType, info);
+            default:
+                throw new IllegalStateException("Unexpected fragment type");
+        }
+    }
+
+    static View createEnableDialogContentView(Context context,
+            AccessibilityServiceTarget target, View.OnClickListener allowListener,
+            View.OnClickListener denyListener) {
+        final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+
+        final View content = inflater.inflate(
+                R.layout.accessibility_enable_service_encryption_warning, /* root= */ null);
+
+        final TextView encryptionWarningView = (TextView) content.findViewById(
+                R.id.accessibility_encryption_warning);
+        if (StorageManager.isNonDefaultBlockEncrypted()) {
+            final String text = context.getString(
+                    R.string.accessibility_enable_service_encryption_warning,
+                    getServiceName(context, target.getLabel()));
+            encryptionWarningView.setText(text);
+            encryptionWarningView.setVisibility(View.VISIBLE);
+        } else {
+            encryptionWarningView.setVisibility(View.GONE);
+        }
+
+        final ImageView dialogIcon = content.findViewById(
+                R.id.accessibility_permissionDialog_icon);
+        dialogIcon.setImageDrawable(target.getIcon());
+
+        final TextView dialogTitle = content.findViewById(
+                R.id.accessibility_permissionDialog_title);
+        dialogTitle.setText(context.getString(R.string.accessibility_enable_service_title,
+                getServiceName(context, target.getLabel())));
+
+        final Button allowButton = content.findViewById(
+                R.id.accessibility_permission_enable_allow_button);
+        final Button denyButton = content.findViewById(
+                R.id.accessibility_permission_enable_deny_button);
+        allowButton.setOnClickListener((view) -> {
+            target.onCheckedChanged(/* isChecked= */ true);
+            allowListener.onClick(view);
+        });
+        denyButton.setOnClickListener((view) -> {
+            target.onCheckedChanged(/* isChecked= */ false);
+            denyListener.onClick(view);
+        });
+
+        return content;
+    }
+
+    // Gets the service name and bidi wrap it to protect from bidi side effects.
+    private static CharSequence getServiceName(Context context, CharSequence label) {
+        final Locale locale = context.getResources().getConfiguration().getLocales().get(0);
+        return BidiFormatter.getInstance(locale).unicodeWrap(label);
+    }
+}
diff --git a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java
new file mode 100644
index 0000000..9d5c374
--- /dev/null
+++ b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java
@@ -0,0 +1,71 @@
+/*
+ * 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.internal.accessibility.dialog;
+
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
+import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
+import static com.android.internal.accessibility.util.ShortcutUtils.isComponentIdExistingInSettings;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
+
+import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
+
+/**
+ * Extension for {@link AccessibilityServiceTarget} with
+ * {@link AccessibilityFragmentType#INVISIBLE_TOGGLE} type.
+ */
+class InvisibleToggleAccessibilityServiceTarget extends AccessibilityServiceTarget {
+
+    InvisibleToggleAccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
+            @NonNull AccessibilityServiceInfo serviceInfo) {
+        super(context,
+                shortcutType,
+                AccessibilityFragmentType.INVISIBLE_TOGGLE,
+                serviceInfo);
+    }
+
+    @Override
+    public void onCheckedChanged(boolean isChecked) {
+        final ComponentName componentName = ComponentName.unflattenFromString(getId());
+
+        if (!isComponentIdExistingInOtherShortcut()) {
+            setAccessibilityServiceState(getContext(), componentName, isChecked);
+        }
+
+        super.onCheckedChanged(isChecked);
+    }
+
+    private boolean isComponentIdExistingInOtherShortcut() {
+        switch (getShortcutType()) {
+            case ACCESSIBILITY_BUTTON:
+                return isComponentIdExistingInSettings(getContext(), UserShortcutType.HARDWARE,
+                        getId());
+            case ACCESSIBILITY_SHORTCUT_KEY:
+                return isComponentIdExistingInSettings(getContext(), UserShortcutType.SOFTWARE,
+                        getId());
+            default:
+                throw new IllegalStateException("Unexpected shortcut type");
+        }
+    }
+}
diff --git a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleWhiteListingFeatureTarget.java b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleWhiteListingFeatureTarget.java
new file mode 100644
index 0000000..acd101b
--- /dev/null
+++ b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleWhiteListingFeatureTarget.java
@@ -0,0 +1,36 @@
+/*
+ * 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.internal.accessibility.dialog;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
+
+import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
+
+/**
+ * Extension for {@link AccessibilityTarget} with {@link AccessibilityFragmentType#INVISIBLE_TOGGLE}
+ * type.
+ */
+class InvisibleToggleWhiteListingFeatureTarget extends AccessibilityTarget {
+
+    InvisibleToggleWhiteListingFeatureTarget(Context context, @ShortcutType int shortcutType,
+            boolean isShortcutSwitched, String id, CharSequence label, Drawable icon, String key) {
+        super(context, shortcutType, AccessibilityFragmentType.INVISIBLE_TOGGLE,
+                isShortcutSwitched, id, label, icon, key);
+    }
+}
diff --git a/core/java/com/android/internal/accessibility/dialog/OnTargetCheckedChangeListener.java b/core/java/com/android/internal/accessibility/dialog/OnTargetCheckedChangeListener.java
new file mode 100644
index 0000000..dab45e4
--- /dev/null
+++ b/core/java/com/android/internal/accessibility/dialog/OnTargetCheckedChangeListener.java
@@ -0,0 +1,30 @@
+/*
+ * 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.internal.accessibility.dialog;
+
+/**
+ * Interface definition for a callback to be invoked when the checked state
+ * of a accessibility target changed.
+ */
+interface OnTargetCheckedChangeListener {
+    /**
+     * Called when the checked state of a accessibility target has changed.
+     *
+     * @param isChecked The new checked state of accessibility target.
+     */
+    void onCheckedChanged(boolean isChecked);
+}
diff --git a/core/java/com/android/internal/accessibility/dialog/OnTargetSelectedListener.java b/core/java/com/android/internal/accessibility/dialog/OnTargetSelectedListener.java
new file mode 100644
index 0000000..b3e976f2
--- /dev/null
+++ b/core/java/com/android/internal/accessibility/dialog/OnTargetSelectedListener.java
@@ -0,0 +1,27 @@
+/*
+ * 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.internal.accessibility.dialog;
+
+/**
+ * Interface definition for a callback to be invoked when a accessibility target is selected.
+ */
+interface OnTargetSelectedListener {
+    /**
+     * Called when a accessibility target has been selected.
+     */
+    void onSelected();
+}
diff --git a/core/java/com/android/internal/accessibility/dialog/ShortcutTargetAdapter.java b/core/java/com/android/internal/accessibility/dialog/ShortcutTargetAdapter.java
new file mode 100644
index 0000000..b7605b7
--- /dev/null
+++ b/core/java/com/android/internal/accessibility/dialog/ShortcutTargetAdapter.java
@@ -0,0 +1,92 @@
+/*
+ * 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.internal.accessibility.dialog;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.R;
+import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
+
+import java.util.List;
+
+/**
+ * Extension for {@link TargetAdapter} and used for AccessibilityShortcutChooserActivity.
+ */
+class ShortcutTargetAdapter extends TargetAdapter {
+    @ShortcutMenuMode
+    private int mShortcutMenuMode = ShortcutMenuMode.LAUNCH;
+    private final List<AccessibilityTarget> mTargets;
+
+    ShortcutTargetAdapter(@NonNull List<AccessibilityTarget> targets) {
+        mTargets = targets;
+    }
+
+    @Override
+    public int getCount() {
+        return mTargets.size();
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return mTargets.get(position);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        final Context context = parent.getContext();
+        ViewHolder holder;
+        if (convertView == null) {
+            convertView = LayoutInflater.from(context).inflate(
+                    R.layout.accessibility_shortcut_chooser_item, parent, /* attachToRoot= */
+                    false);
+            holder = new ViewHolder();
+            holder.mCheckBoxView = convertView.findViewById(
+                    R.id.accessibility_shortcut_target_checkbox);
+            holder.mIconView = convertView.findViewById(R.id.accessibility_shortcut_target_icon);
+            holder.mLabelView = convertView.findViewById(
+                    R.id.accessibility_shortcut_target_label);
+            holder.mSwitchItem = convertView.findViewById(
+                    R.id.accessibility_shortcut_target_switch_item);
+            convertView.setTag(holder);
+        } else {
+            holder = (ViewHolder) convertView.getTag();
+        }
+
+        final AccessibilityTarget target = mTargets.get(position);
+        target.updateActionItem(holder, mShortcutMenuMode);
+
+        return convertView;
+    }
+
+    void setShortcutMenuMode(@ShortcutMenuMode int shortcutMenuMode) {
+        mShortcutMenuMode = shortcutMenuMode;
+    }
+
+    @ShortcutMenuMode
+    int getShortcutMenuMode() {
+        return mShortcutMenuMode;
+    }
+}
diff --git a/core/java/com/android/internal/accessibility/dialog/TargetAdapter.java b/core/java/com/android/internal/accessibility/dialog/TargetAdapter.java
new file mode 100644
index 0000000..1efa17e
--- /dev/null
+++ b/core/java/com/android/internal/accessibility/dialog/TargetAdapter.java
@@ -0,0 +1,35 @@
+/*
+ * 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.internal.accessibility.dialog;
+
+import android.widget.BaseAdapter;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.Switch;
+import android.widget.TextView;
+
+/**
+ * Abstract base class for creating target adapter for chooser activity.
+ */
+abstract class TargetAdapter extends BaseAdapter {
+    static class ViewHolder{
+        CheckBox mCheckBoxView;
+        ImageView mIconView;
+        TextView mLabelView;
+        Switch mSwitchItem;
+    }
+}
diff --git a/core/java/com/android/internal/accessibility/dialog/TargetOperations.java b/core/java/com/android/internal/accessibility/dialog/TargetOperations.java
new file mode 100644
index 0000000..77cc5b4
--- /dev/null
+++ b/core/java/com/android/internal/accessibility/dialog/TargetOperations.java
@@ -0,0 +1,33 @@
+/*
+ * 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.internal.accessibility.dialog;
+
+import android.annotation.NonNull;
+
+import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
+import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
+
+/**
+ * Interface definition for operations with a accessibility target that was invoked.
+ */
+interface TargetOperations {
+    /**
+     * Called when a accessibility target has been invoked and notified to update latest status.
+     */
+    void updateActionItem(@NonNull ViewHolder holder,
+            @ShortcutMenuMode int shortcutMenuMode);
+}
diff --git a/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
new file mode 100644
index 0000000..3a42f7e
--- /dev/null
+++ b/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
@@ -0,0 +1,55 @@
+/*
+ * 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.internal.accessibility.dialog;
+
+import static com.android.internal.accessibility.util.AccessibilityUtils.isAccessibilityServiceEnabled;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
+
+import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
+import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
+import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
+
+/**
+ * Extension for {@link AccessibilityServiceTarget} with {@link AccessibilityFragmentType#TOGGLE}
+ * type.
+ */
+class ToggleAccessibilityServiceTarget extends AccessibilityServiceTarget {
+
+    ToggleAccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
+            @NonNull AccessibilityServiceInfo serviceInfo) {
+        super(context,
+                shortcutType,
+                AccessibilityFragmentType.TOGGLE,
+                serviceInfo);
+    }
+
+    @Override
+    public void updateActionItem(@NonNull ViewHolder holder,
+            @ShortcutMenuMode int shortcutMenuMode) {
+        super.updateActionItem(holder, shortcutMenuMode);
+
+        final boolean isEditMenuMode =
+                shortcutMenuMode == ShortcutMenuMode.EDIT;
+        holder.mSwitchItem.setVisibility(isEditMenuMode ? View.GONE : View.VISIBLE);
+        holder.mSwitchItem.setChecked(isAccessibilityServiceEnabled(getContext(), getId()));
+    }
+}
diff --git a/core/java/com/android/internal/accessibility/dialog/ToggleWhiteListingFeatureTarget.java b/core/java/com/android/internal/accessibility/dialog/ToggleWhiteListingFeatureTarget.java
new file mode 100644
index 0000000..fcbf5ec
--- /dev/null
+++ b/core/java/com/android/internal/accessibility/dialog/ToggleWhiteListingFeatureTarget.java
@@ -0,0 +1,57 @@
+/*
+ * 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.internal.accessibility.dialog;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.provider.Settings;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
+
+import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
+import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
+import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
+
+/**
+ * Extension for {@link AccessibilityTarget} with {@link AccessibilityFragmentType#TOGGLE}
+ * type.
+ */
+class ToggleWhiteListingFeatureTarget extends AccessibilityTarget {
+
+    ToggleWhiteListingFeatureTarget(Context context, @ShortcutType int shortcutType,
+            boolean isShortcutSwitched, String id, CharSequence label, Drawable icon, String key) {
+        super(context, shortcutType, AccessibilityFragmentType.TOGGLE,
+                isShortcutSwitched, id, label, icon, key);
+    }
+
+    @Override
+    public void updateActionItem(@NonNull ViewHolder holder,
+            @ShortcutMenuMode int shortcutMenuMode) {
+        super.updateActionItem(holder, shortcutMenuMode);
+
+        final boolean isEditMenuMode =
+                shortcutMenuMode == ShortcutMenuMode.EDIT;
+        holder.mSwitchItem.setVisibility(isEditMenuMode ? View.GONE : View.VISIBLE);
+        holder.mSwitchItem.setChecked(isFeatureEnabled());
+    }
+
+    private boolean isFeatureEnabled() {
+        return Settings.Secure.getInt(getContext().getContentResolver(),
+                getKey(), /* settingsValueOff */ 0) == /* settingsValueOn */ 1;
+    }
+}
diff --git a/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
new file mode 100644
index 0000000..04f5061
--- /dev/null
+++ b/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
@@ -0,0 +1,78 @@
+/*
+ * 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.internal.accessibility.dialog;
+
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
+import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
+import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
+import android.widget.Toast;
+
+import com.android.internal.R;
+import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
+
+/**
+ * Extension for {@link AccessibilityServiceTarget} with
+ * {@link AccessibilityFragmentType#VOLUME_SHORTCUT_TOGGLE} type.
+ */
+class VolumeShortcutToggleAccessibilityServiceTarget extends AccessibilityServiceTarget {
+
+    VolumeShortcutToggleAccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
+            @NonNull AccessibilityServiceInfo serviceInfo) {
+        super(context,
+                shortcutType,
+                AccessibilityFragmentType.VOLUME_SHORTCUT_TOGGLE,
+                serviceInfo);
+    }
+
+    @Override
+    public void onCheckedChanged(boolean isChecked) {
+        switch (getShortcutType()) {
+            case ACCESSIBILITY_BUTTON:
+                onCheckedFromAccessibilityButton(isChecked);
+                return;
+            case ACCESSIBILITY_SHORTCUT_KEY:
+                super.onCheckedChanged(isChecked);
+                return;
+            default:
+                throw new IllegalStateException("Unexpected shortcut type");
+        }
+    }
+
+    private void onCheckedFromAccessibilityButton(boolean isChecked) {
+        setShortcutEnabled(isChecked);
+        final ComponentName componentName = ComponentName.unflattenFromString(getId());
+        setAccessibilityServiceState(getContext(), componentName, isChecked);
+
+        if (!isChecked) {
+            optOutValueFromSettings(getContext(), UserShortcutType.HARDWARE, getId());
+
+            final String warningText =
+                    getContext().getString(R.string.accessibility_uncheck_legacy_item_warning,
+                            getLabel());
+            Toast.makeText(getContext(), warningText, Toast.LENGTH_SHORT).show();
+        }
+    }
+}
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
index a92a50d..e50b010 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
@@ -15,6 +15,7 @@
  */
 
 package com.android.internal.accessibility.util;
+
 import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
 import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
 
@@ -27,9 +28,11 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.ArraySet;
+import android.view.accessibility.AccessibilityManager;
 
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -129,4 +132,27 @@
                 ? AccessibilityFragmentType.INVISIBLE_TOGGLE
                 : AccessibilityFragmentType.TOGGLE;
     }
+
+    /**
+     * Returns if a {@code componentId} service is enabled.
+     *
+     * @param context The current context.
+     * @param componentId The component id that need to be checked.
+     * @return {@code true} if a {@code componentId} service is enabled.
+     */
+    public static boolean isAccessibilityServiceEnabled(Context context,
+            @NonNull String componentId) {
+        final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
+        final List<AccessibilityServiceInfo> enabledServices =
+                am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+
+        for (AccessibilityServiceInfo info : enabledServices) {
+            final String id = info.getComponentName().flattenToString();
+            if (id.equals(componentId)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
 }
diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
index 7ec80ec..c338a29 100644
--- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
+++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
@@ -25,8 +25,10 @@
 import android.content.Context;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityManager.ShortcutType;
 
+import java.util.List;
 import java.util.StringJoiner;
 
 /**
@@ -97,29 +99,6 @@
     }
 
     /**
-     * Returns if component id existed in one of {@link UserShortcutType} string from Settings.
-     *
-     * @param context The current context.
-     * @param shortcutTypes A combination of {@link UserShortcutType}.
-     * @param componentId The component id that need to be checked existed in Settings.
-     * @return {@code true} if component id existed in Settings.
-     */
-    public static boolean hasValuesInSettings(Context context, @UserShortcutType int shortcutTypes,
-            @NonNull String componentId) {
-        boolean exist = false;
-        if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
-            exist = isComponentIdExistingInSettings(context, UserShortcutType.SOFTWARE,
-                    componentId);
-        }
-        if (((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE)) {
-            exist |= isComponentIdExistingInSettings(context, UserShortcutType.HARDWARE,
-                    componentId);
-        }
-        return exist;
-    }
-
-
-    /**
      * Returns if component id existed in Settings.
      *
      * @param context The current context.
@@ -149,6 +128,21 @@
     }
 
     /**
+     * Returns if a {@code shortcutType} shortcut contains {@code componentId}.
+     *
+     * @param context The current context.
+     * @param shortcutType The preferred shortcut type user selected.
+     * @param componentId The component id that need to be checked.
+     * @return {@code true} if a component id is contained.
+     */
+    public static boolean isShortcutContained(Context context, @ShortcutType int shortcutType,
+            @NonNull String componentId) {
+        final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
+        final List<String> requiredTargets = am.getAccessibilityShortcutTargets(shortcutType);
+        return requiredTargets.contains(componentId);
+    }
+
+    /**
      * Converts {@link UserShortcutType} to {@link Settings.Secure} key.
      *
      * @param type The shortcut type.
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index 73109c5..cee8a92 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -256,6 +256,7 @@
             }
         }
 
+        setPlaceholderCount(0);
         int n;
         if ((currentResolveList != null) && ((n = currentResolveList.size()) > 0)) {
             // We only care about fixing the unfilteredList if the current resolve list and
diff --git a/core/proto/Android.bp b/core/proto/Android.bp
index 6119d71..3b891d6 100644
--- a/core/proto/Android.bp
+++ b/core/proto/Android.bp
@@ -28,13 +28,3 @@
         "android/bluetooth/smp/enums.proto",
     ],
 }
-
-java_library_host {
-    name: "protolog-proto",
-    srcs: [
-        "android/server/protolog.proto"
-    ],
-    proto: {
-        type: "full",
-    },
-}
diff --git a/core/proto/android/app/appexitinfo.proto b/core/proto/android/app/appexitinfo.proto
index 4b9444e..cc3d367 100644
--- a/core/proto/android/app/appexitinfo.proto
+++ b/core/proto/android/app/appexitinfo.proto
@@ -41,7 +41,7 @@
     optional int64 pss = 11;
     optional int64 rss = 12;
     optional int64 timestamp = 13;
-    optional string description = 14;
+    optional string description = 14 [(.android.privacy).dest = DEST_EXPLICIT];
     optional bytes state = 15;
     optional string trace_file = 16;
 }
diff --git a/core/proto/android/content/locusid.proto b/core/proto/android/content/locusid.proto
index 4f0ce6b..e2ba78d 100644
--- a/core/proto/android/content/locusid.proto
+++ b/core/proto/android/content/locusid.proto
@@ -20,8 +20,10 @@
 
 option java_multiple_files = true;
 
+import "frameworks/base/core/proto/android/privacy.proto";
+
 // On disk representation of android.content.LocusId. Currently used by
 // com.android.server.people.ConversationInfoProto.
 message LocusIdProto {
-  optional string locus_id = 1;
+    optional string locus_id = 1 [(.android.privacy).dest = DEST_EXPLICIT];
 }
diff --git a/core/proto/android/nfc/aid_group.proto b/core/proto/android/nfc/aid_group.proto
index 0636519..8810ff7 100644
--- a/core/proto/android/nfc/aid_group.proto
+++ b/core/proto/android/nfc/aid_group.proto
@@ -23,7 +23,7 @@
 
 // Debugging information for android.nfc.cardemulation.AidGroup
 message AidGroupProto {
-    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    option (.android.msg_privacy).dest = DEST_EXPLICIT;
 
     optional string category = 1;
     // A group of Application Identifiers. A hexadecimal string, with an even amount of hexadecimal
diff --git a/core/proto/android/nfc/apdu_service_info.proto b/core/proto/android/nfc/apdu_service_info.proto
index c726ea3..fd110c4 100644
--- a/core/proto/android/nfc/apdu_service_info.proto
+++ b/core/proto/android/nfc/apdu_service_info.proto
@@ -25,7 +25,7 @@
 
 // Debugging information for android.nfc.cardemulation.ApduServiceInfo
 message ApduServiceInfoProto {
-    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    option (.android.msg_privacy).dest = DEST_EXPLICIT;
 
     optional .android.content.ComponentNameProto component_name = 1;
     optional string description = 2;
diff --git a/core/proto/android/nfc/card_emulation.proto b/core/proto/android/nfc/card_emulation.proto
index 474d14a..9c3c6d7 100644
--- a/core/proto/android/nfc/card_emulation.proto
+++ b/core/proto/android/nfc/card_emulation.proto
@@ -75,8 +75,8 @@
 message RegisteredAidCacheProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
     message AidCacheEntry {
-        optional string key = 1;
-        optional string category = 2;
+        optional string key = 1 [(.android.privacy).dest = DEST_EXPLICIT];
+        optional string category = 2 [(.android.privacy).dest = DEST_EXPLICIT];
         optional .android.content.ComponentNameProto default_component = 3;
         repeated .android.nfc.cardemulation.ApduServiceInfoProto services = 4;
     }
@@ -91,7 +91,7 @@
     message Route {
         option (.android.msg_privacy).dest = DEST_AUTOMATIC;
         optional int32 id = 1;
-        repeated string aids = 2;
+        repeated string aids = 2 [(.android.privacy).dest = DEST_EXPLICIT];
     }
     optional int32 default_route = 1;
     repeated Route routes = 2;
@@ -108,7 +108,7 @@
 message SystemCodeRoutingManagerProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
     message T3tIdentifier {
-        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+        option (.android.msg_privacy).dest = DEST_EXPLICIT;
         optional string system_code = 1;
         optional string nfcid2 = 2;
     }
diff --git a/core/proto/android/nfc/ndef.proto b/core/proto/android/nfc/ndef.proto
index 52b3238..5e10084 100644
--- a/core/proto/android/nfc/ndef.proto
+++ b/core/proto/android/nfc/ndef.proto
@@ -31,7 +31,7 @@
 message NdefRecordProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
 
-    optional bytes type = 1;
-    optional bytes id = 2;
+    optional bytes type = 1 [(.android.privacy).dest = DEST_EXPLICIT];
+    optional bytes id = 2 [(.android.privacy).dest = DEST_EXPLICIT];
     optional int32 payload_bytes = 3;
 }
diff --git a/core/proto/android/nfc/nfc_fservice_info.proto b/core/proto/android/nfc/nfc_fservice_info.proto
index eecd7b0..0c4c72a 100644
--- a/core/proto/android/nfc/nfc_fservice_info.proto
+++ b/core/proto/android/nfc/nfc_fservice_info.proto
@@ -24,7 +24,7 @@
 
 // Debugging information for android.nfc.cardemulation.NfcFServiceInfo
 message NfcFServiceInfoProto {
-    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    option (.android.msg_privacy).dest = DEST_EXPLICIT;
 
     optional .android.content.ComponentNameProto component_name = 1;
     // Description of the service
diff --git a/core/proto/android/nfc/nfc_service.proto b/core/proto/android/nfc/nfc_service.proto
index 73a7989..2df1d5d 100644
--- a/core/proto/android/nfc/nfc_service.proto
+++ b/core/proto/android/nfc/nfc_service.proto
@@ -104,7 +104,7 @@
     optional int32 send_flags = 5;
     optional bool send_enabled = 6;
     optional bool receive_enabled = 7;
-    optional string callback_ndef = 8;
+    optional string callback_ndef = 8 [(.android.privacy).dest = DEST_EXPLICIT];
     optional .android.nfc.NdefMessageProto message_to_send = 9;
     repeated string uris_to_send = 10 [(.android.privacy).dest = DEST_EXPLICIT];
 }
diff --git a/core/proto/android/server/notificationhistory.proto b/core/proto/android/server/notificationhistory.proto
index 15f4abb..9896ba5 100644
--- a/core/proto/android/server/notificationhistory.proto
+++ b/core/proto/android/server/notificationhistory.proto
@@ -19,6 +19,8 @@
 
 option java_multiple_files = true;
 
+import "frameworks/base/core/proto/android/privacy.proto";
+
 // On disk data store for historical notifications
 message NotificationHistoryProto {
   message StringPool {
@@ -33,7 +35,7 @@
     optional int32 package_index = 2;
 
     // The name of the NotificationChannel this notification was posted to
-    optional string channel_name = 3;
+    optional string channel_name = 3 [(.android.privacy).dest = DEST_EXPLICIT];
     // channel_name_index contains the index + 1 of the channel name in the string pool
     optional int32 channel_name_index = 4;
 
@@ -49,9 +51,9 @@
     // The time at which the notification was posted
     optional int64 posted_time_ms = 9;
     // The title of the notification
-    optional string title = 10;
+    optional string title = 10 [(.android.privacy).dest = DEST_EXPLICIT];
     // The text of the notification
-    optional string text = 11;
+    optional string text = 11 [(.android.privacy).dest = DEST_EXPLICIT];
     // The small icon of the notification
     optional Icon icon = 12;
 
diff --git a/core/proto/android/server/peopleservice.proto b/core/proto/android/server/peopleservice.proto
index e65a2ab..59556c4 100644
--- a/core/proto/android/server/peopleservice.proto
+++ b/core/proto/android/server/peopleservice.proto
@@ -21,6 +21,7 @@
 option java_multiple_files = true;
 
 import "frameworks/base/core/proto/android/content/locusid.proto";
+import "frameworks/base/core/proto/android/privacy.proto";
 
 // On disk data of conversation infos for a user and app package.
 message ConversationInfosProto {
@@ -40,10 +41,10 @@
   optional .android.content.LocusIdProto locus_id_proto = 2;
 
   // The URI of the contact in the conversation.
-  optional string contact_uri = 3;
+  optional string contact_uri = 3 [(.android.privacy).dest = DEST_EXPLICIT];
 
   // The notification channel id of the conversation.
-  optional string notification_channel_id = 4;
+  optional string notification_channel_id = 4 [(.android.privacy).dest = DEST_EXPLICIT];
 
   // Integer representation of shortcut bit flags.
   optional int32 shortcut_flags = 5;
@@ -52,7 +53,7 @@
   optional int32 conversation_flags = 6;
 
   // The phone number of the contact.
-  optional string contact_phone_number = 7;
+  optional string contact_phone_number = 7 [(.android.privacy).dest = DEST_EXPLICIT];
 }
 
 // On disk data of events.
diff --git a/core/proto/android/server/protolog.proto b/core/proto/android/server/protolog.proto
index 3512c0a..34dc55b 100644
--- a/core/proto/android/server/protolog.proto
+++ b/core/proto/android/server/protolog.proto
@@ -20,8 +20,12 @@
 
 option java_multiple_files = true;
 
+import "frameworks/base/core/proto/android/privacy.proto";
+
 /* represents a single log entry */
 message ProtoLogMessage {
+    option (.android.msg_privacy).dest = DEST_LOCAL;
+
     /* log statement identifier, created from message string and log level. */
     optional sfixed32 message_hash = 1;
     /* log time, relative to the elapsed system time clock. */
@@ -40,6 +44,8 @@
    Encoded, it should start with 0x9 0x50 0x52 0x4f 0x54 0x4f 0x4c 0x4f 0x47 (.PROTOLOG), such
    that they can be easily identified. */
 message ProtoLogFileProto {
+    option (.android.msg_privacy).dest = DEST_LOCAL;
+
     /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L
        (this is needed because enums have to be 32 bits and there's no nice way to put 64bit
         constants into .proto files. */
diff --git a/core/proto/android/server/syncstorageengine.proto b/core/proto/android/server/syncstorageengine.proto
index 87eb1b3..d313747 100644
--- a/core/proto/android/server/syncstorageengine.proto
+++ b/core/proto/android/server/syncstorageengine.proto
@@ -58,7 +58,7 @@
 
     message LastEventInfo {
       optional int64 last_event_time = 1; // time since epoch
-      optional string last_event = 2;
+      optional string last_event = 2 [(.android.privacy).dest = DEST_EXPLICIT];
     }
 
     // Note: version doesn't need to be stored in proto because of how protos store information but
@@ -68,7 +68,7 @@
     optional int32 last_success_source = 4;
     optional int64 last_failure_time = 5; // time since epoch
     optional int32 last_failure_source = 6;
-    optional string last_failure_message = 7;
+    optional string last_failure_message = 7 [(.android.privacy).dest = DEST_EXPLICIT];
     optional int64 initial_failure_time = 8; // time since epoch
     optional bool pending = 9;
     optional bool initialize = 10;
diff --git a/core/proto/android/server/usagestatsservice_v2.proto b/core/proto/android/server/usagestatsservice_v2.proto
index 24b0728..664c22d 100644
--- a/core/proto/android/server/usagestatsservice_v2.proto
+++ b/core/proto/android/server/usagestatsservice_v2.proto
@@ -112,13 +112,13 @@
   optional int32 flags = 4;
   optional int32 type = 5;
   optional .android.content.ConfigurationProto config = 6;
-  optional string shortcut_id = 7;
+  optional string shortcut_id = 7 [(.android.privacy).dest = DEST_EXPLICIT];
   optional int32 standby_bucket = 8;
-  optional string notification_channel_id = 9;
+  optional string notification_channel_id = 9 [(.android.privacy).dest = DEST_EXPLICIT];
   optional int32 instance_id = 10;
   optional string task_root_package = 11;
   optional string task_root_class = 12;
-  optional string locus_id = 13;
+  optional string locus_id = 13 [(.android.privacy).dest = DEST_EXPLICIT];
 }
 
 /**
@@ -128,7 +128,7 @@
   message PackagesMap {
     optional int32 package_token = 1;
     // The list of strings for each package where their indices are the token
-    repeated string strings = 2;
+    repeated string strings = 2 [(.android.privacy).dest = DEST_EXPLICIT];
   }
 
   optional int32 counter = 1;
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 55ea3159..0f5616f 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -310,7 +310,7 @@
     optional bool visible = 24;
     reserved 25; // configuration_container
     optional IdentifierProto identifier = 26;
-    optional string state = 27;
+    optional string state = 27 [(.android.privacy).dest = DEST_EXPLICIT];
     optional bool front_of_task = 28;
     optional int32 proc_id = 29;
     optional bool translucent = 30;
diff --git a/core/proto/android/service/sensor_service.proto b/core/proto/android/service/sensor_service.proto
index 48f6670..7f60534 100644
--- a/core/proto/android/service/sensor_service.proto
+++ b/core/proto/android/service/sensor_service.proto
@@ -166,7 +166,7 @@
     message RecentEventsLog {
         option (android.msg_privacy).dest = DEST_AUTOMATIC;
 
-        optional string name = 1;
+        optional string name = 1 [(.android.privacy).dest = DEST_EXPLICIT];
         optional int32 recent_events_count = 2;
         repeated Event events = 3;
     }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5d6fc76..6e8b9de 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -526,6 +526,7 @@
     <protected-broadcast android:name="com.android.settings.wifi.action.NETWORK_REQUEST" />
 
     <protected-broadcast android:name="NotificationManagerService.TIMEOUT" />
+    <protected-broadcast android:name="NotificationHistoryDatabase.CLEANUP" />
     <protected-broadcast android:name="ScheduleConditionProvider.EVALUATE" />
     <protected-broadcast android:name="EventConditionProvider.EVALUATE" />
     <protected-broadcast android:name="SnoozeHelper.EVALUATE" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index c9c498e..0e40c98 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3897,6 +3897,19 @@
          information. -->
     <string name="gpsVerifNo">No</string>
 
+    <!-- Notification title shown to user to inform them their device location was accessed by
+         an external entity during an emergency (usually an emergency phone call).
+         [CHAR LIMIT=40] -->
+    <string name="gnss_nfw_notification_title">Emergency location accessed</string>
+    <!-- Notification message shown to user to inform them their device location was accessed by
+         something OEM related during an emergency (usually an emergency phone call).
+         [CHAR LIMIT=NONE] -->
+    <string name="gnss_nfw_notification_message_oem">Your device manufacturer accessed your location during a recent emergency session</string>
+    <!-- Notification message shown to user to inform them their device location was accessed by
+         something carrier related during an emergency (usually an emergency phone call).
+         [CHAR LIMIT=NONE] -->
+    <string name="gnss_nfw_notification_message_carrier">Your carrier accessed your location during a recent emergency session</string>
+
     <!-- Error message when the sync tried to delete too many things -->
     <string name="sync_too_many_deletes">Delete limit exceeded</string>
     <!-- Dialog message for when there are too many deletes that would take place and we want user confirmation -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 717f326d..6178fda 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -740,6 +740,9 @@
   <java-symbol type="string" name="gpsNotifTitle" />
   <java-symbol type="string" name="gpsVerifNo" />
   <java-symbol type="string" name="gpsVerifYes" />
+  <java-symbol type="string" name="gnss_nfw_notification_title" />
+  <java-symbol type="string" name="gnss_nfw_notification_message_carrier" />
+  <java-symbol type="string" name="gnss_nfw_notification_message_oem" />
   <java-symbol type="string" name="gsm_alphabet_default_charset" />
   <java-symbol type="string" name="httpError" />
   <java-symbol type="string" name="httpErrorAuth" />
diff --git a/core/tests/coretests/res/raw/install_app1_cert5 b/core/tests/coretests/res/raw/install_app1_cert5
new file mode 100644
index 0000000..138b611
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_app1_cert5
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_app1_cert5_rotated_cert6 b/core/tests/coretests/res/raw/install_app1_cert5_rotated_cert6
new file mode 100644
index 0000000..2da2436
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_app1_cert5_rotated_cert6
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_app1_cert6 b/core/tests/coretests/res/raw/install_app1_cert6
new file mode 100644
index 0000000..256e03a
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_app1_cert6
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_app2_cert5_rotated_cert6 b/core/tests/coretests/res/raw/install_app2_cert5_rotated_cert6
new file mode 100644
index 0000000..30bb647
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_app2_cert5_rotated_cert6
Binary files differ
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index ed2436a..79cb1f9 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -1830,27 +1830,35 @@
      * The following series of tests are related to upgrading apps with
      * different certificates.
      */
-    private int APP1_UNSIGNED = R.raw.install_app1_unsigned;
+    private static final int APP1_UNSIGNED = R.raw.install_app1_unsigned;
 
-    private int APP1_CERT1 = R.raw.install_app1_cert1;
+    private static final int APP1_CERT1 = R.raw.install_app1_cert1;
 
-    private int APP1_CERT2 = R.raw.install_app1_cert2;
+    private static final int APP1_CERT2 = R.raw.install_app1_cert2;
 
-    private int APP1_CERT1_CERT2 = R.raw.install_app1_cert1_cert2;
+    private static final int APP1_CERT1_CERT2 = R.raw.install_app1_cert1_cert2;
 
-    private int APP1_CERT3_CERT4 = R.raw.install_app1_cert3_cert4;
+    private static final int APP1_CERT3_CERT4 = R.raw.install_app1_cert3_cert4;
 
-    private int APP1_CERT3 = R.raw.install_app1_cert3;
+    private static final int APP1_CERT3 = R.raw.install_app1_cert3;
 
-    private int APP2_UNSIGNED = R.raw.install_app2_unsigned;
+    private static final int APP1_CERT5 = R.raw.install_app1_cert5;
 
-    private int APP2_CERT1 = R.raw.install_app2_cert1;
+    private static final int APP1_CERT5_ROTATED_CERT6 = R.raw.install_app1_cert5_rotated_cert6;
 
-    private int APP2_CERT2 = R.raw.install_app2_cert2;
+    private static final int APP1_CERT6 = R.raw.install_app1_cert6;
 
-    private int APP2_CERT1_CERT2 = R.raw.install_app2_cert1_cert2;
+    private static final int APP2_UNSIGNED = R.raw.install_app2_unsigned;
 
-    private int APP2_CERT3 = R.raw.install_app2_cert3;
+    private static final int APP2_CERT1 = R.raw.install_app2_cert1;
+
+    private static final int APP2_CERT2 = R.raw.install_app2_cert2;
+
+    private static final int APP2_CERT1_CERT2 = R.raw.install_app2_cert1_cert2;
+
+    private static final int APP2_CERT3 = R.raw.install_app2_cert3;
+
+    private static final int APP2_CERT5_ROTATED_CERT6 = R.raw.install_app2_cert5_rotated_cert6;
 
     private InstallParams replaceCerts(int apk1, int apk2, boolean cleanUp, boolean fail,
             int retCode) throws Exception {
@@ -2473,6 +2481,26 @@
     }
 
     @LargeTest
+    public void testCheckSignaturesRotatedAgainstOriginal() throws Exception {
+        // checkSignatures should be backwards compatible with pre-rotation behavior; this test
+        // verifies that an app signed with a rotated key results in a signature match with an app
+        // signed with the original key in the lineage.
+        int apk1 = APP1_CERT5;
+        int apk2 = APP2_CERT5_ROTATED_CERT6;
+        checkSignatures(apk1, apk2, PackageManager.SIGNATURE_MATCH);
+    }
+
+    @LargeTest
+    public void testCheckSignaturesRotatedAgainstRotated() throws Exception {
+        // checkSignatures should be successful when both apps have been signed with the same
+        // rotated key since the initial signature comparison between the two apps should
+        // return a match.
+        int apk1 = APP1_CERT5_ROTATED_CERT6;
+        int apk2 = APP2_CERT5_ROTATED_CERT6;
+        checkSignatures(apk1, apk2, PackageManager.SIGNATURE_MATCH);
+    }
+
+    @LargeTest
     public void testInstallNoCertificates() throws Exception {
         int apk1 = APP1_UNSIGNED;
         String apk1Name = "install1.apk";
diff --git a/core/tests/overlaytests/host/TEST_MAPPING b/core/tests/overlaytests/host/TEST_MAPPING
new file mode 100644
index 0000000..e0c03e0
--- /dev/null
+++ b/core/tests/overlaytests/host/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name" : "OverlayHostTests"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index 405410a..97d32d8 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -897,7 +897,7 @@
 
         synchronized (mSessionMap) {
             if (mSessionMap.get(session) != null) {
-                mTunerResourceManager.releaseCasSession(mSessionMap.get(session));
+                mTunerResourceManager.releaseCasSession(mSessionMap.get(session), mClientId);
                 mSessionMap.remove(session);
             }
         }
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 861eeea..ee5ac46 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -263,14 +263,14 @@
     }
 
     private void setFrontendInfoList() {
-        List<Integer> ids = nativeGetFrontendIds();
+        List<Integer> ids = getFrontendIds();
         if (ids == null) {
             return;
         }
         TunerFrontendInfo[] infos = new TunerFrontendInfo[ids.size()];
         for (int i = 0; i < ids.size(); i++) {
             int id = ids.get(i);
-            FrontendInfo frontendInfo = nativeGetFrontendInfo(id);
+            FrontendInfo frontendInfo = getFrontendInfoById(id);
             if (frontendInfo == null) {
                 continue;
             }
@@ -281,6 +281,11 @@
         mTunerResourceManager.setFrontendInfoList(infos);
     }
 
+    /** @hide */
+    public List<Integer> getFrontendIds() {
+        return nativeGetFrontendIds();
+    }
+
     private void setLnbIds() {
         int[] ids = nativeGetLnbIds();
         if (ids == null) {
@@ -345,14 +350,17 @@
     @Override
     public void close() {
         if (mFrontendHandle != null) {
-            mTunerResourceManager.releaseFrontend(mFrontendHandle);
+            nativeCloseFrontendByHandle(mFrontendHandle);
+            mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId);
             mFrontendHandle = null;
+            mFrontend = null;
         }
         if (mLnb != null) {
-            mTunerResourceManager.releaseLnb(mLnbHandle);
+            mTunerResourceManager.releaseLnb(mLnbHandle, mClientId);
             mLnb = null;
+            mLnbHandle = null;
         }
-        nativeClose();
+        TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner");
     }
 
     /**
@@ -374,6 +382,8 @@
      * Native method to open frontend of the given ID.
      */
     private native Frontend nativeOpenFrontendByHandle(int handle);
+    @Result
+    private native int nativeCloseFrontendByHandle(int handle);
     private native int nativeTune(int type, FrontendSettings settings);
     private native int nativeStopTune();
     private native int nativeScan(int settingsType, FrontendSettings settings, int scanType);
@@ -522,6 +532,7 @@
     public int tune(@NonNull FrontendSettings settings) {
         mFrontendType = settings.getType();
         checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND);
+
         mFrontendInfo = null;
         return nativeTune(settings.getType(), settings);
     }
@@ -706,11 +717,16 @@
             throw new IllegalStateException("frontend is not initialized");
         }
         if (mFrontendInfo == null) {
-            mFrontendInfo = nativeGetFrontendInfo(mFrontend.mId);
+            mFrontendInfo = getFrontendInfoById(mFrontend.mId);
         }
         return mFrontendInfo;
     }
 
+    /** @hide */
+    public FrontendInfo getFrontendInfoById(int id) {
+        return nativeGetFrontendInfo(id);
+    }
+
     /**
      * Gets Demux capabilities.
      *
diff --git a/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
index 77cac6e..7077cd1 100644
--- a/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
@@ -85,7 +85,8 @@
      * Updates the available Frontend resources information on the current device.
      *
      * <p><strong>Note:</strong> This update must happen before the first
-     * {@link #requestFrontend(TunerFrontendRequest,int[])} and {@link #releaseFrontend(int)} call.
+     * {@link #requestFrontend(TunerFrontendRequest,int[])} and {@link #releaseFrontend(int, int)}
+     * call.
      *
      * @param infos an array of the available {@link TunerFrontendInfo} information.
      */
@@ -95,7 +96,8 @@
      * Updates the available Cas resource information on the current device.
      *
      * <p><strong>Note:</strong> This update must happen before the first
-     * {@link #requestCasSession(CasSessionRequest, int[])} and {@link #releaseCasSession(int)} call.
+     * {@link #requestCasSession(CasSessionRequest, int[])} and {@link #releaseCasSession(int, int)}
+     * call.
      *
      * @param casSystemId id of the updating CAS system.
      * @param maxSessionNum the max session number of the CAS system that is updated.
@@ -106,7 +108,7 @@
      * Updates the available Lnb resource information on the current device.
      *
      * <p><strong>Note:</strong> This update must happen before the first
-     * {@link #requestLnb(TunerLnbRequest, int[])} and {@link #releaseLnb(int)} call.
+     * {@link #requestLnb(TunerLnbRequest, int[])} and {@link #releaseLnb(int, int)} call.
      *
      * @param lnbIds ids of the updating lnbs.
      */
@@ -132,11 +134,11 @@
      * before this request.
      *
      * @param request {@link TunerFrontendRequest} information of the current request.
-     * @param frontendId a one-element array to return the granted frontendId.
+     * @param frontendHandle a one-element array to return the granted frontendHandle.
      *
      * @return true if there is frontend granted.
      */
-    boolean requestFrontend(in TunerFrontendRequest request, out int[] frontendId);
+    boolean requestFrontend(in TunerFrontendRequest request, out int[] frontendHandle);
 
     /*
      * Requests to share frontend with an existing client.
@@ -240,11 +242,11 @@
      * <p><strong>Note:</strong> {@link #setLnbInfos(int[])} must be called before this request.
      *
      * @param request {@link TunerLnbRequest} information of the current request.
-     * @param lnbId a one-element array to return the granted Lnb id.
+     * @param lnbHandle a one-element array to return the granted Lnb handle.
      *
      * @return true if there is Lnb granted.
      */
-    boolean requestLnb(in TunerLnbRequest request, out int[] lnbId);
+    boolean requestLnb(in TunerLnbRequest request, out int[] lnbHandle);
 
     /*
      * Notifies the TRM that the given frontend has been released.
@@ -254,9 +256,10 @@
      * <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called
      * before this release.
      *
-     * @param frontendId the id of the released frontend.
+     * @param frontendHandle the handle of the released frontend.
+     * @param clientId the id of the client that is releasing the frontend.
      */
-    void releaseFrontend(in int frontendId);
+    void releaseFrontend(in int frontendHandle, int clientId);
 
     /*
      * Notifies the TRM that the Demux with the given handle was released.
@@ -264,8 +267,9 @@
      * <p>Client must call this whenever it releases a demux.
      *
      * @param demuxHandle the handle of the released Tuner Demux.
+     * @param clientId the id of the client that is releasing the demux.
      */
-    void releaseDemux(in int demuxHandle);
+    void releaseDemux(in int demuxHandle, int clientId);
 
     /*
      * Notifies the TRM that the Descrambler with the given handle was released.
@@ -273,8 +277,9 @@
      * <p>Client must call this whenever it releases a descrambler.
      *
      * @param demuxHandle the handle of the released Tuner Descrambler.
+     * @param clientId the id of the client that is releasing the descrambler.
      */
-    void releaseDescrambler(in int descramblerHandle);
+    void releaseDescrambler(in int descramblerHandle, int clientId);
 
     /*
      * Notifies the TRM that the given Cas session has been released.
@@ -284,19 +289,21 @@
      * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this release.
      *
      * @param sessionResourceId the id of the released CAS session.
+     * @param clientId the id of the client that is releasing the cas session.
      */
-    void releaseCasSession(in int sessionResourceId);
+    void releaseCasSession(in int sessionResourceId, int clientId);
 
     /*
-     * Notifies the TRM that the Lnb with the given id was released.
+     * Notifies the TRM that the Lnb with the given handle was released.
      *
      * <p>Client must call this whenever it releases an Lnb.
      *
      * <p><strong>Note:</strong> {@link #setLnbInfos(int[])} must be called before this release.
      *
-     * @param lnbId the id of the released Tuner Lnb.
+     * @param lnbHandle the handle of the released Tuner Lnb.
+     * @param clientId the id of the client that is releasing the lnb.
      */
-    void releaseLnb(in int lnbId);
+    void releaseLnb(in int lnbHandle, int clientId);
 
     /*
      * Compare two clients' priority.
diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
index 2c8899c..b4dcc5d 100644
--- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
+++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
@@ -64,6 +64,7 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     public static final int INVALID_RESOURCE_HANDLE = -1;
+    public static final int INVALID_OWNER_ID = -1;
     /**
      * Tuner resource type to help generate resource handle
      */
@@ -73,6 +74,7 @@
         TUNER_RESOURCE_TYPE_DESCRAMBLER,
         TUNER_RESOURCE_TYPE_LNB,
         TUNER_RESOURCE_TYPE_CAS_SESSION,
+        TUNER_RESOURCE_TYPE_MAX,
      })
     @Retention(RetentionPolicy.SOURCE)
     public @interface TunerResourceType {}
@@ -82,6 +84,7 @@
     public static final int TUNER_RESOURCE_TYPE_DESCRAMBLER = 2;
     public static final int TUNER_RESOURCE_TYPE_LNB = 3;
     public static final int TUNER_RESOURCE_TYPE_CAS_SESSION = 4;
+    public static final int TUNER_RESOURCE_TYPE_MAX = 5;
 
     private final ITunerResourceManager mService;
     private final int mUserId;
@@ -177,7 +180,8 @@
      * Updates the current TRM of the TunerHAL Frontend information.
      *
      * <p><strong>Note:</strong> This update must happen before the first
-     * {@link #requestFrontend(TunerFrontendRequest, int[])} and {@link #releaseFrontend(int)} call.
+     * {@link #requestFrontend(TunerFrontendRequest, int[])} and
+     * {@link #releaseFrontend(int, int)} call.
      *
      * @param infos an array of the available {@link TunerFrontendInfo} information.
      */
@@ -193,7 +197,7 @@
      * Updates the TRM of the current CAS information.
      *
      * <p><strong>Note:</strong> This update must happen before the first
-     * {@link #requestCasSession(CasSessionRequest, int[])} and {@link #releaseCasSession(int)}
+     * {@link #requestCasSession(CasSessionRequest, int[])} and {@link #releaseCasSession(int, int)}
      * call.
      *
      * @param casSystemId id of the updating CAS system.
@@ -211,7 +215,7 @@
      * Updates the TRM of the current Lnb information.
      *
      * <p><strong>Note:</strong> This update must happen before the first
-     * {@link #requestLnb(TunerLnbRequest, int[])} and {@link #releaseLnb(int)} call.
+     * {@link #requestLnb(TunerLnbRequest, int[])} and {@link #releaseLnb(int, int)} call.
      *
      * @param lnbIds ids of the updating lnbs.
      */
@@ -243,16 +247,16 @@
      * before this request.
      *
      * @param request {@link TunerFrontendRequest} information of the current request.
-     * @param frontendId a one-element array to return the granted frontendId. If
-     *                   no frontend granted, this will return {@link #INVALID_FRONTEND_ID}.
+     * @param frontendHandle a one-element array to return the granted frontendHandle. If
+     *                       no frontend granted, this will return {@link #INVALID_RESOURCE_HANDLE}.
      *
      * @return true if there is frontend granted.
      */
     public boolean requestFrontend(@NonNull TunerFrontendRequest request,
-                @Nullable int[] frontendId) {
+                @Nullable int[] frontendHandle) {
         boolean result = false;
         try {
-            result = mService.requestFrontend(request, frontendId);
+            result = mService.requestFrontend(request, frontendHandle);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -393,15 +397,15 @@
      * <p><strong>Note:</strong> {@link #setLnbInfoList(int[])} must be called before this request.
      *
      * @param request {@link TunerLnbRequest} information of the current request.
-     * @param lnbId a one-element array to return the granted Lnb id.
-     *              If no Lnb granted, this will return {@link #INVALID_LNB_ID}.
+     * @param lnbHandle a one-element array to return the granted Lnb handle.
+     *                  If no Lnb granted, this will return {@link #INVALID_RESOURCE_HANDLE}.
      *
      * @return true if there is Lnb granted.
      */
-    public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull int[] lnbId) {
+    public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull int[] lnbHandle) {
         boolean result = false;
         try {
-            result = mService.requestLnb(request, lnbId);
+            result = mService.requestLnb(request, lnbHandle);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -416,11 +420,12 @@
      * <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called
      * before this release.
      *
-     * @param frontendId the id of the released frontend.
+     * @param frontendHandle the handle of the released frontend.
+     * @param clientId the id of the client that is releasing the frontend.
      */
-    public void releaseFrontend(int frontendId) {
+    public void releaseFrontend(int frontendHandle, int clientId) {
         try {
-            mService.releaseFrontend(frontendId);
+            mService.releaseFrontend(frontendHandle, clientId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -432,10 +437,11 @@
      * <p>Client must call this whenever it releases an Demux.
      *
      * @param demuxHandle the handle of the released Tuner Demux.
+     * @param clientId the id of the client that is releasing the demux.
      */
-    public void releaseDemux(int demuxHandle) {
+    public void releaseDemux(int demuxHandle, int clientId) {
         try {
-            mService.releaseDemux(demuxHandle);
+            mService.releaseDemux(demuxHandle, clientId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -447,10 +453,11 @@
      * <p>Client must call this whenever it releases an Descrambler.
      *
      * @param descramblerHandle the handle of the released Tuner Descrambler.
+     * @param clientId the id of the client that is releasing the descrambler.
      */
-    public void releaseDescrambler(int descramblerHandle) {
+    public void releaseDescrambler(int descramblerHandle, int clientId) {
         try {
-            mService.releaseDescrambler(descramblerHandle);
+            mService.releaseDescrambler(descramblerHandle, clientId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -465,10 +472,11 @@
      * release.
      *
      * @param sessionResourceId the id of the released CAS session.
+     * @param clientId the id of the client that is releasing the cas session.
      */
-    public void releaseCasSession(int sessionResourceId) {
+    public void releaseCasSession(int sessionResourceId, int clientId) {
         try {
-            mService.releaseCasSession(sessionResourceId);
+            mService.releaseCasSession(sessionResourceId, clientId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -481,11 +489,12 @@
      *
      * <p><strong>Note:</strong> {@link #setLnbInfoList(int[])} must be called before this release.
      *
-     * @param lnbId the id of the released Tuner Lnb.
+     * @param lnbHandle the handle of the released Tuner Lnb.
+     * @param clientId the id of the client that is releasing the lnb.
      */
-    public void releaseLnb(int lnbId) {
+    public void releaseLnb(int lnbHandle, int clientId) {
         try {
-            mService.releaseLnb(lnbId);
+            mService.releaseLnb(lnbHandle, clientId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 7579ca5..909394f 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -833,6 +833,12 @@
 }
 
 JTuner::~JTuner() {
+    if (mFe != NULL) {
+        mFe->close();
+    }
+    if (mDemux != NULL) {
+        mDemux->close();
+    }
     JNIEnv *env = AndroidRuntime::getJNIEnv();
 
     env->DeleteWeakGlobalRef(mObject);
@@ -908,6 +914,14 @@
             (jint) jId);
 }
 
+jint JTuner::closeFrontendById(int id) {
+    if (mFe != NULL && mFeId == id) {
+        Result r = mFe->close();
+        return (jint) r;
+    }
+    return (jint) Result::SUCCESS;
+}
+
 jobject JTuner::getAnalogFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps) {
     jclass clazz = env->FindClass("android/media/tv/tuner/frontend/AnalogFrontendCapabilities");
     jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V");
@@ -1271,6 +1285,23 @@
     return res;
 }
 
+jint JTuner::close() {
+    Result res = Result::SUCCESS;
+    if (mFe != NULL) {
+        res = mFe->close();
+        if (res != Result::SUCCESS) {
+            return (jint) res;
+        }
+    }
+    if (mDemux != NULL) {
+        res = mDemux->close();
+        if (res != Result::SUCCESS) {
+            return (jint) res;
+        }
+    }
+    return (jint) res;
+}
+
 jobject JTuner::getAvSyncHwId(sp<Filter> filter) {
     if (mDemux == NULL) {
         return NULL;
@@ -2362,6 +2393,13 @@
     return tuner->openFrontendById(id);
 }
 
+static jint android_media_tv_Tuner_close_frontend_by_handle(
+        JNIEnv *env, jobject thiz, jint handle) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    uint32_t id = getResourceIdFromHandle(handle);
+    return tuner->closeFrontendById(id);
+}
+
 static int android_media_tv_Tuner_tune(JNIEnv *env, jobject thiz, jint type, jobject settings) {
     sp<JTuner> tuner = getTuner(env, thiz);
     return tuner->tune(getFrontendSettings(env, type, settings));
@@ -3135,6 +3173,11 @@
     return (jint) tuner->openDemux();
 }
 
+static jint android_media_tv_Tuner_close_tuner(JNIEnv* env, jobject thiz) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    return (jint) tuner->close();
+}
+
 static jint android_media_tv_Tuner_attach_filter(JNIEnv *env, jobject dvr, jobject filter) {
     sp<Dvr> dvrSp = getDvr(env, dvr);
     if (dvrSp == NULL) {
@@ -3424,6 +3467,8 @@
             (void *)android_media_tv_Tuner_get_frontend_ids },
     { "nativeOpenFrontendByHandle", "(I)Landroid/media/tv/tuner/Tuner$Frontend;",
             (void *)android_media_tv_Tuner_open_frontend_by_handle },
+    { "nativeCloseFrontendByHandle", "(I)I",
+            (void *)android_media_tv_Tuner_close_frontend_by_handle },
     { "nativeTune", "(ILandroid/media/tv/tuner/frontend/FrontendSettings;)I",
             (void *)android_media_tv_Tuner_tune },
     { "nativeStopTune", "()I", (void *)android_media_tv_Tuner_stop_tune },
@@ -3460,6 +3505,7 @@
     { "nativeGetDemuxCapabilities", "()Landroid/media/tv/tuner/DemuxCapabilities;",
             (void *)android_media_tv_Tuner_get_demux_caps },
     { "nativeOpenDemuxByhandle", "(I)I", (void *)android_media_tv_Tuner_open_demux },
+    {"nativeClose", "()I", (void *)android_media_tv_Tuner_close_tuner },
 };
 
 static const JNINativeMethod gFilterMethods[] = {
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 6749ba0..750b146 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -172,6 +172,7 @@
     int disconnectCiCam();
     jobject getFrontendIds();
     jobject openFrontendById(int id);
+    jint closeFrontendById(int id);
     jobject getFrontendInfo(int id);
     int tune(const FrontendSettings& settings);
     int stopTune();
@@ -189,6 +190,7 @@
     jobject getDemuxCaps();
     jobject getFrontendStatus(jintArray types);
     Result openDemux();
+    jint close();
 
 protected:
     virtual ~JTuner();
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
index 7b39ba3..345a649 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
@@ -76,7 +76,7 @@
                             callbackHandler.post(callback);
                         }
                     }
-                });
+                }, null /* finishedListener */);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 5c3d17c..502c078 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -19,6 +19,7 @@
 import static android.view.ViewRootImpl.sNewInsetsMode;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
@@ -30,12 +31,14 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.ColorStateList;
+import android.graphics.Rect;
 import android.metrics.LogMaker;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.MathUtils;
 import android.util.Slog;
 import android.util.TypedValue;
 import android.view.LayoutInflater;
@@ -44,6 +47,7 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.WindowInsets;
+import android.view.WindowInsetsAnimation;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 
@@ -63,6 +67,8 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.InjectionInflationController;
 
+import java.util.List;
+
 public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSecurityView {
     private static final boolean DEBUG = KeyguardConstants.DEBUG;
     private static final String TAG = "KeyguardSecurityView";
@@ -114,6 +120,47 @@
     private boolean mIsDragging;
     private float mStartTouchY = -1;
 
+    private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
+            new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+
+                private final Rect mInitialBounds = new Rect();
+                private final Rect mFinalBounds = new Rect();
+
+                @Override
+                public void onPrepare(WindowInsetsAnimation animation) {
+                    mSecurityViewFlipper.getBoundsOnScreen(mInitialBounds);
+                }
+
+                @Override
+                public WindowInsetsAnimation.Bounds onStart(WindowInsetsAnimation animation,
+                        WindowInsetsAnimation.Bounds bounds) {
+                    mSecurityViewFlipper.getBoundsOnScreen(mFinalBounds);
+                    return bounds;
+                }
+
+                @Override
+                public WindowInsets onProgress(WindowInsets windowInsets,
+                        List<WindowInsetsAnimation> list) {
+                    int translationY = 0;
+                    for (WindowInsetsAnimation animation : list) {
+                        if ((animation.getTypeMask() & WindowInsets.Type.ime()) == 0) {
+                            continue;
+                        }
+                        final int paddingBottom = (int) MathUtils.lerp(
+                                mInitialBounds.bottom - mFinalBounds.bottom, 0,
+                                animation.getInterpolatedFraction());
+                        translationY += paddingBottom;
+                    }
+                    mSecurityViewFlipper.setTranslationY(translationY);
+                    return windowInsets;
+                }
+
+                @Override
+                public void onEnd(WindowInsetsAnimation animation) {
+                    mSecurityViewFlipper.setTranslationY(0);
+                }
+            };
+
     // Used to notify the container when something interesting happens.
     public interface SecurityCallback {
         public boolean dismiss(boolean authenticated, int targetUserId,
@@ -162,6 +209,7 @@
         if (mCurrentSecuritySelection != SecurityMode.None) {
             getSecurityView(mCurrentSecuritySelection).onResume(reason);
         }
+        mSecurityViewFlipper.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback);
         updateBiometricRetry();
     }
 
@@ -175,6 +223,7 @@
         if (mCurrentSecuritySelection != SecurityMode.None) {
             getSecurityView(mCurrentSecuritySelection).onPause();
         }
+        mSecurityViewFlipper.setWindowInsetsAnimationCallback(null);
     }
 
     @Override
@@ -333,7 +382,9 @@
         }
     }
 
-    protected void onFinishInflate() {
+    @Override
+    public void onFinishInflate() {
+        super.onFinishInflate();
         mSecurityViewFlipper = findViewById(R.id.view_flipper);
         mSecurityViewFlipper.setLockPatternUtils(mLockPatternUtils);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 9bb253b..2c1bd21 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -195,9 +195,9 @@
     private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
 
     @VisibleForTesting
-    protected ArrayList<Action> mItems;
+    protected final ArrayList<Action> mItems = new ArrayList<>();
     @VisibleForTesting
-    protected ArrayList<Action> mOverflowItems;
+    protected final ArrayList<Action> mOverflowItems = new ArrayList<>();
 
     private ActionsDialog mDialog;
 
@@ -453,19 +453,12 @@
         prepareDialog();
         seedFavorites();
 
-        // If we only have 1 item and it's a simple press action, just do this action.
-        if (mAdapter.getCount() == 1
-                && mAdapter.getItem(0) instanceof SinglePressAction
-                && !(mAdapter.getItem(0) instanceof LongPressAction)) {
-            ((SinglePressAction) mAdapter.getItem(0)).onPress();
-        } else {
-            WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
-            attrs.setTitle("ActionsDialog");
-            attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-            mDialog.getWindow().setAttributes(attrs);
-            mDialog.show();
-            mWindowManagerFuncs.onGlobalActionsShown();
-        }
+        WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
+        attrs.setTitle("ActionsDialog");
+        attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        mDialog.getWindow().setAttributes(attrs);
+        mDialog.show();
+        mWindowManagerFuncs.onGlobalActionsShown();
     }
 
     @VisibleForTesting
@@ -485,7 +478,7 @@
      */
     @VisibleForTesting
     protected int getMaxShownPowerItems() {
-        if (shouldShowControls()) {
+        if (shouldUseControlsLayout()) {
             return mResources.getInteger(com.android.systemui.R.integer.power_menu_max_columns);
         } else {
             return Integer.MAX_VALUE;
@@ -497,10 +490,10 @@
      * whether controls are enabled and whether the max number of shown items has been reached.
      */
     private void addActionItem(Action action) {
-        if (mItems != null && shouldShowAction(action)) {
+        if (shouldShowAction(action)) {
             if (mItems.size() < getMaxShownPowerItems()) {
                 mItems.add(action);
-            } else if (mOverflowItems != null) {
+            } else {
                 mOverflowItems.add(action);
             }
         }
@@ -522,8 +515,8 @@
         mAirplaneModeOn = new AirplaneModeAction();
         onAirplaneModeChanged();
 
-        mItems = new ArrayList<Action>();
-        mOverflowItems = new ArrayList<Action>();
+        mItems.clear();
+        mOverflowItems.clear();
         String[] defaultActions = getDefaultActions();
 
         // make sure emergency affordance action is first, if needed
@@ -588,6 +581,11 @@
         }
     }
 
+    private void onRotate() {
+        // re-allocate actions between main and overflow lists
+        this.createActionItems();
+    }
+
     /**
      * Create the global actions dialog.
      *
@@ -599,11 +597,12 @@
         mAdapter = new MyAdapter();
         mOverflowAdapter = new MyOverflowAdapter();
 
-        mDepthController.setShowingHomeControls(shouldShowControls());
+        mDepthController.setShowingHomeControls(shouldUseControlsLayout());
         ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, mOverflowAdapter,
                 getWalletPanelViewController(), mDepthController, mSysuiColorExtractor,
                 mStatusBarService, mNotificationShadeWindowController,
-                shouldShowControls() ? mControlsUiController : null, mBlurUtils);
+                shouldShowControls() ? mControlsUiController : null, mBlurUtils,
+                shouldUseControlsLayout(), this::onRotate);
         dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
         dialog.setKeyguardShowing(mKeyguardShowing);
 
@@ -704,7 +703,7 @@
 
         @Override
         public boolean shouldBeSeparated() {
-            return !shouldShowControls();
+            return !shouldUseControlsLayout();
         }
 
         @Override
@@ -712,7 +711,7 @@
                 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
             View v = super.create(context, convertView, parent, inflater);
             int textColor;
-            if (shouldShowControls()) {
+            if (shouldUseControlsLayout()) {
                 v.setBackgroundTintList(ColorStateList.valueOf(v.getResources().getColor(
                         com.android.systemui.R.color.global_actions_emergency_background)));
                 textColor = v.getResources().getColor(
@@ -1152,7 +1151,7 @@
     }
 
     private int getActionLayoutId() {
-        if (shouldShowControls()) {
+        if (shouldUseControlsLayout()) {
             return com.android.systemui.R.layout.global_actions_grid_item_v2;
         }
         return com.android.systemui.R.layout.global_actions_grid_item;
@@ -1275,12 +1274,12 @@
     public class MyOverflowAdapter extends BaseAdapter {
         @Override
         public int getCount() {
-            return mOverflowItems != null ? mOverflowItems.size() : 0;
+            return mOverflowItems.size();
         }
 
         @Override
         public Action getItem(int position) {
-            return mOverflowItems != null ? mOverflowItems.get(position) : null;
+            return mOverflowItems.get(position);
         }
 
         @Override
@@ -1888,7 +1887,9 @@
         private final NotificationShadeWindowController mNotificationShadeWindowController;
         private final NotificationShadeDepthController mDepthController;
         private final BlurUtils mBlurUtils;
+        private final boolean mUseControlsLayout;
         private ListPopupWindow mOverflowPopup;
+        private final Runnable mOnRotateCallback;
 
         private ControlsUiController mControlsUiController;
         private ViewGroup mControlsView;
@@ -1898,7 +1899,8 @@
                 NotificationShadeDepthController depthController,
                 SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
                 NotificationShadeWindowController notificationShadeWindowController,
-                ControlsUiController controlsUiController, BlurUtils blurUtils) {
+                ControlsUiController controlsUiController, BlurUtils blurUtils,
+                boolean useControlsLayout, Runnable onRotateCallback) {
             super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
             mContext = context;
             mAdapter = adapter;
@@ -1909,6 +1911,8 @@
             mNotificationShadeWindowController = notificationShadeWindowController;
             mControlsUiController = controlsUiController;
             mBlurUtils = blurUtils;
+            mUseControlsLayout = useControlsLayout;
+            mOnRotateCallback = onRotateCallback;
 
             // Window initialization
             Window window = getWindow();
@@ -2068,7 +2072,7 @@
             }
             if (mBackgroundDrawable == null) {
                 mBackgroundDrawable = new ScrimDrawable();
-                if (mControlsUiController != null) {
+                if (mUseControlsLayout) {
                     mScrimAlpha = 1.0f;
                 } else {
                     mScrimAlpha = mBlurUtils.supportsBlursOnWindows()
@@ -2088,7 +2092,7 @@
         }
 
         private int getGlobalActionsLayoutId(Context context) {
-            if (mControlsUiController != null) {
+            if (mUseControlsLayout) {
                 return com.android.systemui.R.layout.global_actions_grid_v2;
             }
 
@@ -2133,9 +2137,9 @@
             if (!(mBackgroundDrawable instanceof ScrimDrawable)) {
                 return;
             }
-            boolean hasControls = mControlsUiController != null;
             ((ScrimDrawable) mBackgroundDrawable).setColor(
-                    !hasControls && colors.supportsDarkText() ? Color.WHITE : Color.BLACK, animate);
+                    !mUseControlsLayout && colors.supportsDarkText()
+                            ? Color.WHITE : Color.BLACK, animate);
             View decorView = getWindow().getDecorView();
             if (colors.supportsDarkText()) {
                 decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR |
@@ -2177,7 +2181,7 @@
                     .start();
             ViewGroup root = (ViewGroup) mGlobalActionsLayout.getRootView();
             root.setOnApplyWindowInsetsListener((v, windowInsets) -> {
-                if (mControlsUiController != null) {
+                if (mUseControlsLayout) {
                     root.setPadding(windowInsets.getStableInsetLeft(),
                             windowInsets.getStableInsetTop(),
                             windowInsets.getStableInsetRight(),
@@ -2281,10 +2285,14 @@
         public void refreshDialog() {
             initializeLayout();
             mGlobalActionsLayout.updateList();
+            if (mControlsUiController != null) {
+                mControlsUiController.show(mControlsView);
+            }
         }
 
         public void onRotate(int from, int to) {
             if (mShowing) {
+                mOnRotateCallback.run();
                 refreshDialog();
             }
         }
@@ -2317,4 +2325,10 @@
                 && mControlsUiController.getAvailable()
                 && !mControlsServiceInfos.isEmpty();
     }
-}
+
+    // TODO: Remove legacy layout XML and classes.
+    protected boolean shouldUseControlsLayout() {
+        // always use new controls layout
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 5475812..11b54ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -311,6 +311,7 @@
                     mTextView.switchIndication(mTransientIndication);
                 } else if (!TextUtils.isEmpty(mAlignmentIndication)) {
                     mTextView.switchIndication(mAlignmentIndication);
+                    mTextView.setTextColor(Utils.getColorError(mContext));
                 } else if (mPowerPluggedIn) {
                     String indication = computePowerIndication();
                     if (animate) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 31797d1..c9716d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -1780,7 +1780,13 @@
         });
         animator.addListener(new AnimatorListenerAdapter() {
             @Override
+            public void onAnimationStart(Animator animation) {
+                notifyExpandingStarted();
+            }
+
+            @Override
             public void onAnimationEnd(Animator animation) {
+                notifyExpandingFinished();
                 mNotificationStackScroller.resetCheckSnoozeLeavebehind();
                 mQsExpansionAnimator = null;
                 if (onFinishRunnable != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index f7d403f..81dc9e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -157,7 +157,7 @@
     protected void onExpandingStarted() {
     }
 
-    private void notifyExpandingStarted() {
+    protected void notifyExpandingStarted() {
         if (!mExpanding) {
             mExpanding = true;
             onExpandingStarted();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 17cd98f..e65b6fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -197,8 +197,13 @@
                 TelephonyIcons.THREE_G);
         mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EHRPD),
                 TelephonyIcons.THREE_G);
-        mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_UMTS),
+        if (mConfig.show4gFor3g) {
+            mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_UMTS),
+                TelephonyIcons.FOUR_G);
+        } else {
+            mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_UMTS),
                 TelephonyIcons.THREE_G);
+        }
         mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_TD_SCDMA),
                 TelephonyIcons.THREE_G);
 
diff --git a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
index b97f55c..594f0b1 100644
--- a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
+++ b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
@@ -18,7 +18,6 @@
 import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertThat;
 
-import android.content.pm.PackageManager;
 import android.testing.AndroidTestingRunner;
 import android.text.TextUtils;
 import android.util.Log;
@@ -117,12 +116,6 @@
         filter.add(s -> s.startsWith("com.android.systemui")
                 || s.startsWith("com.android.keyguard"));
 
-
-        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
-            // If it's not automotive target, exclude automotive classes from the test.
-            excludeAutomotiveClasses(filter);
-        }
-
         try {
             return scanner.getClassPathEntries(filter);
         } catch (IOException e) {
@@ -131,13 +124,6 @@
         return Collections.emptyList();
     }
 
-    private void excludeAutomotiveClasses(ChainedClassNameFilter filter) {
-        // Modifies the passed in filter.
-        filter.add(s -> !s.startsWith("com.android.systemui.statusbar.car."));
-        filter.add(s -> !s.startsWith("com.android.systemui.qs.car."));
-        filter.add(s -> !s.startsWith("com.android.systemui.car."));
-    }
-
     private String getClsStr() {
         return TextUtils.join(",", Arrays.asList(BASE_CLS_WHITELIST)
                 .stream().map(cls -> cls.getSimpleName()).toArray());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index be5b190..fb40177 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -192,7 +192,8 @@
 
         assertThat(mTextView.getText()).isEqualTo(
                 mContext.getResources().getString(R.string.dock_alignment_slow_charging));
-        assertThat(mTextView.getCurrentTextColor()).isEqualTo(Color.WHITE);
+        assertThat(mTextView.getCurrentTextColor()).isEqualTo(
+                Utils.getColorError(mContext).getDefaultColor());
     }
 
     @Test
@@ -209,7 +210,8 @@
 
         assertThat(mTextView.getText()).isEqualTo(
                 mContext.getResources().getString(R.string.dock_alignment_not_charging));
-        assertThat(mTextView.getCurrentTextColor()).isEqualTo(Color.WHITE);
+        assertThat(mTextView.getCurrentTextColor()).isEqualTo(
+                Utils.getColorError(mContext).getDefaultColor());
     }
 
     @Test
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java
index ff83fd1..de2f90e 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java
@@ -50,9 +50,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.util.Arrays;
-import java.util.List;
-
 /**
  * A class to display tethering-related notifications.
  *
@@ -89,6 +86,9 @@
     static final int NO_ICON_ID = 0;
     @VisibleForTesting
     static final int DOWNSTREAM_NONE = 0;
+    // Refer to TelephonyManager#getSimCarrierId for more details about carrier id.
+    @VisibleForTesting
+    static final int VERIZON_CARRIER_ID = 1839;
     private final Context mContext;
     private final NotificationManager mNotificationManager;
     private final NotificationChannel mChannel;
@@ -114,11 +114,11 @@
     @interface NotificationId {}
 
     private static final class MccMncOverrideInfo {
-        public final List<String> visitedMccMncs;
+        public final String visitedMccMnc;
         public final int homeMcc;
         public final int homeMnc;
-        MccMncOverrideInfo(List<String> visitedMccMncs, int mcc, int mnc) {
-            this.visitedMccMncs = visitedMccMncs;
+        MccMncOverrideInfo(String visitedMccMnc, int mcc, int mnc) {
+            this.visitedMccMnc = visitedMccMnc;
             this.homeMcc = mcc;
             this.homeMnc = mnc;
         }
@@ -127,9 +127,7 @@
     private static final SparseArray<MccMncOverrideInfo> sCarrierIdToMccMnc = new SparseArray<>();
 
     static {
-        // VZW
-        sCarrierIdToMccMnc.put(
-                1839, new MccMncOverrideInfo(Arrays.asList(new String[] {"20404"}), 311, 480));
+        sCarrierIdToMccMnc.put(VERIZON_CARRIER_ID, new MccMncOverrideInfo("20404", 311, 480));
     }
 
     public TetheringNotificationUpdater(@NonNull final Context context,
@@ -200,7 +198,7 @@
         final int carrierId = tm.getSimCarrierId();
         final String mccmnc = tm.getSimOperator();
         final MccMncOverrideInfo overrideInfo = sCarrierIdToMccMnc.get(carrierId);
-        if (overrideInfo != null && overrideInfo.visitedMccMncs.contains(mccmnc)) {
+        if (overrideInfo != null && overrideInfo.visitedMccMnc.equals(mccmnc)) {
             // Re-configure MCC/MNC value to specific carrier to get right resources.
             final Configuration config = res.getConfiguration();
             config.mcc = overrideInfo.homeMcc;
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
index 5f88588..294bf1b 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
@@ -39,6 +39,7 @@
 import com.android.networkstack.tethering.TetheringNotificationUpdater.EVENT_SHOW_NO_UPSTREAM
 import com.android.networkstack.tethering.TetheringNotificationUpdater.NO_UPSTREAM_NOTIFICATION_ID
 import com.android.networkstack.tethering.TetheringNotificationUpdater.RESTRICTED_NOTIFICATION_ID
+import com.android.networkstack.tethering.TetheringNotificationUpdater.VERIZON_CARRIER_ID
 import com.android.testutils.waitForIdle
 import org.junit.After
 import org.junit.Assert.assertEquals
@@ -417,7 +418,7 @@
         assertEquals(config.mcc, res.configuration.mcc)
         assertEquals(config.mnc, res.configuration.mnc)
 
-        doReturn(1839).`when`(telephonyManager).getSimCarrierId()
+        doReturn(VERIZON_CARRIER_ID).`when`(telephonyManager).getSimCarrierId()
         res = notificationUpdater.getResourcesForSubId(context, subId)
         assertEquals(config.mcc, res.configuration.mcc)
         assertEquals(config.mnc, res.configuration.mnc)
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 34d2b73..7750e32 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -256,6 +256,10 @@
     // Package: android
     NOTE_ADB_WIFI_ACTIVE = 62;
 
+    // Notify user there was a non-framework gnss location access during an emergency
+    // Package: android
+    NOTE_GNSS_NFW_LOCATION_ACCESS = 63;
+
     // ADD_NEW_IDS_ABOVE_THIS_LINE
     // Legacy IDs with arbitrary values appear below
     // Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index fe33fae9..ce65110 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -1741,8 +1741,9 @@
 
         final long nowElapsed = mInjector.getElapsedRealtime();
         final long nominalTrigger = convertToElapsed(triggerAtTime, type);
-        // Try to prevent spamming by making sure we aren't firing alarms in the immediate future
-        final long minTrigger = nowElapsed + mConstants.MIN_FUTURITY;
+        // Try to prevent spamming by making sure apps aren't firing alarms in the immediate future
+        final long minTrigger = nowElapsed
+                + (UserHandle.isCore(callingUid) ? 0L : mConstants.MIN_FUTURITY);
         final long triggerElapsed = (nominalTrigger > minTrigger) ? nominalTrigger : minTrigger;
 
         final long maxElapsed;
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 1a58d60..f1ea5d0 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2093,6 +2093,20 @@
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
     }
 
+    private void enforceNetworkFactoryOrSettingsPermission() {
+        enforceAnyPermissionOf(
+                android.Manifest.permission.NETWORK_SETTINGS,
+                android.Manifest.permission.NETWORK_FACTORY,
+                NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
+    }
+
+    private void enforceNetworkFactoryOrTestNetworksPermission() {
+        enforceAnyPermissionOf(
+                android.Manifest.permission.MANAGE_TEST_NETWORKS,
+                android.Manifest.permission.NETWORK_FACTORY,
+                NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
+    }
+
     private boolean checkSettingsPermission() {
         return checkAnyPermissionOf(
                 android.Manifest.permission.NETWORK_SETTINGS,
@@ -2722,7 +2736,9 @@
                     break;
                 }
                 case NetworkAgent.EVENT_NETWORK_PROPERTIES_CHANGED: {
-                    handleUpdateLinkProperties(nai, (LinkProperties) msg.obj);
+                    LinkProperties newLp = (LinkProperties) msg.obj;
+                    processLinkPropertiesFromAgent(nai, newLp);
+                    handleUpdateLinkProperties(nai, newLp);
                     break;
                 }
                 case NetworkAgent.EVENT_NETWORK_INFO_CHANGED: {
@@ -5681,7 +5697,7 @@
 
     @Override
     public int registerNetworkProvider(Messenger messenger, String name) {
-        enforceNetworkFactoryPermission();
+        enforceNetworkFactoryOrSettingsPermission();
         NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger,
                 null /* asyncChannel */, nextNetworkProviderId(),
                 () -> unregisterNetworkProvider(messenger));
@@ -5691,7 +5707,7 @@
 
     @Override
     public void unregisterNetworkProvider(Messenger messenger) {
-        enforceNetworkFactoryPermission();
+        enforceNetworkFactoryOrSettingsPermission();
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_PROVIDER, messenger));
     }
 
@@ -5711,7 +5727,11 @@
 
     @Override
     public void declareNetworkRequestUnfulfillable(NetworkRequest request) {
-        enforceNetworkFactoryPermission();
+        if (request.hasTransport(TRANSPORT_TEST)) {
+            enforceNetworkFactoryOrTestNetworksPermission();
+        } else {
+            enforceNetworkFactoryPermission();
+        }
         mHandler.post(() -> handleReleaseNetworkRequest(request, Binder.getCallingUid(), true));
     }
 
@@ -5817,7 +5837,7 @@
         }
 
         LinkProperties lp = new LinkProperties(linkProperties);
-        lp.ensureDirectlyConnectedRoutes();
+
         // TODO: Instead of passing mDefaultRequest, provide an API to determine whether a Network
         // satisfies mDefaultRequest.
         final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
@@ -5825,8 +5845,11 @@
                 new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc,
                 currentScore, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig),
                 this, mNetd, mDnsResolver, mNMS, providerId);
-        // Make sure the network capabilities reflect what the agent info says.
+
+        // Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says.
         nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc));
+        processLinkPropertiesFromAgent(nai, nai.linkProperties);
+
         final String extraInfo = networkInfo.getExtraInfo();
         final String name = TextUtils.isEmpty(extraInfo)
                 ? nai.networkCapabilities.getSsid() : extraInfo;
@@ -5864,6 +5887,10 @@
         updateUids(nai, null, nai.networkCapabilities);
     }
 
+    private void processLinkPropertiesFromAgent(NetworkAgentInfo nai, LinkProperties lp) {
+        lp.ensureDirectlyConnectedRoutes();
+    }
+
     private void updateLinkProperties(NetworkAgentInfo networkAgent, LinkProperties newLp,
             @NonNull LinkProperties oldLp) {
         int netId = networkAgent.network.netId;
@@ -6391,13 +6418,13 @@
             // Ignore updates for disconnected networks
             return;
         }
-        // newLp is already a defensive copy.
-        newLp.ensureDirectlyConnectedRoutes();
         if (VDBG || DDBG) {
             log("Update of LinkProperties for " + nai.toShortString()
                     + "; created=" + nai.created
                     + "; everConnected=" + nai.everConnected);
         }
+        // TODO: eliminate this defensive copy after confirming that updateLinkProperties does not
+        // modify its oldLp parameter.
         updateLinkProperties(nai, newLp, new LinkProperties(nai.linkProperties));
     }
 
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index 741cb5b..e6b2d26 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -81,6 +81,9 @@
         RUNNING,      // start() called, and the stacked iface is known to be up.
     }
 
+    /** NAT64 prefix currently in use. Only valid in STARTING or RUNNING states. */
+    private IpPrefix mNat64PrefixInUse;
+    /** NAT64 prefix (if any) discovered from DNS via RFC 7050. */
     private IpPrefix mNat64PrefixFromDns;
     private String mBaseIface;
     private String mIface;
@@ -178,9 +181,10 @@
             return;
         }
 
+        mNat64PrefixInUse = getNat64Prefix();
         String addrStr = null;
         try {
-            addrStr = mNetd.clatdStart(baseIface, getNat64Prefix().toString());
+            addrStr = mNetd.clatdStart(baseIface, mNat64PrefixInUse.toString());
         } catch (RemoteException | ServiceSpecificException e) {
             Slog.e(TAG, "Error starting clatd on " + baseIface + ": " + e);
         }
@@ -211,12 +215,13 @@
         } catch (RemoteException | IllegalStateException e) {
             Slog.e(TAG, "Error unregistering clatd observer on " + mBaseIface + ": " + e);
         }
+        mNat64PrefixInUse = null;
         mIface = null;
         mBaseIface = null;
         if (requiresClat(mNetwork)) {
             mState = State.DISCOVERING;
         } else {
-            stopPrefixDiscovery();
+            stopPrefixDiscovery();  // Enters IDLE state.
         }
     }
 
@@ -274,19 +279,32 @@
     private void startPrefixDiscovery() {
         try {
             mDnsResolver.startPrefix64Discovery(getNetId());
-            mState = State.DISCOVERING;
         } catch (RemoteException | ServiceSpecificException e) {
             Slog.e(TAG, "Error starting prefix discovery on netId " + getNetId() + ": " + e);
         }
+        mState = State.DISCOVERING;
     }
 
     private void stopPrefixDiscovery() {
         try {
             mDnsResolver.stopPrefix64Discovery(getNetId());
-            mState = State.IDLE;
         } catch (RemoteException | ServiceSpecificException e) {
             Slog.e(TAG, "Error stopping prefix discovery on netId " + getNetId() + ": " + e);
         }
+        mState = State.IDLE;
+    }
+
+    private void maybeHandleNat64PrefixChange() {
+        final IpPrefix newPrefix = getNat64Prefix();
+        if (!Objects.equals(mNat64PrefixInUse, newPrefix)) {
+            Slog.d(TAG, "NAT64 prefix changed from " + mNat64PrefixInUse + " to "
+                    + newPrefix);
+            stop();
+            // It's safe to call update here, even though this method is called from update, because
+            // stop() is guaranteed to have moved out of STARTING and RUNNING, which are the only
+            // states in which this method can be called.
+            update();
+        }
     }
 
     /**
@@ -325,11 +343,11 @@
                 // Stop clatd and go back into DISCOVERING or idle.
                 if (!shouldStartClat(mNetwork)) {
                     stop();
+                    break;
                 }
+                // Only necessary while clat is actually started.
+                maybeHandleNat64PrefixChange();
                 break;
-                // TODO: support the NAT64 prefix changing after it's been discovered. There is
-                // no need to support this at the moment because it cannot happen without
-                // changes to the Dns64Configuration code in netd.
         }
     }
 
@@ -347,6 +365,8 @@
      * has no idea that 464xlat is running on top of it.
      */
     public void fixupLinkProperties(@NonNull LinkProperties oldLp, @NonNull LinkProperties lp) {
+        // This must be done even if clatd is not running, because otherwise shouldStartClat would
+        // never return true.
         lp.setNat64Prefix(getNat64Prefix());
 
         if (!isRunning()) {
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 3fb713b..e17cca4 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -17,8 +17,9 @@
 package com.android.server.location.gnss;
 
 import android.content.Context;
-import android.database.Cursor;
 import android.net.ConnectivityManager;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
@@ -26,25 +27,26 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.PowerManager;
-import android.provider.Telephony.Carriers;
-import android.telephony.ServiceState;
-import android.telephony.TelephonyManager;
+import android.telephony.PhoneStateListener;
+import android.telephony.PreciseCallState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
-import android.telephony.PreciseCallState;
-import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
 import android.util.Log;
 
 import com.android.internal.location.GpsNetInitiatedHandler;
 
+import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.Arrays;
-import java.util.Map;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
 
 /**
  * Handles network connection requests and network state change updates for AGPS data download.
@@ -385,10 +387,10 @@
     private ConnectivityManager.NetworkCallback createSuplConnectivityCallback() {
         return new ConnectivityManager.NetworkCallback() {
             @Override
-            public void onAvailable(Network network) {
+            public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
                 if (DEBUG) Log.d(TAG, "SUPL network connection available.");
                 // Specific to a change to a SUPL enabled network becoming ready
-                handleSuplConnectionAvailable(network);
+                handleSuplConnectionAvailable(network, linkProperties);
             }
 
             @Override
@@ -496,7 +498,7 @@
         return networkAttributes;
     }
 
-    private void handleSuplConnectionAvailable(Network network) {
+    private void handleSuplConnectionAvailable(Network network, LinkProperties linkProperties) {
         // TODO: The synchronous method ConnectivityManager.getNetworkInfo() should not be called
         //       inside the asynchronous ConnectivityManager.NetworkCallback methods.
         NetworkInfo info = mConnMgr.getNetworkInfo(network);
@@ -528,7 +530,7 @@
                 setRouting();
             }
 
-            int apnIpType = getApnIpType(apn);
+            int apnIpType = getLinkIpType(linkProperties);
             if (DEBUG) {
                 String message = String.format(
                         "native_agps_data_conn_open: mAgpsApn=%s, mApnIpType=%s",
@@ -704,74 +706,32 @@
         }
     }
 
-    private int getApnIpType(String apn) {
+    private int getLinkIpType(LinkProperties linkProperties) {
         ensureInHandlerThread();
-        if (apn == null) {
-            return APN_INVALID;
-        }
-        TelephonyManager phone = (TelephonyManager)
-                mContext.getSystemService(Context.TELEPHONY_SERVICE);
-        // During an emergency call with an active sub id, get the Telephony Manager specific
-        // to the active sub to get the correct value from getServiceState and getNetworkType
-        if (mNiHandler.getInEmergency() && mActiveSubId >= 0) {
-            TelephonyManager subIdTelManager =
-                    phone.createForSubscriptionId(mActiveSubId);
-            if (subIdTelManager != null) {
-                phone = subIdTelManager;
+        boolean isIPv4 = false;
+        boolean isIPv6 = false;
+
+        List<LinkAddress> linkAddresses = linkProperties.getLinkAddresses();
+        for (LinkAddress linkAddress : linkAddresses) {
+            InetAddress inetAddress = linkAddress.getAddress();
+            if (inetAddress instanceof Inet4Address) {
+                isIPv4 = true;
+            } else if (inetAddress instanceof Inet6Address) {
+                isIPv6 = true;
             }
-        }
-        ServiceState serviceState = phone.getServiceState();
-        String projection = null;
-        String selection = null;
-
-        // Carrier configuration may override framework roaming state, we need to use the actual
-        // modem roaming state instead of the framework roaming state.
-        if (serviceState != null && serviceState.getDataRoamingFromRegistration()) {
-            projection = Carriers.ROAMING_PROTOCOL;
-        } else {
-            projection = Carriers.PROTOCOL;
-        }
-        // No SIM case for emergency
-        if (TelephonyManager.NETWORK_TYPE_UNKNOWN == phone.getNetworkType()
-                && AGPS_TYPE_EIMS == mAGpsType) {
-            selection = String.format(
-                "type like '%%emergency%%' and apn = '%s' and carrier_enabled = 1", apn);
-        } else {
-            selection = String.format("current = 1 and apn = '%s' and carrier_enabled = 1", apn);
-        }
-        try (Cursor cursor = mContext.getContentResolver().query(
-                Carriers.CONTENT_URI,
-                new String[]{projection},
-                selection,
-                null,
-                Carriers.DEFAULT_SORT_ORDER)) {
-            if (null != cursor && cursor.moveToFirst()) {
-                return translateToApnIpType(cursor.getString(0), apn);
-            } else {
-                Log.e(TAG, "No entry found in query for APN: " + apn);
-            }
-        } catch (Exception e) {
-            Log.e(TAG, "Error encountered on APN query for: " + apn, e);
+            if (DEBUG) Log.d(TAG, "LinkAddress : " + inetAddress.toString());
         }
 
-        return APN_IPV4V6;
-    }
-
-    private int translateToApnIpType(String ipProtocol, String apn) {
-        if ("IP".equals(ipProtocol)) {
-            return APN_IPV4;
-        }
-        if ("IPV6".equals(ipProtocol)) {
-            return APN_IPV6;
-        }
-        if ("IPV4V6".equals(ipProtocol)) {
+        if (isIPv4 && isIPv6) {
             return APN_IPV4V6;
         }
-
-        // we hit the default case so the ipProtocol is not recognized
-        String message = String.format("Unknown IP Protocol: %s, for APN: %s", ipProtocol, apn);
-        Log.e(TAG, message);
-        return APN_IPV4V6;
+        if (isIPv4) {
+            return APN_IPV4;
+        }
+        if (isIPv6) {
+            return APN_IPV6;
+        }
+        return APN_INVALID;
     }
 
     // AGPS support
diff --git a/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java b/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
index 06fa0ea..75fd7dc 100644
--- a/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
+++ b/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
@@ -21,13 +21,13 @@
 import android.app.AppOpsManager;
 import android.app.Notification;
 import android.app.NotificationManager;
-import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.hardware.gnss.visibility_control.V1_0.IGnssVisibilityControlCallback;
 import android.location.LocationManager;
 import android.os.Handler;
 import android.os.Looper;
@@ -39,12 +39,14 @@
 
 import com.android.internal.R;
 import com.android.internal.location.GpsNetInitiatedHandler;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.FrameworkStatsLog;
 
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Handles GNSS non-framework location access user visibility and control.
@@ -283,21 +285,22 @@
 
     // Represents NfwNotification structure in IGnssVisibilityControlCallback.hal
     private static class NfwNotification {
-        // These must match with NfwResponseType enum in IGnssVisibilityControlCallback.hal.
-        private static final byte NFW_RESPONSE_TYPE_REJECTED = 0;
-        private static final byte NFW_RESPONSE_TYPE_ACCEPTED_NO_LOCATION_PROVIDED = 1;
-        private static final byte NFW_RESPONSE_TYPE_ACCEPTED_LOCATION_PROVIDED = 2;
 
-        private final String mProxyAppPackageName;
-        private final byte mProtocolStack;
-        private final String mOtherProtocolStackName;
-        private final byte mRequestor;
-        private final String mRequestorId;
-        private final byte mResponseType;
-        private final boolean mInEmergencyMode;
-        private final boolean mIsCachedLocation;
+        // These must match with NfwResponseType enum in IGnssVisibilityControlCallback.hal
+        static final byte NFW_RESPONSE_TYPE_REJECTED = 0;
+        static final byte NFW_RESPONSE_TYPE_ACCEPTED_NO_LOCATION_PROVIDED = 1;
+        static final byte NFW_RESPONSE_TYPE_ACCEPTED_LOCATION_PROVIDED = 2;
 
-        private NfwNotification(String proxyAppPackageName, byte protocolStack,
+        final String mProxyAppPackageName;
+        final byte mProtocolStack;
+        final String mOtherProtocolStackName;
+        final byte mRequestor;
+        final String mRequestorId;
+        final byte mResponseType;
+        final boolean mInEmergencyMode;
+        final boolean mIsCachedLocation;
+
+        NfwNotification(String proxyAppPackageName, byte protocolStack,
                 String otherProtocolStackName, byte requestor, String requestorId,
                 byte responseType, boolean inEmergencyMode, boolean isCachedLocation) {
             mProxyAppPackageName = proxyAppPackageName;
@@ -610,43 +613,38 @@
         logEvent(nfwNotification, isPermissionMismatched);
 
         if (nfwNotification.isLocationProvided()) {
-            postEmergencyLocationUserNotification(nfwNotification);
+            displayNfwNotification(nfwNotification);
         }
     }
 
-    private void postEmergencyLocationUserNotification(NfwNotification nfwNotification) {
-        // Emulate deprecated IGnssNi.hal user notification of emergency NI requests.
-        NotificationManager notificationManager = (NotificationManager) mContext
-                .getSystemService(Context.NOTIFICATION_SERVICE);
-        if (notificationManager == null) {
-            Log.w(TAG, "Could not notify user of emergency location request. Notification: "
-                    + nfwNotification);
-            return;
+    private void displayNfwNotification(NfwNotification nfwNotification) {
+        NotificationManager notificationManager = Objects.requireNonNull(
+                mContext.getSystemService(NotificationManager.class));
+
+        String title = mContext.getString(R.string.gnss_nfw_notification_title);
+        String message;
+        if (nfwNotification.mRequestor == IGnssVisibilityControlCallback.NfwRequestor.CARRIER) {
+            message = mContext.getString(R.string.gnss_nfw_notification_message_carrier);
+        } else {
+            message = mContext.getString(R.string.gnss_nfw_notification_message_oem);
         }
 
-        notificationManager.notifyAsUser(/* tag= */ null, /* notificationId= */ 0,
-                createEmergencyLocationUserNotification(mContext), UserHandle.ALL);
-    }
-
-    private static Notification createEmergencyLocationUserNotification(Context context) {
-        // NOTE: Do not reuse the returned notification object as it will not reflect
-        //       changes to notification text when the system language is changed.
-        final String firstLineText = context.getString(R.string.gpsNotifTitle);
-        final String secondLineText =  context.getString(R.string.global_action_emergency);
-        final String accessibilityServicesText = firstLineText + " (" + secondLineText + ")";
-        return new Notification.Builder(context, SystemNotificationChannels.NETWORK_ALERTS)
-                .setSmallIcon(com.android.internal.R.drawable.stat_sys_gps_on)
-                .setWhen(0)
-                .setOngoing(false)
+        Notification.Builder builder = new Notification.Builder(mContext,
+                SystemNotificationChannels.NETWORK_ALERTS)
+                .setSmallIcon(R.drawable.stat_sys_gps_on)
+                .setCategory(Notification.CATEGORY_SYSTEM)
+                .setVisibility(Notification.VISIBILITY_SECRET)
+                .setContentTitle(title)
+                .setTicker(title)
+                .setContentText(message)
+                .setStyle(new Notification.BigTextStyle().bigText(message))
                 .setAutoCancel(true)
-                .setColor(context.getColor(
-                        com.android.internal.R.color.system_notification_accent_color))
-                .setDefaults(0)
-                .setTicker(accessibilityServicesText)
-                .setContentTitle(firstLineText)
-                .setContentText(secondLineText)
-                .setContentIntent(PendingIntent.getBroadcast(context, 0, new Intent(), 0))
-                .build();
+                .setColor(mContext.getColor(R.color.system_notification_accent_color))
+                .setWhen(System.currentTimeMillis())
+                .setShowWhen(true)
+                .setDefaults(0);
+
+        notificationManager.notify(SystemMessage.NOTE_GNSS_NFW_LOCATION_ACCESS, builder.build());
     }
 
     private void logEvent(NfwNotification notification, boolean isPermissionMismatched) {
diff --git a/services/core/java/com/android/server/media/MediaKeyDispatcher.java b/services/core/java/com/android/server/media/MediaKeyDispatcher.java
index e0efd8a..0b96978 100644
--- a/services/core/java/com/android/server/media/MediaKeyDispatcher.java
+++ b/services/core/java/com/android/server/media/MediaKeyDispatcher.java
@@ -16,24 +16,64 @@
 
 package com.android.server.media;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.media.session.ISessionManager;
 import android.media.session.MediaSession;
 import android.os.Binder;
 import android.view.KeyEvent;
+import android.view.ViewConfiguration;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * Provides a way to customize behavior for media key events.
- *
+ * <p>
+ * In order to override the implementation of the single/double/triple click or long press,
+ * {@link #setOverriddenKeyEvents(int, int)} should be called for each key code with the
+ * overridden {@link KeyEventType} bit value set, and the corresponding method,
+ * {@link #onSingleClick(KeyEvent)}, {@link #onDoubleClick(KeyEvent)},
+ * {@link #onTripleClick(KeyEvent)}, {@link #onLongPress(KeyEvent)} should be implemented.
+ * <p>
  * Note: When instantiating this class, {@link MediaSessionService} will only use the constructor
  * without any parameters.
  */
+// TODO: Change API names from using "click" to "tap"
+// TODO: Move this class to apex/media/
 public abstract class MediaKeyDispatcher {
+    @IntDef(flag = true, value = {
+            KEY_EVENT_SINGLE_CLICK,
+            KEY_EVENT_DOUBLE_CLICK,
+            KEY_EVENT_TRIPLE_CLICK,
+            KEY_EVENT_LONG_PRESS
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface KeyEventType {}
+    static final int KEY_EVENT_SINGLE_CLICK = 1 << 0;
+    static final int KEY_EVENT_DOUBLE_CLICK = 1 << 1;
+    static final int KEY_EVENT_TRIPLE_CLICK = 1 << 2;
+    static final int KEY_EVENT_LONG_PRESS = 1 << 3;
+
+    private Map<Integer, Integer> mOverriddenKeyEvents;
+
     public MediaKeyDispatcher() {
         // Constructor used for reflection
+        mOverriddenKeyEvents = new HashMap<>();
+        mOverriddenKeyEvents.put(KeyEvent.KEYCODE_MEDIA_PLAY, 0);
+        mOverriddenKeyEvents.put(KeyEvent.KEYCODE_MEDIA_PAUSE, 0);
+        mOverriddenKeyEvents.put(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0);
+        mOverriddenKeyEvents.put(KeyEvent.KEYCODE_MUTE, 0);
+        mOverriddenKeyEvents.put(KeyEvent.KEYCODE_HEADSETHOOK, 0);
+        mOverriddenKeyEvents.put(KeyEvent.KEYCODE_MEDIA_STOP, 0);
+        mOverriddenKeyEvents.put(KeyEvent.KEYCODE_MEDIA_NEXT, 0);
+        mOverriddenKeyEvents.put(KeyEvent.KEYCODE_MEDIA_PREVIOUS, 0);
     }
 
+    // TODO: Move this method into SessionPolicyProvider.java for better readability.
     /**
      * Implement this to customize the logic for which MediaSession should consume which key event.
      *
@@ -49,4 +89,137 @@
             boolean asSystemService) {
         return null;
     }
+
+    /**
+     * Gets the map of key code -> {@link KeyEventType} that have been overridden.
+     * <p>
+     * The list of valid key codes are the following:
+     * <ul>
+     * <li> {@link KeyEvent#KEYCODE_MEDIA_PLAY}
+     * <li> {@link KeyEvent#KEYCODE_MEDIA_PAUSE}
+     * <li> {@link KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE}
+     * <li> {@link KeyEvent#KEYCODE_MUTE}
+     * <li> {@link KeyEvent#KEYCODE_HEADSETHOOK}
+     * <li> {@link KeyEvent#KEYCODE_MEDIA_STOP}
+     * <li> {@link KeyEvent#KEYCODE_MEDIA_NEXT}
+     * <li> {@link KeyEvent#KEYCODE_MEDIA_PREVIOUS}
+     * </ul>
+     * @see {@link KeyEvent#isMediaSessionKey(int)}
+     */
+    @KeyEventType Map<Integer, Integer> getOverriddenKeyEvents() {
+        return mOverriddenKeyEvents;
+    }
+
+    static boolean isSingleClickOverridden(@KeyEventType int overriddenKeyEvents) {
+        return (overriddenKeyEvents & MediaKeyDispatcher.KEY_EVENT_SINGLE_CLICK) != 0;
+    }
+
+    static boolean isDoubleClickOverridden(@KeyEventType int overriddenKeyEvents) {
+        return (overriddenKeyEvents & MediaKeyDispatcher.KEY_EVENT_DOUBLE_CLICK) != 0;
+    }
+
+    static boolean isTripleClickOverridden(@KeyEventType int overriddenKeyEvents) {
+        return (overriddenKeyEvents & MediaKeyDispatcher.KEY_EVENT_TRIPLE_CLICK) != 0;
+    }
+
+    static boolean isLongPressOverridden(@KeyEventType int overriddenKeyEvents) {
+        return (overriddenKeyEvents & MediaKeyDispatcher.KEY_EVENT_LONG_PRESS) != 0;
+    }
+
+    /**
+     * Sets the value of the given key event type flagged with overridden {@link KeyEventType} to
+     * the given key code. If called multiple times for the same key code, will be overwritten to
+     * the most recently called {@link KeyEventType} value.
+     * <p>
+     * The list of valid key codes are the following:
+     * <ul>
+     * <li> {@link KeyEvent#KEYCODE_MEDIA_PLAY}
+     * <li> {@link KeyEvent#KEYCODE_MEDIA_PAUSE}
+     * <li> {@link KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE}
+     * <li> {@link KeyEvent#KEYCODE_MUTE}
+     * <li> {@link KeyEvent#KEYCODE_HEADSETHOOK}
+     * <li> {@link KeyEvent#KEYCODE_MEDIA_STOP}
+     * <li> {@link KeyEvent#KEYCODE_MEDIA_NEXT}
+     * <li> {@link KeyEvent#KEYCODE_MEDIA_PREVIOUS}
+     * </ul>
+     * @see {@link KeyEvent#isMediaSessionKey(int)}
+     * @param keyCode
+     */
+    void setOverriddenKeyEvents(int keyCode, @KeyEventType int keyEventType) {
+        mOverriddenKeyEvents.put(keyCode, keyEventType);
+    }
+
+    /**
+     * Customized implementation for single click event. Will be run if
+     * {@link #KEY_EVENT_SINGLE_CLICK} flag is on for the corresponding key code from
+     * {@link #getOverriddenKeyEvents()}.
+     *
+     * It is considered a single click if only one {@link KeyEvent} with the same
+     * {@link KeyEvent#getKeyCode()} is dispatched within
+     * {@link ViewConfiguration#getMultiPressTimeout()} milliseconds. Change the
+     * {@link android.provider.Settings.Secure#MULTI_PRESS_TIMEOUT} value to adjust the interval.
+     *
+     * Note: This will only be called once with the {@link KeyEvent#ACTION_UP} KeyEvent.
+     *
+     * @param keyEvent
+     */
+    void onSingleClick(KeyEvent keyEvent) {
+    }
+
+    /**
+     * Customized implementation for double click event. Will be run if
+     * {@link #KEY_EVENT_DOUBLE_CLICK} flag is on for the corresponding key code from
+     * {@link #getOverriddenKeyEvents()}.
+     *
+     * It is considered a double click if two {@link KeyEvent}s with the same
+     * {@link KeyEvent#getKeyCode()} are dispatched within
+     * {@link ViewConfiguration#getMultiPressTimeout()} milliseconds of each other. Change the
+     * {@link android.provider.Settings.Secure#MULTI_PRESS_TIMEOUT} value to adjust the interval.
+     *
+     * Note: This will only be called once with the {@link KeyEvent#ACTION_UP} KeyEvent.
+     *
+     * @param keyEvent
+     */
+    void onDoubleClick(KeyEvent keyEvent) {
+    }
+
+    /**
+     * Customized implementation for triple click event. Will be run if
+     * {@link #KEY_EVENT_TRIPLE_CLICK} flag is on for the corresponding key code from
+     * {@link #getOverriddenKeyEvents()}.
+     *
+     * It is considered a triple click if three {@link KeyEvent}s with the same
+     * {@link KeyEvent#getKeyCode()} are dispatched within
+     * {@link ViewConfiguration#getMultiPressTimeout()} milliseconds of each other. Change the
+     * {@link android.provider.Settings.Secure#MULTI_PRESS_TIMEOUT} value to adjust the interval.
+     *
+     * Note: This will only be called once with the {@link KeyEvent#ACTION_UP} KeyEvent.
+     *
+     * @param keyEvent
+     */
+    void onTripleClick(KeyEvent keyEvent) {
+    }
+
+    /**
+     * Customized implementation for long press event. Will be run if
+     * {@link #KEY_EVENT_LONG_PRESS} flag is on for the corresponding key code from
+     * {@link #getOverriddenKeyEvents()}.
+     *
+     * It is considered a long press if an {@link KeyEvent#ACTION_DOWN} key event is followed by
+     * another {@link KeyEvent#ACTION_DOWN} key event with {@link KeyEvent#FLAG_LONG_PRESS}
+     * enabled, and an {@link KeyEvent#getRepeatCount()} that is equal to 1.
+     *
+     * Note: This will be called for the following key events:
+     * <ul>
+     *   <li>A {@link KeyEvent#ACTION_DOWN} KeyEvent with {@link KeyEvent#FLAG_LONG_PRESS} and
+     *   {@link KeyEvent#getRepeatCount()} equal to 1</li>
+     *   <li>Multiple {@link KeyEvent#ACTION_DOWN} KeyEvents with increasing
+     *   {@link KeyEvent#getRepeatCount()}</li>
+     *   <li>A {@link KeyEvent#ACTION_UP} KeyEvent</li>
+     * </ul>
+     *
+     * @param keyEvent
+     */
+    void onLongPress(KeyEvent keyEvent) {
+    }
 }
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 5757b1a..e5867e7 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -86,10 +86,10 @@
 import java.io.PrintWriter;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * System implementation of MediaSessionManager
@@ -103,12 +103,14 @@
     private static final int WAKELOCK_TIMEOUT = 5000;
     private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000;
     private static final int SESSION_CREATION_LIMIT_PER_UID = 100;
+    private static final int LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout()
+            + /* Buffer for delayed delivery of key event */ 50;
+    private static final int MULTI_PRESS_TIMEOUT = ViewConfiguration.getMultiPressTimeout();
 
     private final Context mContext;
     private final SessionManagerImpl mSessionManagerImpl;
     private final MessageHandler mHandler = new MessageHandler();
     private final PowerManager.WakeLock mMediaEventWakeLock;
-    private final int mLongPressTimeout;
     private final INotificationManager mNotificationManager;
     private final Object mLock = new Object();
     // Keeps the full user id for each user.
@@ -142,8 +144,7 @@
 
     private SessionPolicyProvider mCustomSessionPolicyProvider;
     private MediaKeyDispatcher mCustomMediaKeyDispatcher;
-    private Method mGetSessionForKeyEventMethod;
-    private Method mGetSessionPoliciesMethod;
+    private Map<Integer, Integer> mOverriddenKeyEventsMap;
 
     public MediaSessionService(Context context) {
         super(context);
@@ -151,7 +152,6 @@
         mSessionManagerImpl = new SessionManagerImpl();
         PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
         mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
-        mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
         mNotificationManager = INotificationManager.Stub.asInterface(
                 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
     }
@@ -184,9 +184,10 @@
         mHasFeatureLeanback = mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_LEANBACK);
 
+        updateUser();
+
         instantiateCustomProvider(null);
         instantiateCustomDispatcher(null);
-        updateUser();
     }
 
     private boolean isGlobalPriorityActiveLocked() {
@@ -570,13 +571,9 @@
             String callerPackageName, ISessionCallback cb, String tag, Bundle sessionInfo) {
         synchronized (mLock) {
             int policies = 0;
-            if (mCustomSessionPolicyProvider != null && mGetSessionPoliciesMethod != null) {
-                try {
-                    policies = (int) mGetSessionPoliciesMethod.invoke(
-                            mCustomSessionPolicyProvider, callerUid, callerPackageName);
-                } catch (InvocationTargetException | IllegalAccessException e) {
-                    Log.w(TAG, "Encountered problem while using reflection", e);
-                }
+            if (mCustomSessionPolicyProvider != null) {
+                policies = mCustomSessionPolicyProvider.getSessionPoliciesForApplication(
+                        callerUid, callerPackageName);
             }
 
             FullUserRecord user = getFullUserRecordLocked(userId);
@@ -762,44 +759,46 @@
     }
 
     private void instantiateCustomDispatcher(String nameFromTesting) {
-        mCustomMediaKeyDispatcher = null;
-        mGetSessionForKeyEventMethod = null;
+        synchronized (mLock) {
+            mCustomMediaKeyDispatcher = null;
+            mOverriddenKeyEventsMap = null;
 
-        String customDispatcherClassName = (nameFromTesting == null)
-                ? mContext.getResources().getString(R.string.config_customMediaKeyDispatcher)
-                : nameFromTesting;
-        try {
-            if (!TextUtils.isEmpty(customDispatcherClassName)) {
-                Class customDispatcherClass = Class.forName(customDispatcherClassName);
-                Constructor constructor = customDispatcherClass.getDeclaredConstructor();
-                mCustomMediaKeyDispatcher = (MediaKeyDispatcher) constructor.newInstance();
-                mGetSessionForKeyEventMethod = customDispatcherClass.getDeclaredMethod(
-                        "getSessionForKeyEvent", KeyEvent.class, int.class, boolean.class);
+            String customDispatcherClassName = (nameFromTesting == null)
+                    ? mContext.getResources().getString(R.string.config_customMediaKeyDispatcher)
+                    : nameFromTesting;
+            try {
+                if (!TextUtils.isEmpty(customDispatcherClassName)) {
+                    Class customDispatcherClass = Class.forName(customDispatcherClassName);
+                    Constructor constructor = customDispatcherClass.getDeclaredConstructor();
+                    mCustomMediaKeyDispatcher = (MediaKeyDispatcher) constructor.newInstance();
+                    mOverriddenKeyEventsMap = mCustomMediaKeyDispatcher.getOverriddenKeyEvents();
+                }
+            } catch (ClassNotFoundException | InstantiationException | InvocationTargetException
+                    | IllegalAccessException | NoSuchMethodException e) {
+                mCustomMediaKeyDispatcher = null;
+                Log.w(TAG, "Encountered problem while using reflection", e);
             }
-        } catch (ClassNotFoundException | InstantiationException | InvocationTargetException
-                | IllegalAccessException | NoSuchMethodException e) {
-            Log.w(TAG, "Encountered problem while using reflection", e);
         }
     }
 
     private void instantiateCustomProvider(String nameFromTesting) {
-        mCustomSessionPolicyProvider = null;
-        mGetSessionPoliciesMethod = null;
+        synchronized (mLock) {
+            mCustomSessionPolicyProvider = null;
 
-        String customProviderClassName = (nameFromTesting == null)
-                ? mContext.getResources().getString(R.string.config_customSessionPolicyProvider)
-                : nameFromTesting;
-        try {
-            if (!TextUtils.isEmpty(customProviderClassName)) {
-                Class customProviderClass = Class.forName(customProviderClassName);
-                Constructor constructor = customProviderClass.getDeclaredConstructor();
-                mCustomSessionPolicyProvider = (SessionPolicyProvider) constructor.newInstance();
-                mGetSessionPoliciesMethod = customProviderClass.getDeclaredMethod(
-                        "getSessionPoliciesForApplication", int.class, String.class);
+            String customProviderClassName = (nameFromTesting == null)
+                    ? mContext.getResources().getString(R.string.config_customSessionPolicyProvider)
+                    : nameFromTesting;
+            try {
+                if (!TextUtils.isEmpty(customProviderClassName)) {
+                    Class customProviderClass = Class.forName(customProviderClassName);
+                    Constructor constructor = customProviderClass.getDeclaredConstructor();
+                    mCustomSessionPolicyProvider =
+                            (SessionPolicyProvider) constructor.newInstance();
+                }
+            } catch (ClassNotFoundException | InstantiationException | InvocationTargetException
+                    | IllegalAccessException | NoSuchMethodException e) {
+                Log.w(TAG, "Encountered problem while using reflection", e);
             }
-        } catch (ClassNotFoundException | InstantiationException | InvocationTargetException
-                | IllegalAccessException | NoSuchMethodException e) {
-            Log.w(TAG, "Encountered problem while using reflection", e);
         }
     }
 
@@ -1098,8 +1097,9 @@
                 "android.media.AudioService.WAKELOCK_ACQUIRED";
         private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
 
-        private boolean mVoiceButtonDown = false;
-        private boolean mVoiceButtonHandled = false;
+        private KeyEvent mPendingFirstDownKeyEvent = null;
+        private boolean mIsLongPressing = false;
+        private Runnable mLongPressTimeoutRunnable = null;
 
         @Override
         public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
@@ -1362,12 +1362,12 @@
                             }
                         }
                     }
-                    if (!isGlobalPriorityActive && isVoiceKey(keyEvent.getKeyCode())) {
-                        handleVoiceKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent,
-                                needWakeLock);
-                    } else {
+                    if (isGlobalPriorityActive) {
                         dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
                                 keyEvent, needWakeLock);
+                    } else {
+                        handleKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent,
+                                needWakeLock);
                     }
                 }
             } finally {
@@ -1641,7 +1641,7 @@
         }
 
         /**
-         * Dispaches volume key events. This is called when the foreground activity didn't handled
+         * Dispatches volume key events. This is called when the foreground activity didn't handle
          * the incoming volume key event.
          * <p>
          * Handles the dispatching of the volume button events to one of the
@@ -1662,7 +1662,7 @@
          *            {@link KeyEvent#KEYCODE_VOLUME_DOWN},
          *            or {@link KeyEvent#KEYCODE_VOLUME_MUTE}.
          * @param stream stream type to adjust volume.
-         * @param musicOnly true if both UI nor haptic feedback aren't needed when adjust volume.
+         * @param musicOnly true if both UI and haptic feedback aren't needed when adjusting volume.
          * @see #dispatchVolumeKeyEventToSessionAsSystemService
          */
         @Override
@@ -1707,7 +1707,7 @@
                                         mHandler.obtainMessage(
                                                 MessageHandler.MSG_VOLUME_INITIAL_DOWN,
                                                 mCurrentFullUserRecord.mFullUserId, 0),
-                                        mLongPressTimeout);
+                                        LONG_PRESS_TIMEOUT);
                             }
                             if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) {
                                 mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
@@ -2112,31 +2112,147 @@
             }
         }
 
-        private void handleVoiceKeyEventLocked(String packageName, int pid, int uid,
+        // A long press is determined by:
+        // 1) A KeyEvent with KeyEvent.ACTION_DOWN and repeat count of 0, followed by
+        // 2) A KeyEvent with KeyEvent.ACTION_DOWN and repeat count of 1 and FLAG_LONG_PRESS within
+        //    ViewConfiguration.getLongPressTimeout().
+        // TODO: Add description about what a click is determined by.
+        private void handleKeyEventLocked(String packageName, int pid, int uid,
                 boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
-            int action = keyEvent.getAction();
-            boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
-            if (action == KeyEvent.ACTION_DOWN) {
-                if (keyEvent.getRepeatCount() == 0) {
-                    mVoiceButtonDown = true;
-                    mVoiceButtonHandled = false;
-                } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
-                    mVoiceButtonHandled = true;
-                    startVoiceInput(needWakeLock);
+            if (keyEvent.isCanceled()) {
+                return;
+            }
+
+            int overriddenKeyEvents = (mCustomMediaKeyDispatcher == null) ? 0
+                    : mCustomMediaKeyDispatcher.getOverriddenKeyEvents().get(keyEvent.getKeyCode());
+            cancelPendingIfNeeded(keyEvent);
+            if (!needPending(keyEvent, overriddenKeyEvents)) {
+                dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent,
+                        needWakeLock);
+                return;
+            }
+
+            if (isFirstDownKeyEvent(keyEvent)) {
+                mPendingFirstDownKeyEvent = keyEvent;
+                mIsLongPressing = false;
+                return;
+            }
+
+            if (isFirstLongPressKeyEvent(keyEvent)) {
+                mIsLongPressing = true;
+            }
+            if (mIsLongPressing) {
+                handleLongPressLocked(keyEvent, needWakeLock, overriddenKeyEvents);
+            } else if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
+                mPendingFirstDownKeyEvent = null;
+                // TODO: Replace this with code to determine whether
+                // single/double/triple click and run custom implementations,
+                // if they exist.
+                dispatchDownAndUpKeyEventsLocked(packageName, pid, uid, asSystemService,
+                        keyEvent, needWakeLock);
+            }
+        }
+
+        private void cancelPendingIfNeeded(KeyEvent keyEvent) {
+            if (mPendingFirstDownKeyEvent == null) {
+                return;
+            }
+            if (isFirstDownKeyEvent(keyEvent)) {
+                if (mLongPressTimeoutRunnable != null) {
+                    mHandler.removeCallbacks(mLongPressTimeoutRunnable);
+                    mLongPressTimeoutRunnable.run();
+                } else {
+                    resetLongPressTracking();
                 }
-            } else if (action == KeyEvent.ACTION_UP) {
-                if (mVoiceButtonDown) {
-                    mVoiceButtonDown = false;
-                    if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
-                        // Resend the down then send this event through
-                        KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
-                        dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
-                                downEvent, needWakeLock);
-                        dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
-                                keyEvent, needWakeLock);
-                    }
+                return;
+            }
+            if (mPendingFirstDownKeyEvent.getDownTime() == keyEvent.getDownTime()
+                    && mPendingFirstDownKeyEvent.getKeyCode() == keyEvent.getKeyCode()
+                    && keyEvent.getAction() == KeyEvent.ACTION_DOWN
+                    && keyEvent.getRepeatCount() > 1 && !mIsLongPressing) {
+                resetLongPressTracking();
+            }
+        }
+
+        private boolean needPending(KeyEvent keyEvent, int overriddenKeyEvents) {
+            if (!isFirstDownKeyEvent(keyEvent)) {
+                if (mPendingFirstDownKeyEvent == null) {
+                    return false;
+                } else if (mPendingFirstDownKeyEvent.getDownTime() != keyEvent.getDownTime()
+                        || mPendingFirstDownKeyEvent.getKeyCode() != keyEvent.getKeyCode()) {
+                    return false;
                 }
             }
+            if (overriddenKeyEvents == 0 && !isVoiceKey(keyEvent.getKeyCode())) {
+                return false;
+            }
+            return true;
+        }
+
+        private void handleLongPressLocked(KeyEvent keyEvent, boolean needWakeLock,
+                int overriddenKeyEvents) {
+            if (mCustomMediaKeyDispatcher != null
+                    && mCustomMediaKeyDispatcher.isLongPressOverridden(overriddenKeyEvents)) {
+                mCustomMediaKeyDispatcher.onLongPress(keyEvent);
+
+                if (mLongPressTimeoutRunnable != null) {
+                    mHandler.removeCallbacks(mLongPressTimeoutRunnable);
+                }
+                if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+                    if (mLongPressTimeoutRunnable == null) {
+                        mLongPressTimeoutRunnable = createLongPressTimeoutRunnable(keyEvent);
+                    }
+                    mHandler.postDelayed(mLongPressTimeoutRunnable, LONG_PRESS_TIMEOUT);
+                } else {
+                    resetLongPressTracking();
+                }
+            } else if (isFirstLongPressKeyEvent(keyEvent) && isVoiceKey(keyEvent.getKeyCode())) {
+                // Default implementation
+                startVoiceInput(needWakeLock);
+                resetLongPressTracking();
+            }
+        }
+
+        private Runnable createLongPressTimeoutRunnable(KeyEvent keyEvent) {
+            return new Runnable() {
+                @Override
+                public void run() {
+                    if (mCustomMediaKeyDispatcher != null) {
+                        mCustomMediaKeyDispatcher.onLongPress(createCanceledKeyEvent(keyEvent));
+                    }
+                    resetLongPressTracking();
+                }
+            };
+        }
+
+        private void resetLongPressTracking() {
+            mPendingFirstDownKeyEvent = null;
+            mIsLongPressing = false;
+            mLongPressTimeoutRunnable = null;
+        }
+
+        private KeyEvent createCanceledKeyEvent(KeyEvent keyEvent) {
+            KeyEvent upEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_UP);
+            return KeyEvent.changeTimeRepeat(upEvent, System.currentTimeMillis(), 0,
+                    KeyEvent.FLAG_CANCELED);
+        }
+
+        private boolean isFirstLongPressKeyEvent(KeyEvent keyEvent) {
+            return ((keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0)
+                    && keyEvent.getRepeatCount() == 1;
+        }
+
+        private boolean isFirstDownKeyEvent(KeyEvent keyEvent) {
+            return keyEvent.getAction() == KeyEvent.ACTION_DOWN && keyEvent.getRepeatCount() == 0;
+        }
+
+        private void dispatchDownAndUpKeyEventsLocked(String packageName, int pid, int uid,
+                boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
+            KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
+            dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
+                    downEvent, needWakeLock);
+            dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
+                    keyEvent, needWakeLock);
         }
 
         private void dispatchMediaKeyEventLocked(String packageName, int pid, int uid,
@@ -2149,15 +2265,11 @@
             MediaSessionRecord session = null;
 
             // Retrieve custom session for key event if it exists.
-            if (mCustomMediaKeyDispatcher != null && mGetSessionForKeyEventMethod != null) {
-                try {
-                    Object tokenObject = mGetSessionForKeyEventMethod.invoke(
-                            mCustomMediaKeyDispatcher, keyEvent, uid, asSystemService);
-                    if (tokenObject != null) {
-                        session = getMediaSessionRecordLocked((MediaSession.Token) tokenObject);
-                    }
-                } catch (InvocationTargetException | IllegalAccessException e) {
-                    Log.w(TAG, "Encountered problem while using reflection", e);
+            if (mCustomMediaKeyDispatcher != null) {
+                MediaSession.Token token = mCustomMediaKeyDispatcher.getSessionForKeyEvent(
+                        keyEvent, uid, asSystemService);
+                if (token != null) {
+                    session = getMediaSessionRecordLocked(token);
                 }
             }
 
@@ -2312,12 +2424,11 @@
                 mHandled = true;
                 mHandler.removeCallbacks(this);
                 synchronized (mLock) {
-                    if (!isGlobalPriorityActiveLocked()
-                            && isVoiceKey(mKeyEvent.getKeyCode())) {
-                        handleVoiceKeyEventLocked(mPackageName, mPid, mUid, mAsSystemService,
+                    if (isGlobalPriorityActiveLocked()) {
+                        dispatchMediaKeyEventLocked(mPackageName, mPid, mUid, mAsSystemService,
                                 mKeyEvent, mNeedWakeLock);
                     } else {
-                        dispatchMediaKeyEventLocked(mPackageName, mPid, mUid, mAsSystemService,
+                        handleKeyEventLocked(mPackageName, mPid, mUid, mAsSystemService,
                                 mKeyEvent, mNeedWakeLock);
                     }
                 }
diff --git a/services/core/java/com/android/server/media/SessionPolicyProvider.java b/services/core/java/com/android/server/media/SessionPolicyProvider.java
index 40a3d2d..5f02a07 100644
--- a/services/core/java/com/android/server/media/SessionPolicyProvider.java
+++ b/services/core/java/com/android/server/media/SessionPolicyProvider.java
@@ -29,6 +29,7 @@
  * Note: When instantiating this class, {@link MediaSessionService} will only use the constructor
  * without any parameters.
  */
+// TODO: Move this class to apex/media/
 public abstract class SessionPolicyProvider {
     @IntDef(value = {
             SESSION_POLICY_IGNORE_BUTTON_RECEIVER,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d2481b7..e4b4bfd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5941,8 +5941,25 @@
                     || shouldFilterApplicationLocked(ps2, callingUid, callingUserId)) {
                 return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
             }
-            return compareSignatures(p1.getSigningDetails().signatures,
-                    p2.getSigningDetails().signatures);
+            SigningDetails p1SigningDetails = p1.getSigningDetails();
+            SigningDetails p2SigningDetails = p2.getSigningDetails();
+            int result = compareSignatures(p1SigningDetails.signatures,
+                    p2SigningDetails.signatures);
+            // To support backwards compatibility with clients of this API expecting pre-key
+            // rotation results if either of the packages has a signing lineage the oldest signer
+            // in the lineage is used for signature verification.
+            if (result != PackageManager.SIGNATURE_MATCH && (
+                    p1SigningDetails.hasPastSigningCertificates()
+                            || p2SigningDetails.hasPastSigningCertificates())) {
+                Signature[] p1Signatures = p1SigningDetails.hasPastSigningCertificates()
+                        ? new Signature[]{p1SigningDetails.pastSigningCertificates[0]}
+                        : p1SigningDetails.signatures;
+                Signature[] p2Signatures = p2SigningDetails.hasPastSigningCertificates()
+                        ? new Signature[]{p2SigningDetails.pastSigningCertificates[0]}
+                        : p2SigningDetails.signatures;
+                result = compareSignatures(p1Signatures, p2Signatures);
+            }
+            return result;
         }
     }
 
diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
index c2ecd41..735a9e4 100644
--- a/services/core/java/com/android/server/testharness/TestHarnessModeService.java
+++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
@@ -161,6 +161,10 @@
     private void configureSettings() {
         ContentResolver cr = getContext().getContentResolver();
 
+        // Stop ADB before we enable it, otherwise on userdebug/eng builds, the keys won't have
+        // registered with adbd, and it will prompt the user to confirm the keys.
+        Settings.Global.putInt(cr, Settings.Global.ADB_ENABLED, 0);
+
         // Disable the TTL for ADB keys before enabling ADB
         Settings.Global.putLong(cr, Settings.Global.ADB_ALLOWED_CONNECTION_TIME, 0);
         Settings.Global.putInt(cr, Settings.Global.ADB_ENABLED, 1);
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
index e100ff8..4cdc172 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
@@ -68,6 +68,11 @@
     private Set<Integer> mUsingFrontendIds = new HashSet<>();
 
     /**
+     * List of the Lnb ids that are used by the current client.
+     */
+    private Set<Integer> mUsingLnbIds = new HashSet<>();
+
+    /**
      * Optional arbitrary priority value given by the client.
      *
      * <p>This value can override the default priorotiy calculated from
@@ -131,21 +136,49 @@
         mUsingFrontendIds.add(frontendId);
     }
 
-    public Iterable<Integer> getInUseFrontendIds() {
+    public Set<Integer> getInUseFrontendIds() {
         return mUsingFrontendIds;
     }
 
     /**
      * Called when the client released a frontend.
      *
-     * <p>This could happen when client resource reclaimed.
-     *
      * @param frontendId being released.
      */
     public void releaseFrontend(int frontendId) {
         mUsingFrontendIds.remove(frontendId);
     }
 
+    /**
+     * Set when the client starts to use an Lnb.
+     *
+     * @param lnbId being used.
+     */
+    public void useLnb(int lnbId) {
+        mUsingLnbIds.add(lnbId);
+    }
+
+    public Set<Integer> getInUseLnbIds() {
+        return mUsingLnbIds;
+    }
+
+    /**
+     * Called when the client released an lnb.
+     *
+     * @param lnbId being released.
+     */
+    public void releaseLnb(int lnbId) {
+        mUsingLnbIds.remove(lnbId);
+    }
+
+    /**
+     * Called to reclaim all the resources being used by the current client.
+     */
+    public void reclaimAllResources() {
+        mUsingFrontendIds.clear();
+        mUsingLnbIds.clear();
+    }
+
     @Override
     public String toString() {
         return "ClientProfile[id=" + this.mId + ", tvInputSessionId=" + this.mTvInputSessionId
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java
index 56f6159..7ea62b2 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java
@@ -27,14 +27,7 @@
  *
  * @hide
  */
-public final class FrontendResource {
-    public static final int INVALID_OWNER_ID = -1;
-
-    /**
-     * Id of the current frontend. Should not be changed and should be aligned with the driver level
-     * implementation.
-     */
-    private final int mId;
+public final class FrontendResource extends TunerResourceBasic {
 
     /**
      * see {@link android.media.tv.tuner.frontend.FrontendSettings.Type}
@@ -51,28 +44,12 @@
      */
     private Set<Integer> mExclusiveGroupMemberFeIds = new HashSet<>();
 
-    /**
-     * If the current resource is in use. Once resources under the same exclusive group id is in use
-     * all other resources in the same group would be considered in use.
-     */
-    private boolean mIsInUse;
-
-    /**
-     * The owner client's id if this resource is occupied. Owner of the resource under the same
-     * exclusive group id would be considered as the whole group's owner.
-     */
-    private int mOwnerClientId = INVALID_OWNER_ID;
-
     private FrontendResource(Builder builder) {
-        this.mId = builder.mId;
+        super(builder);
         this.mType = builder.mType;
         this.mExclusiveGroupId = builder.mExclusiveGroupId;
     }
 
-    public int getId() {
-        return mId;
-    }
-
     public int getType() {
         return mType;
     }
@@ -112,32 +89,6 @@
         mExclusiveGroupMemberFeIds.remove(id);
     }
 
-    public boolean isInUse() {
-        return mIsInUse;
-    }
-
-    public int getOwnerClientId() {
-        return mOwnerClientId;
-    }
-
-    /**
-     * Set an owner client on the resource.
-     *
-     * @param ownerClientId the id of the owner client.
-     */
-    public void setOwner(int ownerClientId) {
-        mIsInUse = true;
-        mOwnerClientId = ownerClientId;
-    }
-
-    /**
-     * Remove an owner client from the resource.
-     */
-    public void removeOwner() {
-        mIsInUse = false;
-        mOwnerClientId = INVALID_OWNER_ID;
-    }
-
     @Override
     public String toString() {
         return "FrontendResource[id=" + this.mId + ", type=" + this.mType
@@ -149,13 +100,12 @@
     /**
      * Builder class for {@link FrontendResource}.
      */
-    public static class Builder {
-        private final int mId;
+    public static class Builder extends TunerResourceBasic.Builder {
         @Type private int mType;
         private int mExclusiveGroupId;
 
         Builder(int id) {
-            this.mId = id;
+            super(id);
         }
 
         /**
@@ -183,6 +133,7 @@
          *
          * @return {@link FrontendResource}.
          */
+        @Override
         public FrontendResource build() {
             FrontendResource frontendResource = new FrontendResource(this);
             return frontendResource;
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/LnbResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/LnbResource.java
new file mode 100644
index 0000000..345b4b2
--- /dev/null
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/LnbResource.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 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.server.tv.tunerresourcemanager;
+
+/**
+ * An Lnb resource object used by the Tuner Resource Manager to record the tuner Lnb
+ * information.
+ *
+ * @hide
+ */
+public final class LnbResource extends TunerResourceBasic {
+
+    private LnbResource(Builder builder) {
+        super(builder);
+    }
+
+    @Override
+    public String toString() {
+        return "LnbResource[id=" + this.mId
+                + ", isInUse=" + this.mIsInUse + ", ownerClientId=" + this.mOwnerClientId + "]";
+    }
+
+    /**
+     * Builder class for {@link LnbResource}.
+     */
+    public static class Builder extends TunerResourceBasic.Builder {
+
+        Builder(int id) {
+            super(id);
+        }
+
+        /**
+         * Build a {@link LnbResource}.
+         *
+         * @return {@link LnbResource}.
+         */
+        @Override
+        public LnbResource build() {
+            LnbResource lnb = new LnbResource(this);
+            return lnb;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceBasic.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceBasic.java
new file mode 100644
index 0000000..7f133c3
--- /dev/null
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceBasic.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 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.server.tv.tunerresourcemanager;
+
+import static android.media.tv.tunerresourcemanager.TunerResourceManager.INVALID_OWNER_ID;
+
+/**
+ * A Tuner resource basic object used by the Tuner Resource Manager to record the resource
+ * information.
+ *
+ * @hide
+ */
+public class TunerResourceBasic {
+    /**
+     * Id of the current resource. Should not be changed and should be aligned with the driver level
+     * implementation.
+     */
+    final int mId;
+
+    /**
+     * If the current resource is in use.
+     */
+    boolean mIsInUse;
+
+    /**
+     * The owner client's id if this resource is occupied.
+     */
+    int mOwnerClientId = INVALID_OWNER_ID;
+
+    TunerResourceBasic(Builder builder) {
+        this.mId = builder.mId;
+    }
+
+    public int getId() {
+        return mId;
+    }
+
+    public boolean isInUse() {
+        return mIsInUse;
+    }
+
+    public int getOwnerClientId() {
+        return mOwnerClientId;
+    }
+
+    /**
+     * Set an owner client on the resource.
+     *
+     * @param ownerClientId the id of the owner client.
+     */
+    public void setOwner(int ownerClientId) {
+        mIsInUse = true;
+        mOwnerClientId = ownerClientId;
+    }
+
+    /**
+     * Remove an owner client from the resource.
+     */
+    public void removeOwner() {
+        mIsInUse = false;
+        mOwnerClientId = INVALID_OWNER_ID;
+    }
+
+    /**
+     * Builder class for {@link TunerResourceBasic}.
+     */
+    public static class Builder {
+        private final int mId;
+
+        Builder(int id) {
+            this.mId = id;
+        }
+
+        /**
+         * Build a {@link TunerResourceBasic}.
+         *
+         * @return {@link TunerResourceBasic}.
+         */
+        public TunerResourceBasic build() {
+            TunerResourceBasic resource = new TunerResourceBasic(this);
+            return resource;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 6dded00..7231813 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -63,6 +63,8 @@
 
     // Map of the current available frontend resources
     private Map<Integer, FrontendResource> mFrontendResources = new HashMap<>();
+    // Map of the current available lnb resources
+    private Map<Integer, LnbResource> mLnbResources = new HashMap<>();
 
     @GuardedBy("mLock")
     private Map<Integer, ResourcesReclaimListenerRecord> mListeners = new HashMap<>();
@@ -164,12 +166,13 @@
         }
 
         @Override
-        public void setLnbInfoList(int[] lnbIds) {
+        public void setLnbInfoList(int[] lnbIds) throws RemoteException {
             enforceTrmAccessPermission("setLnbInfoList");
-            if (DEBUG) {
-                for (int i = 0; i < lnbIds.length; i++) {
-                    Slog.d(TAG, "updateLnbInfo(lnbId=" + lnbIds[i] + ")");
-                }
+            if (lnbIds == null) {
+                throw new RemoteException("Lnb id list can't be null");
+            }
+            synchronized (mLock) {
+                setLnbInfoListInternal(lnbIds);
             }
         }
 
@@ -237,26 +240,45 @@
         }
 
         @Override
-        public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull int[] lnbHandle) {
+        public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull int[] lnbHandle)
+                throws RemoteException {
             enforceTunerAccessPermission("requestLnb");
             enforceTrmAccessPermission("requestLnb");
-            if (DEBUG) {
-                Slog.d(TAG, "requestLnb(request=" + request + ")");
+            if (lnbHandle == null) {
+                throw new RemoteException("lnbHandle can't be null");
             }
-            return true;
+            synchronized (mLock) {
+                try {
+                    return requestLnbInternal(request, lnbHandle);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
         }
 
         @Override
-        public void releaseFrontend(int frontendId) {
+        public void releaseFrontend(int frontendHandle, int clientId) throws RemoteException {
             enforceTunerAccessPermission("releaseFrontend");
             enforceTrmAccessPermission("releaseFrontend");
-            if (DEBUG) {
-                Slog.d(TAG, "releaseFrontend(id=" + frontendId + ")");
+            if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND,
+                    frontendHandle)) {
+                throw new RemoteException("frontendHandle can't be invalid");
+            }
+            int frontendId = getResourceIdFromHandle(frontendHandle);
+            FrontendResource fe = getFrontendResource(frontendId);
+            if (fe == null) {
+                throw new RemoteException("Releasing frontend does not exist.");
+            }
+            if (fe.getOwnerClientId() != clientId) {
+                throw new RemoteException("Client is not the current owner of the releasing fe.");
+            }
+            synchronized (mLock) {
+                releaseFrontendInternal(fe);
             }
         }
 
         @Override
-        public void releaseDemux(int demuxHandle) {
+        public void releaseDemux(int demuxHandle, int clientId) {
             enforceTunerAccessPermission("releaseDemux");
             enforceTrmAccessPermission("releaseDemux");
             if (DEBUG) {
@@ -265,7 +287,7 @@
         }
 
         @Override
-        public void releaseDescrambler(int descramblerHandle) {
+        public void releaseDescrambler(int descramblerHandle, int clientId) {
             enforceTunerAccessPermission("releaseDescrambler");
             enforceTrmAccessPermission("releaseDescrambler");
             if (DEBUG) {
@@ -274,7 +296,7 @@
         }
 
         @Override
-        public void releaseCasSession(int sessionResourceId) {
+        public void releaseCasSession(int sessionResourceId, int clientId) {
             enforceTrmAccessPermission("releaseCasSession");
             if (DEBUG) {
                 Slog.d(TAG, "releaseCasSession(sessionResourceId=" + sessionResourceId + ")");
@@ -282,11 +304,22 @@
         }
 
         @Override
-        public void releaseLnb(int lnbId) {
+        public void releaseLnb(int lnbHandle, int clientId) throws RemoteException {
             enforceTunerAccessPermission("releaseLnb");
             enforceTrmAccessPermission("releaseLnb");
-            if (DEBUG) {
-                Slog.d(TAG, "releaseLnb(lnbId=" + lnbId + ")");
+            if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, lnbHandle)) {
+                throw new RemoteException("lnbHandle can't be invalid");
+            }
+            int lnbId = getResourceIdFromHandle(lnbHandle);
+            LnbResource lnb = getLnbResource(lnbId);
+            if (lnb == null) {
+                throw new RemoteException("Releasing lnb does not exist.");
+            }
+            if (lnb.getOwnerClientId() != clientId) {
+                throw new RemoteException("Client is not the current owner of the releasing lnb.");
+            }
+            synchronized (mLock) {
+                releaseLnbInternal(lnb);
             }
         }
 
@@ -393,7 +426,6 @@
             }
         }
 
-        // TODO check if the removing resource is in use or not. Handle the conflict.
         for (int removingId : updatingFrontendIds) {
             // update the exclusive group id member list
             removeFrontendResource(removingId);
@@ -401,6 +433,38 @@
     }
 
     @VisibleForTesting
+    protected void setLnbInfoListInternal(int[] lnbIds) {
+        if (DEBUG) {
+            for (int i = 0; i < lnbIds.length; i++) {
+                Slog.d(TAG, "updateLnbInfo(lnbId=" + lnbIds[i] + ")");
+            }
+        }
+
+        // A set to record the Lnbs pending on updating. Ids will be removed
+        // from this set once its updating finished. Any lnb left in this set when all
+        // the updates are done will be removed from mLnbResources.
+        Set<Integer> updatingLnbIds = new HashSet<>(getLnbResources().keySet());
+
+        // Update lnbResources map and other mappings accordingly
+        for (int i = 0; i < lnbIds.length; i++) {
+            if (getLnbResource(lnbIds[i]) != null) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Lnb id=" + lnbIds[i] + "exists.");
+                }
+                updatingLnbIds.remove(lnbIds[i]);
+            } else {
+                // Add a new lnb resource
+                LnbResource newLnb = new LnbResource.Builder(lnbIds[i]).build();
+                addLnbResource(newLnb);
+            }
+        }
+
+        for (int removingId : updatingLnbIds) {
+            removeLnbResource(removingId);
+        }
+    }
+
+    @VisibleForTesting
     protected boolean requestFrontendInternal(TunerFrontendRequest request, int[] frontendHandle)
             throws RemoteException {
         if (DEBUG) {
@@ -452,10 +516,12 @@
         // When all the resources are occupied, grant the lowest priority resource if the
         // request client has higher priority.
         if (inUseLowestPriorityFrId > -1 && (requestClient.getPriority() > currentLowestPriority)) {
+            if (!reclaimResource(getFrontendResource(inUseLowestPriorityFrId).getOwnerClientId(),
+                    TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
+                return false;
+            }
             frontendHandle[0] = generateResourceHandle(
                     TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, inUseLowestPriorityFrId);
-            reclaimFrontendResource(getFrontendResource(
-                    inUseLowestPriorityFrId).getOwnerClientId());
             updateFrontendClientMappingOnNewGrant(inUseLowestPriorityFrId, request.getClientId());
             return true;
         }
@@ -464,10 +530,85 @@
     }
 
     @VisibleForTesting
+    protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle)
+            throws RemoteException {
+        if (DEBUG) {
+            Slog.d(TAG, "requestLnb(request=" + request + ")");
+        }
+
+        lnbHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+        if (!checkClientExists(request.getClientId())) {
+            Slog.e(TAG, "Request lnb from unregistered client:" + request.getClientId());
+            return false;
+        }
+        ClientProfile requestClient = getClientProfile(request.getClientId());
+        int grantingLnbId = -1;
+        int inUseLowestPriorityLnbId = -1;
+        // Priority max value is 1000
+        int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+        for (LnbResource lnb : getLnbResources().values()) {
+            if (!lnb.isInUse()) {
+                // Grant the unused lnb with lower id first
+                grantingLnbId = lnb.getId();
+                break;
+            } else {
+                // Record the lnb id with the lowest client priority among all the
+                // in use lnb when no available lnb has been found.
+                int priority = getOwnerClientPriority(lnb);
+                if (currentLowestPriority > priority) {
+                    inUseLowestPriorityLnbId = lnb.getId();
+                    currentLowestPriority = priority;
+                }
+            }
+        }
+
+        // Grant Lnb when there is unused resource.
+        if (grantingLnbId > -1) {
+            lnbHandle[0] = generateResourceHandle(
+                    TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, grantingLnbId);
+            updateLnbClientMappingOnNewGrant(grantingLnbId, request.getClientId());
+            return true;
+        }
+
+        // When all the resources are occupied, grant the lowest priority resource if the
+        // request client has higher priority.
+        if (inUseLowestPriorityLnbId > -1
+                && (requestClient.getPriority() > currentLowestPriority)) {
+            if (!reclaimResource(getLnbResource(inUseLowestPriorityLnbId).getOwnerClientId(),
+                    TunerResourceManager.TUNER_RESOURCE_TYPE_LNB)) {
+                return false;
+            }
+            lnbHandle[0] = generateResourceHandle(
+                    TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, inUseLowestPriorityLnbId);
+            updateLnbClientMappingOnNewGrant(inUseLowestPriorityLnbId, request.getClientId());
+            return true;
+        }
+
+        return false;
+    }
+
+    @VisibleForTesting
+    void releaseFrontendInternal(FrontendResource fe) {
+        if (DEBUG) {
+            Slog.d(TAG, "releaseFrontend(id=" + fe.getId() + ")");
+        }
+        updateFrontendClientMappingOnRelease(fe);
+    }
+
+    @VisibleForTesting
+    void releaseLnbInternal(LnbResource lnb) {
+        if (DEBUG) {
+            Slog.d(TAG, "releaseLnb(lnbId=" + lnb.getId() + ")");
+        }
+        updateLnbClientMappingOnRelease(lnb);
+    }
+
+    @VisibleForTesting
     boolean requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle) {
         if (DEBUG) {
             Slog.d(TAG, "requestDemux(request=" + request + ")");
         }
+        // There are enough Demux resources, so we don't manage Demux in R.
         demuxHandle[0] = generateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, 0);
         return true;
     }
@@ -477,6 +618,7 @@
         if (DEBUG) {
             Slog.d(TAG, "requestDescrambler(request=" + request + ")");
         }
+        // There are enough Descrambler resources, so we don't manage Descrambler in R.
         descramblerHandle[0] =
                 generateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_DESCRAMBLER, 0);
         return true;
@@ -530,12 +672,21 @@
     }
 
     @VisibleForTesting
-    protected void reclaimFrontendResource(int reclaimingId) {
-        try {
-            mListeners.get(reclaimingId).getListener().onReclaimResources();
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to reclaim resources on client " + reclaimingId, e);
+    protected boolean reclaimResource(int reclaimingClientId,
+            @TunerResourceManager.TunerResourceType int resourceType) {
+        if (DEBUG) {
+            Slog.d(TAG, "Reclaiming resources because higher priority client request resource type "
+                    + resourceType);
         }
+        try {
+            mListeners.get(reclaimingClientId).getListener().onReclaimResources();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to reclaim resources on client " + reclaimingClientId, e);
+            return false;
+        }
+        ClientProfile profile = getClientProfile(reclaimingClientId);
+        reclaimingResourcesFromClient(profile);
+        return true;
     }
 
     @VisibleForTesting
@@ -568,14 +719,37 @@
         }
     }
 
+    private void updateFrontendClientMappingOnRelease(@NonNull FrontendResource releasingFrontend) {
+        ClientProfile ownerProfile = getClientProfile(releasingFrontend.getOwnerClientId());
+        releasingFrontend.removeOwner();
+        ownerProfile.releaseFrontend(releasingFrontend.getId());
+        for (int exclusiveGroupMember : releasingFrontend.getExclusiveGroupMemberFeIds()) {
+            getFrontendResource(exclusiveGroupMember).removeOwner();
+            ownerProfile.releaseFrontend(exclusiveGroupMember);
+        }
+    }
+
+    private void updateLnbClientMappingOnNewGrant(int grantingId, int ownerClientId) {
+        LnbResource grantingLnb = getLnbResource(grantingId);
+        ClientProfile ownerProfile = getClientProfile(ownerClientId);
+        grantingLnb.setOwner(ownerClientId);
+        ownerProfile.useLnb(grantingId);
+    }
+
+    private void updateLnbClientMappingOnRelease(@NonNull LnbResource releasingLnb) {
+        ClientProfile ownerProfile = getClientProfile(releasingLnb.getOwnerClientId());
+        releasingLnb.removeOwner();
+        ownerProfile.releaseLnb(releasingLnb.getId());
+    }
+
     /**
-     * Get the owner client's priority from the frontend id.
+     * Get the owner client's priority from the resource id.
      *
-     * @param frontend an in use frontend.
-     * @return the priority of the owner client of the frontend.
+     * @param resource a in use tuner resource.
+     * @return the priority of the owner client of the resource.
      */
-    private int getOwnerClientPriority(FrontendResource frontend) {
-        return getClientProfile(frontend.getOwnerClientId()).getPriority();
+    private int getOwnerClientPriority(TunerResourceBasic resource) {
+        return getClientProfile(resource.getOwnerClientId()).getPriority();
     }
 
     @VisibleForTesting
@@ -609,6 +783,9 @@
 
     private void removeFrontendResource(int removingId) {
         FrontendResource fe = getFrontendResource(removingId);
+        if (fe.isInUse()) {
+            releaseFrontendInternal(fe);
+        }
         for (int excGroupmemberFeId : fe.getExclusiveGroupMemberFeIds()) {
             getFrontendResource(excGroupmemberFeId)
                     .removeExclusiveGroupMemberFeId(fe.getId());
@@ -618,6 +795,30 @@
 
     @VisibleForTesting
     @Nullable
+    protected LnbResource getLnbResource(int lnbId) {
+        return mLnbResources.get(lnbId);
+    }
+
+    @VisibleForTesting
+    protected Map<Integer, LnbResource> getLnbResources() {
+        return mLnbResources;
+    }
+
+    private void addLnbResource(LnbResource newLnb) {
+        // Update resource list and available id list
+        mLnbResources.put(newLnb.getId(), newLnb);
+    }
+
+    private void removeLnbResource(int removingId) {
+        LnbResource lnb = getLnbResource(removingId);
+        if (lnb.isInUse()) {
+            releaseLnbInternal(lnb);
+        }
+        mLnbResources.remove(removingId);
+    }
+
+    @VisibleForTesting
+    @Nullable
     protected ClientProfile getClientProfile(int clientId) {
         return mClientProfiles.get(clientId);
     }
@@ -639,6 +840,16 @@
         mListeners.remove(clientId);
     }
 
+    private void reclaimingResourcesFromClient(ClientProfile profile) {
+        for (Integer feId : profile.getInUseFrontendIds()) {
+            getFrontendResource(feId).removeOwner();
+        }
+        for (Integer lnbId : profile.getInUseLnbIds()) {
+            getLnbResource(lnbId).removeOwner();
+        }
+        profile.reclaimAllResources();
+    }
+
     @VisibleForTesting
     protected boolean checkClientExists(int clientId) {
         return mClientProfiles.keySet().contains(clientId);
@@ -651,6 +862,22 @@
                 | (mResourceRequestCount++ & 0xffff);
     }
 
+    @VisibleForTesting
+    protected int getResourceIdFromHandle(int resourceHandle) {
+        if (resourceHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
+            return resourceHandle;
+        }
+        return (resourceHandle & 0x00ff0000) >> 16;
+    }
+
+    private boolean validateResourceHandle(int resourceType, int resourceHandle) {
+        if (resourceHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE
+                || ((resourceHandle & 0xff000000) >> 24) != resourceType) {
+            return false;
+        }
+        return true;
+    }
+
     private void enforceTrmAccessPermission(String apiName) {
         getContext().enforceCallingPermission("android.permission.TUNER_RESOURCE_ACCESS",
                 TAG + ": " + apiName);
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 4fe5843..3f9f95c 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -726,8 +726,7 @@
                     } else {
                         final Region dirtyRegion = mTempRegion3;
                         dirtyRegion.set(mMagnificationRegion);
-                        dirtyRegion.op(mOldMagnificationRegion, Region.Op.UNION);
-                        dirtyRegion.op(nonMagnifiedBounds, Region.Op.INTERSECT);
+                        dirtyRegion.op(mOldMagnificationRegion, Region.Op.XOR);
                         dirtyRegion.getBounds(dirtyRect);
                         mWindow.invalidate(dirtyRect);
                     }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 521ffa5..7a5ff41 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -292,6 +292,7 @@
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 import android.view.animation.Animation;
+import android.window.WindowContainerToken;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -432,6 +433,8 @@
     final boolean stateNotNeeded; // As per ActivityInfo.flags
     @VisibleForTesting
     int mHandoverLaunchDisplayId = INVALID_DISPLAY; // Handover launch display id to next activity.
+    @VisibleForTesting
+    TaskDisplayArea mHandoverTaskDisplayArea; // Handover launch task display area.
     private final boolean componentSpecified;  // did caller specify an explicit component?
     final boolean rootVoiceInteraction;  // was this the root activity of a voice interaction?
 
@@ -1657,7 +1660,11 @@
             if (usageReport != null) {
                 appTimeTracker = new AppTimeTracker(usageReport);
             }
-            // Gets launch display id from options. It returns INVALID_DISPLAY if not set.
+            // Gets launch task display area and display id from options. Returns
+            // null/INVALID_DISPLAY if not set.
+            final WindowContainerToken daToken = options.getLaunchTaskDisplayArea();
+            mHandoverTaskDisplayArea = daToken != null
+                    ? (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()) : null;
             mHandoverLaunchDisplayId = options.getLaunchDisplayId();
         }
     }
@@ -3762,7 +3769,8 @@
                         pendingOptions.getPackageName(),
                         pendingOptions.getCustomEnterResId(),
                         pendingOptions.getCustomExitResId(),
-                        pendingOptions.getOnAnimationStartListener());
+                        pendingOptions.getAnimationStartedListener(),
+                        pendingOptions.getAnimationFinishedListener());
                 break;
             case ANIM_CLIP_REVEAL:
                 displayContent.mAppTransition.overridePendingAppTransitionClipReveal(
@@ -3792,7 +3800,7 @@
                 final GraphicBuffer buffer = pendingOptions.getThumbnail();
                 displayContent.mAppTransition.overridePendingAppTransitionThumb(buffer,
                         pendingOptions.getStartX(), pendingOptions.getStartY(),
-                        pendingOptions.getOnAnimationStartListener(),
+                        pendingOptions.getAnimationStartedListener(),
                         scaleUp);
                 if (intent.getSourceBounds() == null && buffer != null) {
                     intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
@@ -3808,19 +3816,19 @@
                         pendingOptions.getSpecsFuture();
                 if (specsFuture != null) {
                     displayContent.mAppTransition.overridePendingAppTransitionMultiThumbFuture(
-                            specsFuture, pendingOptions.getOnAnimationStartListener(),
+                            specsFuture, pendingOptions.getAnimationStartedListener(),
                             animationType == ANIM_THUMBNAIL_ASPECT_SCALE_UP);
                 } else if (animationType == ANIM_THUMBNAIL_ASPECT_SCALE_DOWN
                         && specs != null) {
                     displayContent.mAppTransition.overridePendingAppTransitionMultiThumb(
-                            specs, pendingOptions.getOnAnimationStartListener(),
+                            specs, pendingOptions.getAnimationStartedListener(),
                             pendingOptions.getAnimationFinishedListener(), false);
                 } else {
                     displayContent.mAppTransition.overridePendingAppTransitionAspectScaledThumb(
                             pendingOptions.getThumbnail(),
                             pendingOptions.getStartX(), pendingOptions.getStartY(),
                             pendingOptions.getWidth(), pendingOptions.getHeight(),
-                            pendingOptions.getOnAnimationStartListener(),
+                            pendingOptions.getAnimationStartedListener(),
                             (animationType == ANIM_THUMBNAIL_ASPECT_SCALE_UP));
                     if (intent.getSourceBounds() == null) {
                         intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 3bccced..fe9e5f3c 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -1053,6 +1053,13 @@
         return true;
     }
 
+    /** Check if caller is allowed to launch activities on specified task display area. */
+    boolean isCallerAllowedToLaunchOnTaskDisplayArea(int callingPid, int callingUid,
+            TaskDisplayArea taskDisplayArea, ActivityInfo aInfo) {
+        return isCallerAllowedToLaunchOnDisplay(callingPid, callingUid,
+                taskDisplayArea != null ? taskDisplayArea.getDisplayId() : DEFAULT_DISPLAY, aInfo);
+    }
+
     /** Check if caller is allowed to launch activities on specified display. */
     boolean isCallerAllowedToLaunchOnDisplay(int callingPid, int callingUid, int launchDisplayId,
             ActivityInfo aInfo) {
@@ -2133,18 +2140,6 @@
                 WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION);
     }
 
-    // TODO(b/152116619): Remove after complete switch to TaskDisplayArea
-    void handleNonResizableTaskIfNeeded(Task task, int preferredWindowingMode,
-            int preferredDisplayId, ActivityStack actualStack) {
-        final DisplayContent preferredDisplayContent = mRootWindowContainer
-                .getDisplayContent(preferredDisplayId);
-        final TaskDisplayArea preferredDisplayArea = preferredDisplayContent != null
-                ? preferredDisplayContent.getDefaultTaskDisplayArea()
-                : null;
-        handleNonResizableTaskIfNeeded(task, preferredWindowingMode, preferredDisplayArea,
-                actualStack);
-    }
-
     void handleNonResizableTaskIfNeeded(Task task, int preferredWindowingMode,
             TaskDisplayArea preferredTaskDisplayArea, ActivityStack actualStack) {
         handleNonResizableTaskIfNeeded(task, preferredWindowingMode, preferredTaskDisplayArea,
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index ad54356..7fad395 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -184,8 +184,8 @@
         }
         final int displayId = taskDisplayArea.getDisplayId();
         options.setLaunchDisplayId(displayId);
-        // TODO(b/152116619): Enable after complete switch to WindowContainerToken
-        //options.setLaunchWindowContainerToken(taskDisplayArea.getWindowContainerToken());
+        options.setLaunchTaskDisplayArea(taskDisplayArea.mRemoteToken
+                .toWindowContainerToken());
 
         // The home activity will be started later, defer resuming to avoid unneccerary operations
         // (e.g. start home recursively) when creating home stack.
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 0bd1aca..0754a34 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -55,7 +55,6 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Process.INVALID_UID;
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
 import static com.android.server.wm.ActivityStackSupervisor.DEFER_RESUME;
@@ -167,8 +166,8 @@
     private int mStartFlags;
     private ActivityRecord mSourceRecord;
 
-    // The display to launch the activity onto, barring any strong reason to do otherwise.
-    private int mPreferredDisplayId;
+    // The task display area to launch the activity onto, barring any strong reason to do otherwise.
+    private TaskDisplayArea mPreferredTaskDisplayArea;
     // The windowing mode to apply to the root task, if possible
     private int mPreferredWindowingMode;
 
@@ -538,7 +537,7 @@
         mDoResume = starter.mDoResume;
         mStartFlags = starter.mStartFlags;
         mSourceRecord = starter.mSourceRecord;
-        mPreferredDisplayId = starter.mPreferredDisplayId;
+        mPreferredTaskDisplayArea = starter.mPreferredTaskDisplayArea;
         mPreferredWindowingMode = starter.mPreferredWindowingMode;
 
         mInTask = starter.mInTask;
@@ -1631,7 +1630,7 @@
         // Update the recent tasks list immediately when the activity starts
         mSupervisor.mRecentTasks.add(mStartActivity.getTask());
         mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTask(),
-                mPreferredWindowingMode, mPreferredDisplayId, mTargetStack);
+                mPreferredWindowingMode, mPreferredTaskDisplayArea, mTargetStack);
 
         return START_SUCCESS;
     }
@@ -1684,9 +1683,9 @@
 
         mSupervisor.getLaunchParamsController().calculate(targetTask, r.info.windowLayout, r,
                 sourceRecord, mOptions, PHASE_BOUNDS, mLaunchParams);
-        mPreferredDisplayId = mLaunchParams.hasPreferredDisplay()
-                ? mLaunchParams.mPreferredDisplayId
-                : DEFAULT_DISPLAY;
+        mPreferredTaskDisplayArea = mLaunchParams.hasPreferredTaskDisplayArea()
+                ? mLaunchParams.mPreferredTaskDisplayArea
+                : mRootWindowContainer.getDefaultTaskDisplayArea();
         mPreferredWindowingMode = mLaunchParams.mWindowingMode;
     }
 
@@ -1703,10 +1702,12 @@
         // Do not start home activity if it cannot be launched on preferred display. We are not
         // doing this in ActivityStackSupervisor#canPlaceEntityOnDisplay because it might
         // fallback to launch on other displays.
-        if (r.isActivityTypeHome() && !mRootWindowContainer.canStartHomeOnDisplay(r.info,
-                mPreferredDisplayId, true /* allowInstrumenting */)) {
-            Slog.w(TAG, "Cannot launch home on display " + mPreferredDisplayId);
-            return START_CANCELED;
+        if (r.isActivityTypeHome()) {
+            if (!mRootWindowContainer.canStartHomeOnDisplayArea(r.info, mPreferredTaskDisplayArea,
+                    true /* allowInstrumenting */)) {
+                Slog.w(TAG, "Cannot launch home on display area " + mPreferredTaskDisplayArea);
+                return START_CANCELED;
+            }
         }
 
         if (mRestrictedBgActivity && (newTask || !targetTask.isUidPresent(mCallingUid))
@@ -1841,10 +1842,10 @@
                 && top.attachedToProcess()
                 && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
                 || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK))
-                // This allows home activity to automatically launch on secondary display when
-                // display added, if home was the top activity on default display, instead of
-                // sending new intent to the home activity on default display.
-                && (!top.isActivityTypeHome() || top.getDisplayId() == mPreferredDisplayId);
+                // This allows home activity to automatically launch on secondary task display area
+                // when it was added, if home was the top activity on default task display area,
+                // instead of sending new intent to the home activity on default display area.
+                && (!top.isActivityTypeHome() || top.getDisplayArea() == mPreferredTaskDisplayArea);
         if (!dontStart) {
             return START_SUCCESS;
         }
@@ -1866,7 +1867,7 @@
         // Don't use mStartActivity.task to show the toast. We're not starting a new activity but
         // reusing 'top'. Fields in mStartActivity may not be fully initialized.
         mSupervisor.handleNonResizableTaskIfNeeded(top.getTask(),
-                mLaunchParams.mWindowingMode, mPreferredDisplayId, topStack);
+                mLaunchParams.mWindowingMode, mPreferredTaskDisplayArea, topStack);
 
         return START_DELIVERED_TO_TOP;
     }
@@ -2010,7 +2011,7 @@
         mDoResume = false;
         mStartFlags = 0;
         mSourceRecord = null;
-        mPreferredDisplayId = INVALID_DISPLAY;
+        mPreferredTaskDisplayArea = null;
         mPreferredWindowingMode = WINDOWING_MODE_UNDEFINED;
 
         mInTask = null;
@@ -2060,9 +2061,9 @@
         // after we located a reusable task (which might be resided in another display).
         mSupervisor.getLaunchParamsController().calculate(inTask, r.info.windowLayout, r,
                 sourceRecord, options, PHASE_DISPLAY, mLaunchParams);
-        mPreferredDisplayId = mLaunchParams.hasPreferredDisplay()
-                ? mLaunchParams.mPreferredDisplayId
-                : DEFAULT_DISPLAY;
+        mPreferredTaskDisplayArea = mLaunchParams.hasPreferredTaskDisplayArea()
+                ? mLaunchParams.mPreferredTaskDisplayArea
+                : mRootWindowContainer.getDefaultTaskDisplayArea();
         mPreferredWindowingMode = mLaunchParams.mWindowingMode;
 
         mLaunchMode = r.launchMode;
@@ -2334,14 +2335,14 @@
             } else {
                 // Otherwise find the best task to put the activity in.
                 intentActivity =
-                        mRootWindowContainer.findTask(mStartActivity, mPreferredDisplayId);
+                        mRootWindowContainer.findTask(mStartActivity, mPreferredTaskDisplayArea);
             }
         }
 
         if (intentActivity != null
                 && (mStartActivity.isActivityTypeHome() || intentActivity.isActivityTypeHome())
-                && intentActivity.getDisplayId() != mPreferredDisplayId) {
-            // Do not reuse home activity on other displays.
+                && intentActivity.getDisplayArea() != mPreferredTaskDisplayArea) {
+            // Do not reuse home activity on other display areas.
             intentActivity = null;
         }
 
@@ -2363,7 +2364,7 @@
         // the same behavior as if a new instance was being started, which means not bringing it
         // to the front if the caller is not itself in the front.
         final boolean differentTopTask;
-        if (mPreferredDisplayId == mTargetStack.getDisplayId()) {
+        if (mTargetStack.getDisplayArea() == mPreferredTaskDisplayArea) {
             final ActivityStack focusStack = mTargetStack.getDisplay().getFocusedStack();
             final ActivityRecord curTop = (focusStack == null)
                     ? null : focusStack.topRunningNonDelayedActivityLocked(mNotTop);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index d92f43b..409e6ff 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2003,7 +2003,7 @@
             if (self.isState(
                     ActivityStack.ActivityState.RESUMED, ActivityStack.ActivityState.PAUSING)) {
                 self.getDisplay().mDisplayContent.mAppTransition.overridePendingAppTransition(
-                        packageName, enterAnim, exitAnim, null);
+                        packageName, enterAnim, exitAnim, null, null);
             }
 
             Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 6f1ddcd..c31d55c 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -1417,23 +1417,7 @@
                 : new TranslateAnimation(0, fromX, 0, fromY);
         set.addAnimation(scale);
         set.addAnimation(translation);
-
-        final IRemoteCallback callback = mAnimationFinishedCallback;
-        if (callback != null) {
-            set.setAnimationListener(new Animation.AnimationListener() {
-                @Override
-                public void onAnimationStart(Animation animation) { }
-
-                @Override
-                public void onAnimationEnd(Animation animation) {
-                    mHandler.sendMessage(PooledLambda.obtainMessage(
-                            AppTransition::doAnimationCallback, callback));
-                }
-
-                @Override
-                public void onAnimationRepeat(Animation animation) { }
-            });
-        }
+        setAppTransitionFinishedCallbackIfNeeded(set);
         return set;
     }
 
@@ -1671,6 +1655,7 @@
                     "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM transit=%s "
                             + "isEntrance=%b Callers=%s",
                     a, appTransitionToString(transit), enter, Debug.getCallers(3));
+            setAppTransitionFinishedCallbackIfNeeded(a);
         } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE) {
             a = loadAnimationRes(mNextAppTransitionPackage, mNextAppTransitionInPlace);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
@@ -1835,7 +1820,7 @@
     }
 
     void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim,
-            IRemoteCallback startedCallback) {
+            IRemoteCallback startedCallback, IRemoteCallback endedCallback) {
         if (canOverridePendingAppTransition()) {
             clear();
             mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM;
@@ -1844,6 +1829,7 @@
             mNextAppTransitionExit = exitAnim;
             postAnimationCallback();
             mNextAppTransitionCallback = startedCallback;
+            mAnimationFinishedCallback = endedCallback;
         }
     }
 
@@ -2331,6 +2317,25 @@
         }
     }
 
+    private void setAppTransitionFinishedCallbackIfNeeded(Animation anim) {
+        final IRemoteCallback callback = mAnimationFinishedCallback;
+        if (callback != null && anim != null) {
+            anim.setAnimationListener(new Animation.AnimationListener() {
+                @Override
+                public void onAnimationStart(Animation animation) { }
+
+                @Override
+                public void onAnimationEnd(Animation animation) {
+                    mHandler.sendMessage(PooledLambda.obtainMessage(
+                            AppTransition::doAnimationCallback, callback));
+                }
+
+                @Override
+                public void onAnimationRepeat(Animation animation) { }
+            });
+        }
+    }
+
     void removeAppTransitionTimeoutCallbacks() {
         mHandler.removeCallbacks(mHandleAppTransitionTimeoutRunnable);
     }
diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java
index a9820ef..4cd3180 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsController.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsController.java
@@ -17,7 +17,6 @@
 package com.android.server.wm;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
@@ -26,6 +25,7 @@
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.app.ActivityOptions;
 import android.content.pm.ActivityInfo.WindowLayout;
 import android.graphics.Rect;
@@ -108,11 +108,13 @@
 
         if (activity != null && activity.requestedVrComponent != null) {
             // Check if the Activity is a VR activity. If so, it should be launched in main display.
-            result.mPreferredDisplayId = DEFAULT_DISPLAY;
+            result.mPreferredTaskDisplayArea = mService.mRootWindowContainer
+                    .getDefaultTaskDisplayArea();
         } else if (mService.mVr2dDisplayId != INVALID_DISPLAY) {
             // Get the virtual display ID from ActivityTaskManagerService. If that's set we
             // should always use that.
-            result.mPreferredDisplayId = mService.mVr2dDisplayId;
+            result.mPreferredTaskDisplayArea = mService.mRootWindowContainer
+                    .getDisplayContent(mService.mVr2dDisplayId).getDefaultTaskDisplayArea();
         }
     }
 
@@ -136,9 +138,10 @@
         mService.deferWindowLayout();
 
         try {
-            if (mTmpParams.hasPreferredDisplay()
-                    && mTmpParams.mPreferredDisplayId != task.getStack().getDisplay().mDisplayId) {
-                mService.moveStackToDisplay(task.getRootTaskId(), mTmpParams.mPreferredDisplayId);
+            if (mTmpParams.mPreferredTaskDisplayArea != null
+                    && task.getDisplayArea() != mTmpParams.mPreferredTaskDisplayArea) {
+                mService.mRootWindowContainer.moveStackToTaskDisplayArea(task.getRootTaskId(),
+                        mTmpParams.mPreferredTaskDisplayArea, true /* onTop */);
             }
 
             if (mTmpParams.hasWindowingMode()
@@ -184,8 +187,9 @@
         /** The bounds within the parent container. */
         final Rect mBounds = new Rect();
 
-        /** The id of the display the {@link Task} would prefer to be on. */
-        int mPreferredDisplayId;
+        /** The display area the {@link Task} would prefer to be on. */
+        @Nullable
+        TaskDisplayArea mPreferredTaskDisplayArea;
 
         /** The windowing mode to be in. */
         int mWindowingMode;
@@ -193,20 +197,20 @@
         /** Sets values back to default. {@link #isEmpty} will return {@code true} once called. */
         void reset() {
             mBounds.setEmpty();
-            mPreferredDisplayId = INVALID_DISPLAY;
+            mPreferredTaskDisplayArea = null;
             mWindowingMode = WINDOWING_MODE_UNDEFINED;
         }
 
         /** Copies the values set on the passed in {@link LaunchParams}. */
         void set(LaunchParams params) {
             mBounds.set(params.mBounds);
-            mPreferredDisplayId = params.mPreferredDisplayId;
+            mPreferredTaskDisplayArea = params.mPreferredTaskDisplayArea;
             mWindowingMode = params.mWindowingMode;
         }
 
         /** Returns {@code true} if no values have been explicitly set. */
         boolean isEmpty() {
-            return mBounds.isEmpty() && mPreferredDisplayId == INVALID_DISPLAY
+            return mBounds.isEmpty() && mPreferredTaskDisplayArea == null
                     && mWindowingMode == WINDOWING_MODE_UNDEFINED;
         }
 
@@ -214,8 +218,8 @@
             return mWindowingMode != WINDOWING_MODE_UNDEFINED;
         }
 
-        boolean hasPreferredDisplay() {
-            return mPreferredDisplayId != INVALID_DISPLAY;
+        boolean hasPreferredTaskDisplayArea() {
+            return mPreferredTaskDisplayArea != null;
         }
 
         @Override
@@ -225,7 +229,7 @@
 
             LaunchParams that = (LaunchParams) o;
 
-            if (mPreferredDisplayId != that.mPreferredDisplayId) return false;
+            if (mPreferredTaskDisplayArea != that.mPreferredTaskDisplayArea) return false;
             if (mWindowingMode != that.mWindowingMode) return false;
             return mBounds != null ? mBounds.equals(that.mBounds) : that.mBounds == null;
         }
@@ -233,7 +237,8 @@
         @Override
         public int hashCode() {
             int result = mBounds != null ? mBounds.hashCode() : 0;
-            result = 31 * result + mPreferredDisplayId;
+            result = 31 * result + (mPreferredTaskDisplayArea != null
+                    ? mPreferredTaskDisplayArea.hashCode() : 0);
             result = 31 * result + mWindowingMode;
             return result;
         }
diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
index 9371c0e..a974332 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
@@ -318,7 +318,9 @@
         final DisplayContent display = mSupervisor.mRootWindowContainer.getDisplayContent(
                 persistableParams.mDisplayUniqueId);
         if (display != null) {
-            outParams.mPreferredDisplayId =  display.mDisplayId;
+            // TODO(b/153764726): Investigate if task display area needs to be persisted vs
+            // always choosing the default one.
+            outParams.mPreferredTaskDisplayArea = display.getDefaultTaskDisplayArea();
         }
         outParams.mWindowingMode = persistableParams.mWindowingMode;
         outParams.mBounds.set(persistableParams.mBounds);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index e8f7ba5..6b9054b 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -139,6 +139,7 @@
 import android.view.DisplayInfo;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
+import android.window.WindowContainerToken;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ResolverActivity;
@@ -1519,8 +1520,7 @@
         if (taskDisplayArea == getDefaultTaskDisplayArea()) {
             homeIntent = mService.getHomeIntent();
             aInfo = resolveHomeActivity(userId, homeIntent);
-        } else if (taskDisplayArea.getDisplayId() == DEFAULT_DISPLAY
-                || shouldPlaceSecondaryHomeOnDisplay(taskDisplayArea.getDisplayId())) {
+        } else if (shouldPlaceSecondaryHomeOnDisplayArea(taskDisplayArea)) {
             Pair<ActivityInfo, Intent> info = resolveSecondaryHomeActivity(userId, taskDisplayArea);
             aInfo = info.first;
             homeIntent = info.second;
@@ -1529,7 +1529,7 @@
             return false;
         }
 
-        if (!canStartHomeOnDisplay(aInfo, taskDisplayArea.getDisplayId(), allowInstrumenting)) {
+        if (!canStartHomeOnDisplayArea(aInfo, taskDisplayArea, allowInstrumenting)) {
             return false;
         }
 
@@ -1625,8 +1625,7 @@
         }
 
         if (aInfo != null) {
-            if (!canStartHomeOnDisplay(aInfo, taskDisplayArea.getDisplayId(),
-                    false /* allowInstrumenting */)) {
+            if (!canStartHomeOnDisplayArea(aInfo, taskDisplayArea, false /* allowInstrumenting */)) {
                 aInfo = null;
             }
         }
@@ -1683,19 +1682,19 @@
     }
 
     /**
-     * Check if the display is valid for secondary home activity.
-     * @param displayId The id of the target display.
+     * Check if the display area is valid for secondary home activity.
+     * @param taskDisplayArea The target display area.
      * @return {@code true} if allow to launch, {@code false} otherwise.
      */
-    boolean shouldPlaceSecondaryHomeOnDisplay(int displayId) {
-        if (displayId == DEFAULT_DISPLAY) {
+    boolean shouldPlaceSecondaryHomeOnDisplayArea(TaskDisplayArea taskDisplayArea) {
+        if (getDefaultTaskDisplayArea() == taskDisplayArea) {
             throw new IllegalArgumentException(
-                    "shouldPlaceSecondaryHomeOnDisplay: Should not be DEFAULT_DISPLAY");
-        } else if (displayId == INVALID_DISPLAY) {
+                    "shouldPlaceSecondaryHomeOnDisplay: Should not be on default task container");
+        } else if (taskDisplayArea == null) {
             return false;
         }
 
-        if (!mService.mSupportsMultiDisplay) {
+        if (taskDisplayArea.getDisplayId() != DEFAULT_DISPLAY && !mService.mSupportsMultiDisplay) {
             // Can't launch home on secondary display if device does not support multi-display.
             return false;
         }
@@ -1704,16 +1703,16 @@
                 mService.mContext.getContentResolver(),
                 Settings.Global.DEVICE_PROVISIONED, 0) != 0;
         if (!deviceProvisioned) {
-            // Can't launch home on secondary display before device is provisioned.
+            // Can't launch home on secondary display areas before device is provisioned.
             return false;
         }
 
         if (!StorageManager.isUserKeyUnlocked(mCurrentUser)) {
-            // Can't launch home on secondary displays if device is still locked.
+            // Can't launch home on secondary display areas if device is still locked.
             return false;
         }
 
-        final DisplayContent display = getDisplayContent(displayId);
+        final DisplayContent display = taskDisplayArea.getDisplayContent();
         if (display == null || display.isRemoved() || !display.supportsSystemDecorations()) {
             // Can't launch home on display that doesn't support system decorations.
             return false;
@@ -1725,11 +1724,11 @@
     /**
      * Check if home activity start should be allowed on a display.
      * @param homeInfo {@code ActivityInfo} of the home activity that is going to be launched.
-     * @param displayId The id of the target display.
+     * @param taskDisplayArea The target display area.
      * @param allowInstrumenting Whether launching home should be allowed if being instrumented.
      * @return {@code true} if allow to launch, {@code false} otherwise.
      */
-    boolean canStartHomeOnDisplay(ActivityInfo homeInfo, int displayId,
+    boolean canStartHomeOnDisplayArea(ActivityInfo homeInfo, TaskDisplayArea taskDisplayArea,
             boolean allowInstrumenting) {
         if (mService.mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL
                 && mService.mTopAction == null) {
@@ -1745,13 +1744,15 @@
             return false;
         }
 
+        final int displayId = taskDisplayArea != null ? taskDisplayArea.getDisplayId()
+                : INVALID_DISPLAY;
         if (displayId == DEFAULT_DISPLAY || (displayId != INVALID_DISPLAY
                 && displayId == mService.mVr2dDisplayId)) {
             // No restrictions to default display or vr 2d display.
             return true;
         }
 
-        if (!shouldPlaceSecondaryHomeOnDisplay(displayId)) {
+        if (!shouldPlaceSecondaryHomeOnDisplayArea(taskDisplayArea)) {
             return false;
         }
 
@@ -2208,15 +2209,14 @@
         }
     }
 
-    ActivityRecord findTask(ActivityRecord r, int preferredDisplayId) {
+    ActivityRecord findTask(ActivityRecord r, TaskDisplayArea preferredTaskDisplayArea) {
         if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Looking for task of " + r);
         mTmpFindTaskResult.clear();
 
-        // Looking up task on preferred display first
-        final DisplayContent preferredDisplay = getDisplayContent(preferredDisplayId);
-        if (preferredDisplay != null) {
-            preferredDisplay.getDefaultTaskDisplayArea().findTaskLocked(r,
-                    true /* isPreferredDisplay */, mTmpFindTaskResult);
+        // Looking up task on preferred display area first
+        if (preferredTaskDisplayArea != null) {
+            preferredTaskDisplayArea.findTaskLocked(r, true /* isPreferredDisplay */,
+                    mTmpFindTaskResult);
             if (mTmpFindTaskResult.mIdealMatch) {
                 return mTmpFindTaskResult.mRecord;
             }
@@ -2224,14 +2224,17 @@
 
         for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
             final DisplayContent display = getChildAt(displayNdx);
-            if (display.mDisplayId == preferredDisplayId) {
-                continue;
-            }
+            for (int tdaNdx = display.getTaskDisplayAreaCount() - 1; tdaNdx >= 0; --tdaNdx) {
+                final TaskDisplayArea taskDisplayArea = display.getTaskDisplayAreaAt(tdaNdx);
+                if (taskDisplayArea == preferredTaskDisplayArea) {
+                    continue;
+                }
 
-            display.getDefaultTaskDisplayArea().findTaskLocked(r, false /* isPreferredDisplay */,
-                    mTmpFindTaskResult);
-            if (mTmpFindTaskResult.mIdealMatch) {
-                return mTmpFindTaskResult.mRecord;
+                taskDisplayArea.findTaskLocked(r, false /* isPreferredDisplay */,
+                        mTmpFindTaskResult);
+                if (mTmpFindTaskResult.mIdealMatch) {
+                    return mTmpFindTaskResult.mRecord;
+                }
             }
         }
 
@@ -2823,11 +2826,15 @@
             int realCallingUid) {
         int taskId = INVALID_TASK_ID;
         int displayId = INVALID_DISPLAY;
+        TaskDisplayArea taskDisplayArea = null;
 
         // We give preference to the launch preference in activity options.
         if (options != null) {
             taskId = options.getLaunchTaskId();
             displayId = options.getLaunchDisplayId();
+            final WindowContainerToken daToken = options.getLaunchTaskDisplayArea();
+            taskDisplayArea = daToken != null
+                    ? (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()) : null;
         }
 
         // First preference for stack goes to the task Id set in the activity options. Use the stack
@@ -2846,30 +2853,34 @@
         final int activityType = resolveActivityType(r, options, candidateTask);
         ActivityStack stack = null;
 
-        // Next preference for stack goes to the display Id set the candidate display.
-        if (launchParams != null && launchParams.mPreferredDisplayId != INVALID_DISPLAY) {
-            displayId = launchParams.mPreferredDisplayId;
+        // Next preference for stack goes to the taskDisplayArea candidate.
+        if (launchParams != null && launchParams.mPreferredTaskDisplayArea != null) {
+            taskDisplayArea = launchParams.mPreferredTaskDisplayArea;
         }
-        final boolean canLaunchOnDisplayFromStartRequest =
-                realCallingPid != 0 && realCallingUid > 0 && r != null
-                        && mStackSupervisor.canPlaceEntityOnDisplay(displayId, realCallingPid,
-                        realCallingUid, r.info);
-        // Checking if the activity's launch caller, or the realCallerId of the activity from
-        // start request (i.e. entity that invokes PendingIntent) is allowed to launch on the
-        // display.
-        if (displayId != INVALID_DISPLAY && (canLaunchOnDisplay(r, displayId)
-                || canLaunchOnDisplayFromStartRequest)) {
-            if (r != null) {
-                stack = getValidLaunchStackOnDisplay(displayId, r, candidateTask, options,
-                        launchParams);
-                if (stack != null) {
-                    return stack;
-                }
+
+        if (taskDisplayArea == null && displayId != INVALID_DISPLAY) {
+            final DisplayContent displayContent = getDisplayContent(displayId);
+            if (displayContent != null) {
+                taskDisplayArea = displayContent.getDefaultTaskDisplayArea();
             }
-            final DisplayContent display = getDisplayContentOrCreate(displayId);
-            if (display != null) {
+        }
+
+        if (taskDisplayArea != null) {
+            final int tdaDisplayId = taskDisplayArea.getDisplayId();
+            final boolean canLaunchOnDisplayFromStartRequest =
+                    realCallingPid != 0 && realCallingUid > 0 && r != null
+                            && mStackSupervisor.canPlaceEntityOnDisplay(tdaDisplayId,
+                            realCallingPid, realCallingUid, r.info);
+            if (canLaunchOnDisplayFromStartRequest || canLaunchOnDisplay(r, tdaDisplayId)) {
+                if (r != null) {
+                    final ActivityStack result = getValidLaunchStackInTaskDisplayArea(
+                            taskDisplayArea, r, candidateTask, options, launchParams);
+                    if (result != null) {
+                        return result;
+                    }
+                }
                 // Falling back to default task container
-                final TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
+                taskDisplayArea = taskDisplayArea.mDisplayContent.getDefaultTaskDisplayArea();
                 stack = taskDisplayArea.getOrCreateStack(r, options, candidateTask, activityType,
                         onTop);
                 if (stack != null) {
@@ -2936,40 +2947,37 @@
     }
 
     /**
-     * Get a topmost stack on the display, that is a valid launch stack for specified activity.
+     * Get a topmost stack on the display area, that is a valid launch stack for specified activity.
      * If there is no such stack, new dynamic stack can be created.
-     * @param displayId Target display.
+     * @param taskDisplayArea Target display area.
      * @param r Activity that should be launched there.
      * @param candidateTask The possible task the activity might be put in.
      * @return Existing stack if there is a valid one, new dynamic stack if it is valid or null.
      */
     @VisibleForTesting
-    ActivityStack getValidLaunchStackOnDisplay(int displayId, @NonNull ActivityRecord r,
-            @Nullable Task candidateTask, @Nullable ActivityOptions options,
+    ActivityStack getValidLaunchStackInTaskDisplayArea(@NonNull TaskDisplayArea taskDisplayArea,
+            @NonNull ActivityRecord r, @Nullable Task candidateTask,
+            @Nullable ActivityOptions options,
             @Nullable LaunchParamsController.LaunchParams launchParams) {
-        final DisplayContent displayContent = getDisplayContentOrCreate(displayId);
-        if (displayContent == null) {
-            throw new IllegalArgumentException(
-                    "Display with displayId=" + displayId + " not found.");
-        }
-
-        if (!r.canBeLaunchedOnDisplay(displayId)) {
+        if (!r.canBeLaunchedOnDisplay(taskDisplayArea.getDisplayId())) {
             return null;
         }
 
-        // If {@code r} is already in target display and its task is the same as the candidate task,
-        // the intention should be getting a launch stack for the reusable activity, so we can use
-        // the existing stack.
+        // If {@code r} is already in target display area and its task is the same as the candidate
+        // task, the intention should be getting a launch stack for the reusable activity, so we can
+        // use the existing stack.
         if (candidateTask != null && (r.getTask() == null || r.getTask() == candidateTask)) {
-            final int attachedDisplayId = r.getDisplayId();
-            if (attachedDisplayId == INVALID_DISPLAY || attachedDisplayId == displayId) {
+            // TODO(b/153920825): Fix incorrect evaluation of attached state
+            final TaskDisplayArea attachedTaskDisplayArea = r.getTask() != null
+                    ? r.getTask().getDisplayArea() : r.getDisplayArea();
+            if (attachedTaskDisplayArea == null || attachedTaskDisplayArea == taskDisplayArea) {
                 return candidateTask.getStack();
             }
             // Or the candidate task is already a root task that can be reused by reparenting
             // it to the target display.
             if (candidateTask.isRootTask()) {
                 final ActivityStack stack = candidateTask.getStack();
-                stack.reparent(displayContent.getDefaultTaskDisplayArea(), true /* onTop */);
+                stack.reparent(taskDisplayArea, true /* onTop */);
                 return stack;
             }
         }
@@ -2984,39 +2992,30 @@
             windowingMode = options != null ? options.getLaunchWindowingMode()
                     : r.getWindowingMode();
         }
+        windowingMode = taskDisplayArea.validateWindowingMode(windowingMode, r, candidateTask,
+                r.getActivityType());
 
         // Return the topmost valid stack on the display.
-        for (int tdaNdx = displayContent.getTaskDisplayAreaCount() - 1; tdaNdx >= 0; --tdaNdx) {
-            final TaskDisplayArea taskDisplayArea = displayContent.getTaskDisplayAreaAt(tdaNdx);
-            final int validatedWindowingMode = taskDisplayArea
-                    .validateWindowingMode(windowingMode, r, candidateTask, r.getActivityType());
-            for (int sNdx = taskDisplayArea.getStackCount() - 1; sNdx >= 0; --sNdx) {
-                final ActivityStack stack = taskDisplayArea.getStackAt(sNdx);
-                if (isValidLaunchStack(stack, r, validatedWindowingMode)) {
-                    return stack;
-                }
+        for (int i = taskDisplayArea.getStackCount() - 1; i >= 0; --i) {
+            final ActivityStack stack = taskDisplayArea.getStackAt(i);
+            if (isValidLaunchStack(stack, r, windowingMode)) {
+                return stack;
             }
         }
 
-        // If there is no valid stack on the external display - check if new dynamic stack will do.
-        if (displayId != DEFAULT_DISPLAY) {
+        // If there is no valid stack on the secondary display area - check if new dynamic stack
+        // will do.
+        if (taskDisplayArea != getDisplayContent(taskDisplayArea.getDisplayId())
+                .getDefaultTaskDisplayArea()) {
             final int activityType =
                     options != null && options.getLaunchActivityType() != ACTIVITY_TYPE_UNDEFINED
                             ? options.getLaunchActivityType() : r.getActivityType();
-            final TaskDisplayArea taskDisplayArea = displayContent.getDefaultTaskDisplayArea();
             return taskDisplayArea.createStack(windowingMode, activityType, true /*onTop*/);
         }
 
         return null;
     }
 
-    ActivityStack getValidLaunchStackOnDisplay(int displayId, @NonNull ActivityRecord r,
-            @Nullable ActivityOptions options,
-            @Nullable LaunchParamsController.LaunchParams launchParams) {
-        return getValidLaunchStackOnDisplay(displayId, r, null /* candidateTask */, options,
-                launchParams);
-    }
-
     // TODO: Can probably be consolidated into getLaunchStack()...
     private boolean isValidLaunchStack(ActivityStack stack, ActivityRecord r, int windowingMode) {
         switch (stack.getActivityType()) {
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index a7b5368..b71ecbb 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -18,10 +18,11 @@
 
 import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.view.Display.INVALID_DISPLAY;
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
@@ -36,6 +37,7 @@
 import android.os.UserHandle;
 import android.util.Slog;
 import android.view.RemoteAnimationAdapter;
+import android.window.WindowContainerToken;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -206,8 +208,20 @@
                 throw new SecurityException(msg);
             }
         }
-        // Check if someone tries to launch an activity on a private display with a different
-        // owner.
+        // Check if the caller is allowed to launch on the specified display area.
+        final WindowContainerToken daToken = options.getLaunchTaskDisplayArea();
+        final TaskDisplayArea taskDisplayArea = daToken != null
+                ? (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()) : null;
+        if (aInfo != null && taskDisplayArea != null
+                && !supervisor.isCallerAllowedToLaunchOnTaskDisplayArea(callingPid, callingUid,
+                taskDisplayArea, aInfo)) {
+            final String msg = "Permission Denial: starting " + getIntentString(intent)
+                    + " from " + callerApp + " (pid=" + callingPid
+                    + ", uid=" + callingUid + ") with launchTaskDisplayArea=" + taskDisplayArea;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+        // Check if the caller is allowed to launch on the specified display.
         final int launchDisplayId = options.getLaunchDisplayId();
         if (aInfo != null && launchDisplayId != INVALID_DISPLAY
                 && !supervisor.isCallerAllowedToLaunchOnDisplay(callingPid, callingUid,
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ea5967a..299b3bf 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -433,7 +433,6 @@
     static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;
     private int mForceHiddenFlags = 0;
 
-
     SurfaceControl.Transaction mMainWindowSizeChangeTransaction;
 
     private final FindRootHelper mFindRootHelper = new FindRootHelper();
@@ -3388,7 +3387,9 @@
         final Intent baseIntent = getBaseIntent();
         // Make a copy of base intent because this is like a snapshot info.
         // Besides, {@link RecentTasks#getRecentTasksImpl} may modify it.
+        final int baseIntentFlags = baseIntent == null ? 0 : baseIntent.getFlags();
         info.baseIntent = baseIntent == null ? new Intent() : baseIntent.cloneFilter();
+        info.baseIntent.setFlags(baseIntentFlags);
         info.baseActivity = mReuseActivitiesReport.base != null
                 ? mReuseActivitiesReport.base.intent.getComponent()
                 : null;
@@ -4261,7 +4262,7 @@
 
     void setActivityWindowingMode(int windowingMode) {
         PooledConsumer c = PooledLambda.obtainConsumer(ActivityRecord::setWindowingMode,
-            PooledLambda.__(ActivityRecord.class), windowingMode);
+                PooledLambda.__(ActivityRecord.class), windowingMode);
         forAllActivities(c);
         c.recycle();
     }
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index da4401a..11c20b6 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -48,6 +48,7 @@
 import android.util.Slog;
 import android.view.Gravity;
 import android.view.View;
+import android.window.WindowContainerToken;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wm.LaunchParamsController.LaunchParams;
@@ -134,13 +135,15 @@
             return RESULT_SKIP;
         }
 
-        // STEP 1: Determine the display to launch the activity/task.
-        final int displayId = getPreferredLaunchDisplay(task, options, source, currentParams);
-        outParams.mPreferredDisplayId = displayId;
-        DisplayContent display = mSupervisor.mRootWindowContainer.getDisplayContent(displayId);
+        // STEP 1: Determine the display area to launch the activity/task.
+        final TaskDisplayArea taskDisplayArea = getPreferredLaunchTaskDisplayArea(task,
+                options, source, currentParams);
+        outParams.mPreferredTaskDisplayArea = taskDisplayArea;
+        // TODO(b/152116619): Update the usages of display to use taskDisplayArea below.
+        final DisplayContent display = taskDisplayArea.mDisplayContent;
         if (DEBUG) {
-            appendLog("display-id=" + outParams.mPreferredDisplayId + " display-windowing-mode="
-                    + display.getWindowingMode());
+            appendLog("task-display-area=" + outParams.mPreferredTaskDisplayArea
+                    + " display-area-windowing-mode=" + taskDisplayArea.getWindowingMode());
         }
 
         if (phase == PHASE_DISPLAY) {
@@ -210,8 +213,8 @@
         // layout and display conditions are not contradictory to their suggestions. It's important
         // to carry over their values because LaunchParamsController doesn't automatically do that.
         if (!currentParams.isEmpty() && !hasInitialBounds
-                && (!currentParams.hasPreferredDisplay()
-                    || displayId == currentParams.mPreferredDisplayId)) {
+                && (currentParams.mPreferredTaskDisplayArea == null
+                    || currentParams.mPreferredTaskDisplayArea == taskDisplayArea)) {
             // Only set windowing mode if display is in freeform. If the display is in fullscreen
             // mode we should only launch a task in fullscreen mode.
             if (currentParams.hasWindowingMode() && display.inFreeformWindowingMode()) {
@@ -270,19 +273,19 @@
                 : display.getWindowingMode();
         if (fullyResolvedCurrentParam) {
             if (resolvedMode == WINDOWING_MODE_FREEFORM) {
-                // Make sure bounds are in the display if it's possibly in a different display.
-                if (currentParams.mPreferredDisplayId != displayId) {
+                // Make sure bounds are in the display if it's possibly in a different display/area.
+                if (currentParams.mPreferredTaskDisplayArea != taskDisplayArea) {
                     adjustBoundsToFitInDisplay(display, outParams.mBounds);
                 }
                 // Even though we want to keep original bounds, we still don't want it to stomp on
                 // an existing task.
                 adjustBoundsToAvoidConflictInDisplay(display, outParams.mBounds);
             }
-        } else if (display.inFreeformWindowingMode()) {
+        } else if (taskDisplayArea.inFreeformWindowingMode()) {
             if (source != null && source.inFreeformWindowingMode()
                     && resolvedMode == WINDOWING_MODE_FREEFORM
                     && outParams.mBounds.isEmpty()
-                    && source.getDisplayId() == display.getDisplayId()) {
+                    && source.getDisplayArea() == taskDisplayArea) {
                 // Set bounds to be not very far from source activity.
                 cascadeBounds(source.getConfiguration().windowConfiguration.getBounds(),
                         display, outParams.mBounds);
@@ -293,54 +296,87 @@
         return RESULT_CONTINUE;
     }
 
-    private int getPreferredLaunchDisplay(@Nullable Task task,
+    private TaskDisplayArea getPreferredLaunchTaskDisplayArea(@Nullable Task task,
             @Nullable ActivityOptions options, ActivityRecord source, LaunchParams currentParams) {
-        if (!mSupervisor.mService.mSupportsMultiDisplay) {
-            return DEFAULT_DISPLAY;
+        TaskDisplayArea taskDisplayArea = null;
+
+        final WindowContainerToken optionLaunchTaskDisplayAreaToken = options != null
+                ? options.getLaunchTaskDisplayArea() : null;
+        if (optionLaunchTaskDisplayAreaToken != null) {
+            taskDisplayArea = (TaskDisplayArea) WindowContainer.fromBinder(
+                    optionLaunchTaskDisplayAreaToken.asBinder());
+            if (DEBUG) appendLog("display-area-from-option=" + taskDisplayArea);
         }
 
-        int displayId = INVALID_DISPLAY;
-        final int optionLaunchId = options != null ? options.getLaunchDisplayId() : INVALID_DISPLAY;
-        if (optionLaunchId != INVALID_DISPLAY) {
-            if (DEBUG) appendLog("display-from-option=" + optionLaunchId);
-            displayId = optionLaunchId;
+        // If task display area is not specified in options - try display id
+        if (taskDisplayArea == null) {
+            final int optionLaunchId =
+                    options != null ? options.getLaunchDisplayId() : INVALID_DISPLAY;
+            if (optionLaunchId != INVALID_DISPLAY) {
+                final DisplayContent dc = mSupervisor.mRootWindowContainer
+                        .getDisplayContent(optionLaunchId);
+                if (dc != null) {
+                    taskDisplayArea = dc.getDefaultTaskDisplayArea();
+                    if (DEBUG) appendLog("display-from-option=" + optionLaunchId);
+                }
+            }
         }
 
-        // If the source activity is a no-display activity, pass on the launch display id from
-        // source activity as currently preferred.
-        if (displayId == INVALID_DISPLAY && source != null && source.noDisplay) {
-            displayId = source.mHandoverLaunchDisplayId;
-            if (DEBUG) appendLog("display-from-no-display-source=" + displayId);
+        // If the source activity is a no-display activity, pass on the launch display area token
+        // from source activity as currently preferred.
+        if (taskDisplayArea == null && source != null
+                && source.noDisplay) {
+            taskDisplayArea = source.mHandoverTaskDisplayArea;
+            if (taskDisplayArea != null) {
+                if (DEBUG) appendLog("display-area-from-no-display-source=" + taskDisplayArea);
+            } else {
+                // Try handover display id
+                final int displayId = source.mHandoverLaunchDisplayId;
+                final DisplayContent dc =
+                        mSupervisor.mRootWindowContainer.getDisplayContent(displayId);
+                if (dc != null) {
+                    taskDisplayArea = dc.getDefaultTaskDisplayArea();
+                    if (DEBUG) appendLog("display-from-no-display-source=" + displayId);
+                }
+            }
         }
 
-        ActivityStack stack =
-                (displayId == INVALID_DISPLAY && task != null) ? task.getStack() : null;
+        ActivityStack stack = (taskDisplayArea == null && task != null)
+                ? task.getStack() : null;
         if (stack != null) {
             if (DEBUG) appendLog("display-from-task=" + stack.getDisplayId());
-            displayId = stack.getDisplayId();
+            taskDisplayArea = stack.getDisplayArea();
         }
 
-        if (displayId == INVALID_DISPLAY && source != null) {
-            final int sourceDisplayId = source.getDisplayId();
-            if (DEBUG) appendLog("display-from-source=" + sourceDisplayId);
-            displayId = sourceDisplayId;
+        if (taskDisplayArea == null && source != null) {
+            final TaskDisplayArea sourceDisplayArea = source.getDisplayArea();
+            if (DEBUG) appendLog("display-area-from-source=" + sourceDisplayArea);
+            taskDisplayArea = sourceDisplayArea;
         }
 
-        if (displayId == INVALID_DISPLAY && options != null) {
+        if (taskDisplayArea == null && options != null) {
             final int callerDisplayId = options.getCallerDisplayId();
-            if (DEBUG) appendLog("display-from-caller=" + callerDisplayId);
-            displayId = callerDisplayId;
+            final DisplayContent dc =
+                    mSupervisor.mRootWindowContainer.getDisplayContent(callerDisplayId);
+            if (dc != null) {
+                taskDisplayArea = dc.getDefaultTaskDisplayArea();
+                if (DEBUG) appendLog("display-from-caller=" + callerDisplayId);
+            }
         }
 
-        if (displayId != INVALID_DISPLAY
-                && mSupervisor.mRootWindowContainer.getDisplayContent(displayId) == null) {
-            displayId = currentParams.mPreferredDisplayId;
+        if (taskDisplayArea == null) {
+            taskDisplayArea = currentParams.mPreferredTaskDisplayArea;
         }
-        displayId = (displayId == INVALID_DISPLAY) ? currentParams.mPreferredDisplayId : displayId;
 
-        return (displayId != INVALID_DISPLAY
-                && mSupervisor.mRootWindowContainer.getDisplayContent(displayId) != null)
-                ? displayId : DEFAULT_DISPLAY;
+        // Fallback to default display if the device didn't declare support for multi-display
+        if (taskDisplayArea != null && !mSupervisor.mService.mSupportsMultiDisplay
+                && taskDisplayArea.getDisplayId() != DEFAULT_DISPLAY) {
+            taskDisplayArea = mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea();
+        }
+
+        return (taskDisplayArea != null)
+                ? taskDisplayArea
+                : mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea();
     }
 
     private boolean canInheritWindowingModeFromSource(@NonNull DisplayContent display,
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 8b27667..160978d 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -186,12 +186,7 @@
         return syncId;
     }
 
-    private int sanitizeAndApplyChange(WindowContainer container,
-            WindowContainerTransaction.Change change) {
-        if (!(container instanceof Task)) {
-            throw new RuntimeException("Invalid token in task transaction");
-        }
-        final Task task = (Task) container;
+    private int applyChanges(WindowContainer container, WindowContainerTransaction.Change change) {
         // The "client"-facing API should prevent bad changes; however, just in case, sanitize
         // masks here.
         final int configMask = change.getConfigSetMask() & CONTROLLABLE_CONFIGS;
@@ -211,11 +206,38 @@
                 effects |= TRANSACT_EFFECTS_LIFECYCLE;
             }
         }
-        if ((change.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) {
-            if (task.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, change.getHidden())) {
-                effects |= TRANSACT_EFFECTS_LIFECYCLE;
+
+        final int windowingMode = change.getWindowingMode();
+        if (windowingMode > -1) {
+            container.setWindowingMode(windowingMode);
+        }
+        return effects;
+    }
+
+    private int applyTaskChanges(Task tr, WindowContainerTransaction.Change c) {
+        int effects = 0;
+        final SurfaceControl.Transaction t = c.getBoundsChangeTransaction();
+
+        if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) {
+            if (tr.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, c.getHidden())) {
+                effects = TRANSACT_EFFECTS_LIFECYCLE;
             }
         }
+
+        final int childWindowingMode = c.getActivityWindowingMode();
+        if (childWindowingMode > -1) {
+            tr.setActivityWindowingMode(childWindowingMode);
+        }
+
+        if (t != null) {
+            tr.setMainWindowSizeChangeTransaction(t);
+        }
+
+        Rect enterPipBounds = c.getEnterPipBounds();
+        if (enterPipBounds != null) {
+            mService.mStackSupervisor.updatePictureInPictureMode(tr, enterPipBounds, true);
+        }
+
         return effects;
     }
 
@@ -283,30 +305,20 @@
         return TRANSACT_EFFECTS_LIFECYCLE;
     }
 
+    private void sanitizeWindowContainer(WindowContainer wc) {
+        if (!(wc instanceof Task) && !(wc instanceof DisplayArea)) {
+            throw new RuntimeException("Invalid token in task or displayArea transaction");
+        }
+    }
+
     private int applyWindowContainerChange(WindowContainer wc,
             WindowContainerTransaction.Change c) {
-        int effects = sanitizeAndApplyChange(wc, c);
+        sanitizeWindowContainer(wc);
 
-        final Task tr = wc.asTask();
+        int effects = applyChanges(wc, c);
 
-        final SurfaceControl.Transaction t = c.getBoundsChangeTransaction();
-        if (t != null) {
-            tr.setMainWindowSizeChangeTransaction(t);
-        }
-
-        Rect enterPipBounds = c.getEnterPipBounds();
-        if (enterPipBounds != null) {
-            mService.mStackSupervisor.updatePictureInPictureMode(tr,
-                    enterPipBounds, true);
-        }
-
-        final int windowingMode = c.getWindowingMode();
-        if (windowingMode > -1) {
-            tr.setWindowingMode(windowingMode);
-        }
-        final int childWindowingMode = c.getActivityWindowingMode();
-        if (childWindowingMode > -1) {
-            tr.setActivityWindowingMode(childWindowingMode);
+        if (wc instanceof Task) {
+            effects |= applyTaskChanges(wc.asTask(), c);
         }
 
         return effects;
diff --git a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
new file mode 100644
index 0000000..8070bd1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.om."
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
index fcbd507..965304f 100644
--- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
@@ -32,6 +32,7 @@
 import android.media.tv.tunerresourcemanager.TunerDescramblerRequest;
 import android.media.tv.tunerresourcemanager.TunerFrontendInfo;
 import android.media.tv.tunerresourcemanager.TunerFrontendRequest;
+import android.media.tv.tunerresourcemanager.TunerLnbRequest;
 import android.media.tv.tunerresourcemanager.TunerResourceManager;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
@@ -49,6 +50,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Map;
 
 /**
@@ -96,13 +98,6 @@
             }
         };
 
-    private static int getResourceIdFromHandle(int resourceHandle) {
-        if (resourceHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
-            return resourceHandle;
-        }
-        return (resourceHandle & 0x00ff0000) >> 16;
-    }
-
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -247,7 +242,7 @@
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        assertThat(getResourceIdFromHandle(frontendHandle[0]))
+        assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
                 .isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE);
     }
 
@@ -275,7 +270,7 @@
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        assertThat(getResourceIdFromHandle(frontendHandle[0]))
+        assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
                 .isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE);
     }
 
@@ -307,7 +302,8 @@
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        assertThat(getResourceIdFromHandle(frontendHandle[0])).isEqualTo(0);
+        assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
+                .isEqualTo(0);
     }
 
     @Test
@@ -344,7 +340,8 @@
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        assertThat(getResourceIdFromHandle(frontendHandle[0])).isEqualTo(infos[0].getId());
+        assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
+                .isEqualTo(infos[0].getId());
 
         request =
                 new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
@@ -354,7 +351,8 @@
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        assertThat(getResourceIdFromHandle(frontendHandle[0])).isEqualTo(infos[1].getId());
+        assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
+                .isEqualTo(infos[1].getId());
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
                 .isInUse()).isTrue();
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[2].getId())
@@ -464,7 +462,11 @@
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        assertThat(getResourceIdFromHandle(frontendHandle[0])).isEqualTo(infos[0].getId());
+        assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
+                .isEqualTo(infos[0].getId());
+        assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
+                .getInUseFrontendIds()).isEqualTo(
+                        new HashSet<Integer>(Arrays.asList(infos[0].getId(), infos[1].getId())));
 
         request =
                 new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS);
@@ -474,7 +476,8 @@
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        assertThat(getResourceIdFromHandle(frontendHandle[0])).isEqualTo(infos[1].getId());
+        assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
+                .isEqualTo(infos[1].getId());
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
                 .isInUse()).isTrue();
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
@@ -487,6 +490,143 @@
     }
 
     @Test
+    public void releaseFrontendTest_UnderTheSameExclusiveGroup() {
+        // Register clients
+        ResourceClientProfile[] profiles = new ResourceClientProfile[1];
+        profiles[0] = new ResourceClientProfile("0" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+        int[] clientId = new int[1];
+        TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
+        mTunerResourceManagerService.registerClientProfileInternal(profiles[0], listener, clientId);
+        assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+
+        // Init frontend resources.
+        TunerFrontendInfo[] infos = new TunerFrontendInfo[2];
+        infos[0] =
+                new TunerFrontendInfo(0 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/);
+        infos[1] =
+                new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBS, 1 /*exclusiveGroupId*/);
+        mTunerResourceManagerService.setFrontendInfoListInternal(infos);
+
+        TunerFrontendRequest request =
+                new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
+        int[] frontendHandle = new int[1];
+        try {
+            assertThat(mTunerResourceManagerService
+                    .requestFrontendInternal(request, frontendHandle)).isTrue();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        int frontendId = mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]);
+        assertThat(frontendId).isEqualTo(infos[0].getId());
+        assertThat(mTunerResourceManagerService
+                .getFrontendResource(infos[1].getId()).isInUse()).isTrue();
+
+        // Release frontend
+        mTunerResourceManagerService.releaseFrontendInternal(mTunerResourceManagerService
+                .getFrontendResource(frontendId));
+        assertThat(mTunerResourceManagerService
+                .getFrontendResource(frontendId).isInUse()).isFalse();
+        assertThat(mTunerResourceManagerService
+                .getFrontendResource(infos[1].getId()).isInUse()).isFalse();
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(clientId[0]).getInUseFrontendIds().size()).isEqualTo(0);
+    }
+
+    @Test
+    public void requestLnbTest_NoLnbAvailable_RequestWithHigherPriority() {
+        // Register clients
+        ResourceClientProfile[] profiles = new ResourceClientProfile[2];
+        profiles[0] = new ResourceClientProfile("0" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+        profiles[1] = new ResourceClientProfile("1" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+        int[] clientPriorities = {100, 500};
+        int[] clientId0 = new int[1];
+        int[] clientId1 = new int[1];
+        TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
+        mTunerResourceManagerService.registerClientProfileInternal(
+                profiles[0], listener, clientId0);
+        assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        mTunerResourceManagerService.getClientProfile(clientId0[0])
+                .setPriority(clientPriorities[0]);
+        mTunerResourceManagerService.registerClientProfileInternal(
+                profiles[1], new TestResourcesReclaimListener(), clientId1);
+        assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        mTunerResourceManagerService.getClientProfile(clientId1[0])
+                .setPriority(clientPriorities[1]);
+
+        // Init lnb resources.
+        int[] lnbIds = {1};
+        mTunerResourceManagerService.setLnbInfoListInternal(lnbIds);
+
+        TunerLnbRequest request = new TunerLnbRequest(clientId0[0]);
+        int[] lnbHandle = new int[1];
+        try {
+            assertThat(mTunerResourceManagerService
+                    .requestLnbInternal(request, lnbHandle)).isTrue();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        assertThat(mTunerResourceManagerService.getResourceIdFromHandle(lnbHandle[0]))
+                .isEqualTo(lnbIds[0]);
+        assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
+                .getInUseLnbIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(lnbIds[0])));
+
+        request = new TunerLnbRequest(clientId1[0]);
+        try {
+            assertThat(mTunerResourceManagerService
+                    .requestLnbInternal(request, lnbHandle)).isTrue();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        assertThat(mTunerResourceManagerService.getResourceIdFromHandle(lnbHandle[0]))
+                .isEqualTo(lnbIds[0]);
+        assertThat(mTunerResourceManagerService.getLnbResource(lnbIds[0])
+                .isInUse()).isTrue();
+        assertThat(mTunerResourceManagerService.getLnbResource(lnbIds[0])
+                .getOwnerClientId()).isEqualTo(clientId1[0]);
+        assertThat(listener.isRelaimed()).isTrue();
+        assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
+                .getInUseLnbIds().size()).isEqualTo(0);
+    }
+
+    @Test
+    public void releaseLnbTest() {
+        // Register clients
+        ResourceClientProfile[] profiles = new ResourceClientProfile[1];
+        profiles[0] = new ResourceClientProfile("0" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+        int[] clientId = new int[1];
+        TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
+        mTunerResourceManagerService.registerClientProfileInternal(profiles[0], listener, clientId);
+        assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+
+        // Init lnb resources.
+        int[] lnbIds = {0};
+        mTunerResourceManagerService.setLnbInfoListInternal(lnbIds);
+
+        TunerLnbRequest request = new TunerLnbRequest(clientId[0]);
+        int[] lnbHandle = new int[1];
+        try {
+            assertThat(mTunerResourceManagerService
+                    .requestLnbInternal(request, lnbHandle)).isTrue();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        int lnbId = mTunerResourceManagerService.getResourceIdFromHandle(lnbHandle[0]);
+        assertThat(lnbId).isEqualTo(lnbIds[0]);
+
+        // Release lnb
+        mTunerResourceManagerService.releaseLnbInternal(mTunerResourceManagerService
+                .getLnbResource(lnbId));
+        assertThat(mTunerResourceManagerService
+                .getLnbResource(lnbId).isInUse()).isFalse();
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(clientId[0]).getInUseLnbIds().size()).isEqualTo(0);
+    }
+
+    @Test
     public void unregisterClientTest_usingFrontend() {
         // Register client
         ResourceClientProfile profile = new ResourceClientProfile("0" /*sessionId*/,
@@ -513,7 +653,8 @@
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        assertThat(getResourceIdFromHandle(frontendHandle[0])).isEqualTo(infos[0].getId());
+        assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
+                .isEqualTo(infos[0].getId());
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
                 .isInUse()).isTrue();
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
@@ -543,7 +684,8 @@
         TunerDemuxRequest request = new TunerDemuxRequest(clientId[0]);
         assertThat(mTunerResourceManagerService.requestDemuxInternal(request, demuxHandle))
                 .isTrue();
-        assertThat(getResourceIdFromHandle(demuxHandle[0])).isEqualTo(0);
+        assertThat(mTunerResourceManagerService.getResourceIdFromHandle(demuxHandle[0]))
+                .isEqualTo(0);
     }
 
     @Test
@@ -560,6 +702,6 @@
         TunerDescramblerRequest request = new TunerDescramblerRequest(clientId[0]);
         assertThat(mTunerResourceManagerService.requestDescramblerInternal(request, desHandle))
                 .isTrue();
-        assertThat(getResourceIdFromHandle(desHandle[0])).isEqualTo(0);
+        assertThat(mTunerResourceManagerService.getResourceIdFromHandle(desHandle[0])).isEqualTo(0);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 97734ff..a84a0a2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -426,7 +426,7 @@
 
         // Start activity and delivered new intent.
         starter.getIntent().setComponent(splitSecondReusableActivity.mActivityComponent);
-        doReturn(splitSecondReusableActivity).when(mRootWindowContainer).findTask(any(), anyInt());
+        doReturn(splitSecondReusableActivity).when(mRootWindowContainer).findTask(any(), any());
         final int result = starter.setReason("testSplitScreenDeliverToTop").execute();
 
         // Ensure result is delivering intent to top.
@@ -462,7 +462,7 @@
 
         // Start activity and delivered new intent.
         starter.getIntent().setComponent(splitSecondReusableActivity.mActivityComponent);
-        doReturn(splitSecondReusableActivity).when(mRootWindowContainer).findTask(any(), anyInt());
+        doReturn(splitSecondReusableActivity).when(mRootWindowContainer).findTask(any(), any());
         final int result = starter.setReason("testSplitScreenMoveToFront").execute();
 
         // Ensure result is moving task to front.
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
index 8a9504d..61de7d8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
@@ -19,7 +19,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -112,7 +111,7 @@
         final ActivityRecord activity = new ActivityBuilder(mService).setComponent(name)
                 .setUid(userId).build();
         final LaunchParams expected = new LaunchParams();
-        expected.mPreferredDisplayId = 3;
+        expected.mPreferredTaskDisplayArea = mock(TaskDisplayArea.class);
         expected.mWindowingMode = WINDOWING_MODE_PINNED;
         expected.mBounds.set(200, 300, 400, 500);
 
@@ -183,7 +182,7 @@
         final LaunchParams params = new LaunchParams();
         params.mWindowingMode = WINDOWING_MODE_FREEFORM;
         params.mBounds.set(0, 0, 30, 20);
-        params.mPreferredDisplayId = 3;
+        params.mPreferredTaskDisplayArea = mock(TaskDisplayArea.class);
 
         final InstrumentedPositioner positioner2 = new InstrumentedPositioner(RESULT_CONTINUE,
                 params);
@@ -228,8 +227,8 @@
      */
     @Test
     public void testVrPreferredDisplay() {
-        final int vr2dDisplayId = 1;
-        mService.mVr2dDisplayId = vr2dDisplayId;
+        final TestDisplayContent vrDisplay = createNewDisplayContent();
+        mService.mVr2dDisplayId = vrDisplay.mDisplayId;
 
         final LaunchParams result = new LaunchParams();
         final ActivityRecord vrActivity = new ActivityBuilder(mService).build();
@@ -238,16 +237,17 @@
         // VR activities should always land on default display.
         mController.calculate(null /*task*/, null /*layout*/, vrActivity /*activity*/,
                 null /*source*/, null /*options*/, PHASE_BOUNDS, result);
-        assertEquals(DEFAULT_DISPLAY, result.mPreferredDisplayId);
+        assertEquals(mRootWindowContainer.getDefaultTaskDisplayArea(),
+                result.mPreferredTaskDisplayArea);
 
         // Otherwise, always lands on VR 2D display.
         final ActivityRecord vr2dActivity = new ActivityBuilder(mService).build();
         mController.calculate(null /*task*/, null /*layout*/, vr2dActivity /*activity*/,
                 null /*source*/, null /*options*/, PHASE_BOUNDS, result);
-        assertEquals(vr2dDisplayId, result.mPreferredDisplayId);
+        assertEquals(vrDisplay.getDefaultTaskDisplayArea(), result.mPreferredTaskDisplayArea);
         mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/,
                 null /*options*/, PHASE_BOUNDS, result);
-        assertEquals(vr2dDisplayId, result.mPreferredDisplayId);
+        assertEquals(vrDisplay.getDefaultTaskDisplayArea(), result.mPreferredTaskDisplayArea);
 
         mService.mVr2dDisplayId = INVALID_DISPLAY;
     }
@@ -282,9 +282,7 @@
         final LaunchParams params = new LaunchParams();
         final TestDisplayContent display = createNewDisplayContent();
         final TaskDisplayArea preferredTaskDisplayArea = display.getDefaultTaskDisplayArea();
-        // TODO(b/152116619): Enable after complete switch to WindowContainerToken
-        //params.mPreferredWindowContainerToken = preferredTaskDisplayAreaToken;
-        params.mPreferredDisplayId = display.mDisplayId;
+        params.mPreferredTaskDisplayArea = preferredTaskDisplayArea;
         final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params);
         final Task task = new TaskBuilder(mService.mStackSupervisor).build();
 
@@ -433,7 +431,7 @@
         void saveTask(Task task, DisplayContent display) {
             final int userId = task.mUserId;
             final ComponentName realActivity = task.realActivity;
-            mTmpParams.mPreferredDisplayId = task.getDisplayId();
+            mTmpParams.mPreferredTaskDisplayArea = task.getDisplayArea();
             mTmpParams.mWindowingMode = task.getWindowingMode();
             if (task.mLastNonFullscreenBounds != null) {
                 mTmpParams.mBounds.set(task.mLastNonFullscreenBounds);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
index 6a71a7d..9bf86d2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
@@ -19,7 +19,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -27,6 +26,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Matchers.any;
@@ -163,7 +163,7 @@
 
         mTarget.getLaunchParams(mTestTask, null, mResult);
 
-        assertEquals(mTestDisplay.mDisplayId, mResult.mPreferredDisplayId);
+        assertEquals(mTestDisplay.getDefaultTaskDisplayArea(), mResult.mPreferredTaskDisplayArea);
         assertEquals(TEST_WINDOWING_MODE, mResult.mWindowingMode);
         assertEquals(TEST_BOUNDS, mResult.mBounds);
     }
@@ -177,7 +177,7 @@
 
         mTarget.getLaunchParams(null, activity, mResult);
 
-        assertEquals(mTestDisplay.mDisplayId, mResult.mPreferredDisplayId);
+        assertEquals(mTestDisplay.getDefaultTaskDisplayArea(), mResult.mPreferredTaskDisplayArea);
         assertEquals(TEST_WINDOWING_MODE, mResult.mWindowingMode);
         assertEquals(TEST_BOUNDS, mResult.mBounds);
     }
@@ -190,7 +190,7 @@
 
         mTarget.getLaunchParams(mTestTask, null, mResult);
 
-        assertEquals(INVALID_DISPLAY, mResult.mPreferredDisplayId);
+        assertNull(mResult.mPreferredTaskDisplayArea);
         assertEquals(TEST_WINDOWING_MODE, mResult.mWindowingMode);
         assertEquals(TEST_BOUNDS, mResult.mBounds);
     }
@@ -223,7 +223,7 @@
         mTaskWithDifferentComponent.mWindowLayoutAffinity = TEST_WINDOW_LAYOUT_AFFINITY;
         mTarget.getLaunchParams(mTaskWithDifferentComponent, null, mResult);
 
-        assertEquals(mTestDisplay.mDisplayId, mResult.mPreferredDisplayId);
+        assertEquals(mTestDisplay.getDefaultTaskDisplayArea(), mResult.mPreferredTaskDisplayArea);
         assertEquals(TEST_WINDOWING_MODE, mResult.mWindowingMode);
         assertEquals(TEST_BOUNDS, mResult.mBounds);
     }
@@ -241,7 +241,7 @@
 
         mTarget.getLaunchParams(mTaskWithDifferentComponent, null, mResult);
 
-        assertEquals(mTestDisplay.mDisplayId, mResult.mPreferredDisplayId);
+        assertEquals(mTestDisplay.getDefaultTaskDisplayArea(), mResult.mPreferredTaskDisplayArea);
         assertEquals(TEST_WINDOWING_MODE, mResult.mWindowingMode);
         assertEquals(TEST_BOUNDS, mResult.mBounds);
     }
@@ -282,7 +282,7 @@
 
         target.getLaunchParams(mTestTask, null, mResult);
 
-        assertEquals(mTestDisplay.mDisplayId, mResult.mPreferredDisplayId);
+        assertEquals(mTestDisplay.getDefaultTaskDisplayArea(), mResult.mPreferredTaskDisplayArea);
         assertEquals(TEST_WINDOWING_MODE, mResult.mWindowingMode);
         assertEquals(TEST_BOUNDS, mResult.mBounds);
     }
@@ -301,7 +301,7 @@
         mTaskWithDifferentComponent.mWindowLayoutAffinity = TEST_WINDOW_LAYOUT_AFFINITY;
         target.getLaunchParams(mTaskWithDifferentComponent, null, mResult);
 
-        assertEquals(mTestDisplay.mDisplayId, mResult.mPreferredDisplayId);
+        assertEquals(mTestDisplay.getDefaultTaskDisplayArea(), mResult.mPreferredTaskDisplayArea);
         assertEquals(TEST_WINDOWING_MODE, mResult.mWindowingMode);
         assertEquals(TEST_BOUNDS, mResult.mBounds);
     }
@@ -328,7 +328,7 @@
 
         target.getLaunchParams(mTaskWithDifferentComponent, null, mResult);
 
-        assertEquals(mTestDisplay.mDisplayId, mResult.mPreferredDisplayId);
+        assertEquals(mTestDisplay.getDefaultTaskDisplayArea(), mResult.mPreferredTaskDisplayArea);
         assertEquals(TEST_WINDOWING_MODE, mResult.mWindowingMode);
         assertEquals(TEST_BOUNDS, mResult.mBounds);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index 3c90515..5dba0045 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -537,8 +537,8 @@
 
         doReturn(true).when(mRootWindowContainer)
                 .ensureVisibilityAndConfig(any(), anyInt(), anyBoolean(), anyBoolean());
-        doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplay(
-                any(), anyInt(), anyBoolean());
+        doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(),
+                anyBoolean());
 
         mRootWindowContainer.startHomeOnAllDisplays(0, "testStartHome");
 
@@ -578,17 +578,19 @@
 
         // Can not start home if we don't want to start home while home is being instrumented.
         doReturn(true).when(app).isInstrumenting();
-        assertFalse(mRootWindowContainer.canStartHomeOnDisplay(info, DEFAULT_DISPLAY,
+        final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer
+                .getDefaultTaskDisplayArea();
+        assertFalse(mRootWindowContainer.canStartHomeOnDisplayArea(info, defaultTaskDisplayArea,
                 false /* allowInstrumenting*/));
 
         // Can start home for other cases.
-        assertTrue(mRootWindowContainer.canStartHomeOnDisplay(info, DEFAULT_DISPLAY,
+        assertTrue(mRootWindowContainer.canStartHomeOnDisplayArea(info, defaultTaskDisplayArea,
                 true /* allowInstrumenting*/));
 
         doReturn(false).when(app).isInstrumenting();
-        assertTrue(mRootWindowContainer.canStartHomeOnDisplay(info, DEFAULT_DISPLAY,
+        assertTrue(mRootWindowContainer.canStartHomeOnDisplayArea(info, defaultTaskDisplayArea,
                 false /* allowInstrumenting*/));
-        assertTrue(mRootWindowContainer.canStartHomeOnDisplay(info, DEFAULT_DISPLAY,
+        assertTrue(mRootWindowContainer.canStartHomeOnDisplayArea(info, defaultTaskDisplayArea,
                 true /* allowInstrumenting*/));
     }
 
@@ -694,8 +696,8 @@
         resolutions.add(resolveInfo);
         doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(),
                 refEq(secondaryHomeIntent));
-        doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplay(
-                any(), anyInt(), anyBoolean());
+        doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(),
+                anyBoolean());
 
         // Run the test.
         final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer
@@ -747,8 +749,8 @@
         resolutions.add(infoFake2);
         doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(), any());
 
-        doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplay(
-                any(), anyInt(), anyBoolean());
+        doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(),
+                anyBoolean());
 
         // Run the test.
         final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer
@@ -781,8 +783,8 @@
         resolutions.add(infoFake2);
         doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(), any());
 
-        doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplay(
-                any(), anyInt(), anyBoolean());
+        doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(),
+                anyBoolean());
 
         // Use the first one of matched activities in the same package as selected primary home.
         final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer
@@ -836,8 +838,9 @@
                 .setTask(task).build();
 
         // Make sure the root task is valid and can be reused on default display.
-        final ActivityStack stack = mRootWindowContainer.getValidLaunchStackOnDisplay(
-                DEFAULT_DISPLAY, activity, task, null, null);
+        final ActivityStack stack = mRootWindowContainer.getValidLaunchStackInTaskDisplayArea(
+                mRootWindowContainer.getDefaultTaskDisplayArea(), activity, task, null,
+                null);
         assertEquals(task, stack);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index 1a38ff2..a69231b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -30,6 +30,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
@@ -44,7 +45,6 @@
 import android.graphics.Rect;
 import android.os.Build;
 import android.platform.test.annotations.Presubmit;
-import android.view.Display;
 import android.view.Gravity;
 
 import androidx.test.filters.SmallTest;
@@ -106,53 +106,45 @@
     // Display ID Related Tests
     // =============================
     @Test
-    public void testDefaultToPrimaryDisplay() {
+    public void testDefaultToPrimaryDisplayArea() {
         createNewDisplayContent(WINDOWING_MODE_FREEFORM);
 
         assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
                 mActivity, /* source */ null, /* options */ null, mCurrent, mResult));
 
-        assertEquals(DEFAULT_DISPLAY, mResult.mPreferredDisplayId);
+        assertEquals(mRootWindowContainer.getDefaultTaskDisplayArea(),
+                mResult.mPreferredTaskDisplayArea);
     }
 
     @Test
-    public void testUsesDefaultDisplayIfPreviousDisplayNotExists() {
-        mCurrent.mPreferredDisplayId = 19;
-
-        assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
-                mActivity, /* source */ null, /* options */ null, mCurrent, mResult));
-
-        assertEquals(DEFAULT_DISPLAY, mResult.mPreferredDisplayId);
-    }
-
-    @Test
-    public void testUsesPreviousDisplayIdIfSet() {
+    public void testUsesPreviousDisplayAreaIfSet() {
         createNewDisplayContent(WINDOWING_MODE_FREEFORM);
         final TestDisplayContent display = createNewDisplayContent(WINDOWING_MODE_FULLSCREEN);
 
-        mCurrent.mPreferredDisplayId = display.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = display.getDefaultTaskDisplayArea();
 
         assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
                 mActivity, /* source */ null, /* options */ null, mCurrent, mResult));
 
-        assertEquals(display.mDisplayId, mResult.mPreferredDisplayId);
+        assertEquals(display.getDefaultTaskDisplayArea(), mResult.mPreferredTaskDisplayArea);
     }
 
     @Test
-    public void testUsesSourcesDisplayIdIfSet() {
+    public void testUsesSourcesDisplayAreaIfSet() {
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
         final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FULLSCREEN);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
 
         ActivityRecord source = createSourceActivity(fullscreenDisplay);
 
         assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
                 mActivity, source, /* options */ null, mCurrent, mResult));
 
-        assertEquals(fullscreenDisplay.mDisplayId, mResult.mPreferredDisplayId);
+        assertEquals(fullscreenDisplay.getDefaultTaskDisplayArea(),
+                mResult.mPreferredTaskDisplayArea);
     }
 
     @Test
@@ -162,7 +154,7 @@
         final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FULLSCREEN);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
         ActivityRecord source = createSourceActivity(freeformDisplay);
 
         ActivityOptions options = ActivityOptions.makeBasic();
@@ -171,28 +163,51 @@
         assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
                 mActivity, source, options, mCurrent, mResult));
 
-        assertEquals(fullscreenDisplay.mDisplayId, mResult.mPreferredDisplayId);
+        assertEquals(fullscreenDisplay.getDefaultTaskDisplayArea(),
+                mResult.mPreferredTaskDisplayArea);
     }
 
     @Test
-    public void testUsesTasksDisplayIdPriorToSourceIfSet() {
+    public void testUsesOptionsDisplayAreaTokenIfSet() {
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
         final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FULLSCREEN);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
+        ActivityRecord source = createSourceActivity(freeformDisplay);
+
+        ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchTaskDisplayArea(fullscreenDisplay.getDefaultTaskDisplayArea()
+                .mRemoteToken.toWindowContainerToken());
+
+        assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+                mActivity, source, options, mCurrent, mResult));
+
+        assertEquals(fullscreenDisplay.getDefaultTaskDisplayArea(),
+                mResult.mPreferredTaskDisplayArea);
+    }
+
+    @Test
+    public void testUsesTasksDisplayAreaIdPriorToSourceIfSet() {
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
+                WINDOWING_MODE_FREEFORM);
+        final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
+                WINDOWING_MODE_FULLSCREEN);
+
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
         ActivityRecord reusableActivity = createSourceActivity(fullscreenDisplay);
         ActivityRecord source = createSourceActivity(freeformDisplay);
 
         assertEquals(RESULT_CONTINUE, mTarget.onCalculate(reusableActivity.getTask(),
                 /* layout */ null, mActivity, source, /* options */ null, mCurrent, mResult));
 
-        assertEquals(fullscreenDisplay.mDisplayId, mResult.mPreferredDisplayId);
+        assertEquals(fullscreenDisplay.getDefaultTaskDisplayArea(),
+                mResult.mPreferredTaskDisplayArea);
     }
 
     @Test
-    public void testUsesTaskDisplayIdIfSet() {
+    public void testUsesTaskDisplayAreaIdIfSet() {
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
         ActivityRecord source = createSourceActivity(freeformDisplay);
@@ -200,7 +215,8 @@
         assertEquals(RESULT_CONTINUE, mTarget.onCalculate(source.getTask(), null /* layout */,
                 null /* activity */, null /* source */, null /* options */, mCurrent, mResult));
 
-        assertEquals(freeformDisplay.mDisplayId, mResult.mPreferredDisplayId);
+        assertEquals(freeformDisplay.getDefaultTaskDisplayArea(),
+                mResult.mPreferredTaskDisplayArea);
     }
 
     @Test
@@ -210,7 +226,7 @@
         final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FULLSCREEN);
 
-        mCurrent.mPreferredDisplayId = fullscreenDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = fullscreenDisplay.getDefaultTaskDisplayArea();
         ActivityRecord reusableActivity = createSourceActivity(fullscreenDisplay);
         ActivityRecord source = createSourceActivity(freeformDisplay);
         source.mHandoverLaunchDisplayId = freeformDisplay.mDisplayId;
@@ -219,7 +235,28 @@
         assertEquals(RESULT_CONTINUE, mTarget.onCalculate(reusableActivity.getTask(),
                 null /* layout */, mActivity, source, null /* options */, mCurrent, mResult));
 
-        assertEquals(freeformDisplay.mDisplayId, mResult.mPreferredDisplayId);
+        assertEquals(freeformDisplay.getDefaultTaskDisplayArea(),
+                mResult.mPreferredTaskDisplayArea);
+    }
+
+    @Test
+    public void testUsesNoDisplaySourceHandoverDisplayAreaIdIfSet() {
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
+                WINDOWING_MODE_FREEFORM);
+        final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
+                WINDOWING_MODE_FULLSCREEN);
+
+        mCurrent.mPreferredTaskDisplayArea = fullscreenDisplay.getDefaultTaskDisplayArea();
+        ActivityRecord reusableActivity = createSourceActivity(fullscreenDisplay);
+        ActivityRecord source = createSourceActivity(freeformDisplay);
+        source.mHandoverTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
+        source.noDisplay = true;
+
+        assertEquals(RESULT_CONTINUE, mTarget.onCalculate(reusableActivity.getTask(),
+                null /* layout */, mActivity, source, null /* options */, mCurrent, mResult));
+
+        assertEquals(freeformDisplay.getDefaultTaskDisplayArea(),
+                mResult.mPreferredTaskDisplayArea);
     }
 
     // =====================================
@@ -233,7 +270,7 @@
         final ActivityOptions options = ActivityOptions.makeBasic();
         options.setLaunchBounds(new Rect(0, 0, 100, 100));
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
 
         assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
                 mActivity, /* source */ null, options, mCurrent, mResult));
@@ -247,7 +284,7 @@
         final ActivityOptions options = ActivityOptions.makeBasic();
         options.setLaunchBounds(new Rect(0, 0, 100, 100));
 
-        mCurrent.mPreferredDisplayId = DEFAULT_DISPLAY;
+        mCurrent.mPreferredTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
 
         assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
                 mActivity, /* source */ null, options, mCurrent, mResult));
@@ -278,7 +315,7 @@
         final ActivityOptions options = ActivityOptions.makeBasic();
         options.setLaunchWindowingMode(WINDOWING_MODE_PINNED);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
 
         assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
                 mActivity, /* source */ null, options, mCurrent, mResult));
@@ -296,7 +333,7 @@
         options.setLaunchWindowingMode(WINDOWING_MODE_PINNED);
         options.setLaunchBounds(new Rect(0, 0, 100, 100));
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
 
         assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
                 mActivity, /* source */ null, options, mCurrent, mResult));
@@ -310,7 +347,7 @@
         final ActivityOptions options = ActivityOptions.makeBasic();
         options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
 
-        mCurrent.mPreferredDisplayId = DEFAULT_DISPLAY;
+        mCurrent.mPreferredTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
 
         assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
                 mActivity, /* source */ null, options, mCurrent, mResult));
@@ -324,7 +361,7 @@
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
 
         final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
                 .setWidth(120).setHeight(80).build();
@@ -341,7 +378,7 @@
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
 
         final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
                 .setGravity(Gravity.LEFT).build();
@@ -358,7 +395,7 @@
         final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
                 .setWidth(120).setHeight(80).build();
 
-        mCurrent.mPreferredDisplayId = DEFAULT_DISPLAY;
+        mCurrent.mPreferredTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
 
         assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, layout, mActivity,
                 /* source */ null, /* options */ null, mCurrent, mResult));
@@ -369,7 +406,7 @@
 
     @Test
     public void testLaunchesFullscreenOnFullscreenDisplayWithFreeformHistory() {
-        mCurrent.mPreferredDisplayId = Display.INVALID_DISPLAY;
+        mCurrent.mPreferredTaskDisplayArea = null;
         mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
         mCurrent.mBounds.set(0, 0, 200, 100);
 
@@ -385,7 +422,7 @@
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
         mCurrent.mWindowingMode = WINDOWING_MODE_FULLSCREEN;
 
         assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
@@ -400,7 +437,7 @@
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
         mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
         mCurrent.mBounds.set(0, 0, 200, 100);
 
@@ -421,7 +458,7 @@
         options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
         options.setLaunchBounds(new Rect(0, 0, 200, 100));
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
         mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
         mCurrent.mBounds.set(0, 0, 200, 100);
 
@@ -446,7 +483,7 @@
         options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
         options.setLaunchBounds(expectedLaunchBounds);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
         mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
         mCurrent.mBounds.set(expectedLaunchBounds);
 
@@ -467,7 +504,7 @@
         options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
         options.setLaunchBounds(new Rect(0, 0, 200, 100));
 
-        mCurrent.mPreferredDisplayId = DEFAULT_DISPLAY;
+        mCurrent.mPreferredTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
         mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
         mCurrent.mBounds.set(0, 0, 200, 100);
 
@@ -568,7 +605,7 @@
         final Rect expected = new Rect(0, 0, 100, 100);
         options.setLaunchBounds(expected);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
 
         assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
                 mActivity, /* source */ null, options, mCurrent, mResult));
@@ -598,7 +635,7 @@
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
 
         final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
                 .setGravity(Gravity.LEFT).build();
@@ -614,7 +651,7 @@
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
 
         final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
                 .setGravity(Gravity.TOP).build();
@@ -630,7 +667,7 @@
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
 
         final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
                 .setGravity(Gravity.TOP | Gravity.LEFT).build();
@@ -647,7 +684,7 @@
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
 
         final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
                 .setGravity(Gravity.RIGHT).build();
@@ -663,7 +700,7 @@
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
 
         final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
                 .setGravity(Gravity.BOTTOM).build();
@@ -679,7 +716,7 @@
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
 
         final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
                 .setGravity(Gravity.BOTTOM | Gravity.RIGHT).build();
@@ -696,7 +733,7 @@
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
 
         final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
                 .setWidth(120).setHeight(80).build();
@@ -712,7 +749,7 @@
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
 
         final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
                 .setWidth(120).setHeight(80).setGravity(Gravity.LEFT).build();
@@ -728,7 +765,7 @@
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
 
         final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
                 .setWidth(120).setHeight(80).setGravity(Gravity.TOP).build();
@@ -744,7 +781,7 @@
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
 
         final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
                 .setWidth(120).setHeight(80).setGravity(Gravity.TOP | Gravity.LEFT).build();
@@ -760,7 +797,7 @@
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
 
         final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
                 .setWidth(120).setHeight(80).setGravity(Gravity.RIGHT).build();
@@ -776,7 +813,7 @@
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
 
         final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
                 .setWidth(120).setHeight(80).setGravity(Gravity.BOTTOM).build();
@@ -792,7 +829,7 @@
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
 
         final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
                 .setWidth(120).setHeight(80).setGravity(Gravity.BOTTOM | Gravity.RIGHT).build();
@@ -808,7 +845,7 @@
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
 
         final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
                 .setWidthFraction(0.125f).setHeightFraction(0.1f).build();
@@ -824,7 +861,7 @@
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
         mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
         mCurrent.mBounds.set(0, 0, 200, 100);
 
@@ -839,7 +876,7 @@
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
         mCurrent.mWindowingMode = WINDOWING_MODE_FULLSCREEN;
         mCurrent.mBounds.set(0, 0, 200, 100);
 
@@ -1217,7 +1254,7 @@
 
     @Test
     public void returnsNonFullscreenBoundsOnFullscreenDisplayWithFreeformHistory() {
-        mCurrent.mPreferredDisplayId = Display.INVALID_DISPLAY;
+        mCurrent.mPreferredTaskDisplayArea = null;
         mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
         mCurrent.mBounds.set(0, 0, 200, 100);
 
@@ -1233,7 +1270,7 @@
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
-        mCurrent.mPreferredDisplayId = Display.INVALID_DISPLAY;
+        mCurrent.mPreferredTaskDisplayArea = null;
         mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
         mCurrent.mBounds.set(-100, -200, 200, 100);
 
@@ -1253,7 +1290,7 @@
 
         addFreeformTaskTo(freeformDisplay, new Rect(0, 0, 200, 100));
 
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
         mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
         mCurrent.mBounds.set(0, 0, 200, 100);
 
@@ -1284,13 +1321,14 @@
     public void testNoMultiDisplaySupports() {
         final boolean orgValue = mService.mSupportsMultiDisplay;
         final TestDisplayContent display = createNewDisplayContent(WINDOWING_MODE_FULLSCREEN);
-        mCurrent.mPreferredDisplayId = display.mDisplayId;
+        mCurrent.mPreferredTaskDisplayArea = display.getDefaultTaskDisplayArea();
 
         try {
             mService.mSupportsMultiDisplay = false;
             assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
                     mActivity, /* source */ null, /* options */ null, mCurrent, mResult));
-            assertEquals(DEFAULT_DISPLAY, mResult.mPreferredDisplayId);
+            assertEquals(mRootWindowContainer.getDefaultTaskDisplayArea(),
+                    mResult.mPreferredTaskDisplayArea);
         } finally {
             mService.mSupportsMultiDisplay = orgValue;
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
similarity index 94%
rename from services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
rename to services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index e41d4dc..53cc09b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -39,6 +39,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import static org.junit.Assert.assertEquals;
@@ -70,7 +71,6 @@
 
 import androidx.test.filters.SmallTest;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -87,7 +87,7 @@
 @SmallTest
 @Presubmit
 @RunWith(WindowTestRunner.class)
-public class TaskOrganizerTests extends WindowTestsBase {
+public class WindowOrganizerTests extends WindowTestsBase {
     private ITaskOrganizer registerMockOrganizer(int windowingMode) {
         final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
         when(organizer.asBinder()).thenReturn(new Binder());
@@ -307,11 +307,7 @@
         final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mRoot)
                 .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
         final Task task = stack.getTopMostTask();
-        WindowContainerTransaction t = new WindowContainerTransaction();
-        Rect newBounds = new Rect(10, 10, 100, 100);
-        t.setBounds(task.mRemoteToken.toWindowContainerToken(), new Rect(10, 10, 100, 100));
-        mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
-        assertEquals(newBounds, task.getBounds());
+        testTransaction(task);
     }
 
     @Test
@@ -321,24 +317,41 @@
                 .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
         StackInfo info =
                 mWm.mAtmService.getStackInfo(WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
-        WindowContainerTransaction t = new WindowContainerTransaction();
         assertEquals(stack.mRemoteToken.toWindowContainerToken(), info.stackToken);
+        testTransaction(stack);
+    }
+
+    @Test
+    public void testDisplayAreaTransaction() {
+        removeGlobalMinSizeRestriction();
+        final DisplayArea displayArea = new DisplayArea<>(mWm, ABOVE_TASKS, "DisplayArea");
+        testTransaction(displayArea);
+    }
+
+    private void testTransaction(WindowContainer wc) {
+        WindowContainerTransaction t = new WindowContainerTransaction();
         Rect newBounds = new Rect(10, 10, 100, 100);
-        t.setBounds(info.stackToken, new Rect(10, 10, 100, 100));
+        t.setBounds(wc.mRemoteToken.toWindowContainerToken(), new Rect(10, 10, 100, 100));
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
-        assertEquals(newBounds, stack.getBounds());
+        assertEquals(newBounds, wc.getBounds());
     }
 
     @Test
     public void testSetWindowingMode() {
         final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mRoot)
-            .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+                .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+        testSetWindowingMode(stack);
+
+        final DisplayArea displayArea = new DisplayArea<>(mWm, ABOVE_TASKS, "DisplayArea");
+        displayArea.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        testSetWindowingMode(displayArea);
+    }
+
+    private void testSetWindowingMode(WindowContainer wc) {
         final WindowContainerTransaction t = new WindowContainerTransaction();
-
-        t.setWindowingMode(stack.mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_FULLSCREEN);
+        t.setWindowingMode(wc.mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_FULLSCREEN);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
-
-        assertEquals(WINDOWING_MODE_FULLSCREEN, stack.getWindowingMode());
+        assertEquals(WINDOWING_MODE_FULLSCREEN, wc.getWindowingMode());
     }
 
     @Test
@@ -400,7 +413,8 @@
         final int origScreenHDp = task.getConfiguration().screenHeightDp;
         t = new WindowContainerTransaction();
         // verify that setting config overrides on parent restricts children.
-        t.setScreenSizeDp(stack.mRemoteToken.toWindowContainerToken(), origScreenWDp, origScreenHDp);
+        t.setScreenSizeDp(stack.mRemoteToken
+                .toWindowContainerToken(), origScreenWDp, origScreenHDp);
         t.setBounds(task.mRemoteToken.toWindowContainerToken(), new Rect(10, 10, 150, 200));
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
         assertEquals(origScreenHDp, task.getConfiguration().screenHeightDp);
@@ -665,7 +679,7 @@
         BLASTSyncEngine bse = new BLASTSyncEngine();
 
         BLASTSyncEngine.TransactionReadyListener transactionListener =
-            mock(BLASTSyncEngine.TransactionReadyListener.class);
+                mock(BLASTSyncEngine.TransactionReadyListener.class);
 
         int id = bse.startSyncSet(transactionListener);
         bse.addToSyncSet(id, task);
@@ -689,7 +703,7 @@
         BLASTSyncEngine bse = new BLASTSyncEngine();
 
         BLASTSyncEngine.TransactionReadyListener transactionListener =
-            mock(BLASTSyncEngine.TransactionReadyListener.class);
+                mock(BLASTSyncEngine.TransactionReadyListener.class);
 
         int id = bse.startSyncSet(transactionListener);
         assertEquals(true, bse.addToSyncSet(id, task));
@@ -715,7 +729,7 @@
         BLASTSyncEngine bse = new BLASTSyncEngine();
 
         BLASTSyncEngine.TransactionReadyListener transactionListener =
-            mock(BLASTSyncEngine.TransactionReadyListener.class);
+                mock(BLASTSyncEngine.TransactionReadyListener.class);
 
         int id = bse.startSyncSet(transactionListener);
         bse.addToSyncSet(id, task);
@@ -738,7 +752,7 @@
         BLASTSyncEngine bse = new BLASTSyncEngine();
 
         BLASTSyncEngine.TransactionReadyListener transactionListener =
-            mock(BLASTSyncEngine.TransactionReadyListener.class);
+                mock(BLASTSyncEngine.TransactionReadyListener.class);
 
         int id = bse.startSyncSet(transactionListener);
         bse.addToSyncSet(id, task);
@@ -764,7 +778,7 @@
         BLASTSyncEngine bse = new BLASTSyncEngine();
 
         BLASTSyncEngine.TransactionReadyListener transactionListener =
-            mock(BLASTSyncEngine.TransactionReadyListener.class);
+                mock(BLASTSyncEngine.TransactionReadyListener.class);
 
         int id = bse.startSyncSet(transactionListener);
         assertEquals(true, bse.addToSyncSet(id, task));
@@ -817,8 +831,8 @@
         mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o, WINDOWING_MODE_PINNED);
         final ActivityRecord record = makePipableActivity();
 
-        final PictureInPictureParams p =
-            new PictureInPictureParams.Builder().setAspectRatio(new Rational(1, 2)).build();
+        final PictureInPictureParams p = new PictureInPictureParams.Builder()
+                .setAspectRatio(new Rational(1, 2)).build();
         assertTrue(mWm.mAtmService.enterPictureInPictureMode(record.token, p));
         waitUntilHandlersIdle();
         assertNotNull(o.mInfo);
@@ -838,15 +852,15 @@
         mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o, WINDOWING_MODE_PINNED);
 
         final ActivityRecord record = makePipableActivity();
-        final PictureInPictureParams p =
-            new PictureInPictureParams.Builder().setAspectRatio(new Rational(1, 2)).build();
+        final PictureInPictureParams p = new PictureInPictureParams.Builder()
+                .setAspectRatio(new Rational(1, 2)).build();
         assertTrue(mWm.mAtmService.enterPictureInPictureMode(record.token, p));
         waitUntilHandlersIdle();
         assertNotNull(o.mInfo);
         assertNotNull(o.mInfo.pictureInPictureParams);
 
-        final PictureInPictureParams p2 =
-            new PictureInPictureParams.Builder().setAspectRatio(new Rational(3, 4)).build();
+        final PictureInPictureParams p2 = new PictureInPictureParams.Builder()
+                .setAspectRatio(new Rational(3, 4)).build();
         mWm.mAtmService.setPictureInPictureParams(record.token, p2);
         waitUntilHandlersIdle();
         assertNotNull(o.mChangedInfo);
diff --git a/tests/net/common/java/android/net/NetworkProviderTest.kt b/tests/net/common/java/android/net/NetworkProviderTest.kt
new file mode 100644
index 0000000..4601c4b
--- /dev/null
+++ b/tests/net/common/java/android/net/NetworkProviderTest.kt
@@ -0,0 +1,158 @@
+/*
+ * 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 android.net
+
+import android.app.Instrumentation
+import android.content.Context
+import android.net.NetworkCapabilities.TRANSPORT_TEST
+import android.os.Build
+import android.os.HandlerThread
+import android.os.Looper
+import android.net.NetworkProviderTest.TestNetworkCallback.CallbackEntry.OnUnavailable
+import android.net.NetworkProviderTest.TestNetworkProvider.CallbackEntry.OnNetworkRequested
+import android.net.NetworkProviderTest.TestNetworkProvider.CallbackEntry.OnNetworkRequestWithdrawn
+import androidx.test.InstrumentationRegistry
+import com.android.testutils.ArrayTrackRecord
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import java.util.UUID
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val DEFAULT_TIMEOUT_MS = 5000L
+private val instrumentation: Instrumentation
+    get() = InstrumentationRegistry.getInstrumentation()
+private val context: Context get() = InstrumentationRegistry.getContext()
+private val PROVIDER_NAME = "NetworkProviderTest"
+
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.Q)
+class NetworkProviderTest {
+    private val mCm = context.getSystemService(ConnectivityManager::class.java)
+    private val mHandlerThread = HandlerThread("${javaClass.simpleName} handler thread")
+
+    @Before
+    fun setUp() {
+        instrumentation.getUiAutomation().adoptShellPermissionIdentity()
+        mHandlerThread.start()
+    }
+
+    @After
+    fun tearDown() {
+        mHandlerThread.quitSafely()
+        instrumentation.getUiAutomation().dropShellPermissionIdentity()
+    }
+
+    private class TestNetworkProvider(context: Context, looper: Looper) :
+            NetworkProvider(context, looper, PROVIDER_NAME) {
+        private val seenEvents = ArrayTrackRecord<CallbackEntry>().newReadHead()
+
+        sealed class CallbackEntry {
+            data class OnNetworkRequested(
+                val request: NetworkRequest,
+                val score: Int,
+                val id: Int
+            ) : CallbackEntry()
+            data class OnNetworkRequestWithdrawn(val request: NetworkRequest) : CallbackEntry()
+        }
+
+        override fun onNetworkRequested(request: NetworkRequest, score: Int, id: Int) {
+            seenEvents.add(OnNetworkRequested(request, score, id))
+        }
+
+        override fun onNetworkRequestWithdrawn(request: NetworkRequest) {
+            seenEvents.add(OnNetworkRequestWithdrawn(request))
+        }
+
+        inline fun <reified T : CallbackEntry> expectCallback(
+            crossinline predicate: (T) -> Boolean
+        ) = seenEvents.poll(DEFAULT_TIMEOUT_MS) { it is T && predicate(it) }
+    }
+
+    private fun createNetworkProvider(): TestNetworkProvider {
+        return TestNetworkProvider(context, mHandlerThread.looper)
+    }
+
+    @Test
+    fun testOnNetworkRequested() {
+        val provider = createNetworkProvider()
+        assertEquals(provider.getProviderId(), NetworkProvider.ID_NONE)
+        mCm.registerNetworkProvider(provider)
+        assertNotEquals(provider.getProviderId(), NetworkProvider.ID_NONE)
+
+        val specifier = StringNetworkSpecifier(UUID.randomUUID().toString())
+        val nr: NetworkRequest = NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_TEST)
+                .setNetworkSpecifier(specifier)
+                .build()
+        val cb = ConnectivityManager.NetworkCallback()
+        mCm.requestNetwork(nr, cb)
+        provider.expectCallback<OnNetworkRequested>() {
+            callback -> callback.request.getNetworkSpecifier() == specifier &&
+            callback.request.hasTransport(TRANSPORT_TEST)
+        }
+
+        mCm.unregisterNetworkCallback(cb)
+        provider.expectCallback<OnNetworkRequestWithdrawn>() {
+            callback -> callback.request.getNetworkSpecifier() == specifier &&
+            callback.request.hasTransport(TRANSPORT_TEST)
+        }
+        mCm.unregisterNetworkProvider(provider)
+        // Provider id should be ID_NONE after unregister network provider
+        assertEquals(provider.getProviderId(), NetworkProvider.ID_NONE)
+        // unregisterNetworkProvider should not crash even if it's called on an
+        // already unregistered provider.
+        mCm.unregisterNetworkProvider(provider)
+    }
+
+    private class TestNetworkCallback : ConnectivityManager.NetworkCallback() {
+        private val seenEvents = ArrayTrackRecord<CallbackEntry>().newReadHead()
+        sealed class CallbackEntry {
+            object OnUnavailable : CallbackEntry()
+        }
+
+        override fun onUnavailable() {
+            seenEvents.add(OnUnavailable)
+        }
+
+        inline fun <reified T : CallbackEntry> expectCallback(
+            crossinline predicate: (T) -> Boolean
+        ) = seenEvents.poll(DEFAULT_TIMEOUT_MS) { it is T && predicate(it) }
+    }
+
+    @Test
+    fun testDeclareNetworkRequestUnfulfillable() {
+        val provider = createNetworkProvider()
+        mCm.registerNetworkProvider(provider)
+
+        val specifier = StringNetworkSpecifier(UUID.randomUUID().toString())
+        val nr: NetworkRequest = NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_TEST)
+                .setNetworkSpecifier(specifier)
+                .build()
+
+        val cb = TestNetworkCallback()
+        mCm.requestNetwork(nr, cb)
+        provider.declareNetworkRequestUnfulfillable(nr)
+        cb.expectCallback<OnUnavailable>() { nr.getNetworkSpecifier() == specifier }
+        mCm.unregisterNetworkProvider(provider)
+    }
+}
\ No newline at end of file
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index b864e37..dad0363 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -5969,6 +5969,9 @@
         final LinkAddress myIpv6 = new LinkAddress("2001:db8:1::1/64");
         final String kNat64PrefixString = "2001:db8:64:64:64:64::";
         final IpPrefix kNat64Prefix = new IpPrefix(InetAddress.getByName(kNat64PrefixString), 96);
+        final String kOtherNat64PrefixString = "64:ff9b::";
+        final IpPrefix kOtherNat64Prefix = new IpPrefix(
+                InetAddress.getByName(kOtherNat64PrefixString), 96);
         final RouteInfo defaultRoute = new RouteInfo((IpPrefix) null, myIpv6.getAddress(),
                                                      MOBILE_IFNAME);
         final RouteInfo ipv6Subnet = new RouteInfo(myIpv6, null, MOBILE_IFNAME);
@@ -6082,6 +6085,24 @@
         }
         reset(mMockNetd);
 
+        // Change the NAT64 prefix without first removing it.
+        // Expect clatd to be stopped and started with the new prefix.
+        mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */,
+                kOtherNat64PrefixString, 96);
+        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
+                (lp) -> lp.getStackedLinks().size() == 0);
+        verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME);
+        assertRoutesRemoved(cellNetId, stackedDefault);
+
+        verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kOtherNat64Prefix.toString());
+        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
+                (lp) -> lp.getNat64Prefix().equals(kOtherNat64Prefix));
+        clat.interfaceLinkStateChanged(CLAT_PREFIX + MOBILE_IFNAME, true);
+        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
+                (lp) -> lp.getStackedLinks().size() == 1);
+        assertRoutesAdded(cellNetId, stackedDefault);
+        reset(mMockNetd);
+
         // Add ipv4 address, expect that clatd and prefix discovery are stopped and stacked
         // linkproperties are cleaned up.
         cellLp.addLinkAddress(myIpv4);
@@ -6096,7 +6117,7 @@
         networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         LinkProperties actualLpAfterIpv4 = mCm.getLinkProperties(mCellNetworkAgent.getNetwork());
         LinkProperties expected = new LinkProperties(cellLp);
-        expected.setNat64Prefix(kNat64Prefix);
+        expected.setNat64Prefix(kOtherNat64Prefix);
         assertEquals(expected, actualLpAfterIpv4);
         assertEquals(0, actualLpAfterIpv4.getStackedLinks().size());
         assertRoutesRemoved(cellNetId, stackedDefault);
@@ -6115,7 +6136,7 @@
 
         // Stopping prefix discovery causes netd to tell us that the NAT64 prefix is gone.
         mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, false /* added */,
-                kNat64PrefixString, 96);
+                kOtherNat64PrefixString, 96);
         networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
                 (lp) -> lp.getNat64Prefix() == null);
 
diff --git a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
index a392ae3..0a603b8 100644
--- a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
@@ -18,6 +18,8 @@
 
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+import static android.net.NetworkCapabilities.MAX_TRANSPORT;
+import static android.net.NetworkCapabilities.MIN_TRANSPORT;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
@@ -30,6 +32,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.reset;
@@ -44,16 +47,19 @@
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.net.ResolverOptionsParcel;
 import android.net.ResolverParamsParcel;
 import android.net.RouteInfo;
 import android.net.shared.PrivateDnsConfig;
 import android.provider.Settings;
 import android.test.mock.MockContentResolver;
+import android.util.SparseArray;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.util.MessageUtils;
 import com.android.internal.util.test.FakeSettingsProvider;
 
 import org.junit.Before;
@@ -354,4 +360,23 @@
         expectedParams.resolverOptions = new ResolverOptionsParcel();
         assertResolverParamsEquals(actualParams, expectedParams);
     }
+
+    @Test
+    public void testTransportTypesEqual() throws Exception {
+        SparseArray<String> ncTransTypes = MessageUtils.findMessageNames(
+                new Class[] { NetworkCapabilities.class }, new String[]{ "TRANSPORT_" });
+        SparseArray<String> dnsTransTypes = MessageUtils.findMessageNames(
+                new Class[] { IDnsResolver.class }, new String[]{ "TRANSPORT_" });
+        assertEquals(0, MIN_TRANSPORT);
+        assertEquals(MAX_TRANSPORT + 1, ncTransTypes.size());
+        // TRANSPORT_UNKNOWN in IDnsResolver is defined to -1 and only for resolver.
+        assertEquals("TRANSPORT_UNKNOWN", dnsTransTypes.get(-1));
+        assertEquals(ncTransTypes.size(), dnsTransTypes.size() - 1);
+        for (int i = MIN_TRANSPORT; i < MAX_TRANSPORT; i++) {
+            String name = ncTransTypes.get(i, null);
+            assertNotNull("Could not find NetworkCapabilies.TRANSPORT_* constant equal to "
+                    + i, name);
+            assertEquals(name, dnsTransTypes.get(i));
+        }
+    }
 }
diff --git a/tools/protologtool/Android.bp b/tools/protologtool/Android.bp
index d1a86c2..ce551bd 100644
--- a/tools/protologtool/Android.bp
+++ b/tools/protologtool/Android.bp
@@ -6,7 +6,7 @@
     static_libs: [
         "protolog-common",
         "javaparser",
-        "protolog-proto",
+        "platformprotos",
         "jsonlib",
     ],
 }